Linux内核代码分析1Linux时间子系统及HRTIMER实现

Posted 与光同程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核代码分析1Linux时间子系统及HRTIMER实现相关的知识,希望对你有一定的参考价值。

Linux时间子系统软件架构

(1)嵌入式设备需要较好的电源管理策略。传统的linux会有一个周期性的时钟,即便是系统无事可做的时候也要醒来,这样导致系统不断的从低功耗(idle)状态进入高功耗的状态。这样的设计不符合电源管理的需求。
(2)多媒体的应用程序需要非常精确的timer,例如为了避免视频的跳帧、音频回放中的跳动,这些需要系统提供足够精度的timer
和低精度timer不同,高精度timer使用了人类的最直观的时间单位ns(低精度timer使用的tick是和内核配置相关,不够直接)。本质上linux

早期时间子系统如图

kernel提供了高精度timer之后,其实不必提供低精度timer了,不过由于低精度timer存在了很长的历史,并且在渗入到内核各个部分,如果去掉低精度timer很容易引起linux
kernel稳定性和健壮性的问题,因此目前的linux kernel保持了低精度timer和高精度timer并存。
在新的需求的推动下,内核开发者对linux的时间子系统的软件框架进行修改,让代码层次更清晰,同时又是灵活可配置的,一个示意性的block图如下所示:

Linux kernel 时间子系统的源文件位于linux/kernel/time/目录下,整理如下:

在Linux内核中有两种不同的clock设备,一种是clock source设备,另一种是clock event设备。Clocksource设备一般是一个根据固定频率不停增加的计数器,内核利用该设备可以计算出从系统启动到当前所经过的时间,再加上RTC所提供的初始时间就能得到当前时间(墙上时间)。Clockevent设备则用来提供中断,Clockevent设备可以配置为按固定周期发生中断(periodic)或者产生一个特定时间间隔后的中断(onshot)。

Linux时间描述

jiffies

内核用jiffies变量记录系统启动以来经过的时钟滴答数,它的声明如下:

extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

在32位的系统上,jiffies是一个32位的无符号数,系统每过1/HZ秒,jiffies的值就会加1,最终该变量可能会溢出,所以内核同时又定义了一个64位的变量jiffies_64,链接的脚本保证jiffies变量和jiffies_64变量的内存地址是相同的,通常,我们可以直接访问jiffies变量,但是要获得jiffies_64变量,必须通过辅助函数get_jiffies_64来实现。jiffies是内核的低精度定时器的计时单位,所以内核配置的HZ数决定了低精度定时器的精度,如果HZ数被设定为1000,那么,低精度定时器(timer_list)的精度就是1ms=1/1000秒。因为jiffies变量可能存在溢出的问题,所以在用基于jiffies进行比较时,应该使用以下辅助宏来实现

time_after(a,b)
time_before(a,b)
time_after_eq(a,b)
time_before_eq(a,b)
time_in_range(a,b,c)

同时,内核还提供了一些辅助函数用于jiffies和毫秒以及纳秒之间的转换

unsigned int jiffies_to_msecs(const unsigned long j);
unsigned int jiffies_to_usecs(const unsigned long j);
unsigned long msecs_to_jiffies(const unsigned int m);
unsigned long usecs_to_jiffies(const unsigned int u);

struct timeval

struct timeval 
	__kernel_time_t		tv_sec;		/* seconds /
	__kernel_suseconds_t	tv_usec;	/ microseconds */
;

__kernel_time_t和 __kernel_suseconds_t 实际上都是long型的整数。gettimeofday和settimeofday使用timeval作为时间单位

struct timespec

struct timespec 
	__kernel_time_t	tv_sec;			/* seconds */
	long		tv_nsec;		/* nanoseconds */
;

timekeeper中的xtime字段用timespec作为时间单位。

struct ktime

union ktime 
	s64	tv64;
#if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
	struct 
# ifdef __BIG_ENDIAN
	s32	sec, nsec;
# else
	s32	nsec, sec;
# endif
	 tv;
#endif
;

clocksource 设备

struct clocksource结构

struct clocksource 
	/*
	 * Hotpath data, fits in a single cache line when the
	 * clocksource itself is cacheline aligned.
	 */
	cycle_t (*read)(struct clocksource *cs);
	cycle_t cycle_last;
	cycle_t mask;
	u32 mult;
	u32 shift;
	u64 max_idle_ns;
	u32 maxadj;
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
	struct arch_clocksource_data archdata;
