内核2.6.X中的时钟与定时器
时间篇之linux系统时间和RTC时间

时间篇之linux系统时间和RTC时间⼀、linux系统下包含两个时间:系统时间(刚启动时读取的是rtc时间)和RTC时间。
⼀般情况下都会选择芯⽚上最⾼精度的定时器作为系统时间的定时基准,以避免在系统运⾏较长时间后出现⼤的时间偏移。
特点是掉电后不保存。
所以⼀旦你重启机器后,那么系统需要重新从RTC上重新获取时间,保存到系统内核⽂件中。
RTC(real_time clock)驱动程序,可以在E:\linux内核\linux-2.6.0\linux-2.6.0\drivers\char\rtc.c中找到。
设备接⼝就是 /dev/rtc, 他负责跟rtc打交道,并读取rtc中维护的时间.它是⼀个从系统定时器中独⽴出来的虚拟设备,⽤于设置系统时钟,提供报警器或周期性的定时器.那么系统时间⼀直运⾏吗?显然在操作系统关闭或重启期间,服务器宕机期间,整个服务器的时间就依赖于RTC芯⽚。
从这我们看出linux系统时间和RTC时间是两套独⽴的计时体系,但它们之间⼜是相互依存的:1)刚安装操作系统后,若在安装过程不设置系统时间,那么默认的系统时间就是从服务器的RTC芯⽚中获取当前的硬件时间;2)在linux操作系统中,⼀旦修改系统时间后,⼜重启或关闭Linux系统,则OS通常会将系统时间更新到RTC;3)在操作系统再次启动的时候,Linux OS则会再次从RTC中获取当前的时间。
服务器异常下电后,待操作系统重新启动后,发现系统时间发⽣了跳变?其原因通常是:修改了操作系统时间,在服务器异常下电后,操作系统并未及时将修改后的时间更新到RTC,导致操作系统重新启动后,就会从RTC芯⽚中加载了之前“⽼”的时间,从⽽在操作系统层⾯体现为“时间跳变”⼆、关于jiffies⼀次中断时间间隔叫⼀个tick,即每个触发周期的时间叫做tick,⼀秒内时钟中断的次数(每个时间间隔就是⼀次)等于Hzhz别名就是tick rate(HZ)linux系统查看hz:[root@k3master ~]# cat /boot/config-`uname -r` | grep 'CONFIG_HZ='CONFIG_HZ=10001hz就是每秒1000次中断每次时钟中断处理程序即每发⽣⼀次tick都会增加jiffies该变量的值,jiffies⼀秒内增加的值也就是Hz(频率),⽐如:linux下默认是 1000次时钟中断次数/秒系统运⾏时间以秒为单位,换算⽅法等于jiffies/Hz。
linux 内核定时器timer_list详解

在模块的编写过程中,我们经常使用定时器来等待一段时间之后再来执行某一个操作。
为方便分析,写了下列一段测试程序:#include <linux/config.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/interrupt.h>#include <linux/delay.h>#include <linux/timer.h>MODULE_LICENSE("GPL");void test_timerfuc(unsigned long x){printk("Eric xiao test ......\n");}//声明一个定个器struct timer_list test_timer = TIMER_INITIALIZER(test_timerfuc, 0, 0);int kernel_test_init(){printk("test_init\n");//修改定时器到期时间。
为3个HZ。
一个HZ产生一个时钟中断mod_timer(&test_timer,jiffies+3*HZ);//把定时器加入时钟软中断处理链表add_timer(&test_timer);}int kernel_test_exit(){printk("test_exit\n");return 0;}module_init(kernel_test_init);module_exit(kernel_test_exit);上面的例子程序比较简单,我们从这个例子开始研究linux下的定时器实现。
TIMER_INITIALIZER():1):TIMER_INITIALIZER()用来声明一个定时器,它的定义如下:#define TIMER_INITIALIZER(_function, _expires, _data) { \.function = (_function), \.expires = (_expires), \.data = (_data), \.base = NULL, \.magic = TIMER_MAGIC, \.lock = SPIN_LOCK_UNLOCKED, \}Struct timer_list定义如下:struct timer_list {//用来形成链表struct list_head entry;//定始器到达时间unsigned long expires;spinlock_t lock;unsigned long magic;//定时器时间到达后,所要运行的函数void (*function)(unsigned long);//定时器函数对应的参数unsigned long data;//挂载这个定时器的tvec_t_base_s.这个结构我们等会会看到,当该次中断顺利执行后,该值也将清空为NULLstruct tvec_t_base_s *base;};从上面的过程中我们可以看到TIMER_INITIALIZER()只是根据传入的参数初始化了struct timer_list结构.并把magic 成员初始化成TIMER_MAGIC2): mod_timer():修改定时器的到时时间int mod_timer(struct timer_list *timer, unsigned long expires){//如果该定时器没有定义fuctionBUG_ON(!timer->function);//判断timer的magic是否为TIMER_MAGIC.如果不是,则将其修正为TIMER_MAGIC check_timer(timer);//如果要调整的时间就是定时器的定时时间而且已经被激活,则直接返回if (timer->expires == expires && timer_pending(timer))return 1;//调用_mod_timer().呆会再给出分析return __mod_timer(timer, expires);}3): add_timer()用来将定时器挂载到定时软中断队列,激活该定时器static inline void add_timer(struct timer_list * timer){__mod_timer(timer, timer->expires);}可以看到mod_timer与add_timer 最后都会调用__mod_timer().为了分析这个函数,我们先来了解一下定时系统相关的数据结构.tvec_bases: per cpu变量,它的定义如下:static DEFINE_PER_CPU(tvec_base_t, tvec_bases) = { SPIN_LOCK_UNLOCKED };由此可以看到tves_bases的数型数据为teves_base_t.数据结构的定义如下:typedef struct tvec_t_base_s tvec_base_t;struct tvec_t_base_s的定义:struct tvec_t_base_s {spinlock_t lock;//上一次运行计时器的jiffies 值这个值很关键,正是这个值保证了不会遗漏定时器中断,timer中断中每次循环查找后,该值加一unsigned long timer_jiffies;struct timer_list *running_timer;//tv1 tv2 tv3 tv4 tv5是五个链表数组tvec_root_t tv1;tvec_t tv2;tvec_t tv3;tvec_t tv4;tvec_t tv5;} ____cacheline_aligned_in_smp;Tves_root_t与tvec_t的定义如下:#define TVN_BITS 6#define TVR_BITS 8#define TVN_SIZE (1 << TVN_BITS)#define TVR_SIZE (1 << TVR_BITS)#define TVN_MASK (TVN_SIZE - 1)#define TVR_MASK (TVR_SIZE - 1)typedef struct tvec_s {struct list_head vec[TVN_SIZE];} tvec_t;typedef struct tvec_root_s {struct list_head vec[TVR_SIZE];} tvec_root_t;系统规定定时器最大超时时间间隔为0xFFFFFFFF.即为一个32位数.即使在64位系统上.如果超过此值也会将其强制设这oxFFFFFFFF(这在后面的代码分析中可以看到).内核最关心的就是间隔在0~255个HZ之间的定时器.次重要的是间隔在255~1<<(8+6)之间的定时器.第三重要的是间隔在1<<(8+6) ~ 1<<(8+6+6)之间的定器.依次往下推.也就是把32位的定时间隔为份了五个部份.1个8位.4个6位.所以内核定义了五个链表数组.第一个链表数组大小为8位大小,也即上面定义的 #define TVR_SIZE (1 << TVR_BITS).其它的四个数组大小为6位大小.即上面定义的#define TVN_SIZE (1 << TVN_BITS)在加入定时器的时候,按照时间间隔把定时器加入到相应的数组即可.了解这点之后,就可以来看__mod_timer()的代码了://修改timer或者新增一个timer都会调用此接口int __mod_timer(struct timer_list *timer, unsigned long expires){tvec_base_t *old_base, *new_base;unsigned long flags;int ret = 0;//入口参数检测BUG_ON(!timer->function);check_timer(timer);spin_lock_irqsave(&timer->lock, flags);//取得当前CPU对应的tvec_basesnew_base = &__get_cpu_var(tvec_bases);repeat://该定时器所在的tvec_bases.对于新增的timer.它的base字段为NULLold_base = timer->base;/** Prevent deadlocks via ordering by old_base < new_base.*///在把timer从当前tvec_bases摘下来之前,要充分考虑好竞争的情况if (old_base && (new_base != old_base)) {//按次序获得锁if (old_base < new_base) {spin_lock(&new_base->lock);spin_lock(&old_base->lock);} else {spin_lock(&old_base->lock);spin_lock(&new_base->lock);}/** The timer base might have been cancelled while we were* trying to take the lock(s):*///如果timer->base != old_base.那就是说在Lock的时候.其它CPU更改它的值 //那就解锁.重新判断if (timer->base != old_base) {spin_unlock(&new_base->lock);spin_unlock(&old_base->lock);goto repeat;}} else {//old_base == NULl 或者是 new_base==old_base的情况//获得锁spin_lock(&new_base->lock);//同理,在Lock的时候timer会生了改变if (timer->base != old_base) {spin_unlock(&new_base->lock);goto repeat;}}/** Delete the previous timeout (if there was any), and install* the new one:*///将其从其它的tvec_bases上删除.注意运行到这里的话,说话已经被Lock了 if (old_base) {list_del(&timer->entry);ret = 1;}//修改它的定时器到达时间timer->expires = expires;//将其添加到new_base中internal_add_timer(new_base, timer);//修改base字段timer->base = new_base;//操作完了,解锁if (old_base && (new_base != old_base))spin_unlock(&old_base->lock);spin_unlock(&new_base->lock);spin_unlock_irqrestore(&timer->lock, flags);return ret;}internal_add_timer()的代码如下:static void internal_add_timer(tvec_base_t *base, struct timer_list *timer){//定时器到达的时间unsigned long expires = timer->expires;//计算时间间间隔unsigned long idx = expires - base->timer_jiffies;struct list_head *vec;//根据时间间隔,将timer放入相应数组的相应位置if (idx < TVR_SIZE) {int i = expires & TVR_MASK;vec = base->tv1.vec + i;} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {int i = (expires >> TVR_BITS) & TVN_MASK;vec = base->tv2.vec + i;} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;vec = base->tv3.vec + i;} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;vec = base->tv4.vec + i;} else if ((signed long) idx < 0) {/** Can happen if you add a timer with expires == jiffies,* or you set a timer to go off in the past*///如果间隔小于0vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);} else {int i;/* If the timeout is larger than 0xffffffff on 64-bit* architectures then we use the maximum timeout:*///时间间隔超长,将其设为oxFFFFFFFFif (idx > 0xffffffffUL) {idx = 0xffffffffUL;expires = idx + base->timer_jiffies;}i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;vec = base->tv5.vec + i;}/** Timers are FIFO:*///加入到链表末尾list_add_tail(&timer->entry, vec);}计算时间间隔即可知道要加入到哪一个数组.哪又怎么计算加入到该数组的那一项呢?对于间隔时间在0~255的定时器: 它的计算方式是将定时器到达时间的低八位与低八位为1的数相与而成对于第1个六位,它是先将到达时间右移8位.然后与低六位全为1的数相与而成对于第2个六位, 它是先将到达时间右移8+6位.然后与低六位全为1的数相与而成依次为下推…在后面结合超时时间到达的情况再来分析相关部份4):定时器更新每过一个HZ,就会检查当前是否有定时器的定时器时间到达.如果有,运行它所注册的函数,再将其删除.为了分析这一过程,我们先从定时器系统的初始化看起.asmlinkage void __init start_kernel(void){……init_timers();……}Init_timers()的定义如下:void __init init_timers(void){timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,(void *)(long)smp_processor_id());register_cpu_notifier(&timers_nb);//注册TIMER_SOFTIRQ软中断open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);}timer_cpu_notify()àinit_timers_cpu():代码如下:static void /* __devinit */ init_timers_cpu(int cpu){int j;tvec_base_t *base;//初始化各个数组中的链表base = &per_cpu(tvec_bases, cpu);spin_lock_init(&base->lock);for (j = 0; j < TVN_SIZE; j++) {INIT_LIST_HEAD(base->tv5.vec + j);INIT_LIST_HEAD(base->tv4.vec + j);INIT_LIST_HEAD(base->tv3.vec + j);INIT_LIST_HEAD(base->tv2.vec + j);}for (j = 0; j < TVR_SIZE; j++)INIT_LIST_HEAD(base->tv1.vec + j);//将最近到达时间设为当前jiffiesbase->timer_jiffies = jiffies;}我们在前面分析过,每当时钟当断函数到来的时候,就会打开定时器的软中断.运行其软中断函数.run_timer_softirq()代码如下:static void run_timer_softirq(struct softirq_action *h){//取得当于CPU的tvec_base_t结构tvec_base_t *base = &__get_cpu_var(tvec_bases);//如果jiffies > base->timer_jiffiesif (time_after_eq(jiffies, base->timer_jiffies))__run_timers(base);}__run_timers()代码如下:static inline void __run_timers(tvec_base_t *base){struct timer_list *timer;unsigned long flags;spin_lock_irqsave(&base->lock, flags);//因为CPU可能关闭中断,引起时钟中断信号丢失.可能jiffies要大base->timer_jiffies 好几个//HZwhile (time_after_eq(jiffies, base->timer_jiffies)) {//定义并初始化一个链表struct list_head work_list = LIST_HEAD_INIT(work_list);struct list_head *head = &work_list;int index = base->timer_jiffies & TVR_MASK;/** Cascade timers:*///当index == 0时,说明已经循环了一个周期//则将tv2填充tv1.如果tv2为空,则用tv3填充tv2.依次类推......if (!index &&(!cascade(base, &base->tv2, INDEX(0))) &&(!cascade(base, &base->tv3, INDEX(1))) &&!cascade(base, &base->tv4, INDEX(2)))cascade(base, &base->tv5, INDEX(3));//更新base->timer_jiffies++base->timer_jiffies;//将base->tv1.vec项移至work_list.并将base->tv1.vec置空list_splice_init(base->tv1.vec + index, &work_list);repeat://work_List中的定时器是已经到时的定时器if (!list_empty(head)) {void (*fn)(unsigned long);unsigned long data;//遍历链表中的每一项.运行它所对应的函数,并将定时器从链表上脱落timer = list_entry(head->next,struct timer_list,entry);fn = timer->function;data = timer->data;list_del(&timer->entry);set_running_timer(base, timer);smp_wmb();timer->base = NULL;spin_unlock_irqrestore(&base->lock, flags);fn(data);spin_lock_irq(&base->lock);goto repeat;}}set_running_timer(base, NULL);spin_unlock_irqrestore(&base->lock, flags);}如果base->timer_jiffies低八位为零.说明它向第九位有进位.所以把第九位到十五位对应的定时器搬到前八位对应的数组.如果第九位到十五位为空的话.就到它的上个六位去搬数据.上面的代码也说明.要经过1<<8个HZ才会更新全部数组中的定时器.这样做的效率是很高的. 分析下里面的两个重要的子函数:static int cascade(tvec_base_t *base, tvec_t *tv, int index){/* cascade all the timers from tv up one level */struct list_head *head, *curr;//取数组中序号对应的链表head = tv->vec + index;curr = head->next;/** We are removing _all_ timers from the list, so we don't have to* detach them individually, just clear the list afterwards.*///遍历这个链表,将定时器重新插入到base中while (curr != head) {struct timer_list *tmp;tmp = list_entry(curr, struct timer_list, entry);BUG_ON(tmp->base != base);curr = curr->next;internal_add_timer(base, tmp);}//将链表设为初始化状态INIT_LIST_HEAD(head);return index;}//将list中的数据放入head中,并将list置为空static inline void list_splice_init(struct list_head *list, struct list_head *head) {if (!list_empty(list)) {__list_splice(list, head);INIT_LIST_HEAD(list);}}//将list中的数据放入headstatic inline void __list_splice(struct list_head *list, struct list_head *head){//list的第一个元素struct list_head *first = list->next;//list的最后一个元素struct list_head *last = list->prev;//head的第一个元素struct list_head *at = head->next;将first对应的链表链接至headfirst->prev = head;head->next = first;//将head 原有的数据加入到链表末尾last->next = at;at->prev = last;}5):del_timer()删除定时器//删除一个timerint del_timer(struct timer_list *timer){unsigned long flags;tvec_base_t *base;check_timer(timer);repeat:base = timer->base;//该定时器没有被激活if (!base)return 0;//加锁spin_lock_irqsave(&base->lock, flags);//如果在加锁的过程中,有其它操作改变了timerif (base != timer->base) {spin_unlock_irqrestore(&base->lock, flags);goto repeat;}//将timer从链表中删除list_del(&timer->entry);timer->base = NULL;spin_unlock_irqrestore(&base->lock, flags);return 1;}6): del_timer_sync()有竞争情况下的定时器删除在SMP系统中,可能要删除的定时器正在某一个CPU上运行.为了防止这种在情况.在删除定时器的时候,应该优先使用del_timer_synsc().它会一直等待所有CPU上的定时器执行完成. int del_timer_sync(struct timer_list *timer){tvec_base_t *base;int i, ret = 0;check_timer(timer);del_again://删除些定时器ret += del_timer(timer);//遍历CPUfor_each_online_cpu(i) {base = &per_cpu(tvec_bases, i);//如果此CPU正在运行这个timerif (base->running_timer == timer) {//一直等待,直到这个CPU执行完while (base->running_timer == timer) {cpu_relax();preempt_check_resched();}break;}}smp_rmb();//如果这个timer又被调用.再删除if (timer_pending(timer))goto del_again;return ret;}定时器部份到这里就介绍完了.为了管理定时器.内核用了一个很巧妙的数据结构.值得好好的体会.。
hrtimer及内核clock

