Linux内核定时器综述.docx
- 文档编号:8202389
- 上传时间:2023-01-29
- 格式:DOCX
- 页数:39
- 大小:40.64KB
Linux内核定时器综述.docx
《Linux内核定时器综述.docx》由会员分享,可在线阅读,更多相关《Linux内核定时器综述.docx(39页珍藏版)》请在冰豆网上搜索。
Linux内核定时器综述
目录
linux内核定时器的实现和使用1
使用:
1
实现原理2
深入剖析linux内核的定时器实现机制-动态刷新维护18
linux内核定时器的实现和使用
使用:
LINUX内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于
被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则:
1)没有current指针、不允许访问用户空间。
因为没有进程上下文,相关代码和被中断的进程没有任何联系。
2)不能执行休眠(或可能引起休眠的函数)和调度。
3)任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。
内核定时器的调度函数运行过一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。
在SMP系统中,调度函数总是在注册它的同一CPU上运行,以尽可能获得缓存的局域性。
定时器API
内核定时器的数据结构
structtimer_list{
structlist_headentry;
unsignedlongexpires;
void(*function)(unsignedlong);
unsignedlongdata;
structtvec_base*base;
/*...*/
};
其中expires字段表示期望定时器执行的jiffies值,到达该jiffies值时,将调用function函数,并传递data作为参数。
当一个定时器被注册到内核之后,entry字段用来连接该定时器到一个内核链表中。
base字段是内核内部实现所用的。
需要注意的是expires的值是32位的,因为内核定时器并不适用于长的未来时间点。
初始化
在使用structtimer_list之前,需要初始化该数据结构,确保所有的字段都被正确地设置。
初始化有两种方法。
方法一:
DEFINE_TIMER(timer_name,function_name,expires_value,data);
该宏会静态创建一个名叫timer_name内核定时器,并初始化其function,expires,name和base字段。
方法二:
structtimer_listmytimer;
setup_timer(&mytimer,(*function)(unsignedlong),unsignedlongdata);
mytimer.expires=jiffies+5*HZ;
方法3:
structtimer_listmytimer;
init_timer(&mytimer);
mytimer->timer.expires=jiffies+5*HZ;
mytimer->timer.data=(unsignedlong)dev;
mytimer->timer.function=&corkscrew_timer;/*timerhandler*/
通过init_timer()动态地定义一个定时器,此后,将处理函数的地址和参数绑定给一个timer_list,
注意,无论用哪种方法初始化,其本质都只是给字段赋值,所以只要在运行add_timer()之前,expires,function和data字段都可以直接再修改。
关于上面这些宏和函数的定义,参见include/linux/timer.h。
注册
定时器要生效,还必须被连接到内核专门的链表中,这可以通过add_timer(structtimer_list*timer)来实现。
重新注册
要修改一个定时器的调度时间,可以通过调用mod_timer(structtimer_list*timer,unsignedlongexpires)。
mod_timer()会重新注册定时器到内核,而不管定时器函数是否被运行过。
注销
注销一个定时器,可以通过del_timer(structtimer_list*timer)或del_timer_sync(structtimer_list*timer)。
其中del_timer_sync是用在SMP系统上的(在非SMP系统上,它等于del_timer),当要被注销的定时器函数正在另一个cpu上运行时,del_timer_sync()会等待其运行完,所以这个函数会休眠。
另外还应避免它和被调度的函数争用同一个锁。
对于一个已经被运行过且没有重新注册自己的定时器而言,注销函数其实也没什么事可做。
inttimer_pending(conststructtimer_list*timer)
这个函数用来判断一个定时器是否被添加到了内核链表中以等待被调度运行。
注意,当一个定时器函数即将要被运行前,内核会把相应的定时器从内核链表中删除(相当于注销)
实现原理
由于linux还不是一个实时的操作系统,因此如果需要更高精度,或者更精确的定时的话,可能就需要打一些实时的补丁,或者用商用版的实时linux,.
这里内的定时器最小间隔也就是1个tick.
这里还有一个要注意的,我这里的分析并没有分析内核新的hrt定时器.这个定时器是MontaVista加入到内核的一个高精度的定时器的实现.
先来看几个相关的数据结构.
///这个是一个最主要的数据结构,表示一个完整的定时器级联表
Java代码
1.struct tvec_base {
2.///自旋锁
3.spinlock_t lock;
4.///表示由本地cpu正在处理的定时器链表
5.struct timer_list *running_timer;
6.///这个表示当前的定时器级联表中最快要超时的定时器的jiffer
7.unsigned long timer_jiffies;
8.///下面表示了5级的定时器级联表.
9.struct tvec_root tv1;
10.struct tvec tv2;
11.struct tvec tv3;
12.struct tvec tv4;
13.struct tvec tv5;
14.} ____cacheline_aligned;
structtvec_base{
///自旋锁
spinlock_tlock;
///表示由本地cpu正在处理的定时器链表
structtimer_list*running_timer;
///这个表示当前的定时器级联表中最快要超时的定时器的jiffer
unsignedlongtimer_jiffies;
///下面表示了5级的定时器级联表.
structtvec_roottv1;
structtvectv2;
structtvectv3;
structtvectv4;
structtvectv5;
}____cacheline_aligned;
下面来看tvec和tvec_root的结构:
Java代码
1.struct tvec {
2.struct list_head vec[TVN_SIZE];
3.};
4.
5.struct tvec_root {
6.struct list_head vec[TVR_SIZE];
7.};
structtvec{
structlist_headvec[TVN_SIZE];
};
structtvec_root{
structlist_headvec[TVR_SIZE];
};
可以看到这两个结构也就是hash链表.每次通过超时jiffies来计算slot,然后插入到链表.这里链表是FIFO的.这里除了tv5外其他几个都是简单的与TVR_MASK按位与计算.
Java代码
1.struct timer_list {
2.struct list_head entry;
3.///超时节拍数
4.unsigned long expires;
5.///定时器将要执行的回调函数
6.void (*function)(unsigned long);
7.///传递给回调函数的参数
8.unsigned long data;
9.///从属于那个base
10.struct tvec_base *base;
11.};
structtimer_list{
structlist_headentry;
///超时节拍数
unsignedlongexpires;
///定时器将要执行的回调函数
void(*function)(unsignedlong);
///传递给回调函数的参数
unsignedlongdata;
///从属于那个base
structtvec_base*base;
};
///定义了一个percpu变量.这里要知道定时器的注册和触发执行一定是在相同的cpu上的.
structtvec_baseboot_tvec_bases;
staticDEFINE_PER_CPU(structtvec_base*,tvec_bases)=&boot_tvec_bases;
内核注册定时器最终都会通过调用internal_add_timer来实现.具体的工作方式是这样的:
1如果定时器在接下来的0~255个jiffies中到期,则将定时器添加到tv1.
2如果定时器是在接下来的256*64个jiffies中到期,则将定时器添加到tv2.
3如果定时器是在接下来的256*64*64个jiffies中到期,则将定时器添加到tv3.
4如果定时器是在接下来的256*64*64*64个jiffies中到期,则将定时器添加到tv4.
5如果更大的超时,则利用0xffffffff来计算hash,然后插入到tv5(这个只会出现在64的系统).
看下面的图就能比较清晰了:
接下来看源码:
Java代码
1.static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
2.{
3.///取出超时jiffies
4. unsigned long expires = timer->expires;
5.///得到定时器还有多长时间到期(这里是相比于最短的那个定时器)
6. unsigned long idx = expires - base->timer_jiffies;
7. struct list_head *vec;
8.
9.///开始判断该把定时器加入到那个队列.依次为tv1到tv5
10. if (idx < TVR_SIZE) {
11. int i = expires & TVR_MASK;
12. vec = base->tv1.vec + i;
13. } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
14. int i = (expires >> TVR_BITS) & TVN_MASK;
15. vec = base->tv2.vec + i;
16. } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
17. int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
18. vec = base->tv3.vec + i;
19. } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
20. int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
21. vec = base->tv4.vec + i;
22. } else if ((signed long) idx < 0) {
23. /*
24. * Can happen if you add a timer with expires == jiffies,
25. * or you set a timer to go off in the past
26. */
27. vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
28. } else {
29. int i;
30. /* If the timeout is larger than 0xffffffff on 64-bit
31. * architectures then we use the maximum timeout:
32. */
33. if (idx > 0xffffffffUL) {
34. idx = 0xffffffffUL;
35. expires = idx + base->timer_jiffies;
36. }
37. i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
38. vec = base->tv5.vec + i;
39. }
40. /*
41. * Timers are FIFO:
42. */
43.///最终加入链表
44. list_add_tail(&timer->entry, vec);
45.}
staticvoidinternal_add_timer(structtvec_base*base,structtimer_list*timer)
{
///取出超时jiffies
unsignedlongexpires=timer->expires;
///得到定时器还有多长时间到期(这里是相比于最短的那个定时器)
unsignedlongidx=expires-base->timer_jiffies;
structlist_head*vec;
///开始判断该把定时器加入到那个队列.依次为tv1到tv5
if(idx inti=expires&TVR_MASK; vec=base->tv1.vec+i; }elseif(idx<1<<(TVR_BITS+TVN_BITS)){ inti=(expires>>TVR_BITS)&TVN_MASK; vec=base->tv2.vec+i; }elseif(idx<1<<(TVR_BITS+2*TVN_BITS)){ inti=(expires>>(TVR_BITS+TVN_BITS))&TVN_MASK; vec=base->tv3.vec+i; }elseif(idx<1<<(TVR_BITS+3*TVN_BITS)){ inti=(expires>>(TVR_BITS+2*TVN_BITS))&TVN_MASK; vec=base->tv4.vec+i; }elseif((signedlong)idx<0){ /* *Canhappenifyouaddatimerwithexpires==jiffies, *oryousetatimertogooffinthepast */ vec=base->tv1.vec+(base->timer_jiffies&TVR_MASK); }else{ inti; /*Ifthetimeoutislargerthan0xffffffffon64-bit *architecturesthenweusethemaximumtimeout: */ if(idx>0xffffffffUL){ idx=0xffffffffUL; expires=idx+base->timer_jiffies; } i=(expires>>(TVR_BITS+3*TVN_BITS))&TVN_MASK; vec=base->tv5.vec+i; } /* *TimersareFIFO: */ ///最终加入链表 list_add_tail(&timer->entry,vec); } 这里要知道内核中的软定时器是用软中断来实现的,软中断的注册以及实现可以看我前面的blog,这里就不介绍了.我们来看timer模块的初始化: Java代码 1.void __init init_timers(void) 2.{ 3.///主要是初始化boot_tvec_bases(如果是smp,则会初始化所有cpu上的boot_tvec_bases) 4. int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE, 5. (void *)(long)smp_processor_id()); 6. init_timer_stats(); 7. 8. BUG_ON(err == NOTIFY_BAD); 9.///注册到cpu的notify chain(这个我前面的blog也有介绍) 10. register_cpu_notifier(&timers_nb); 11.///注册软中断 12. open_softirq(TIMER_SOFTIRQ, run_timer_softirq); 13.} void__initinit_timers(void) { ///主要是初始化boot_tvec_bases(如果是smp,则会初始化所有cpu上的boot_tvec_bases) interr=timer_cpu_notify(&timers_nb,(unsignedlong)CPU_UP_PREPARE, (void*)(long)smp_processor_id()); init_timer_stats(); BUG_ON(err==NOTIFY_BAD); ///注册到cpu的notifychain(这个我前面的blog也有介绍) register_cpu_notifier(&timers_nb); ///注册软中断 open_softirq(TIMER_SOFTIRQ,run_timer_softirq); } ok,接下来我们就来看timer_cpu_notify这个函数,其实这个函数还是定时器注册的cpu的notifychain的action: Java代码 1.static struct notifier_block __cpuinitdata timers_nb = { 2. .notifier_call = timer_cpu_notify, 3.}; 4. 5.static int __cpuinit timer_cpu_notify(struct notifier_block *self, 6. unsigned long action, void *hcpu) 7.{ 8. long cpu = (long)hcpu; 9. switch(action) { 10. case CPU_UP_PREPARE: 11. case CPU_UP_PREPARE_FROZEN: 12.///模块初始化的时候就会调用这个函数 13. if (init_timers_cpu(cpu) < 0) 14. return NOTIFY_BAD; 15. break; 16..................................... 17. return NOTIFY_OK; 18.} staticstructnotifier_block__cpuinitdatatimers_nb={ .notifier_call=timer_cpu_notify, }; staticint__cpuinittimer_cpu_notify(structnotifier_block*self, unsignedlongaction,void*hcpu) { longcpu=(long)hcpu; switch(action){ caseCPU_UP_PREPARE: caseCPU_UP_PREPARE_FROZEN: ///模块初始化的时候就会调用这个函数 if(init_timers_cpu(cpu)<0) returnNOTIFY_BAD; b
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Linux 内核 定时器 综述