#endif
 
	const char *name;
	struct list_head list;
	int rating;
	int (*enable)(struct clocksource *cs);
	void (*disable)(struct clocksource *cs);
	unsigned long flags;
	void (*suspend)(struct clocksource *cs);
	void (*resume)(struct clocksource *cs);
 
	/* private: */
#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
	/* Watchdog related data, used by the framework */
	struct list_head wd_list;
	cycle_t cs_last;
	cycle_t wd_last;
#endif
 ____cacheline_aligned;

rating:时钟源的精度

同一个设备下,可以有多个时钟源,每个时钟源的精度由驱动它的时钟频率决定,比如一个由10MHz时钟驱动的时钟源,他的精度就是100nS。clocksource结构中有一个rating字段,代表着该时钟源的精度范围,它的取值范围如下:

1--99: 不适合于用作实际的时钟源,只用于启动过程或用于测试;
100--199:基本可用,可用作真实的时钟源,但不推荐;
200--299:精度较好,可用作真实的时钟源;
300--399:很好,精确的时钟源;
400--499:理想的时钟源,如有可能就必须选择它作为时钟源;

read回调函数

时钟源本身不会产生中断,要获得时钟源的当前计数,只能通过主动调用它的read回调函数来获得当前的计数值,注意这里只能获得计数值,也就是所谓的cycle,要获得相应的时间,必须要借助clocksource的mult和shift字段进行转换计算。

mult和shift字段

因为从clocksource中读到的值是一个cycle计数值,要转换为时间,我们必须要知道驱动clocksource的时钟频率F,一个简单的计算就可以完成:

t = cycle/F;

可是clocksource并没有保存时钟的频率F,因为使用上面的公式进行计算,需要使用浮点运算,这在内核中是不允许的,因此,内核使用了另外一个变通的办法,根据时钟的频率和期望的精度,事先计算出两个辅助常数mult和shift,然后使用以下公式进行cycle和t的转换:

t = (cycle * mult) >> shift;

只要我们保证:

F = (1 << shift) / mult;

内核内部使用64位进行该转换计算:

static inline s64 clocksource_cyc2ns(cycle_t cycles, u32 mult, u32 shift)

        return ((u64) cycles * mult) >> shift;

clocksource注册与初始化

由上图可见,最终大部分工作会转由__clocksource_register_scale完成,该函数首先完成对mult和shift值的计算,然后根据mult和shift值,最终通过clocksource_max_deferment获得该clocksource可接受的最大IDLE时间,并记录在clocksource的max_idle_ns字段中。clocksource_enqueue函数负责按clocksource的rating的大小,把该clocksource按顺序挂在全局链表clocksource_list上,rating值越大,在链表上的位置越靠前。
每次新的clocksource注册进来,都会触发clocksource_select函数被调用,它按照rating值选择最好的clocksource,并记录在全局变量curr_clocksource中,然后通过timekeeping_notify函数通知timekeeping,当前clocksource已经变更。

注册clocksource

在系统的启动阶段,内核注册了一个基于jiffies的clocksource,代码位于kernel/time/jiffies.c

struct clocksource clocksource_jiffies = 
	.name		= "jiffies",
	.rating		= 1, /* lowest valid rating*/
	.read		= jiffies_read,
	.mask		= 0xffffffff, /*32bits*/
	.mult		= NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */
	.shift		= JIFFIES_SHIFT,
;
......
 
static int __init init_jiffies_clocksource(void)

	return clocksource_register(&clocksource_jiffies);

 
core_initcall(init_jiffies_clocksource);

它的精度只有1/HZ秒,rating值为1,如果平台的代码没有提供定制的clocksource_default_clock函数,它将返回该clocksource

然后,在初始化的后段,clocksource的代码会把全局变量curr_clocksource设置为上述的clocksource:

static int __init clocksource_done_booting(void)

	mutex_lock(&clocksource_mutex);
	curr_clocksource = clocksource_default_clock();
	finished_booting = 1;
	__clocksource_watchdog_kthread();
	clocksource_select();
	mutex_unlock(&clocksource_mutex);
	return 0;