hrtimer及内核clock/timer子系统[嵌入式]发布时间:2010-06-30 09:54:39转过来,研究android过程中发现linux kernel内核定时器这块较以前2.4版本改动非常大,在网上收到这篇,粗看了下,但时间紧张没仔细看,等忙完android的这个分析,回头仔细看。
kernel-2.6.22中的arm arch加入了对dynticks, clocksource/event支持. 找了些kernelclock及timer子系统近来的变化, 总结一下.一般来说Soft-Timer (timer wheel / hrtimer)都是由Hardware-Timer(时钟中断之类)以及相关的clock source(e.g GPT in Soc)驱动,所以我打算先从clock这层开始介绍, 接着是soft-timer, kernel timekeeping,最后来看一些应用.Clock Sourceclock source定义了一个clock device的基本属性及行为, 这些clock device一般都有计数,定时, 产生中断能力, 比如GPT. 结构定义如下:struct clocksource {char *name;struct list_head list;int rating;cycle_t (*read)(void);cycle_t mask;u32 mult; /* cycle -> xtime interval, maybe two clock cy cle trigger oneinterrupt (one xtime interval) */u32 shift;unsigned long flags;cycle_t (*vread)(void);void (*resume)(void);/* timekeeping specific data, ignore */cycle_t cycle_interval; /* just the rate of GPT count p er OS HZ */u64 xtime_interval; /* xtime_interval = cycle_interv al * mult. */cycle_t cycle_last ____cacheline_aligned_in_smp; /* las t cycle in rate count */u64 xtime_nsec; /* cycle count, remain from _ns ec* now nsec rate count offset = xtime_nsec +* _nsec << shift */s64 error;};最重要的成员是read(), cycle_last和cycle_interval. 分别定义了读取clock device count寄存器当前计数值接口, 保存上一次周期计数值和每个tick周期间隔值. 这个结构内的值,无论是cycle_t, 还是u64类型(实际cycle_t就是u64)都是计数值(cycle), 而不是nsec,sec和jiffies. read()是整个kernel读取精确的单调时间计数的接口,kernel会用它来计算其他时间, 比如:jiffies, xtime.clocksource的引入, 解决了之前kernel各个arch都有自己的clock device的管理方式,基本都隐藏在MSL层, kernel core 及driver很难访问的问题. 它导出了以下接口:1) clocksource_register() 注册clocksource2) clocksource_get_next() 获取当前clocksource设备3) clocksource_read() 读取clock, 实际跑到clocksource->read()当driver处理的时间精度比较高的时, 可以通过上面的接口, 直接拿clock device来读.当然目前ticker时钟中断源也会以clocksource的形式存在.Clock EventClock event的主要作用是分发clock事件及设置下一次触发条件. 在没有clock event之前,时钟中断都是周期性地产生, 也就是熟知的jiffies和HZ.Clock Event device主要的结构:struct clock_event_device {const char *name;unsigned int features;unsigned long max_delta_ns;unsigned long min_delta_ns;unsigned long mult;int shift;int rating;int irq;cpumask_t cpumask;int (*set_next_event)(unsigned long evt,struct clock_event_device *);void (*set_mode)(enum clock_event_mode mode,struct clock_event_device *);void (*event_handler)(struct clock_event_devi ce *);void (*broadcast)(cpumask_t mask);struct list_head list;enum clock_event_mode mode;ktime_t next_event;};最重要的是set_next_event(), event_handler(). 前者是设置下一个clock事件的触发条件,一般就是往clock device里重设一下定时器. 后者是event handler, 事件处理函数.该处理函数会在时钟中断ISR里被调用. 如果这个clock用来做为ticker时钟,那么handler的执行和之前kernel的时钟中断ISR基本相同, 类似timer_tick().事件处理函数可以在运行时动态替换, 这就给kernel一个改变整个时钟中断处理方式的机会,也就给highres tick及dynamic tick一个动态挂载的机会.目前kernel内部有periodic/highres/dynamic tick三种时钟中断处理方式. 后面会介绍.hrtimer & timer wheel首先说一下timer wheel. 它就是kernel一直采用的基于jiffies的timer机制,接口包括init_timer(), mod_timer(), del_timer()等, 很熟悉把.hrtimer 的出现, 并没有抛弃老的timer wheel机制(也不太可能抛弃:)).hrtimer做为kernel里的timer定时器, 而timer wheel则主要用来做timeout定时器.分工比较明确. hrtimers采用红黑树来组织timers, 而timer wheel采用链表和桶.hrtimer精度由原来的timer wheel的jiffies提高到nanosecond.主要用于向应用层提供nanosleep, posix-timers和itimer接口,当然驱动和其他子系统也会需要high resolution的timer.kernel 里原先每秒周期性地产生HZ个ticker(中断),被在下一个过期的hrtimer的时间点上产生中断代替. 也就是说时钟中断不再是周期性的,而是由timer来驱动(靠clockevent的set_next_event接口设置下一个事件中断),只要没有hrtimer加载, 就没有中断. 但是为了保证系统时间(进程时间统计,jiffies的维护)更新, 每个tick_period(NSEC_PER_SEC/HZ,再次强调hrtimer精度是nsec)都会有一个叫做tick_sched_timer的hrtimer加载.接下来对比一下, hrtimer引入之前及之后, kernel里时钟中断的处理的不同. (这里都是基于armarch的source去分析)1)no hrtimerkernel 起来, setup_arch()之后的time_init()会去初始化相应machine结构下的timer.初始化timer函数都在各个machine的体系结构代码中, 初始化完硬件时钟, 注册中断服务函数,使能时钟中断. 中断服务程序会清中断, 调用timer_tick(), 它执行:1. profile_tick(); /* kernel profile, 不是很了解*/2. do_timer(1); /* 更新jiffies */3. update_process_times(); /* 计算进程耗时, 唤起TIMER_SOFTIRQ(timer wheel),重新计算调度时间片等等*/最后中断服务程序设置定时器, 使其在下一个tick产生中断.这样的框架, 使得high-res的timer很难加入. 所有中断处理code都在体系结构代码里被写死,并且代码重用率很低, 毕竟大多的arch都会写同样的中断处理函数.2)hrtimerkernel 里有了clockevent/source的引入, 就把clocksource的中断以一种事件的方式被抽象出来.事件本身的处理交给event handler. handler可以在kernel里做替换从而改变时钟中断的行为.时钟中断ISR会看上去象这样:static irqreturn_t timer_interrupt(int irq, void *dev_id) {/* clear timer interrupt flag */...../* call clock event handler */arch_clockevent.event_handler(&arch_clockevent);....return IRQ_HANDLED;}event_handler 在注册clockevent device时, 会被默认设置成tick_handle_periodic().所以kernel刚起来的时候, 时钟处理机制仍然是periodic的, ticker中断周期性的产生.tick_handle_periodic()会做和timer_tick差不多的事情,然后调用clockevents_program_event() =>arch_clockevent.set_next_event()去设置下一个周期的定时器.tick-common.c里把原来kernel时钟的处理方式在clockevent框架下实现了, 这就是periodictick的时钟机制.hres tick机制在第一个TIMER SOFTIRQ里会替换掉periodic tick, 当然要符合一定条件,比如command line里没有把hres(highres=off)禁止掉,clocksource/event支持hres和oneshot的能力. 这里的切换做的比较ugly,作者的comments也提到了, 每次timer softirq被调度,都要调用hrtimer_run_queues()检查一遍hres是否active,如果能在timer_init()里就把clocksource/event的条件check过, 直接切换到hres就最好了,不知道是不是有什么限制条件. TIMER SOFTIRQ代码如下:static void run_timer_softirq(struct softirq_action *h) {tvec_base_t *base = __get_cpu_var(tvec_bases);hrtimer_run_queues(); /* 有机会就切换到hres或者nohz */if (time_after_eq(jiffies, base->timer_jiffies)) __run_timers(base); /* timer wheel */}切换的过程比较简单, 用hrtimer_interrupt()替换当前clockevent hander, 加载一个hrtimer:tick_sched_timer在下一个tick_period过期, retrigger下一次事件.hrtimer_interrupt ()将过期的hrtimers从红黑树上摘下来,放到相应clock_base->cpu_base->cb_pending列表里,这些过期timers会在HRTIMER_SOFTIRQ里执行.然后根据剩余的最早过期的timer来retrigger下一个event, 再调度HRTIMER_SOFTIRQ. hrtimer softirq执行那些再cb_pending上的过期定时器函数.tick_sched_timer这个hrtimer在每个tick_period都会过期, 执行过程和timer_tick()差不多,只是在最后调用hrtimer_forward将自己加载到下一个周期里去,保证每个tick_period都能正确更新kernel内部时间统计.TimekeepingTimekeeping子系统负责更新xtime, 调整误差, 及提供get/settimeofday接口. 为了便于理解,首先介绍一些概念:Times in Kernelkernel的time基本类型:1) system timeA monotonically increasing value that represents the amount of time the system has been running. 单调增长的系统运行时间, 可以通过time source,xtime及wall_to_monotonic计算出来.2) wall timeA value representing the the human time of day, as seen on a wrist-watch.Realtime时间: xtime.3) time sourceA representation of a free running counter running at a known frequency, usually in hardware, e.g GPT. 可以通过clocksource->read()得到counter值4) tickA periodic interrupt generated by a hardware-timer, typically with a fixed interval defined by HZ: jiffies这些time之间互相关联, 互相可以转换.system_time = xtime + cyc2ns(clock->read() - clock->cycle_last) +wall_to_monotonic;real_time = xtime + cyc2ns(clock->read() - clock->cycle_last)也就是说real time是从1970年开始到现在的nanosecond, 而systemtime是系统启动到现在的nanosecond.这两个是最重要的时间, 由此hrtimer可以基于这两个time来设置过期时间. 所以引入两个clock base.Clock BaseCLOCK_REALTIME: base在实际的wall timeCLOCK_MONOTONIC: base在系统运行system timehrtimer可以选择其中之一, 来设置expire time, 可以是实际的时间, 也可以是相对系统的时间.他们提供get_time()接口:CLOCK_REALTIME 调用ktime_get_real()来获得真实时间,该函数用上面提到的等式计算出realtime.CLOCK_MONOTONIC 调用ktime_get(), 用system_time的等式获得monotonic time.timekeeping提供两个接口do_gettimeofday()/do_settimeofday(), 都是针对realtime操作. 用户空间对gettimeofday的syscall也会最终跑到这里来.do_gettimeofday()会调用__get_realtime_clock_ts()获得时间, 然后转成timeval.do_settimeofday(), 将用户设置的时间更新到xtime, 重新计算xtime到monotonic的转换值,最后通知hrtimers子系统时间变更.int do_settimeofday(struct timespec *tv){unsigned long flags;time_t wtm_sec, sec = tv->tv_sec;long wtm_nsec, nsec = tv->tv_nsec;if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC) return -EINVAL;write_seqlock_irqsave(&xtime_lock, flags);nsec -= __get_nsec_offset();wtm_sec = wall_to__sec + (_sec - se c);wtm_nsec = wall_to__nsec + (_nsec - nsec);set_normalized_timespec(&xtime, sec, nsec); /* 重新计算x time:用户设置的时间减去上一个周期到现在的nsec */set_normalized_timespec(&wall_to_monotonic, wtm_sec, w tm_nsec); /*重新调整wall_to_monotonic */clock->error = 0;ntp_clear();update_vsyscall(&xtime, clock);write_sequnlock_irqrestore(&xtime_lock, flags);/* signal hrtimers about time change */clock_was_set();return 0;}Userspace Applicationhrtimer的引入, 对用户最有用的接口如下:Clock APIclock_gettime(clockid_t, struct timespec *)获取对应clock的时间clock_settime(clockid_t, const struct timespec *)设置对应clock时间clock_nanosleep(clockid_t, int, const struct timespec *, struct timespec *)进程nano sleepclock_getres(clockid_t, struct timespec *)获取时间精度, 一般是nanosecclockid_t 定义了四种clock:CLOCK_REALTIMESystem-wide realtime clock. Setting this clock requires appropriate privileges. CLOCK_MONOTONICClock that cannot be set and represents monotonic time since some unspecified starting point.CLOCK_PROCESS_CPUTIME_IDHigh-resolution per-process timer from the CPU.CLOCK_THREAD_CPUTIME_IDThread-specific CPU-time clock.前两者前面提到了, 后两个是和进程/线程统计时间有关系, 还没有仔细研究过,是utime/stime之类的时间. 应用层可以利用这四种clock, 提高灵活性及精度.Timer APITimer 可以建立进程定时器,单次或者周期性定时。
linux内核定时器详解及实例

