_Linux 系统上最常用的定时器

b1a8ee89-f008-eb11-8da9-e4434bdf6706.png

有时我们需要定时完成一些任务。简单的方法是使用 while 循环加 sleep。比如每隔 1 分钟检查链接情况的 heartbeat 任务等。比如:

while

这可以满足很多程序的定时需要,但假如您不希望程序“偷懒”,即上例中 sleep 的时候您还是希望程序做些有用的工作,那么使用定时器是通常的选择。Linux 系统上最常用的定时器是 setitmer 计时器。

setitimer

Linux 系统为每个进程提供三个间隔计时器,每个计时器在不同的时间域递减。当任何计时器
到期时,向进程发送一个信号,然后计时器(可能)重新启动。

ITIMER_REAL:减少实际时间,到期的时候发出 SIGALRM 信号。

REAL 时间,即我们人类自然感受的时间,英文计算机文档中也经常使用 wall-clock 这个术语。说白了就是我们通常所说的时间,比如现在是中午12 点 10 分,那么一分钟的 REAL 时间之后就是中午 12点 11 分。

ITIMER_VIRTUAL:减少有效时间 (进程执行的时间),产生 SIGVTALRM 信号。

VIRTUAL 时间是进程执行的时间,Linux 是一个多用户多任务系统,在过去的 1 分钟内,指定进程实际在 CPU 上的执行时间往往并没有 1 分钟,因为其他进程会被 Linux 调度执行,在那些时间内,虽然自然时间在流逝,但指定进程并没有真正的运行。

ITIMER_PROF:减少进程的有效时间和系统时间 (为进程调度用的时间)。这个经常和上面一个使用用来计算系统内核时间和用户时间。产生 SIGPROF 信号。

PROF 时间比较独特,对进程 P1 来说从 12点 10 分开始的 1 分钟内,虽然自己的执行时间为 30 秒,但实际上还有 10 秒钟内核是在执行 P1 发起的系统调用,那么这 10 秒钟也被加入到 PROF 时间。

setitimer Timer 需要的API

#include 

计时器值由以下结构定义:

struct 

setitimer() 以 new_value 设置特定的定时器,如果 old_value 非空,则它返回 which 类型时间间隔定时器的前一个值。定时器从 it_value 递减到零,然后产生一个信号,并重新设置为 it_interval,如果此时 it_interval 为零,则该定时器停止。任何时候,只要 it_value 设置为零,该定时器就会停止。

getitimer 函数得到间隔计时器的时间值,保存在 value 中。

来看看下面的例子。

#include 

b4a8ee89-f008-eb11-8da9-e4434bdf6706.png

这个程序使用 PROF 时间,每经过两秒 PROF 时间之后就会打印一下 timer fired 字符串。

需要指出:setitimer 计时器的精度为 ms,即 1000 分之 1 秒,足以满足绝大多数应用程序的需要。但多媒体等应用可能需要更高精度的定时,那么就需要考虑使用下一类定时器:POSIX Timer。

POSIX Timer

最强大的定时器接口来自POSIX时钟系列,其创建、初始化以及删除一个定时器的行动被分为三个不同的函数:timer_create()(创建定时器)、timer_settime()(初始化定时器)以及 timer_delete()(销毁它)。POSIX Timer 对 setitimer 进行了增强,克服了 setitimer 的诸多问题:

1、一个进程同一时刻只能有一个 timer。假如应用需要同时维护多个 Interval 不同的计时器,必须自己写代码来维护。这非常不方便。使用 POSIX Timer,一个进程可以创建任意多个 Timer。
2、setitmer 计时器时间到达时,只能使用信号方式通知使用 timer 的进程,而 POSIX timer 可以有多种通知方式,比如信号,或者启动线程。
3、使用 setitimer 时,通知信号的类别不能改变:SIGALARM,SIGPROF 等,而这些都是传统信号,而不是实时信号,因此有 timer overrun 的问题;而 POSIX Timer 则可以使用实时信号。
4、 setimer 的精度是 ms,POSIX Timer 是针对有实时要求的应用所设计的,接口支持 ns 级别的时钟精度。