如果平台级的代码在初始化时也会注册真正的硬件clocksource,所以经过clocksource_select()函数后,curr_clocksource将会被设为最合适的clocksource。如果clocksource_select函数认为需要切换更好的时钟源,它会通过timekeeping_notify通知timekeeping系统,使用新的clocksource进行时间计数和更新操作。

时间维护者timekeeper

clocksource,以及内核内部时间的一些表示方法,但是对于真实的用户来说,我们感知的是真实世界的真实时间,也就是所谓的墙上时间,clocksource只能提供一个按给定频率不停递增的周期计数,timekeeper 把它和真实的墙上时间相关联

RTC时间 在PC中,RTC时间又叫CMOS时间,它通常由一个专门的计时硬件来实现,软件可以读取该硬件来获得年月日、时分秒等时间信息,而在嵌入式系统中,有使用专门的RTC芯片,也有直接把RTC集成到Soc芯片中,读取Soc中的某个寄存器即可获取当前时间信息。一般来说,RTC是一种可持续计时的,也就是说,不管系统是否上电,RTC中的时间信息都不会丢失,计时会一直持续进行,硬件上通常使用一个后备电池对RTC硬件进行单独的供电。因为RTC硬件的多样性,开发者需要为每种RTC时钟硬件提供相应的驱动程序,内核和用户空间通过驱动程序访问RTC硬件来获取或设置时间信息。

xtime xtime和RTC时间一样,都是人们日常所使用的墙上时间,只是RTC时间的精度通常比较低,大多数情况下只能达到毫秒级别的精度,如果是使用外部的RTC芯片,访问速度也比较慢,为此,内核维护了另外一个wall time时间:xtime,取决于用于对xtime计时的clocksource,它的精度甚至可以达到纳秒级别,因为xtime实际上是一个内存中的变量,它的访问速度非常快,内核大部分时间都是使用xtime来获得当前时间信息。xtime记录的是自1970年1月1日24时到当前时刻所经历的纳秒数。

monotonic time 该时间自系统开机后就一直单调地增加,它不像xtime可以因用户的调整时间而产生跳变,不过该时间不计算系统休眠的时间,也就是说,系统休眠时,monotoic时间不会递增。

raw monotonic time 该时间与monotonic时间类似,也是单调递增的时间,唯一的不同是:raw monotonic time“更纯净”,他不会受到NTP时间调整的影响,它代表着系统独立时钟硬件对时间的统计。

boot time 与monotonic时间相同,不过会累加上系统休眠的时间,它代表着系统上电后的总时间。

struct timekeeper

struct timekeeper 
	struct clocksource *clock;    /* Current clocksource used for timekeeping. */
	u32	mult;    /* NTP adjusted clock multiplier */
	int	shift;	/* The shift value of the current clocksource. */
	cycle_t cycle_interval;	/* Number of clock cycles in one NTP interval. */
	u64	xtime_interval;	/* Number of clock shifted nano seconds in one NTP interval. */
	s64	xtime_remainder;	/* shifted nano seconds left over when rounding cycle_interval */
	u32	raw_interval;	/* Raw nano seconds accumulated per NTP interval. */
 
	u64	xtime_nsec;	/* Clock shifted nano seconds remainder not stored in xtime.tv_nsec. */
	/* Difference between accumulated time and NTP time in ntp
	 * shifted nano seconds. */
	s64	ntp_error;
	/* Shift conversion between clock shifted nano seconds and
	 * ntp shifted nano seconds. */
	int	ntp_error_shift;
 
	struct timespec xtime;	/* The current time */
 
	struct timespec wall_to_monotonic;
	struct timespec total_sleep_time;	/* time spent in suspend */
	struct timespec raw_time;	/* The raw monotonic time for the CLOCK_MONOTONIC_RAW posix clock. */
 
	ktime_t offs_real;	/* Offset clock monotonic -> clock realtime */
 
	ktime_t offs_boot;	/* Offset clock monotonic -> clock boottime */
 
	seqlock_t lock;	/* Seqlock for all timekeeper values */
;

初始化

timekeeper的初始化由timekeeping_init完成,该函数在start_kernel的初始化序列中被调用,timekeeping_init首先从RTC中获取当前时间:

void __init timekeeping_init(void)

	struct clocksource *clock;
	unsigned long flags;
	struct timespec now, boot;
 
	read_persistent_clock(&now);
	read_boot_clock(&boot);
	seqlock_init(&timekeeper.lock);
	ntp_init();