Linux内核定时器详解80X86体系结构上,常用的定时器电路实时时钟(RTC)RTC内核通过IRQ8上发出周期性的中断,频率在2-8192HZ之间,掉电后依然工作,内核通过访问0x70和0x71 I/O端口访问RTC。
时间戳计时器(TSC)利用CLK输入引线,接收外部振荡器的时钟信号,该计算器是利用64位的时间戳计时器寄存器来实现额,与可编程间隔定时器传递来的时间测量相比,更为精确。
可编程间隔定时器(PIT)PIT的作用类似于微波炉的闹钟,PIT永远以内核确定的固定频率发出中断,但频率不算高。
CPU本地定时器利用PIC或者APIC总线的时钟计算。
高精度时间定时器(HPET)功能比较强大,家机很少用,也不去记了。
ACPI电源管理定时器它的时钟信号拥有大约为3.58MHZ的固定频率,该设备实际上是一个简单的计数器,为了读取计算器的值,内核需要访问某个I/O端口,需要初始化定时器的数据结构利用timer_opts描述定时器Timer_opts的数据结构Name :标志定时器员的一个字符串Mark_offset :记录上一个节拍开始所经过的时间,由时钟中断处理程序调用Get_offset 返回自上一个节拍开始所经过的时间Monotonic_clock :返回自内核初始化开始所经过的纳秒数Delay:等待制定数目的“循环”定时插补就好像我们要为1小时35分34秒进行定时,我们不可能用秒表去统计,肯定先使用计算时的表,再用计算分的,最后才用秒表,在80x86架构的定时器也会使用各种定时器去进行定时插补,我们可以通过cur_timer指针来实现。
单处理器系统上的计时体系结构所有与定时有关的活动都是由IRQ线0上的可编程间隔定时器的中断触发。
初始化阶段1. 初始化间,time_init()函数被调用来建立计时体系结构2. 初始化xtime变量(xtime变量存放当前时间和日期,它是一个timespec 类型的数据结构)3. 初始化wall_to_monotonic变量,它跟xtime是同一类型的,但它存放将加在xtime上的描述和纳秒数,这样即使突发改变xtime也不会受到影响。
Linux教程第15章 内核时钟与定时器

