Linux下的定时器.docx_第1页
Linux下的定时器.docx_第2页
Linux下的定时器.docx_第3页
Linux下的定时器.docx_第4页
Linux下的定时器.docx_第5页
已阅读5页,还剩4页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

简介这篇文章主要记录我在试图解决如何尽可能精确地在某个特定的时间间隔执行某项具体任务时的思路历程,并在后期对相关的API进行的归纳和总结,以备参考。问题引出很多时候,我们会有类似“每隔多长时间执行某项任务”的需求,乍看这个问题并不难解决,实则并不容易,有很多隐含条件需要考虑,诸如:时间精度是多少?时间是否允许出现偏差,允许的偏差是多少,偏差之后如何处理?系统的负载如何?这个程序允许占用的系统资源是否有限制?这个程序运行的硬件平台如何?为了便于分析,我们锁定题目为“每隔2妙打印当前的系统时间(距离UNIX纪元的秒数)”。基于sleep的朴素解法看到这个题目,我想大家的想法和我一样,都是首先想到类似这样的解法:#includeintmain(intargc,char*argv)while(1)printf(%dn,time(NULL);sleep(2);return0;如果对时间精度要求不高,以上代码确实能工作的很好。因为sleep的时间精度只能到1s:#includeunsignedintsleep(unsignedintseconds);所以对于更高的时间精度(比如说毫秒)来说,sleep就不能奏效了。如果沿着这个思路走下去,还分别有精确到微妙和纳秒的函数usleep和nanosleep可用:#includeintusleep(useconds_t usec);FeatureTestMacro Requirementsforglibc(see feature_test_macros(7):usleep():_BSD_SOURCE|_XOPEN_SOURCE=500#includeintnanosleep(conststructtimespec*req,structtimespec*rem);FeatureTestMacro Requirementsforglibc(see feature_test_macros(7):nanosleep():_POSIX_C_SOURCE=199309L既然有了能精确到纳秒的nanosleep可用,上面的较低精度的函数也就可以休息了。实际上在Linux系统下,sleep和usleep就是通过一个系统调用nanosleep实现的。用带有超时功能的API变相实现睡眠如果开发者不知道有usleep和nanosleep,这个时候他可能会联想到select类的系统调用: According to POSIX.1-2001 */#include/* According to earlier standards */#include#include#includeintselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);#includeintpoll(structpollfd*fds,nfds_t nfds,inttimeout);#includeintepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);intepoll_pwait(intepfd,structepoll_event*events,intmaxevents,inttimeout,constsigset_t*sigmask);从函数原型和相关手册来看,poll和epoll_wait能提供的时间精度为毫秒,select比他们两个略胜一筹,为微秒,和前述的usleep相当。但是,果真如此么?这需要我们深入到Linux的具体实现,在内核里,这几个系统调用的超时功能都是通过内核中的动态定时器实现的,而动态定时器的时间精度是由当前内核的HZ数决定的。如果内核的HZ是100,那么动态定时器的时间精度就是1/HZ=1/100=10毫秒。目前,X86系统的HZ最大可以定义为1000,也就是说X86系统的动态定时器的时间精度最高只能到1毫秒。由此来看,select用来指示超时的timeval数据结构,只是看起来很美,实际上精度和poll/epoll_wait相当。基于定时器的实现除了基于sleep的实现外,还有基于能用信号进行异步提醒的定时器实现:#include#includeintmain(intargc,char*argv)sigset_t block;sigemptyset(&block);sigaddset(&block,SIGALRM);sigprocmask(SIG_BLOCK,&block,NULL);while(1)printf(%dn,time(NULL);alarm(2);sigwaitinfo(&block,NULL);return0;显然,上面的代码并没有利用信号进行异步提醒,而是通过先阻塞信号的传递,然后用sigwaitinfo等待并将信号取出的方法将异步化同步。这样做的目的是为了尽可能减少非必要的信号调用消耗,因为这个程序只需要执行这个简单的单一任务,所以异步除了带来消耗外,并无任何好处。读者可能已经发现上面的代码无非是把最初的代码中的sleep换成了alarm和sigwaitinfo两个调用,除了复杂了代码之外,好像并没有什么额外的好处。alarm的时间精度只能到1s,并且alarm和sigwaitinfo的确也可以看成是sleep的一种实现,实际上有的sleep确实是透过alarm来实现的,请看sleep的手册页:BUGS sleep() may be implemented using SIGALRM; mixing calls to alarm(2) and sleep() is a bad idea. Using longjmp(3) from a signal handler or modifying the handling of SIGALRM while sleeping will cause undefined results.但是,这只是表象,本质他们是不同的,sleep是拨了一个临时实时定时器并等待定时器到期,而alarm是用进程唯一的实时定时器来定时唤醒等待信号到来的进程执行。如果需要更高的时间精度,可以采用精度为微秒的alarm版本ualarm:#includeuseconds_t ualarm(useconds_t usecs,useconds_t interval);FeatureTestMacro Requirementsforglibc(see feature_test_macros(7):ualarm():_BSD_SOURCE|_XOPEN_SOURCE=500或者是直接用setitimer操纵进程的实时定时器:#includeintgetitimer(intwhich,structitimerval*value);intsetitimer(intwhich,conststructitimerval*value,structitimerval*ovalue);细心的你应该已经注意到了,ualarm和setitimer都额外提供了间隔时间的设置以便于间隔定时器用SIGALRM周期性的唤醒进程,这对于我们的需求有什么意义呢?请听我慢慢道来。一般来说,需要定时执行的任务所消耗的时间都很短,至少都会少于间隔时间,否则这个需求就是无法实现的。我们前面的程序实现,都是假设任务消耗时间为0,实际上的任务并不总是像打印当前系统时间这么简单,即便它们持续的时间真的短到相对来说可以忽略不计,如果这些小的忽略不计累积起来,也还是可能会造成长时间后的大偏差,所以我们有必要将这段时间计算进来。一种补救的措施是在任务执行的前后执行gettimeofday得到系统的时间,然后做差得到任务消耗时间并在接下来的“sleep”中将其扣除。问题看似解决了,但是我们毕竟没有将系统进行上下文切换的时间和计算消耗时间的时间考虑进来,这样的话,还是会存在较大的误差。另一种计算量相对小些的算法是:直接通过时间间隔计算下一次超时的绝对时间,然后根据当前的绝对时间算出需要等待的时间并睡眠。但是,这也只是修修补补而已,并没有从根本上解决问题。间隔定时器的出现从根本上解决了上面所提的问题,它自身就提供周期唤醒的功能,从而避免了每次都计算的负担。因为ualarm已经被放弃,所以用setitimer再次改写代码:#include#include#includeintmain(intargc,char*argv)sigset_t block;structitimervalitv;sigemptyset(&block);sigaddset(&block,SIGALRM);sigprocmask(SIG_BLOCK,&block,NULL);itv.it_interval.tv_sec=2;itv.it_interval.tv_usec=0;itv.it_value=itv.it_interval;setitimer(ITIMER_REAL,&itv,NULL);while(1)printf(%dn,time(NULL);sigwaitinfo(&block,NULL);return0;进程的间隔计时器能够提供的时间精度为微秒,对于大多数的应用来说,应该已经足够,如果需要更高的时间精度,或者需要多个定时器,那么每个进程一个的实时间隔定时器就无能为力了,这个时候我们可以选择POSIX实时扩展中的定时器:#include#includeinttimer_create(clockid_t clockid,structsigevent*restrict evp,timer_t*restrict timerid);inttimer_getoverrun(timer_t timerid);inttimer_gettime(timer_t timerid,structitimerspec*value);inttimer_settime(timer_t timerid,intflags,conststructitimerspec*restrict value,structitimerspec*restrict ovalue);它实际上就是进程间隔定时器的增强版,除了可以定制时钟源(nanosleep也存在能定制时钟源的版本:clock_nanosleep)和时间精度提高到纳秒外,它还能通过将evp-sigev_notify设定为如下值来定制定时器到期后的行为: SIGEV_SIGNAL: 发送由evp-sigev_sino指定的信号到调用进程,evp-sigev_value的值将被作为siginfo_t结构体中si_value的值。 SIGEV_NONE:什么都不做,只提供通过timer_gettime和timer_getoverrun查询超时信息。 SIGEV_THREAD: 以evp-sigev_notification_attributes为线程属性创建一个线程,在新建的线程内部以evp-sigev_value为参数调用evp-sigev_notification_function。 SIGEV_THREAD_ID:和SIGEV_SIGNAL类似,不过它只将信号发送到线程号为evp-sigev_notify_thread_id的线程,注意:这里的线程号不一定是POSIX线程号,而是线程调用gettid返回的实际线程号,并且这个线程必须实际存在且属于当前的调用进程。更新后的程序如下(需要连接实时扩展库: -lrt):#include#include#include#include#includeintmain(intargc,char*argv)timer_t timer;structitimerspec timeout;sigset_t block;structsched_param param;sigemptyset(&block);sigaddset(&block,SIGALRM);sigprocmask(SIG_BLOCK,&block,NULL);timer_create(CLOCK_MONOTONIC,NULL,&timer);timeout.it_interval.tv_sec=2;timeout.it_interval.tv_nsec=0;timeout.it_value=timeout.it_interval;timer_settime(timer,0,&timeout,NULL);while(1)fprintf(stderr,%dn,time(NULL);sigwaitinfo(&block,NULL);return0;至于时钟源为什么是CLOCK_MONOTONIC而不是CLOCK_REALTIME,主要是考虑到系统的实时时钟可能会在程序运行过程中更改,所以存在一定的不确定性,而CLOCK_MONOTONIC则不会,较为稳定。至此为止,我们已经找到了目前Linux提供的精度最高的定时器API,它应该能满足大多数情况的要求了。其它问题传统信号的不可靠性传统UNIX信号是不可靠的,也就是说如果当前的信号没有被

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论