接着获取默认的clocksource,如果平台没有重新实现clocksource_default_clock函数,默认的clocksource就是基于jiffies的clocksource_jiffies,然后通过timekeeper_setup_inernals内部函数把timekeeper和clocksource进行关联:

	write_seqlock_irqsave(&timekeeper.lock, flags);
	clock = clocksource_default_clock();
	if (clock->enable)
		clock->enable(clock);
	timekeeper_setup_internals(clock);

利用RTC的当前时间,初始化xtime,raw_time,wall_to_monotonic等字段:

	timekeeper.xtime.tv_sec = now.tv_sec;
	timekeeper.xtime.tv_nsec = now.tv_nsec;
	timekeeper.raw_time.tv_sec = 0;
	timekeeper.raw_time.tv_nsec = 0;
	if (boot.tv_sec == 0 && boot.tv_nsec == 0) 
		boot.tv_sec = timekeeper.xtime.tv_sec;
		boot.tv_nsec = timekeeper.xtime.tv_nsec;
	
	set_normalized_timespec(&timekeeper.wall_to_monotonic,
				-boot.tv_sec, -boot.tv_nsec);

最后,初始化代表实时时间和monotonic时间之间偏移量的offs_real字段,total_sleep_time字段初始化为0:

	update_rt_offset();
	timekeeper.total_sleep_time.tv_sec = 0;
	timekeeper.total_sleep_time.tv_nsec = 0;
	write_sequnlock_irqrestore(&timekeeper.lock, flags);

xtime字段因为是保存在内存中,系统掉电后无法保存时间信息,所以每次启动时都要通过timekeeping_init从RTC中同步正确的时间信息。其中,read_persistent_clock和read_boot_clock是平台级的函数,分别用于获取RTC硬件时间和启动时的时间,不过值得注意到是。如果平台没有实现该函数,内核提供了一个默认的实现:

void __attribute__((weak)) read_persistent_clock(struct timespec *ts)

	ts->tv_sec = 0;
	ts->tv_nsec = 0;

时间更新

xtime一旦初始化完成后,timekeeper就开始独立于RTC,利用自身关联的clocksource进行时间的更新操作,根据内核的配置项的不同,更新时间的操作发生的频度也不尽相同,如果没有配置NO_HZ选项,通常每个tick的定时中断周期,do_timer会被调用一次,相反,如果配置了NO_HZ选项,可能会在好几个tick后,do_timer才会被调用一次,当然传入的参数是本次更新离上一次更新时相隔了多少个tick周期,系统会保证在clocksource的max_idle_ns时间内调用do_timer,以防止clocksource的溢出:

void do_timer(unsigned long ticks)

	jiffies_64 += ticks;
	update_wall_time();
	calc_global_load(ticks);

获取时间

timekeeper提供了一系列的接口用于获取各种时间信息。

void getboottime(struct timespec *ts);    获取系统启动时刻的实时时间
void get_monotonic_boottime(struct timespec *ts);     获取系统启动以来所经过的时间,包含休眠时间
ktime_t ktime_get_boottime(void);   获取系统启动以来所经过的c时间,包含休眠时间,返回ktime类型
ktime_t ktime_get(void);    获取系统启动以来所经过的c时间,不包含休眠时间,返回ktime类型
void ktime_get_ts(struct timespec *ts) ;   获取系统启动以来所经过的c时间,不包含休眠时间,返回timespec结构
unsigned long get_seconds(void);    返回xtime中的秒计数值
struct timespec current_kernel_time(void);    返回内核最后一次更新的xtime时间,不累计最后一次更新至今clocksource的计数值
void getnstimeofday(struct timespec *ts);    获取当前时间,返回timespec结构
void do_gettimeofday(struct timeval *tv);    获取当前时间,返回timeval结构

clock_event_device 定时设备

早期的内核版本中,进程的调度基于一个称之为tick的时钟滴答,通常使用时钟中断来定时地产生tick信号,每次tick定时中断都会进行进程的统计和调度,并对tick进行计数,记录在一个jiffies变量中,定时器的设计也是基于jiffies。这时候的内核代码中,几乎所有关于时钟的操作都是在machine级的代码中实现,很多公共的代码要在每个平台上重复实现。随后,随着通用时钟框架的引入,内核需要支持高精度的定时器,为此,通用时间框架为定时器硬件定义了一个标准的接口:clock_event_device,machine级的代码只要按这个标准接口实现相应的硬件控制功能,剩下的与平台无关的特性则统一由通用时间框架层来实现。