第15章内核时钟与定时器实验目的●学习Linux系统中的时钟和定时器原理●分析理解Linux内核时间的实现机制●分析比较ITIMER_REAL、ITIMER_VIRTUAL、ITIMER_PROF●学习理解内核中各种定时器的实现机制●掌握操作定时器的命令,掌握定时器的使用实验内容针对一个计算fibonacci数的进程,设定三个定时器,获取该进程在用户模式的运行时间,在内核模式的运行时间,以及总的运行时间。
提示:setitimer()/getitimer()系统调用的使用。
ITIMER_REAL实时计数;ITIMER_VIRTUAL统计进程在用户模式(进程本身执行)执行的时间;ITIMER_PROF统计进程在用户模式(进程本身执行)和内核模式(系统代表进程执行)下的执行时间,与ITIMER_VIRTUAL比较,这个计时器记录的时间多了该进程内核模式执行过程中消耗的时间。
实验原理15.1 关于时钟和定时器一台装有操作系统的计算机里一般有两个时钟:硬件时钟和软件时钟。
硬件时钟从根本上讲是CMOS时钟,是由小型电池供电的时钟,这种电池一般可持续供电三年左右。
因为有自己的电池,所以当计算机断电的时候CMOS时钟可以继续运行,这就是为什么你的计算机总是知道正确的日期和时间的原因。
而软件时钟则是由操作系统本身维护的,所以又称系统时钟。
这个时钟是在系统启动时,读取硬件时钟获得的,而不是靠记忆计时。
在得到硬件时钟之后,就完全由系统本身维护。
之所以使用两套时钟的原因是因为硬件时钟的读取太麻烦,所消耗的时间太长。
硬件时钟的主要作用就是提供计时和产生精确时钟中断。
而软件时钟的作用则可以归纳为下面的几条:●保存正确时间。
●防止进程超额使用CPU。
●记录CPU的使用情况。
●处理用户进程发出的系统调用。
Linux内核中的jiffies

Linux内核中的jiffies定义:全局变量jiffies用来记录自系统启动以来产生的节拍的总数。
启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序都会增加该变量的值。
一秒内时钟中断的次数等于Hz,所以jiffies一秒内增加的值也就是Hz。
硬件给内核提供一个系统定时器用以计算和管理时间,内核通过编程预设系统定时器的频率,即节拍率(tick rate),每一个周期称作一个tick(节拍)。
Linux内核从2.5版内核开始把频率从100调高到1000(当然带来了很多优点,也有一些缺点).“在Linux 2.6 中,系统时钟每1 毫秒中断一次(时钟频率,用HZ 宏表示,定义为1000,即每秒中断1000 次,2.4 中定义为100,很多应用程序也仍然沿用100 的时钟频率),这个时间单位称为一个jiffie。
""jiffies 与绝对时间之间的转换, 用两个宏来完成两种时间单位的互换:JIFFIES_TO_NS()、NS_TO_JIFFIES()"jiffies是内核中的一个全局变量,用来记录自系统启动一来产生的节拍数。
譬如,如果计算系统运行了多长时间,可以用jiffies/tick rate 来计算。
jiffies定义在文件<linux/jiffies.h>中:可以利用jiffies设置超时等,譬如:jiffies的回绕wrap around当jiffies的值超过它的最大存放范围后就会发生溢出。
对于32位无符号长整型,最大取值为(2^32)-1,即429496795。
如果节拍计数达到了最大值后还要继续增加,它的值就会回绕到0。
内核提供了四个宏来帮助比较节拍计数,它们能正确的处理节拍计数回绕的问题:1./*2. * These inlines deal with timer wrapping correctly. You are3. * strongly encouraged to use them4. * 1. Because people otherwise forget5. * 2. Because if the timer wrap changes in future you won't have to6. * alter your driver code.7. *8. * time_after(a,b) returns true if the time a is after time b.9. *10. * Do this with "<0" and ">=0" to only test the sign of the result. A11. * good compiler would generate better code (and a really good compiler12. * wouldn't care). Gcc is currently neither.13. */14.#define time_after(a,b) \15. (typecheck(unsigned long, a) && \16. typecheck(unsigned long, b) && \17. ((long)(b) - (long)(a) < 0))18.#define time_before(a,b) time_after(b,a)19.20.#define time_after_eq(a,b) \21. (typecheck(unsigned long, a) && \22. typecheck(unsigned long, b) && \23. ((long)(a) - (long)(b) >= 0))24.#define time_before_eq(a,b) time_after_eq(b,a)25.26./* Same as above, but does so with platform independent 64bit types.27. * These must be used when utilizing jiffies_64 (i.e. return value of28. * get_jiffies_64() */29.#define time_after64(a,b) \30. (typecheck(__u64, a) && \31. typecheck(__u64, b) && \32. ((__s64)(b) - (__s64)(a) < 0))33.#define time_before64(a,b) time_after64(b,a)34.35.#define time_after_eq64(a,b) \36. (typecheck(__u64, a) && \37. typecheck(__u64, b) && \38. ((__s64)(a) - (__s64)(b) >= 0))39.#define time_before_eq64(a,b) time_after_eq64(b,a)内核提供了四个宏来比较节拍计数,这些宏定义在文件<linux/jiffies.h>中:用户空间和HZ问题提出:在2.6以前的内核中,如果改变内核中的HZ值会给用户空间中某些程序造成异常结果。
linux内核时钟

init_timer(&key_timer)
3、设置动态定时器的超时处理函数及超时时间
key_timer.key_timer_handle;
key_timer.data =100;//传给超时处理函数的参数
key_timer.expires = jiffies+HZ/2;
4、定义超时处理函数
6、在超时处理函数中,设置下一次的超时时间,并启动timer
int mod_timer(struct timer_list *timer, unsigned long expires);
例:
设置超时的时间是500ms后的时间,即没500ms超时一次。
void key_timer_handle(unsigned long data)
.init_irq = s5pv210_init_irq,
.map_io = gec210_map_io,
.init_machine = gec210_machine_init,
.timer = &s5p_systimer, //内核定时器的初始化--->s5p_timer_init(void)
[ 0.000000] HZ[256]
HZ的值如何设置?
#make menuconfig --->设置,如果make menuconfig中没有,去Kconfig中找。
#vi arch/arm/Kconfig
所以HZ对linux内核来说是一个全局的常数值,可以直接在代码中引用。
四、内核中的动态定时器使用
内核中,有动态的定时器,可以类似于硬件的定时器,产生周期性定时器处理。
如何使用内核的动态定时器?
Linux定时器使用

Linux定时器的使用内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于<linux/timer.h> 和kernel/timer.c 文件中。
被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则:1) 没有current 指针、不允许访问用户空间。
因为没有进程上下文,相关代码和被中断的进程没有任何联系。
2) 不能执行休眠(或可能引起休眠的函数)和调度。
3) 任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。
内核定时器的调度函数运行过一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。
在SMP系统中,调度函数总是在注册它的同一CPU上运行,以尽可能获得缓存的局域性。
内核定时器的数据结构struct timer_list {struct list_head entry;unsigned long expires;void (*function)(unsigned long);unsigned long data;struct tvec_base *base;/* ... */};其中expires 字段表示期望定时器执行的jiffies 值,到达该jiffies 值时,将调用function 函数,并传递data 作为参数。
当一个定时器被注册到内核之后,entry 字段用来连接该定时器到一个内核链表中。
base 字段是内核内部实现所用的。
需要注意的是expires 的值是32位的,因为内核定时器并不适用于长的未来时间点。
初始化在使用struct timer_list 之前,需要初始化该数据结构,确保所有的字段都被正确地设置。
初始化有两种方法。
方法一:DEFINE_TIMER(timer_name, function_name, expires_value, data);该宏会定义一个名叫timer_name 内核定时器,并初始化其function, expires, name 和base 字段。
Linux内核中的时钟中断tic...

第七章 Linux内核的时钟中断(By 詹荣开,NUDT)Copyright © 2003 by 詹荣开E-mail:***************Linux-2.4.0Version 1.0.0,2003-2-14摘要:本文主要从内核实现的角度分析了Linux 2.4.0内核的时钟中断、内核对时间的表示等。
本文是为那些想要了解Linux I/O子系统的读者和Linux驱动程序开发人员而写的。
关键词:Linux、时钟、定时器申明:这份文档是按照自由软件开放源代码的精神发布的,任何人可以免费获得、使用和重新发布,但是你没有限制别人重新发布你发布内容的权利。
发布本文的目的是希望它能对读者有用,但没有任何担保,甚至没有适合特定目的的隐含的担保。
更详细的情况请参阅GNU通用公共许可证(GPL),以及GNU自由文档协议(GFDL)。
你应该已经和文档一起收到一份GNU通用公共许可证(GPL)的副本。
如果还没有,写信给:The Free Software Foundation, Inc., 675 Mass Ave, Cambridge,MA02139, USA欢迎各位指出文档中的错误与疑问。
前言时间在一个操作系统内核中占据着重要的地位,它是驱动一个OS内核运行的“起博器”。
一般说来,内核主要需要两种类型的时间:1. 在内核运行期间持续记录当前的时间与日期,以便内核对某些对象和事件作时间标记(timestamp,也称为“时间戳”),或供用户通过时间syscall进行检索。
2. 维持一个固定周期的定时器,以提醒内核或用户一段时间已经过去了。
PC机中的时间是有三种时钟硬件提供的,而这些时钟硬件又都基于固定频率的晶体振荡器来提供时钟方波信号输入。
这三种时钟硬件是:(1)实时时钟(Real Time Clock,RTC);(2)可编程间隔定时器(Programmable Interval Timer,PIT);(3)时间戳计数器(Time Stamp Counter,TSC)。
内核定时器的使用流程

