Linux 下定时器的实现分析
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Linux下定时器的实现方式分析
简介:定时器属于基本的基础组件,不管是用户空间的程序开发,还是内核空间的程序开发,很多时候都需要有定时器作为基础组件的支持,但使用场景的不同,对定时器的实现考虑也不尽相同,本文讨论了在Linux环境下,应用层和内核层的定时器的各种实现方法,并分析了各种实现方法的利弊以及适宜的使用环境。
概论
定时器属于基本的基础组件,不管是用户空间的程序开发,还是内核空间的程序开发,很多时候都需要有定时器作为基础组件的支持,但使用场景的不同,对定时器的实现考虑也不尽相同,本文讨论了在Linux环境下,应用层和内核层的定时器的各种实现方法,并分析了各种实现方法的利弊以及适宜的使用环境。
首先,给出一个基本模型,定时器的实现,需要具备以下几个行为,这也是在后面评判各种定时器实现的一个基本模型[1]:
StartTimer(Interval,TimerId,ExpiryAction)
注册一个时间间隔为Interval后执行ExpiryAction的定时器实例,其中,返回TimerId以区分在定时器系统中的其他定时器实例。
StopTimer(TimerId)
根据TimerId找到注册的定时器实例并执行Stop。
PerTickBookkeeping()
在一个Tick内,定时器系统需要执行的动作,它最主要的行为,就是检查定时器系统中,是否有定时器实例已经到期。注意,这里的Tick 实际上已经隐含了一个时间粒度(granularity)的概念。
ExpiryProcessing()
在定时器实例到期之后,执行预先注册好的ExpiryAction行为。
上面说了基本的定时器模型,但是针对实际的使用情况,又有以下2种基本行为的定时器:
Single-Shot Timer
这种定时器,从注册到终止,仅仅只执行一次。
Repeating Timer
这种定时器,在每次终止之后,会自动重新开始。本质上,可以认为Repeating Timer是在Single-Shot Timer终止之后,再次注册到定时器系统里的Single-Shot Timer,因此,在支持Single-Shot Timer的基础上支持Repeating Timer并不算特别的复杂。
基于链表和信号实现定时器(2.4版内核情况下)
在2.4的内核中,并没有提供POSIX timer[2]的支持,要在进程环境中支持多个定时器,只能自己来实现,好在Linux提供了setitimer(2)的接口。它是一个具有间隔功能的定时器(interval timer),但如果想在进程环境中支持多个计时器,不得不自己来管理所有的计时器。setitimer(2)的定义如下:
memset(&timer_list,0,sizeof(struct timer_list));
LIST_INIT(&timer_list.header);
timer_list.max_num=count;
/*Register our internal signal handler and store o
if((timer_list.old_sigfunc=signal(SIGALRM,sig_f
return-1;
}
timer_list.new_sigfunc=sig_func;
/*Setting our interval timer for driver our mutil-time timer_list.value.it__sec=TIMER_START;
timer_list.value.it__usec=0;
timer_list.value.it__sec=TIMER_TICK;
timer_list.value.it__usec=0;
ret=setitimer(ITIMER_REAL,&timer_list.value,&ti
return ret;
}
/**
*Destroy the timer list.
*
*@return0means ok,the other means fail.
*/
int destroy_timer(void)
{
struct timer*node=NULL;
if((signal(SIGALRM,timer_list.old_sigfunc))==SI
return-1;
}
if((setitimer(ITIMER_REAL,&timer_list.ovalue,&tim
return-1;
}
中,各节点的值都不小于该子树根节点的值。一个最小堆的例子如下图2:
图2.最小堆
一个最小堆,一般支持以下几种操作:
Insert(TimerHeap,Timer):在堆中插入一个值,并保持最小堆性质,具体对应于定时器的实现,则是把定时器插入到定时器堆中。根据最小堆的插入算法分析,可以知道该操作的时间复杂度为O(lgn)。
Minimum(TimerHeap):获取最小堆的中最小值;在定时器系统中,则是返回定时器堆中最先可能终止的定时器。由于是最小堆,只需返回堆的
撞针则撞击枪膛,如果枪膛中有子弹,则会被击发;与之相对应的是:对于PerTickBookkeeping,其最本质的工作在于以Tick为单位增加时钟,如果发现有任何定时器到期,则调用相应的ExpiryProcessing。设定一个循环为N个Tick单元,当前时间是在S个循环之后指向元素i (i>=0and i<=N-1),则当前时间(Current Time)Tc可以表示为:Tc= S*N+i;如果此时插入一个时间间隔(Time Interval)为Ti的定时器,设定它将会放入元素n(Next)中,则n=(Tc+Ti)mod N=(S*N+i+ Ti)mod N=(i+Ti)mod N。如果我们的N足够的大,显然StartTimer,StopTimer,PerTickBookkeeping时,算法复杂度分别为O(1),O(1),O(1)。在[5]中,给出了一个简单定时器轮实现的定时。下图3是一个简单的时间轮定时器:
图3.简单时间轮
如果需要支持的定时器范围非常的大,上面的实现方式则不能满足这样的需求。因为这样将消耗非常可观的内存,假设需要表示的定时器范围为:0–2^3-1ticks,则简单时间轮需要2^32个元素空间,这对于内存