Linux内核系统定时器TIMER实现过程分析

转自http://blog.youkuaiyun.com/yyplc/article/details/7065722

Linux系统定时器,在内核中扮演着重要角色。内核的许多重要实现如任务调度,工作队列等均以系统定时器关系密切。系统定时器能以可编程的频率中断处理,这一中断叫做软中断。此频率即为每秒的定时器节拍数HZ。HZ的越大,说明定时器节拍越小,线程调度的准确性会越高。但HZ设得过大,对一个系统来说并不好,会导CPU开销过大,反而造成任务调度效率降低。滴答jiffies 变量记录系统启动以来,系统定时器已经触发的次数。也就是说每过一秒jiffies的增量为HZ,一般HZ=100,HZ是可以配置的,在S3C2440 arm linux中配置为200.

下面基于Linux2.6.30.4源码来探讨其实现原理及其过程。

要理解系统定时器实现原理,先来看看关系到系统定时器的各种数据结构,其具体的描述参数。

结构体structtimer_list来描述timer的参数,其数据结构如下:

  1. structtimer_list{
  2. structlist_headentry;//timer双向链表
  3. unsignedlongexpires;//timer超时变量
  4. void(*function)(unsignedlong);//timer超时回调函数
  5. unsignedlongdata;//传递给回调函数的数据,也就是定时器数据
  6. structtvec_base*base;//timerbase向量表用于timer_list的挂载和链表管理
  7. //timer的一些扩展参数
  8. #ifdefCONFIG_TIMER_STATS
  9. void*start_site;
  10. charstart_comm[16];
  11. intstart_pid;
  12. #endif
  13. #ifdefCONFIG_LOCKDEP
  14. structlockdep_maplockdep_map;
  15. #endif
  16. };

其中:

  1. list_entry结构:
  2. structlist_head{
  3. structlist_head*next,*prev;
  4. };
  5. tevc_base的结构:
  6. structtvec_base{
  7. spinlock_tlock;//自旋锁lock
  8. structtimer_list*running_timer;//指向已经挂载进来的timer_list
  9. unsignedlongtimer_jiffies;//timerjiffies用于记录定时器当前jiffies
  10. structtvec_roottv1;//5组tvec_base,从tv1~tv5,成员数各不相同
  11. structtvectv2;//其成员数TVR_SIZE,TVN_SIZE决定
  12. structtvectv3;
  13. structtvectv4;
  14. structtvectv5;
  15. }____cacheline_aligned;
  16. #defineTVN_BITS(CONFIG_BASE_SMALL?4:6)
  17. #defineTVR_BITS(CONFIG_BASE_SMALL?6:8)
  18. #defineTVN_SIZE(1<<TVN_BITS)
  19. #defineTVR_SIZE(1<<TVR_BITS)
  20. #defineTVN_MASK(TVN_SIZE-1)
  21. #defineTVR_MASK(TVR_SIZE-1)
  22. structtvec{
  23. structlist_headvec[TVN_SIZE];//tv2~t5个数为64的数组
  24. };
  25. structtvec_root{
  26. structlist_headvec[TVR_SIZE];//tv1个数为256的数组
  27. };

可见涉及到系统定时器的数据结构并不多,那么:对于一个linux系统中,定时器个数可能会很多,而且每个定时器的超时事件时间并不相同,所以如何管理和处理定时器超时事件,关系到内核性能的高低。它根据不同的定时事件,按时间间分组,将新增的timer定时器建成双向链表,然后按照一定方式存放于5组tv1~tv5变量中称为tec_base。对于对称式多理器(SMP)系统还考虑到了TIMER从一个CPU迁移到另一个CPU的情况,相应的tev_base也跟着更改。那它在系统是怎样实现的呢?现在先从一个简单的系统定时器应用例子来看看它的实现过程:

  1. #include<linux/init.h>
  2. #include<linux/module.h>
  3. #include<linux/timer.h>
  4. structtimer_listmy_timer;
  5. staticvoidmy_function(unsignedlongdata)
  6. {
  7. staticinti=0;
  8. printk("timer’scallbackfunction\n");
  9. printk("timer’sdata=%lu\n",data);
  10. return;
  11. }
  12. staticintmy_timer_init(void)
  13. {
  14. printk(“timerinit…\n”);
  15. my_timer.data=0xff;
  16. my_timer.function=my_function;
  17. my_timer.expires=jiffies+3*HZ;
  18. init_timer(&my_timer);
  19. add_timer(&my_timer);
  20. return0;
  21. }
  22. staticvoidmy_timer_exit(void)
  23. {
  24. printk("timerexit…\n");
  25. }
  26. module_init(my_timer_init);
  27. module_exit(my_timer_exit);
  28. MODULE_AUTHOR("itspy");
  29. MODULE_LICENSE("GPL");
  30. MODULE_DESCRIPTION("linuxkerneltimerprogramming");