内核定时器的使用流程1. 概述内核定时器是操作系统内核提供的一种功能,用于在预定的时间间隔内执行某个操作或任务。
使用内核定时器可以实现一些重要的功能,比如定时任务、周期性数据采集等。
本文将介绍内核定时器的使用流程,帮助读者能够快速上手使用内核定时器。
2. 内核定时器的注册首先,我们需要在内核中注册一个定时器。
注册定时器的方式可能有所不同,具体取决于操作系统的内核版本和使用的编程语言。
一般来说,我们可以通过创建一个定时器对象并设置其触发时间间隔、回调函数等属性来完成定时器的注册。
下面是一个示例代码片段:timer = create_timer(interval, callback_func)start_timer(timer)在上述代码中,create_timer函数用于创建一个定时器对象,并传入触发时间间隔和回调函数。
start_timer函数则用于启动定时器。
3. 回调函数的编写在注册定时器时,我们需要为定时器绑定一个回调函数,当定时器触发时,该函数将被执行。
因此,我们需要编写一个合适的回调函数。
回调函数的编写内容会根据具体的需求而有所不同。
如果定时器用于执行特定的任务,那么回调函数就应该包含这个任务的代码实现。
下面是一个示例:def callback_func():// 执行任务代码pass在这个示例中,callback_func函数即是我们定义的回调函数,可以根据实际需求编写具体的任务代码。
4. 定时器的启动和停止一旦定时器被注册并绑定了回调函数,我们就可以开始启动定时器了。
启动定时器的方式可能会有所不同,具体取决于操作系统的内核版本和使用的编程语言。
在一些操作系统中,可以使用start_timer函数来启动定时器。
而停止定时器则可以通过stop_timer函数实现。
下面是一个示例代码片段:start_timer(timer)// 运行一段时间后stop_timer(timer)在上述代码中,start_timer函数用于启动定时器,而stop_timer函数则用于停止定时器。
linux驱动之内核定时器驱动设计

linux驱动之内核定时器驱动设计我的环境:Fedora 14 内核版本为2.6.38.1开发板:ARM9 TQ2440移植内核版本:linux-2.6.30.4定时器在linux内核中主要是采用一个结构体实现的。
但是需要注意定时器是一个只运行一次的对象,也就是当一个定时器结束以后,还需要重现添加定时器。
但是可以采用mod_timer()函数动态的改变定时器到达时间。
这个驱动主要实现内核定时器的基本操作。
内核定时器主要是是通过下面的结构体struct timer_list实现。
需要的头文件包括#include;,但是在实际开发过程中不需要包含该头文件,因为在sched.h中包含了该头文件。
struct timer_list {struct list_head entry;unsigned long expires;void (*function)(unsigned long);unsigned long data;struct tvec_base *base;#ifdef CONFIG_TIMER_STATSvoid *start_site;char start_comm[16];int start_pid;#endif#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;#endif};定时器的实现主要是该结构体的填充和部分函数的配合即可完成。
其中红色的部分是最主要的几个元素,1、expires主要是用来定义定时器到期的时间,通常采用jiffies这个全局变量和HZ这个全局变量配合设置该元素的值。
比如expires = jiffies + n*HZ,其中jiffies 是自启动以来的滴答数,HZ是一秒种的滴答数。
2、function可以知道是一个函数指针,该函数就是定时器的处理函数,类似我们在中断中的中断函数,其实定时器和中断有很大的相似性。
定时器处理函数是自己定义的函数。
Linux定时器的使用

Linux定时器的使用内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于<linux/timer.h> 和kernel/timer.c 文件中。
被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则:1) 没有current 指针、不允许访问用户空间。
因为没有进程上下文,相关代码和被中断的进程没有任何联系。
2) 不能执行休眠(或可能引起休眠的函数)和调度。
3) 任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。
内核定时器的调度函数运行过一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。
在SMP系统中,调度函数总是在注册它的同一CPU上运行,以尽可能获得缓存的局域性。
内核定时器的数据结构struct timer_list {struct list_head entry;unsigned long expires;void (*function)(unsigned long);unsigned long data;struct tvec_base *base;/* ... */};其中expires 字段表示期望定时器执行的jiffies 值,到达该jiffies 值时,将调用function 函数,并传递data 作为参数。
当一个定时器被注册到内核之后,entry 字段用来连接该定时器到一个内核链表中。
base 字段是内核内部实现所用的。
需要注意的是expires 的值是32位的,因为内核定时器并不适用于长的未来时间点。
初始化在使用struct timer_list 之前,需要初始化该数据结构,确保所有的字段都被正确地设置。
初始化有两种方法。
方法一:DEFINE_TIMER(timer_name, function_name, expires_value, data);该宏会定义一个名叫timer_name 内核定时器,并初始化其function, expires, name 和base 字段。
Linux kernel时钟机制

如果说cfs是linux的一个很有创意的机制的话,那么linux中另一个创意就是nohz,我在前面已经写了好几篇关于nohz的文章了,因此本文就不再阐述代码细节了,linux的创意在于设计而不在代码,代码主要解决的问题是实用性,就像gcc一样,就是一个编译器,应用编译原理设计而出,它内部却充实着编译原理之外的巧妙。
有血有肉才活得精彩,如果说nohz之前的linux内核是骨架的话,那么从 nohz之后,linux开始了精彩,之后几乎瞬时,cfs出现了,然后是cgroup...cgroup正式开始了虚拟容器,从此linux再也不用被 unix老大们看作是小孩子了,nohz标志着linux开始成熟起来。
nohz为何这么重要呢?因为它直接关系到了性能,直接联系着系统的心跳,在之前,系统总是被动的接受时钟中断,然后运行中断处理程序最终可能导致调度的发生,如果实在没有任务可以运行,那么就执行idle,这也许也算一种创意,可是时钟中断还是会周期性的打破idle,然后查询有没有需要做的事情,如果没有继续idle,这种方式没有什么问题,可是我们总是希望系统可以主动的做些事情,比如不是被动的接受中断而是主动的设置什么时候中断,因此必须将系统时钟发生中断这件事进行向上抽象,于是相应的clocksource和 clock_event_device,这两个结构体就是时钟以及时钟行为的抽象,clocksource代表了一个时钟源,一般都会有一个计数器,其中的read回调函数就是负责读出其计数器的值,可是我们为何找不到write或者set之类的回调函数呢?这些回调函数其实不应该在closksource中,而应该在clock_event_device中,实际上,clockevent只是一个钟,你可以类比我们用的钟表,clocksource就是一个钟表,我们需要一个钟表就是需要读出它的指针的值从而知道现在几点,就是这些,因此钟表都会有显示盘用于读数,至于钟表怎么运作,那就是钟表内部的机械原理了,记住,钟表就是用来读数的,另外我们为了害怕误事而需要闹铃,需要的是闹铃在一个时间段之后把我们唤醒,这是个事情,而这个事情不一定非要有钟表,当然钟表的读数会为我们提供有用的参考值,这样的话钟表和闹铃就解耦合了,再重申一遍,不要因为有闹钟的存在就说钟表都会响铃或者说闹铃都有钟表,它们其实是两个东西,钟表为你展示某些事情,而闹铃需要你的设置,设想一个场景,你手边有一个没有闹铃的钟表,还有一个没有钟表的闹铃,这个闹铃只能设置绝对时间,然后到期振铃,你现在不知道几点,可是你要睡觉并且得到通知必须在四个小时后去参加一个聚会,那么你现在要做什么?你肯定要看看你的钟表,然后设置你的闹钟。
Linux操作系统分析课件7

从pentium开始,很多80x86微处理器都引入 了一个TSC
一个64位的、用作时间戳计数器的寄存器 它在每个时钟信号(CLK)到来时+1
例如时钟频率400MHz的微处理器,TSC每2.5ns就+1
rdtsc指令用于读该寄存器
Linux Operating Systems Analysis 7
12
如何计算CPU的时钟频率CLK
Linux在初始化的时候,利用可编程间隔定时 器获得CPU的频率 观察calibrate_tsc(),了解如何计算CPU的频 率
已知:PIT的频率 未知:CLK频率 方法:统计在PIT已知的一段时间内(50ms), CLK发生了多少次;然后计算出CLK频率(次数 /50ms)
例如:Motorala 146818
能在IRQ8上发出周期性的中断,频率在 2HZ~8192之间
可以对其编程实现一个闹钟
Linux Operating Systems Analysis 5
xlanchen@2007.10.9
Linux本身只使用RTC获得时间和日期 对应的设备文件为/dev/rtc
Linux Operating Systems Analysis 13
xlanchen@2007.10.9
Linux的计时体系结构
Linux要周期性的执行一些任务,例如
更新系统自启动以来所经过的时间 更新时间和日期 确定进程运行了多久 检查每个软定时器是否已经到期
xlanchen@2007.10.9
xlanchen@2007.10.9
Linux Operating Systems Analysis
Linux 2.6.19.x内核编译配置选项简介(1)