POSIX Timer 函数

//创建一个定时器

进程可以通过调用 timer_create() 创建特定的定时器,定时器是每个进程自己的,不是在 fork 时继承的。

参数:

clock_id 说明定时器是基于哪个时钟的,*timerid 装载的是被创建的定时器的 ID。该函数创建了定时器,并将他的 ID 放入timerid指向的位置中。参数evp指定了定时器到期要产生的异步通知。

如果evp为 NULL,那么定时器到期会产生默认的信号,对 CLOCK_REALTIMER来说,默认信号就是SIGALRM。如果要产生除默认信号之外的其它信号,程序必须将 evp->sigev_signo设置为期望的信号码。

struct sigevent 结构中的成员 evp->sigev_notify说明了定时器到期时应该采取的行动。通常,这个成员的值为SIGEV_SIGNAL,这个值说明在定时器到期时,会产生一个信号。程序可以将成员 evp->sigev_notify设为SIGEV_NONE来防止定时器到期时产生信号。

clock_id取值为以下:

CLOCK_REALTIME 
//启动一个定时器

timer_create()所创建的定时器并未启动。要将它关联到一个到期时间以及启动时钟周期,可以使用timer_settime()。

如同settimer(),it_value用于指定当前的定时器到期时间。当定时器到期,it_value的值会被更新成it_interval 的值。如果it_interval的值为0,则定时器不是一个时间间隔定时器,一旦it_value到期就会回到未启动状态。timespec的结构提供了纳秒级分辨率:

struct 

参数:

如果flags的值为TIMER_ABSTIME,则value所指定的时间值会被解读成绝对值(此值的默认的解读方式为相对于当前的时间)。这个经修改的行为可避免取得当前时间、计算“该时间”与“所期望的未来时间”的相对差额以及启动定时器期间造成竞争条件。 如果ovalue的值不是NULL,则之前的定时器到期时间会被存入其所提供的itimerspec。如果定时器之前处在未启动状态,则此结构的成员全都会被设定成0。

//获得一个活动定时器的剩余时间
//取得一个定时器的超限运行次数

有可能一个定时器到期了,而同一定时器上一次到期时产生的信号还处于挂起状态。在这种情况下,其中的一个信号可能会丢失。这就是定时器超限。程序可以通过调用timer_getoverrun来确定一个特定的定时器出现这种超限的次数。定时器超限只能发生在同一个定时器产生的信号上。由多个定时器,甚至是那些使用相同的时钟和信号的定时器,所产生的信号都会排队而不会丢失。

执行成功时,timer_getoverrun()会返回定时器初次到期与通知进程(例如通过信号)定时器已到期之间额外发生的定时器到期次数。

//删除一个定时器

一次成功的timer_delete()调用会销毁关联到timerid的定时器并且返回0。执行失败时,此调用会返回-1并将errno设定会 EINVAL,这个唯一的错误情况代表timerid不是一个有效的定时器。

POSIX Timer 到期通知方式

SIGEV_NONE 定时器到期时不产生通知。。。 SIGEV_SIGNAL 定时器到期时将给进程投递一个信号,sigev_signo 可以用来指定使用什么信号。 SIGEV_THREAD 定时器到期时将启动新的线程进行需要的处理 SIGEV_THREAD_ID(仅针对 Linux) 定时器到期时将向指定线程发送信号。

设置通知方式:

SIGEV_NONE 	

这里将通知方式设为 SIGEV_THREAD,handle为入口函数。然后设置定时器间隔,并启动 Timer:

ts
#include 

编译输出:

b5a8ee89-f008-eb11-8da9-e4434bdf6706.png

总结

使用定时器的目的无非是为了周期性的执行某一任务,或者是到了一个指定时间去执行某一个任务。本文讲述了 Linux 系统提供的大多数关于时间的编程方法。 但Linux 系统是如何实现这些机制是否十分好奇。但这得了解到一些硬件时钟设备的简单知识了。

参考:https://www.ibm.com/developerworks/cn/linux/1307_liuming_linuxtime1/index.html?ca=drs-

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值