UNIX 下 timer 的实现
------------------------------------------------------------
unix 下,timer 一般是通过 SIGALRM 这个信号(signal)来实现的。
信号(signal)是 unix 下软中断(software interrupt)机制,其模仿了硬件的中断
机制,给每个进程(process)发送信号,以处理特定的事件。
--- alarm() 实现 timer ---
unsigned int alarm(unsigned int sec);
例子:
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
static int counter = 0;
static void
my_alarm(int signo)
{
printf("got it!\n");
counter++;
alarm(1);
alarm(1);
while (counter < 10)
pause();
return 0;
useconds_t ualarm(useconds_t start, useconds_t interval);
alarm() 仅是 sec 秒后让系统发送一次 SIGALRM,因此,my_alarm() 中必须再次
调用 alarm()。且 alarm() 精度不高,可以换用 ualarm(),设置 microsecond
级别的精度(1,000,000 ms == 1 sec)。并且 interval 不为 0 的时候,SIGALRM
是定期发送的。
对于 ualarm(),start 是距 ualarm() 调用后第一次 SIGALRM 发生的时间,而
interval 则是以后每次 SIGALRM 的时间间隔。如下:
| ------- start ------ | -- interval -- |
@ -------------------> # -------------> * -------------> * ...
ualarm() SIGALRM SIGALRM
例子:
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
static int counter = 0;
static void
my_alarm(int signo)
{
printf("got it!\n");
counter++;
int
main(void)
{
if (signal(SIGALRM, my_alarm) == SIG_ERR)
{
perror("signal error");
exit(-1);
}
ualarm(1 * USPS, 1 * USPS);
while (counter < 10)
pause();
return 0;
除了 alarm/ualarm,标准还定义了 setitimer(),也可控制 SIGALRM。
(setitimer() 还可以控制 SIGVTALRM、SIGPROF,这里就不讨论了)。
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
int setitimer(int which, const struct itimerval *value, struct itimerval
*oldvalue);
标准规定,只要 it_interval 不为 0,则 SIGALRM 会定期发送;否则,只发送一
次 SIGALRM。
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
static int counter = 0;
static void
my_alarm(int signo)
{
printf("got it!\n");
counter++;
/* 第一次 SIGALRM 的触发时间 */
new.it_value.tv_sec = 1;
new.it_value.tv_usec = 0;
/* 以后每次 SIGALRM 的触发间隔 */
new.it_interval.tv_sec = 1;
new.it_interval.tv_usec = 0;
if (signal(SIGALRM, my_alarm) == SIG_ERR)
{
perror("signal() error\n");
exit(1);
}
if (setitimer(ITIMER_REAL, &new, &old) < 0)
{
printf("setitimer error : %s\n", strerror(errno));
exit(1);
}
while ( counter < 10 )
pause();
return 0;
上面可以看出,alarm/ualarm 的功能都包含在了 setitimer() 中,完全可以用
setitimer() 来实现 alarm/ualarm,OpenBSD 便是这么做的。其中,
alarm/ualarm 都是作为 library call 来实现的,而 setitimer 是 sys call。
/* /lib/libc/gen/ualarm.c */
#define USPS 1000000 /* # of microseconds in a second */
/*
* Generate a SIGALRM signal in ``usecs'' microseconds.
* If ``reload'' is non-zero, keep generating SIGALRM
* every ``reload'' microseconds after the first signal.
*/
useconds_t
ualarm(useconds_t usecs, useconds_t reload)
{
struct itimerval new, old;
new.it_interval.tv_usec = reload % USPS;
new.it_interval.tv_sec = reload / USPS;
new.it_value.tv_usec = usecs % USPS;
new.it_value.tv_sec = usecs / USPS;
if (setitimer(ITIMER_REAL, &new, &old) == 0)
return ((useconds_t)(old.it_value.tv_sec * USPS +
old.it_value.tv_usec));
/* else */
return ((useconds_t)-1);
unsigned int
alarm(unsigned int secs)
{
struct itimerval it, oitv;
struct itimerval *itp = ⁢
timerclear(&itp->it_interval);
itp->it_value.tv_sec = secs;
itp->it_value.tv_usec = 0;
if (setitimer(ITIMER_REAL, itp, &oitv) < 0)
return ((unsigned int) -1);
if (oitv.it_value.tv_usec)
oitv.it_value.tv_sec++;
return (oitv.it_value.tv_sec);
因为历史原因,某些系统上,使用 signal() 设定的信号处理函数,在信号处置时
会被重置为默认值。虽然可以通过如下办法来解决这个问题:
static void
my_alarm(int signo)
{
signal(SIGALRM, my_alarm);
printf("got it!\n");
counter++;
评论