Linux 2.6.19.x内核编译配置选项简介(1)Code maturity level options代码成熟度选项Prompt for development and/or incomplete code/drivers显示尚在开发中或尚未完成的代码与驱动.除非你是测试人员或者开发者,否则请勿选择General setup常规设置Local version - append to kernel release在内核版本后面加上自定义的版本字符串(小于64字符),可以用"uname -a"命令看到Automatically append version information to the version string自动在版本字符串后面添加版本信息,编译时需要有perl以及git仓库支持Support for paging of anonymous memory (swap)使用交换分区或者交换文件来做为虚拟内存System V IPCSystem V进程间通信(IPC)支持,许多程序需要这个功能.必选,除非你知道自己在做什么IPC NamespacesIPC命名空间支持,不确定可以不选POSIX Message QueuesPOSIX消息队列,这是POSIX IPC中的一部分BSD Process Accounting将进程的统计信息写入文件的用户级系统调用,主要包括进程的创建时间/创建者/内存占用等信息BSD Process Accounting version 3 file format使用新的第三版文件格式,可以包含每个进程的PID和其父进程的PID,但是不兼容老版本的文件格式Export task/process statistics through netlink通过netlink接口向用户空间导出任务/进程的统计信息,与BSD Process Accounting的不同之处在于这些统计信息在整个任务/进程生存期都是可用的Enable per-task delay accounting在统计信息中包含进程等候系统资源(cpu,IO同步,内存交换等)所花费的时间UTS NamespacesUTS名字空间支持,不确定可以不选Auditing support审计支持,某些内核模块(例如SELinux)需要它,只有同时选择其子项才能对系统调用进行审计Enable system-call auditing support支持对系统调用的审计Kernel .config support把内核的配置信息编译进内核中,以后可以通过scripts/extract-ikconfig脚本来提取这些信息Enable access to .config through /proc/config.gz允许通过/proc/config.gz访问内核的配置信息Cpuset support只有含有大量CPU(大于16个)的SMP系统或NUMA(非一致内存访问)系统才需要它Kernel->user space relay support (formerly relayfs)在某些文件系统上(比如debugfs)提供从内核空间向用户空间传递大量数据的接口Initramfs source file(s)initrd已经被initramfs取代,如果你不明白这是什么意思,请保持空白Optimize for size (Look out for broken compilers!)编译时优化内核尺寸(使用"-Os"而不是"-O2"参数编译),有时会产生错误的二进制代码Enable extended accounting over taskstats收集额外的进程统计信息并通过taskstats接口发送到用户空间Configure standard kernel features (for small systems)配置标准的内核特性(为小型系统)Enable 16-bit UID system calls允许对UID系统调用进行过时的16-bit包装Sysctl syscall support不需要重启就能修改内核的某些参数和变量,如果你也选择了支持/proc,将能从/proc/sys存取可以影响内核行为的参数或变量Load all symbols for debugging/kksymoops装载所有的调试符号表信息,仅供调试时选择Include all symbols in kallsyms在kallsyms中包含内核知道的所有符号,内核将会增大300KDo an extra kallsyms pass除非你在kallsyms中发现了bug并需要报告这个bug才打开该选项Support for hot-pluggable devices支持热插拔设备,如usb与pc卡等,Udev也需要它Enable support for printk允许内核向终端打印字符信息,在需要诊断内核为什么不能运行时选择BUG() support显示故障和失败条件(BUG和W ARN),禁用它将可能导致隐含的错误被忽略Enable ELF core dumps内存转储支持,可以帮助调试ELF格式的程序Enable full-sized data structures for core在内核中使用全尺寸的数据结构.禁用它将使得某些内核的数据结构减小以节约内存,但是将会降低性能Enable futex support快速用户空间互斥体可以使线程串行化以避免竞态条件,也提高了响应速度.禁用它将导致内核不能正确的运行基于glibc的程序Enable eventpoll support支持事件轮循的系统调用Use full shmem filesystem启用shmem支持.shmem是基于共享内存的文件系统(可能用到swap),在启用TMPFS后可以挂载为tmpfs供用户空间使用,它比简单的ramfs先进许多Use full SLAB allocator使用SLAB完全取代SLOB进行内存分配,SLAB是一种优秀的内存分配管理器,推荐使用Enable VM event counters for /proc/vmstat允许在/proc/vmstat中包含虚拟内存事件记数器Loadable module support可加载模块支持Enable loadable module support打开可加载模块支持,如果打开它则必须通过"make modules_install"把内核模块安装在/lib/modules/中Module unloading允许卸载已经加载的模块Forced module unloading允许强制卸载正在使用中的模块(比较危险)Module versioning support允许使用其他内核版本的模块(可能会出问题)Source checksum for all modules为所有的模块校验源码,如果你不是自己编写内核模块就不需要它Automatic kernel module loading让内核通过运行modprobe来自动加载所需要的模块,比如可以自动解决模块的依赖关系Block layer块设备层Enable the block layer块设备支持,使用硬盘/USB/SCSI设备者必选Support for Large Block Devices仅在使用大于2TB的块设备时需要Support for tracing block io actions块队列IO跟踪支持,它允许用户查看在一个块设备队列上发生的所有事件,可以通过blktrace 程序获得磁盘当前的详细统计数据Support for Large Single Files仅在可能使用大于2TB的文件时需要IO SchedulersIO调度器Anticipatory I/O scheduler适用于大多数环境,但不太合适数据库应用Deadline I/O scheduler通常与Anticipatory相当,但更简洁小巧,更适合于数据库应用CFQ I/O scheduler为所有进程分配等量的带宽,适合于桌面多任务及多媒体应用Default I/O scheduler默认IO调度器Processor type and features中央处理器(CPU)类型及特性Symmetric multi-processing support对称多处理器支持,如果你有多个CPU或者使用的是多核CPU就选上.此时"Enhanced Real Time Clock Support"选项必须开启,"Advanced Power Management"选项必须关闭Subarchitecture Type处理器的子架构,大多数人都应当选择"PC-compatible"Processor family处理器系列,请按照你实际使用的CPU选择Generic x86 support通用x86支持,如果你的CPU能够在上述"Processor family"中找到就别选HPET Timer SupportHPET是替代8254芯片的新一代定时器,i686及以上级别的主板都支持,可以安全的选上Maximum number of CPUs支持的最大CPU数,每增加一个内核将增加8K体积SMT (Hyperthreading) scheduler support支持Intel的超线程(HT)技术Multi-core scheduler support针对多核CPU进行调度策略优化Preemption Model内核抢占模式No Forced Preemption (Server)适合服务器环境的禁止内核抢占V oluntary Kernel Preemption (Desktop)适合普通桌面环境的自愿内核抢占Preemptible Kernel (Low-Latency Desktop)适合运行实时程序的主动内核抢占Preempt The Big Kernel Lock可以抢占大内核锁,应用于实时要求高的场合,不适合服务器环境Machine Check Exception让CPU检测到系统故障时通知内核,以便内核采取相应的措施(如过热关机等)Check for non-fatal errors on AMD Athlon/Duron / Intel Pentium 4每5秒检测一次这些cpu的非致命错误并纠正它们,同时记入日志check for P4 thermal throttling interrupt当P4的cpu过热时显示一条警告消息Enable VM86 support虚拟X86支持,在DOSEMU下运行16-bit程序或XFree86通过BIOS初始化某些显卡的时候才需要Toshiba Laptop supportToshiba笔记本模块支持Dell laptop supportDell笔记本模块支持Enable X86 board specific fixups for reboot修正某些旧x86主板的重起bug,这种主板基本绝种了/dev/cpu/microcode - Intel IA32 CPU microcode support使用不随Linux内核发行的IA32微代码,你必需有IA32微代码二进制文件,仅对Intel的CPU 有效/dev/cpu/*/msr - Model-specific register support在多cpu系统中让特权CPU访问x86的MSR寄存器/dev/cpu/*/cpuid - CPU information support能从/dev/cpu/x/cpuid获得CPU的唯一标识符(CPUID)Firmware Drivers固件驱动程序BIOS Enhanced Disk Drive calls determine boot disk有些BIOS支持从某块特定的硬盘启动(如果BIOS不支持则可能无法启动),目前大多数BIOS 还不支持BIOS update support for DELL systems via sysfs仅适用于DELL机器Dell Systems Management Base Driver仅适用于DELL机器High Memory Support最高内存支持,总内存小于等于1G的选"off",大于4G的选"64G"Memory split如果你不是绝对清楚自己在做什么,不要改动这个选项Memory model一般选"Flat Memory",其他选项涉及内存热插拔64 bit Memory and IO resources使用64位的内存和IO资源Allocate 3rd-level pagetables from highmem在内存很多(大于4G)的机器上将用户空间的页表放到高位内存区,以节约宝贵的低端内存Math emulation数学协处理器仿真,486DX以上的cpu就不要选它了MTRR (Memory Type Range Register) support打开它可以提升PCI/AGP总线上的显卡2倍以上的速度,并且可以修正某些BIOS错误Boot from EFI supportEFI是一种可代替传统BIOS的技术(目前的Grub/LILO尚不能识别它),但是现在远未普及Enable kernel irq balancing让内核将irq中断平均分配给多个CPU以进行负载均衡,但是要配合irqbanlance守护进程才行Use register arguments使用"-mregparm=3"参数编译内核,将前3个参数以寄存器方式进行参数调用,可以生成更紧凑和高效的代码Enable seccomp to safely compute untrusted bytecode只有嵌入式系统可以不选Timer frequency内核时钟频率,桌面推荐"1000 HZ",服务器推荐"100 HZ"或"250 HZ"kexec system call提供kexec系统调用,可以不必重启而切换到另一个内核kernel crash dumps被kexec启动后产生内核崩溃转储Physical address where the kernel is loaded内核加载的物理地址,除非你知道自己在做什么,否则不要修改.在提供kexec系统调用的情况下可能要修改它Support for hot-pluggable CPUs对热插拔CPU提供支持Compat VDSO support如果Glibc版本大于等于2.3.3就不选,否则就选上Power management options电源管理选项Power Management support电源管理有APM和ACPI两种标准且不能同时使用.即使关闭该选项,X86上运行的Linux也会在空闲时发出HLT指令将CPU进入睡眠状态Legacy Power Management API传统的电源管理API,比如软关机和系统休眠等接口Power Management Debug Support仅供调试使用Driver model /sys/devices/.../power/state files内核帮助文档反对使用该选项,即将被废除ACPI (Advanced Configuration and Power Interface) Support必须运行acpid守护程序ACPI才能起作用.ACPI是为了取代APM而设计的,因此应该尽量使用ACPI而不是APMAC Adapter如果你的系统可以在AC和电池之间转换就可以选Battery通过/proc/acpi/battery向用户提供电池状态信息,用电池的笔记本可以选Button守护程序捕获Power,Sleep,Lid按钮事件,并根据/proc/acpi/event做相应的动作,软件控制的poweroff需要它Video仅对集成在主板上的显卡提供ACPI2.0支持,且不是所有集成显卡都支持Generic Hotkey统一的热键驱动,建议不选Fan允许通过用户层的程序来对系统风扇进行控制(开,关,查询状态),支持它的硬件并不多Dock支持由ACPI控制的集线器(docking stations)Processor让ACPI处理空闲状态,并使用ACPI C2和C3处理器状态在空闲时节省电能,同时它还被cpufreq的"Performance-state drivers"选项所依赖Thermal Zone系统温度过高时可以利用ACPI thermal zone及时调整工作状态以避免你的CPU被烧毁ASUS/Medion Laptop ExtrasASUS笔记本专用,以提供额外按钮的支持,用户可以通过/proc/acpi/asus来打开或者关闭LCD 的背光/调整亮度/定制LED的闪烁指示等功能IBM ThinkPad Laptop ExtrasIBM ThinkPad专用Toshiba Laptop ExtrasToshiba笔记本专用Disable ACPI for systems before Jan 1st this year输入四位数的年份,在该年的1月1日前不使用ACPI的功能("0"表示一直使用)Debug Statements详细的ACPI调试信息,不搞开发就别选Power Management Timer Support这个Timer在所有ACPI兼容的平台上都可用,且不会受PM功能的影响,建议总是启用它.如果你在kernel log中看到了'many lost ticks'那就必须启用它ACPI0004,PNP0A05 and PNP0A06 Container Driver支持内存和CPU的热插拔Smart Battery System支持依赖于I2C的"智能电池".这种电池非常老旧且罕见,还与当前的ACPI标准兼容性差APM (Advanced Power Management) BIOS SupportAPM在SMP机器上必须关闭,一般来说当前的笔记本都支持ACPI,所以应尽量关闭该该选项Ignore USER SUSPEND只有NEC Versa M系列的笔记本才需要选择这一项Enable PM at boot time系统启动时即启用APM,选上这个选项能让系统自动的进行电源管理,但常常导致启动时死机Make CPU Idle calls when idle系统空闲时调用空闲指令(halt),只有老式的CPU才需要选它,且对于SMP系统必须关闭Enable console blanking using APM在屏幕空白时关闭LCD背光,事实上对所有的笔记本都无效RTC stores time in GMT将硬件时钟应该设为格林威治时间,否则视为本地时间.建议你使用GMT,这样你无须为时区的改变而担心Allow interrupts during APM BIOS calls允许APM的BIOS调用时中断,IBM Thinkpad的一些新机器需要这项.如果休眠时挂机(包括睡下去就醒不来),可以试试它Use real mode APM BIOS call to power off此驱动为某些有Bug的BIOS准备,如果你的系统不能正常关机或关机时崩溃,可以试试它CPU Frequency scaling允许动态改变CPU主频,达到省电和降温的目的,必须同时启用下面的一种governor才行Enable CPUfreq debugging允许对CPUfreq进行调试CPU frequency translation statistics通过sysfs文件系统输出CPU频率变换的统计信息CPU frequency translation statistics details输出详细的CPU频率变换统计信息Default CPUFreq governor默认的CPU频率调节器'performance' governor'性能'优先,静态的将频率设置为cpu支持的最高频率'powersave' governor'节能'优先,静态的将频率设置为cpu支持的最低频率'userspace' governor for userspace frequency scaling既允许手动调整cpu频率,也允许用户空间的程序动态的调整cpu频率(需要额外的调频软件,比如cpufreqd)'ondemand' cpufreq policy governor'立即响应',周期性的考察CPU负载并自动的动态调整cpu频率(不需要额外的调频软件),适合台式机'conservative' cpufreq governor'保守',和'ondemand'相似,但是频率的升降是渐变式的(幅度不会很大),更适合用于笔记本/PDA/AMD64环境ACPI Processor P-States driver将ACPI2.0的处理器性能状态报告给CPUFreq processor drivers以决定如何调整频率,该选项依赖于ACPI->Processor{省略的部分请按照自己实际使用的CPU选择}/proc/acpi/processor/../performance interface内核帮助文档反对使用该选项,即将被废除Relaxed speedstep capability checks放松对系统的speedstep兼容性检查,仅在某些老旧的Intel系统上需要打开Bus options (PCI, PCMCIA, EISA, MCA, ISA)总线选项PCI supportPCI支持,如果使用了PCI或PCI Express设备就必选PCI access modePCI访问模式,强列建议选"Any"(系统将优先使用"MMConfig",然后使用"BIOS",最后使用"Direct"检测PCI设备)PCI Express supportPCI Express支持(目前主要用于显卡和千兆网卡)PCI Express Hotplug driver如果你的主板和设备都支持PCI Express热插拔就可以选上Use polling mechanism for hot-plug events对热插拔事件采用轮询机制,仅用于测试目的Root Port Advanced Error Reporting support由PCI Express AER驱动程序处理发送到Root Port的错误信息Message Signaled Interrupts (MSI and MSI-X)充许设备通过PCI总线写入内存堆栈产生一个中断而不是使用默认的IRQ中断,建议不选PCI Debugging将PCI调试信息输出到系统日志里Interrupts on hypertransport devices允许本地的hypertransport设备使用中断ISA support现在基本上没有ISA的设备了,如果你有就选吧MCA support微通道总线,老旧的IBM的台式机和笔记本上可能会有这种总线NatSemi SCx200 support在使用AMD Geode处理器的机器上才可能有PCCARD (PCMCIA/CardBus) supportPCMCIA卡(主要用于笔记本)支持Enable PCCARD debugging仅供调试16-bit PCMCIA support一些老的PCMCIA卡使用16位的CardBus32-bit CardBus support当前的PCMCIA卡基本上都是32位的CardBusCardBus yenta-compatible bridge support使用PCMCIA卡的基本上都需要选择这一项,子项请按照自己实际使用的PCMCIA卡选择{省略的部分请按照自己实际使用的PCMCIA卡选择}PCI Hotplug SupportPCI热插拔支持,如果你有这样的设备就到子项中去选吧Executable file formats可执行文件格式Kernel support for ELF binariesELF是开放平台下最常用的二进制文件格式,支持动态连接,支持不同的硬件平台.除非你知道自己在做什么,否则必选Kernel support for a.out and ECOFF binaries早期UNIX系统的可执行文件格式,目前已经被ELF格式取代Kernel support for MISC binaries允许插入二进制的封装层到内核中,使用Java,.NET,Python,Lisp等语言编写的程序时需要它Networking网络Networking options网络选项Network packet debugging在调试不合格的包时加上额外的附加信息,但在遇到Dos攻击时你可能会被日志淹没Packet socket这种Socket可以让应用程序(比如tcpdump,iptables)直接与网络设备通讯,而不通过内核中的其它中介协议Packet socket: mmapped IO让Packet socket驱动程序使用IO映射机制以使连接速度更快Unix domain sockets一种仅运行于本机上的效率高于TCP/IP的Socket,简称Unix socket.许多程序都使用它在操作系统内部进行进程间通信(IPC),比如X Window和syslogTransformation user configuration interface为IPsec(可在ip层加密)之类的工具提供XFRM用户配置接口支持Transformation sub policy supportXFRM子策略支持,仅供开发者使用PF_KEY sockets用于可信任的密钥管理程序和操作系统内核内部的密钥管理进行通信,IPsec依赖于它TCP/IP networkingTCP/IP协议当然要选IP: multicasting群组广播,似乎与网格计算有关,仅在使用MBONE的时候才需要IP: advanced router高级路由,如果想做一个路由器就选吧IP: policy routing策略路由IP: equal cost multipath用于路由的基于目的地址的负载均衡IP: verbose route monitoring显示冗余的路由监控信息IP: kernel level autoconfiguration在内核启动时自动配置ip地址/路由表等,需要从网络启动的无盘工作站才需要这个东西IP: tunnelingIP隧道,将一个IP报文封装在另一个IP报文内的技术IP: GRE tunnels over IP基于IP的GRE(通用路由封装)隧道IP: multicast routing多重传播路由IP: ARP daemon support这东西尚处于试验阶段就已经被废弃了IP: TCP syncookie support抵抗SYN flood攻击的好东西,要启用它必须同时启用/proc文件系统和"Sysctl support",然后在系统启动并挂载了/proc之后执行"echo 1 >/proc/sys/net/ipv4/tcp_syncookies"命令IP: AH transformationIPsec验证头(AH)实现了数据发送方的验证处理,可确保数据既对于未经验证的站点不可用也不能在路由过程中更改IP: ESP transformationIPsec封闭安全负载(ESP)实现了发送方的验证处理和数据加密处理,用以确保数据不会被拦截/查看或复制IP: IPComp transformationIPComp(IP静荷载压缩协议),用于支持IPsecIP: IPsec transport modeIPsec传输模式,常用于对等通信,用以提供内网安全.数据包经过了加密但IP头没有加密,因此任何标准设备或软件都可查看和使用IP头IP: IPsec tunnel modeIPsec隧道模式,用于提供外网安全(包括虚拟专用网络).整个数据包(数据头和负载)都已经过加密处理且分配有新的ESP头/IP头和验证尾,从而能够隐藏受保护站点的拓扑结构IP: IPsec BEET modeIPsec BEET模式INET: socket monitoring interfacesocket监视接口,一些Linux本地工具(如:包含ss的iproute2)需要使用它TCP: advanced congestion control高级拥塞控制,如果没有特殊需求(比如无线网络)就别选了,内核会自动将默认的拥塞控制设为"Cubic"并将"Reno"作为候补IP: Virtual Server ConfigurationIP虚拟服务器允许你基于多台物理机器构建一台高性能的虚拟服务器,不玩集群就别选了The IPv6 protocol你要是需要IPv6就选吧NetLabel subsystem supportNetLabel子系统为诸如CIPSO与RIPSO之类能够在分组信息上添加标签的协议提供支持,如果你看不懂就别选了。
linux内核定时器实现机制剖析