clocksource不能被编程,没有产生事件的能力,它主要被用于timekeeper来实现对真实时间进行精确的统计,而clock_event_device则是可编程的,它可以工作在周期触发或单次触发模式,系统可以对它进行编程,以确定下一次事件触发的时间,clock_event_device主要用于实现普通定时器和高精度定时器,同时也用于产生tick事件,供给进程调度子系统使用。时钟事件设备与通用时间框架中的其他模块的关系如下图所示:

与clocksource一样,系统中可以存在多个clock_event_device,系统会根据它们的精度和能力,选择合适的clock_event_device对系统提供时钟事件服务。在smp系统中,为了减少处理器间的通信开销,基本上每个cpu都会具备一个属于自己的本地clock_event_device,独立地为该cpu提供时钟事件服务,smp中的每个cpu基于本地的clock_event_device,建立自己的tick_device,普通定时器和高精度定时器。
在软件架构上看,clock_event_device被分为了两层,与硬件相关的被放在了machine层,而与硬件无关的通用代码则被集中到了通用时间框架层,这符合内核对软件的设计需求,平台的开发者只需实现平台相关的接口即可,无需关注复杂的上层时间框架。
tick_device是基于clock_event_device的进一步封装,用于代替原有的时钟滴答中断,给内核提供tick事件,以完成进程的调度和进程信息统计,负载平衡和时间更新等操作。

时钟事件设备相关数据结构

struct clock_event_device

时钟事件设备的核心数据结构是clock_event_device结构,它代表着一个时钟硬件设备,该设备就好像是一个具有事件触发能力(通常就是指中断)的clocksource,它不停地计数,当计数值达到预先编程设定的数值那一刻,会引发一个时钟事件中断,继而触发该设备的事件处理回调函数,以完成对时钟事件的处理。clock_event_device结构的定义如下:

struct clock_event_device 
	void			(*event_handler)(struct clock_event_device *);
	int			(*set_next_event)(unsigned long evt,
						  struct clock_event_device *);
	int			(*set_next_ktime)(ktime_t expires,
						  struct clock_event_device *);
	ktime_t			next_event;
	u64			max_delta_ns;
	u64			min_delta_ns;
	u32			mult;
	u32			shift;
	enum clock_event_mode	mode;
	unsigned int		features;
	unsigned long		retries;
 
	void			(*broadcast)(const struct cpumask *mask);
	void			(*set_mode)(enum clock_event_mode mode,
					    struct clock_event_device *);
	unsigned long		min_delta_ticks;
	unsigned long		max_delta_ticks;
 
	const char		*name;
	int			rating;
	int			irq;
	const struct cpumask	*cpumask;
	struct list_head	list;
 ____cacheline_aligned;

event_handler 该字段是一个回调函数指针,通常由通用框架层设置,在时间中断到来时,machine底层的的中断服务程序会调用该回调,框架层利用该回调实现对时钟事件的处理。

set_next_event 设置下一次时间触发的时间,使用类似于clocksource的cycle计数值(离现在的cycle差值)作为参数。

set_next_ktime 设置下一次时间触发的时间,直接使用ktime时间作为参数。

max_delta_ns 可设置的最大时间差,单位是纳秒。

min_delta_ns 可设置的最小时间差,单位是纳秒。

mult shift 与clocksource中的类似,只不过是用于把纳秒转换为cycle。

mode 该时钟事件设备的工作模式,两种主要的工作模式分别是:

CLOCK_EVT_MODE_PERIODIC  周期触发模式,设置后按给定的周期不停地触发事件;
CLOCK_EVT_MODE_ONESHOT  单次触发模式,只在设置好的触发时刻触发一次;

set_mode 函数指针,用于设置时钟事件设备的工作模式。

rating 表示该设备的精度等级。

list 系统中注册的时钟事件设备用该字段挂在全局链表变量clockevent_devices上。

全局变量clockevent_devices

系统中所有注册的clock_event_device都会挂在该链表下面,它在kernel/time/clockevents.c中定义:

static LIST_HEAD(clockevent_devices);

全局变量clockevents_chain