上面例子,实现了一个定时器事件,将在3 HZ(秒)发生。my_imer_init函数中调用到的定时器API只有:

init_timer(&my_timer); //用于定时器初始化

add_timer(&my_timer); //增加一个新的定时器到tev_base向量表中

其中init_timer中调用了__init_timer(),这个函数才是真正初始化定时器的:

  1. staticvoid__init_timer(structtimer_list*timer,
  2. constchar*name,
  3. structlock_class_key*key)
  4. {
  5. timer->entry.next=NULL;//对于新增的timer实例,其下一各总是指向NULL。
  6. timer->base=__raw_get_cpu_var(tvec_bases);//SMP中,获得当前处理器的tev_base
  7. //这个tev_bases是根据一定规律变化的,稍后会将到
  8. }

新增的定时器初始化,就是完成了一个timer_list结构初始化过程。

add_timer() --> mod_timer() --> __mod_timer()

其中:
  1. staticinlineint
  2. __mod_timer(structtimer_list*timer,unsignedlongexpires,boolpending_only)
  3. {
  4. structtvec_base*base,*new_base;
  5. unsignedlongflags;
  6. intret;
  7. ret=0;
  8. BUG_ON(!timer->function);//BUG检测,确保回调函数为非空NULL
  9. base=lock_timer_base(timer,&flags);//获取本地cpu的tev_base,这是一个临
  10. //界资源,里边是一个for(;;)循环,如果找不到说明已经迁移到了别的CPU
  11. if(timer_pending(timer)){//当已挂载的timer定时超时发生后,会被卸载摘除
  12. detach_timer(timer,0);
  13. ret=1;
  14. }else{
  15. if(pending_only)//新增一个定时器时,pending_only为false
  16. gotoout_unlock;
  17. }
  18. new_base=__get_cpu_var(tvec_bases);//获取本地cpu中的tevc_bases
  19. if(base!=new_base){//由于之前base可能已被迁移到其他CPU的tev_base向量表,会造成base!=new_base
  20. if(likely(base->running_timer!=timer)){//由于在timer正在运行时,我们不能直接更改base,位与一个叫做DEFERRABLE(可延后标志)后处理
  21. /*Seethecommentinlock_timer_base()*/
  22. timer_set_base(timer,NULL);
  23. spin_unlock(&base->lock);
  24. base=new_base;
  25. spin_lock(&base->lock);
  26. timer_set_base(timer,base);
  27. }
  28. }
  29. timer->expires=expires;
  30. internal_add_timer(base,timer);//分析timerexpires及建表过程
  31. out_unlock:
  32. spin_unlock_irqrestore(&base->lock,flags);
  33. returnret;
  34. }

对于新增的timer最后调用internal_add_timer(base, timer)加入到相应的timer_list中以完成初始化过程。,这是一个建表的过程,表的建立情况,关系到表的管理效率。之前我们说到它共有tv1~tv5 组,tv1是一个很特别的组。每个tv中有各个组员,每个timer是如何添加的呢,看看internal_add_timer()的实现过程:

  1. staticvoidinternal_add_timer(structtvec_base*base,structtimer_list*timer)
  2. {
  3. unsignedlongexpires=timer->expires;
  4. unsignedlongidx=expires-base->timer_jiffies;
  5. structlist_head*vec;
  6. if(idx<TVR_SIZE){
  7. inti=expires&TVR_MASK;
  8. vec=base->tv1.vec+i;
  9. }elseif(idx<1<<(TVR_BITS+TVN_BITS)){
  10. inti=(expires>>TVR_BITS)&TVN_MASK;
  11. vec=base->tv2.vec+i;
  12. }elseif(idx<1<<(TVR_BITS+2*TVN_BITS)){
  13. inti=(expires>>(TVR_BITS+TVN_BITS))&TVN_MASK;
  14. vec=base->tv3.vec+i;
  15. }elseif(idx<1<<(TVR_BITS+3*TVN_BITS)){
  16. inti=(expires>>(TVR_BITS+2*TVN_BITS))&TVN_MASK;
  17. vec=base->tv4.vec+i;
  18. }elseif((signedlong)idx<0){
  19. /*
  20. *Canhappenifyouaddatimerwithexpires==jiffies,
  21. *oryousetatimertogooffinthepast
  22. */
  23. vec=base->tv1.vec+(base->timer_jiffies&TVR_MASK);
  24. }else{
  25. inti;
  26. /*Ifthetimeoutislargerthan0xffffffffon64-bit
  27. *architecturesthenweusethemaximumtimeout:
  28. */
  29. if(idx>0xffffffffUL){
  30. idx=0xffffffffUL;
  31. expires=idx+base->timer_jiffies;
  32. }
  33. i=(expires>>(TVR_BITS+3*TVN_BITS))&TVN_MASK;
  34. vec=base->tv5.vec+i;
  35. }
  36. /*
  37. *TimersareFIFO:
  38. */
  39. list_add_tail(&timer->entry,vec);
  40. }