内核定时器,分级结构,定时器迁移刷新,DEFINE_TIMER,init_timer,setup_timer,add_timer,mod_timer,del_timer1 内核定时器概述Linux内核2.4版中去掉了老版本内核中的静态定时器机制,而只留下动态定时器。
动态定时器与静态定时器这二个概念是相对于Linux内核定时器机制的可扩展功能而言的,动态定时器是指内核的定时器队列是可以动态变化的,然而就定时器本身而言,二者并无本质的区别。
考虑到静态定时器机制的能力有限,因此Linux内核2.4版中完全去掉了以前的静态定时器机制。
2.6内核为了支持SMP及CPU热插拔,对定时器相关结构又做了改动。
本文所有代码基于2.6.19内核(摘自)Linux11 struct list_head entry;12 unsigned long expires;1314 void (*function)(unsigned long);15 unsigned long data;1617 struct tvec_t_base_s *base;18}; 各数据成员的含义如下:双向链表元素entry:用来将多个定时器连接成一条双向循环队列。
expires:指定定时器到期的时间,这个时间被表示成自系统启动以来的时钟滴答计数(也即时钟节拍数)。
当一个定时器的expires值小于或等于jiffies变量时,我们就说这个定时器已经超时或到期了。
在初始化一个定时器后,通常把它的expires域设置成当前expires变量的当前值加上某个时间间隔值(以时钟滴答次数计)。
函数指针function:指向一个可执行函数。
当定时器到期时,内核就执行function所指定的函数。
data域:被内核用作function函数的调用参数。
base:当前timer所属的base。
由于考虑了SMP的情况,每个CPU都含有一个base。
2 动态内核定时器的组织结构Linux是怎样为其内核定时器机制提供动态扩展能力的呢?其关键就在于“定时器向量”的概念。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
内核2.6.X中的时钟与定时器2007年07月11日星期三 15:47时钟和定时器对Linux内核来说十分重要。
首先内核要管理系统的运行时间(uptime)和当前墙上时间(wall time),即当前实际时间。
其次,内核中大量的活动由时间驱动(time driven)。
其中一些活动是周期性的,比如调度调度器(scheduler)中的运行队列(runqueue)或者刷新屏幕这样的活动,它们以固有的频率定时发生;同时,内核要非周期性地调度某些函数在未来某个时间发生,比如推迟执行的磁盘I/O操作等。
实时时钟--------------------------------------------------------- 内核必须借助硬件来实现时间管理。
实时时钟(real time clock)是用来持久存放系统时间的设备,它与CMOS集成在一起,并通过主板电池供电,所以即便在关闭计算机系统之后,实时时钟仍然能继续工作。
系统启动时,内核读取实时时钟,将所读的时间存放在变量xtime 中作为墙上时间(wall time),xtime保存着从1970年1月1日0:00到当前时刻所经历的秒数。
虽然在Intel x86机器上,内核会周期性地将当前时间存回实时时钟中,但应该明确,实时时钟的主要作用就是在启动时初始化墙上时间xtime。
系统定时器与动态定时器--------------------------------------------------------- 周期性发生的事件都是由系统定时器(system timer)驱动。
在X86体系结构上,系统定时器通常是一种可编程硬件芯片(如8254 CMOS芯片),又称可编程间隔定时器(PIT, Programmable Interval Timer),其产生的中断就是时钟中断(timer interrupt)。
时钟中断对应的处理程序负责更新系统时间和执行周期性运行的任务。
系统定时器的频率称为节拍率(tick rate),在内核中表示为HZ。
以X86为例,在2.4之前的内核中其大小为100;从内核2.6开始,HZ = 1000,也就是说每秒时钟中断发生1000次。
这一变化使得系统定时器的精度(resolution)由10ms提高到1ms,这大大提高了系统对于时间驱动事件调度的精确性。
过于频繁的时钟中断不可避免地增加了系统开销(overhead),但是总的来说,在现在计算机系统上,HZ = 1000不会导致难以接受的系统开销。
与系统定时器相对的是动态定时器(dynamic timer),它是调度事件(执行调度程序)在未来某个时刻发生的时机。
内核可以动态地创建或销毁动态定时器。
系统定时器及其中断处理程序是内核管理机制的中枢,下面是一些利用系统定时器周期执行的工作(中断处理程序所做的工作):(1) 更新系统运行时间(uptime)(2) 更新当前墙上时间(wall time)(3) 在对称多处理器系统(SMP)上,均衡调度各处理器上的运行队列(4) 检查当前进程是否用完了时间片(time slice),如果用尽,则进行重新调度(5) 运行超时的动态定时器(6) 更新资源耗尽和处理器时间的统计值内核动态定时器依赖于系统时钟中断,因为只有在系统时钟中断发生后内核才会去检查当前是否有超时的动态定时器。
X86体系结构中时钟资源还包括CPU本地APIC(local Advanced Programmable Interrupt Controller)中的定时器和时间戳计时器TSC(Time Stamp Counter)。
高精度定时器将使用CPU本地APIC作为高精度定时中断源。
高精度定时器的设计与实现---------------------------------------------------------X86体系结构中,内核2.6.X的HZ = 1000,即系统时钟中断执行粒度为1ms,这意味着系统中周期事情最快为1ms执行一次,而不可能有更高的精度。
动态定时器随时都可能超时,但由于只有在系统时钟中断到来时内核才会检查执行超时的动态定时器,所以动态定时器的平均误差大约为半个系统时钟周期(即0.5ms).对于实时要求较高的电信应用来说,普通Linux在实时性方面与电信平台的要求之间还存在一定的差距。
CGL为了增强Linux的软实时能力,在以下方面对内核进行了改进:提供高精度的动态定时器;提供可抢占式内核(preemption kernel)等。
下面主要介绍高精度实时器的设计思想及实现,该实现遵循PISIX 1003.1b中时钟和定时器相关API标准,方便应用程序开发人员的使用。
高精度定时器的基本设计思想为:用(jiffies+sub_jiffie)表示动态定时器的超时时间,PIT仍然按频率HZ = 1000产生系统时钟中断。
如果在一个时钟中断tick与下一个时钟中断(tick+1)之间,即[jiffies, jiffies+1)之间,有高精度动态定时器等待处理(超时时间表示为(jiffies+sub_jiffie), sub_jiffie < 1),那么用最近的动态定时器超时值sub_jiffie对硬件定时器(PIT或local APIC)进行设定,使其在时刻(jiffies+sub_jiffie)产生中断,通知内核对该高精度定时器进行处理。
而不必总是等到系统时钟中断到来后才检查执行所有超时的定时器,从而达到提高动态定时器精度的目的。
高精度定时器的中断源--------------------------------------------------------- 高精度定时器在内核中,仍然使用可编程间隔定时器PIC产生每秒HZ次的系统时钟中断,对于采用哪种硬件定时器产生高精度定时器中断则取决于CPU上是否有本地APIC。
若CPU上没有本地APIC,那么仍可使用PIT产生高精度定时中断,虽然这种然PIT即产生系统时钟中断又产生高精度定时器中断的做法效率不高。
获取高精度定时器的发生时间--------------------------------------------------------- 高精度定时器发生在连续两个jiffies之间(即时刻(jiffies+sub_jiffie)),要确定其产生时间,就必须确定sub_jiffie 的大小。
通过函数get_arch_cycles(ref_jiffies)可获取sub_jiffie 的值,sub_jiffe以CPU时钟周期为最小计时单位。
函数具体实现思想是,通过访问计数器TSC,计算从上一个jiffies到当前时刻ref_jiffies之间的TSC差值,最终确定sub_jiffies的大小。
The high-resolution timer API---------------------------------------------------------Last September, this page featured an article on the ktimers patch by Thomas Gleixner. The new timer abstraction was designedto enable the provision of high-resolution timers in the kernel and to address some of the inefficiencies encountered when the current timer code is used in this mode. Since then, there has been a large amount of discussion, and the code has seen significant work. The end product of that work, now called "hrtimers," was merged for the 2.6.16 release.At its core, the hrtimer mechanism remains the same. Rather than using the "timer wheel" data structure, hrtimers live on a time-sorted linked list, with the next timer to expire being at the head of the list. A separate red/black tree is also used to enable the insertion and removal of timer events without scanning through the list. But while the core remains the same, just about everything else has changed, at least superficially.struct ktime_t-----------------------------There is a new type, ktime_t, which is used to store a time value in nanoseconds. This type, found in <linux/ktime.h>, is meant to be used as an opaque structure. And, interestingly, its definition changes depending on the underlying architecture. On64-bit systems, a ktime_t is really just a 64-bit integer value in nanoseconds. On 32-bit machines, however, it is a two-field structure: one 32-bit value holds the number of seconds, and the other holds nanoseconds. The order of the two fields depends on whether the host architecture is big-endian or not; they are always arranged so that the two values can, when needed, be treated as a single, 64-bit value. Doing things this way complicates the header files, but it provides for efficient time value manipulation on all architectures.struct--ktime_ttypedef union {On 64-bit systems|----------------------|| s64 tv64; ||----------------------|On 32-bit machines|----------------------|| struct { || s32 sec, nsec; || s32 nsec, sec; || } tv; ||----------------------|} ktime_t;初始化ktime_t-----------------------------A whole set of functions and macros has been provided for working with ktime_t values, starting with the traditional two ways to declare and initialize them(ktime_t values):(1) Initialize to zeroDEFINE_KTIME(name);(2) ktime_t kt;kt = ktime_set(long secs, long nanosecs);初始化高精度时钟hrtimer-----------------------------The interface for hrtimers can be found in <linux/hrtimer.h>.A timer is represented by struct hrtimer, which must be initialized with:void hrtimer_init(struct hrtimer *timer, clockid_twhich_clock);System clocks-----------------------------Every hrtimer is bound to a specific clock. The system currently supports two clocks, being:* CLOCK_MONOTONIC: a clock which is guaranteed always to move forward in time, but which does not reflect "wall clock time" in any specific way. In the current implementation, CLOCK_MONOTONIC resembles the jiffies tick count in that it starts at zero when the system boots and increases monotonically from there.* CLOCK_REALTIME which matches the current real-world time.The difference between the two clocks can be seen when the system time is adjusted, perhaps as a result of administrator action, tweaking by the network time protocol code, or suspending and resuming the system. In any of these situations,CLOCK_MONOTONIC will tick forward as if nothing had happened, while CLOCK_REALTIME may see discontinuous changes. Which clock should be used will depend mainly on whether the timer needs to be tied to time as the rest of the world sees it or not. The call to hrtimer_init() will tie an hrtimer to a specific clock, but that clock can be changed with:void hrtimer_rebase(struct hrtimer *timer, clockid_tnew_clock);hrtimer_start()-----------------------------Actually setting a timer is accomplished with:int hrtimer_start(struct hrtimer *timer,ktime_t time,enum hrtimer_mode mode);The mode parameter describes how the time parameter should be interpreted. A mode of HRTIMER_ABS indicates that time is an absolute value, while HRTIMER_REL indicates that time should be interpreted relative to the current time.。