Ø http://www.ibm.com/developerworks/cn/linux/1307_liuming_linuxtime1/
Ø 《linux系统编程》第“10.9 定时器”章节
要在linux中使用定时器,有如下三种方法:
定时器方式 |
一个进程允许 使用的数量 |
通知方式 |
简单的闹钟 - alarm |
1个 |
Ø 信号: SIGALRM |
间歇定时器 - setitimer |
1个 |
Ø 信号: SIGALRM、SIGVTALRM、SIGPROF |
高级定时器 - timer_create |
无限制 |
Ø 信号: 可以自己选择用什么信号 Ø 启动线程 就不会产生信号,可以避免慢系统调用被信号中断的问题 |
有关慢系统调用被信号中断的问题可以参考我此前写的一篇文章《信号中断与慢系统调用》:http://blog.csdn.net/benkaoya/article/details/17262053
alarm()是最简单的定时器接口,对该函数的调用会在真实时间(real time)seconds秒之后将SIGALRM信号发给调用进程。它不能自动重启(即定时器到后要再次定时,需要再调用一次alarm)。
间歇定时器setitimer系统调用,他可以提供比alarm()更多的控制。它能够自动重启。
linux 为每一个进程提供了 3 个 setitimer 间隔计时器:
Ø ITIMER_REAL:减少实际时间,到期的时候发出 SIGALRM 信号。
Ø ITIMER_VIRTUAL:减少有效时间 (进程执行的时间),产生 SIGVTALRM 信号。
Ø ITIMER_PROF:减少进程的有效时间和系统时间 (为进程调度用的时间)。这个经常和上面一个使用用来计算系统内核时间和用户时间。产生 SIGPROF 信号。
所谓 REAL 时间,即我们人类自然感受的时间,英文计算机文档中也经常使用 wall-clock 这个术语。说白了就是我们通常所说的时间,比如现在是下午 5 点 10 分,那么一分钟的 REAL 时间之后就是下午 5 点 11 分。
VIRTUAL 时间是进程执行的时间,Linux 是一个多用户多任务系统,在过去的 1 分钟内,指定进程实际在 CPU 上的执行时间往往并没有 1 分钟,因为其他进程会被 Linux 调度执行,在那些时间内,虽然自然时间在流逝,但指定进程并没有真正的运行。VIRTUAL 时间就是指定进程真正的有效执行时间。比如 5 点 10 分开始的 1 分钟内,进程 P1 被 Linux 调度并占用 CPU 的执行时间为 30 秒,那么 VIRTUAL 时间对于进程 P1 来讲就是 30 秒。此时自然时间已经到了 5 点 11 分,但从进程 P1 的眼中看来,时间只过了 30 秒。
PROF 时间比较独特,对进程 P1 来说从 5 点 10 分开始的 1 分钟内,虽然自己的执行时间为 30 秒,但实际上还有 10 秒钟内核是在执行 P1 发起的系统调用,那么这 10 秒钟也被加入到 PROF 时间。这种时间定义主要用于全面衡量进程的性能,因为在统计程序性能的时候,10 秒的系统调用时间也应该算到 P1 的头上。这也许就是 PROF 这个名字的来历吧。
间隔定时器 setitimer 有一些重要的缺点,POSIX Timer 对 setitimer 进行了增强,克服了 setitimer 的诸多问题:
Ø 首先,一个进程同一时刻只能有一个 timer。假如应用需要同时维护多个 Interval 不同的计时器,必须自己写代码来维护。这非常不方便。使用 POSIX Timer,一个进程可以创建任意多个 Timer。
Ø setitmer 计时器时间到达时,只能使用信号方式通知使用 timer 的进程,而 POSIX timer 可以有多种通知方式,比如信号,或者启动线程。
Ø 使用 setitimer 时,通知信号的类别不能改变:SIGALARM,SIGPROF 等,而这些都是传统信号,而不是实时信号,因此有 timer overrun 的问题;而 POSIX Timer 则可以使用实时信号。
备注:通过kill –l可以查看系统支持的所有信号列表。编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。
Ø setimer 的精度是 ms,POSIX Timer 是针对有实时要求的应用所设计的,接口支持 ns 级别的时钟精度。
表 2. POSIX Timer 函数
函数名 |
功能描述 |
timer_create |
创建一个新的 Timer;并且指定定时器到时通知机制 |
timer_delete |
删除一个 Timer |
timer_gettime |
Get the time remaining on a POSIX.1b interval timer |
timer_settime |
开始或者停止某个定时器。 |
timer_getoverrun |
获取丢失的定时通知个数。 |
使用 Posix Timer 的基本流程很简单,首先创建一个 Timer。创建的时候可以指定该 Timer 的一些特性,比如 clock ID。clock ID 即 Timer 的种类,可以为下表中的任意一种:
表 3. POSIX Timer clock ID
Clock ID |
描述 |
CLOCK_REALTIME |
Settable system-wide real-time clock; |
CLOCK_MONOTONIC |
Nonsettable monotonic clock |
CLOCK_PROCESS_CPUTIME_ID |
Per-process CPU-time clock |
CLOCK_THREAD_CPUTIME_ID |
Per-thread CPU-time clock |
Ø CLOCK_REALTIME 时间是系统保存的时间,即可以由 date 命令显示的时间,该时间可以重新设置。比如当前时间为上午 10 点 10 分,Timer 打算在 10 分钟后到时。假如 5 分钟后,我用 date 命令修改当前时间为 10 点 10 分,那么 Timer 还会再等十分钟到期,因此实际上 Timer 等待了 15 分钟。假如您希望无论任何人如何修改系统时间,Timer 都严格按照 10 分钟的周期进行触发,那么就可以使用CLOCK_MONOTONIC。
Ø CLOCK_PROCESS_CPUTIME_ID 的含义与 setitimer 的 ITIMER_VIRTUAL 类似。计时器只记录当前进程所实际花费的时间;比如还是上面的例子,假设系统非常繁忙,当前进程只能获得 50%的 CPU 时间,为了让进程真正地运行 10 分钟,应该到 10 点 30 分才允许 Timer 到期。
Ø CLOCK_THREAD_CPUTIME_ID 以线程为计时实体,当前进程中的某个线程真正地运行了一定时间才触发 Timer。
设置到期通知方式
timer_create 的第二个参数 struct sigevent 用来设置定时器到时时的通知方式。该数据结构如下:
清单 10,结构sigevent
struct sigevent {
int sigev_notify; /* Notification method */
int sigev_signo; /* Notification signal */
union sigval sigev_value; /* Data passed with
notification */
void (*sigev_notify_function) (union sigval);
/* Function used for thread
notification (SIGEV_THREAD) */
void *sigev_notify_attributes;
/* Attributes for notification thread
(SIGEV_THREAD) */
pid_t sigev_notify_thread_id;
/* ID of thread to signal (SIGEV_THREAD_ID) */
};
其中sigev_notify 表示通知方式,有如下几种:
表 3. POSIX Timer 到期通知方式
通知方式 |
描述 |
SIGEV_NONE |
定时器到期时不产生通知。。。 |
SIGEV_SIGNAL |
定时器到期时将给进程投递一个信号,sigev_signo 可以用来指定使用什么信号。 |
SIGEV_THREAD |
定时器到期时将启动新的线程进行需要的处理 |
SIGEV_THREAD_ID(仅针对 Linux) |
定时器到期时将向指定线程发送信号。 |
Ø 如果采用 SIGEV_NONE 方式,使用者必须调用timer_gettime 函数主动读取定时器已经走过的时间。类似轮询。
Ø 如果采用 SIGEV_SIGNAL 方式,使用者可以选择使用什么信号,用 sigev_signo 表示信号值,比如 SIG_ALARM。
Ø 如果使用 SIGEV_THREAD 方式,则需要设置 sigev_notify_function,当 Timer 到期时,将使用该函数作为入口启动一个线程来处理信号;sigev_value 保存了传入 sigev_notify_function 的参数。sigev_notify_attributes 如果非空,则应该是一个指向 pthread_attr_t 的指针,用来设置线程的属性(比如 stack 大小,detach 状态等)。
Ø SIGEV_THREAD_ID 通常和 SIGEV_SIGNAL 联合使用,这样当 Timer 到期时,系统会向由 sigev_notify_thread_id 指定的线程发送信号,否则可能进程中的任意线程都可能收到该信号。这个选项是 Linux 对 POSIX 标准的扩展,目前主要是 GLibc 在实现 SIGEV_THREAD 的时候使用到,应用程序很少会需要用到这种模式。