通用时间框架初始化时会注册一个通知链(NOTIFIER),当系统中的时钟时间设备的状态发生变化时,利用该通知链通知系统的其它模块。

/* Notification for clock events */
static RAW_NOTIFIER_HEAD(clockevents_chain);

初始化

start_kernel调用了time_init函数,该函数通常定义在体系相关的代码中,正如前面所讨论的一样,它主要完成machine级别对时钟系统的初始化工作,最终通过clockevents_register_device注册系统中的时钟事件设备,把每个时钟时间设备挂在clockevent_device全局链表上,最后通过clockevent_do_notify触发框架层事先注册好的通知链,其实就是调用了tick_notify函数,我们主要关注CLOCK_EVT_NOTIFY_ADD通知,其它通知请自行参考代码,下面是tick_notify的简化版本:

static int tick_notify(struct notifier_block *nb, unsigned long reason,
			       void *dev)

	switch (reason) 
 
	case CLOCK_EVT_NOTIFY_ADD:
		return tick_check_new_device(dev);
 
	case CLOCK_EVT_NOTIFY_BROADCAST_ON:
	case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
	case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
            ......
	case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
	case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
            ......
	case CLOCK_EVT_NOTIFY_CPU_DYING:
            ......
	case CLOCK_EVT_NOTIFY_CPU_DEAD:
            ......
	case CLOCK_EVT_NOTIFY_SUSPEND:
            ......
	case CLOCK_EVT_NOTIFY_RESUME:
            ......
	
 
	return NOTIFY_OK;

可见,对于新注册的clock_event_device,会发出CLOCK_EVT_NOTIFY_ADD通知,最终会进入函数:tick_check_new_device,这个函数比对当前cpu所使用的与新注册的clock_event_device之间的特性,如果认为新的clock_event_device更好,则会进行切换工作。下一节将会详细的讨论该函数。到这里,每个cpu已经有了自己的clock_event_device,在这以后,框架层的代码会根据内核的配置项(CONFIG_NO_HZ、CONFIG_HIGH_RES_TIMERS),对注册的clock_event_device进行不同的设置,从而为系统的tick和高精度定时器提供服务,这些内容我们留在本系列的后续文章进行讨论。

tick_device

当内核没有配置成支持高精度定时器时,系统的tick由tick_device产生,tick_device其实是clock_event_device的简单封装,它内嵌了一个clock_event_device指针和它的工作模式:

struct tick_device 
	struct clock_event_device *evtdev;
	enum tick_device_mode mode;
;

在kernel/time/tick-common.c中,定义了一个per-cpu的tick_device全局变量,tick_cpu_device:

DEFINE_PER_CPU(struct tick_device, tick_cpu_device);

前面曾经说过,当machine的代码为每个cpu注册clock_event_device时,通知回调函数tick_notify会被调用,进而进入tick_check_new_device函数,下面让我们看看该函数如何工作,首先,该函数先判断注册的clock_event_device是否可用于本cpu,然后从per-cpu变量中取出本cpu的tick_device:

void tick_check_new_device(struct clock_event_device *newdev)

	struct clock_event_device *curdev;
	struct tick_device *td;
	int cpu;

	cpu = smp_processor_id();
	if (!cpumask_test_cpu(cpu, newdev->cpumask))
		goto out_bc;

	td = &per_cpu(tick_cpu_device, cpu);
	curdev = td->evtdev;

	/* cpu local device ? */
	if (!tick_check_percpu(curdev, newdev, cpu))
		goto out_bc;

	/* Preference decision */
	if (!tick_check_preferred(curdev, newdev))
		goto out_bc;

	if (!try_module_get(newdev->owner))
		return;

	/*
	 * Replace the eventually existing device by the new
	 * device. If the current device is the broadcast device, do
	 * not give it back to the clockevents layer !
	 */
	if (tick_is_broadcast_device(curdev)) 
		clockevents_shutdown(curdev);
		curdev = NULL;
	
	clockevents_exchange_device(curdev, newdev);
	tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
	if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
		tick_oneshot_notify()《Linux内核分析》第三周 构建一个简单的Linux系统MenuOS

Linux基础入门1Linux系统简介

Linux课程总结:从系统的角度分析影响程序执行性能的因素

1Linux基础认识

TCP/IP协议栈在Linux内核中的运行时序分析万字长文

Linux内核分析——汇编代码执行及堆栈变化