内核2.6.X中的时钟与定时器

合集下载

时间篇之linux系统时间和RTC时间

时间篇之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详解

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

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内核定时器详解及实例

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章 内核时钟与定时器

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

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内核时钟

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定时器使用

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 字段。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 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.。

相关文档
最新文档