通过代码我们知道它的过程是这样的:首先它根据每个timer中的超时差值idx来决定timer所处的tev_base组别tv1~tv5.所以超时事件越后发生,那么它所处的组位置越靠后。对于tv1组,超时插值idx为0~255之间。差值idx即为所属组tv1中的数组下标。从中可知tv1组中相邻定时器的超时事件间隔1 jiffies发生。对于tv2组,超时差值idx为 256~2^14(16386) 之间,组中相邻定时时器超事件时间隔256^1 = 256 jiffies发生。以此类推,tv3 组超时差值idx为(16387~2^20)之间,组中相邻定时器超时时间间隔256^2 = 65536 jiffies发生 … 最后,新增的timer加入到当前节点(超时差值相等)的尾部list_add_tail()形成一个双向链表。这样分组timer双向链表方便了后面对定时器的迁移更新管理过程,以及最终提高了CPU的处理效率,因为在__run_timers()时,我们只需扫描tv1组中即将到来的定时器事件就行了。

我们知道启动过程时start_kernel()对定时器的初始化是这样的 :

init_timers() -> run_timer_softirq() -> __run_timers()…

timer_interrupt() ->update_process_times() ->run_local_timers() ->raise_softirq(TIMER_SOFTIRQ);

之前我写的一篇《内核窥秘之一:start_kernel()运行过程记录》也有提到过.

__run_timers()是系统定时器超时事件的服务程序。这是run_timer_softirq()中一部分,是通过软中断的实现的,它是在软中断下半部处理的。

  1. staticinlinevoid__run_timers(structtvec_base*base)
  2. {
  3. while(time_after_eq(jiffies,base->timer_jiffies)){//确定当前tvec_base->timer_jiffies是否有效
  4. structlist_headwork_list;
  5. structlist_head*head=&work_list;
  6. intindex=base->timer_jiffies&TVR_MASK;//只需扫描tv1组,看当前jiffies时刻是否有超时发生
  7. if(!index&&//cascade()定时器队列级联函数实现了
  8. (!cascade(base,&base->tv2,INDEX(0)))&&//tv5~tv2组迁移过程
  9. (!cascade(base,&base->tv3,INDEX(1)))&&
  10. !cascade(base,&base->tv4,INDEX(2)))
  11. cascade(base,&base->tv5,INDEX(3));
  12. ++base->timer_jiffies;//更新当前tvec_base->timer_jiffies
  13. list_replace_init(base->tv1.vec+index,&work_list);//链表更新新、旧取代
  14. while(!list_empty(head)){//判定是否有定时器超时事件发生,非空为有,知道处理完链表中所有相同的定时器事件为止
  15. void(*fn)(unsignedlong);
  16. unsignedlongdata;
  17. timer=list_first_entry(head,structtimer_list,entry);//这是一个宏,获取第一个实体(对应的是入口的下一个)的地址
  18. fn=timer->function;
  19. data=timer->data;
  20. fn(data);//timer超时时回调函数入口
  21. }
  22. }
  23. }

对于cascade()函数它是确保之前定时器建立时internal_add_timer()定时器队列以及队列租得迁移更新工作,为什么要迁移,因为,系统在处理定时器时,比较的只是tv1组而已,也就是说,原来的tv1执行完之后,那么剩下的tv2,tv3,tv4,tv5将会先后迁移到tv1组:tv5 -> tv4 -> tv3 -> tv2-> tv1,这样定时器超时事件服务程序并不需要对每组的tv的超时事件进行检测,相比而言,也就提高了CPU的处理效率。那么这样一来timer 链表将发生变化,所以需要重新计算,重新实现internal_add_timer(),所以cascade ()函数代码如下:

  1. taticintcascade(structtvec_base*base,structtvec*tv,intindex)
  2. {
  3. /*cascadeallthetimersfromtvuponelevel*/
  4. structtimer_list*timer,*tmp;
  5. structlist_headtv_list;
  6. list_replace_init(tv->vec+index,&tv_list);//
  7. list_for_each_entry_safe(timer,tmp,&tv_list,entry){
  8. BUG_ON(tbase_get_base(timer->base)!=base);//确保本地cpu的tvec_base没
  9. internal_add_timer(base,timer);//有发生改变
  10. }
  11. returnindex;
  12. }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值