Linux定时器的实现
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <signal.h>
int count = 0;
void set_timer()
{
struct itimerval itv, oldtv;
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 1000;
itv.it_value.tv_sec = 0;
itv.it_value.tv_usec = 1000;
setitimer(ITIMER_REAL, &itv, &oldtv);
}
void sigalrm_handler(int sig)
{
count++;
}
int main( )
{
signal(SIGALRM, sigalrm_handler);
set_timer();
while(count < 10000){
}
printf("count = %d/n", count);
exit(0);
}
setitimer函数的精度决定于系统时钟的分辨率,在我的系统中要40s才能返回,说明分辨率不到1ms。如果改成间隔10ms(10000us)的话,系统100s返回,很准确。一般操作系统的分辨率都为10ms。注意,信号会中断低速系统调用,所以这里用循环延迟,而不是用sleep之类的函数。
还有一种方法是基于RTC,可以提供更高的分辨率。
#include <stdio.h>
#include <linux/rtc.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int main(void)
{
int i, fd, retval, irqcount = 0;
unsigned long tmp, data;
fd = open ("/dev/rtc", O_RDONLY);
if (fd == -1) {
perror("/dev/rtc");
exit(errno);
}
fprintf(stderr, "/n/tRTC Driver Test Example./n");
/* Turn on update interrupts (one per second) */
retval = ioctl(fd, RTC_UIE_ON, 0);
if (retval == -1) {
perror("ioctl");
exit(errno);
}
fprintf(stderr, "Counting 5 update (1/sec) interrupts from reading /dev/rtc:");
fflush(stderr);
for (i=1; i<6; i++) {
/* This read will block */
retval = read(fd, &data, sizeof(unsigned long));
if (retval == -1) {
perror("read");
exit(errno);
}
fprintf(stderr, " %d",i);
fflush(stderr);
irqcount++;
}
/* Turn off update interrupts */
retval = ioctl(fd, RTC_UIE_OFF, 0);
if (retval == -1) {
perror("ioctl");
exit(errno);
}
/* Read periodic IRQ rate */
retval = ioctl(fd, RTC_IRQP_READ, &tmp);
if (retval == -1) {
perror("ioctl");
exit(errno);
}
fprintf(stderr, "/nPeriodic IRQ rate was %ldHz./n", tmp);
fprintf(stderr, "Counting 20 interrupts at:");
fflush(stderr);
/* The frequencies 128Hz, 256Hz, ... 8192Hz are only allowed for root. */
for (tmp=2; tmp<=64; tmp*=2) {
retval = ioctl(fd, RTC_IRQP_SET, tmp);
if (retval == -1) {
perror("ioctl");
exit(errno);
}
fprintf(stderr, "/n%ldHz:/t", tmp);
fflush(stderr);
/* Enable periodic interrupts */
retval = ioctl(fd, RTC_PIE_ON, 0);
if (retval == -1) {
perror("ioctl");
exit(errno);
}
for (i=1; i<21; i++) {
/* This blocks */
retval = read(fd, &data, sizeof(unsigned long));
if (retval == -1) {
perror("read");
exit(errno);
}
fprintf(stderr, " %d",i);
fflush(stderr);
irqcount++;
}
/* Disable periodic interrupts */
retval = ioctl(fd, RTC_PIE_OFF, 0);
if (retval == -1) {
perror("ioctl");
exit(errno);
}
}
fprintf(stderr, "/n/t *** Test complete ***/n");
fprintf(stderr, "/nTyping /"cat /proc/interrupts/" will show %d more events on IRQ 8./n", irqcount);
close(fd);
return 0;
}
返回值是unsigned long型变量,低字节表示中断的类型(update-done, alarm-rang, or periodic),其他字节表示从上次读以来产生的中断次数。注意在高频率或高负载的情况下,会出现interrupt pileup,即中断次数大于一次,所以当大于时钟中断频率(1000HZ)时应检查返回值。
这两种定时器的实现都无法在一个进程里面同时设置多个定时器,需要自己实现一个timer管理器。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/stat.h>
#include <signal.h>
#include <errno.h>
#define TRUE 1
#define FALSE 0
#define MUTEX 0
#define MAX_TIMERS 20 /* number of timers */
#define VERY_LONG_TIME {0x7FFFFFFF, 0} /* longest time possible */
typedef struct timeval TIME; /* how time is actually stored */
struct timer {
int inuse; /* TRUE if in use */
TIME time; /* relative time to wait */
void (*callback)(void *); /* callback function */
void *param; /* callback parameter */
} timers[MAX_TIMERS]; /* set of timers */
struct timer *timer_next = NULL;/* timer we expect to run down next */
TIME time_timer_set; /* time when physical timer was set */
int semid; /* semaphore set */
sigset_t newmask, oldmask; /* signal mask */
void timers_update();
void set_timer(TIME time);
int sem_init(int nsems,int flag)
{
int semid;
semid = semget(IPC_PRIVATE, nsems, flag);
if (semid == -1)
perror("semget");
return semid;
}
int sem_setval(int semid, int semnum, int val)
{
if (val < 0)
return -1;
semctl(semid, semnum, SETVAL, val);
return 0;
}
int sem_wait(int semid, int semnum)
{
struct sembuf sem;
int ret;
sem.sem_num = semnum;
sem.sem_op = -1;
sem.sem_flg = SEM_UNDO;
while(((ret=semop(semid, &sem, 1)) == -1) && (errno == EINTR));
if (ret == -1){
perror("sem_wait");
return -1;
}
else
return 0;
}
int sem_signal(int semid, int semnum)
{
struct sembuf sem;
int ret;
sem.sem_num = semnum;
sem.sem_op = 1;
sem.sem_flg = SEM_UNDO;
while(((ret=semop(semid, &sem, 1)) == -1) && (errno == EINTR));
if (ret == -1){
perror("sem_signal");
return -1;
}
else
return 0;
}
void sem_delete(int semid, int semnum)
{
semctl(semid, semnum, IPC_RMID);
}
int disable_interrupt()
{
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
perror("SIG_BLOCK");
return -1;
} else {
return 0;
}
}
int enable_interrupt()
{
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
perror("SIG_SETMASK");
return -1;
} else {
return 0;
}
}
int time_now(TIME *time_val)
{
struct timezone tz;
int ret;
ret = gettimeofday(time_val, &tz);
return ret;
}
int time_after(TIME t1, TIME t2)
{
if ((t1.tv_sec - t2.tv_sec) > 0)
return 1;
else if ((t1.tv_sec - t2.tv_sec) < 0)
return -1;
else {
if ((t1.tv_usec - t2.tv_usec) > 0)
return 1;
else if ((t1.tv_usec - t2.tv_usec) < 0)
return -1;
else
return 0;
}
}
TIME subtract(TIME t1, TIME t2)
{
TIME tmp;
if ((t1.tv_usec - t2.tv_usec) < 0) {
t1.tv_sec--;
t1.tv_usec += 1000000;
tmp.tv_sec = t1.tv_sec - t2.tv_sec;
tmp.tv_usec = t1.tv_usec - t2.tv_usec;
} else {
tmp.tv_sec = t1.tv_sec - t2.tv_sec;
tmp.tv_usec = t1.tv_usec - t2.tv_usec;
}
return tmp;
}
TIME add(TIME t1, TIME t2)
{
TIME tmp;
if ((t1.tv_usec + t2.tv_usec) < 1000000) {
tmp.tv_sec = t1.tv_sec + t2.tv_sec;
tmp.tv_usec = t1.tv_usec + t2.tv_usec;
} else {
tmp.tv_sec = t1.tv_sec + t2.tv_sec + 1;
tmp.tv_usec = t1.tv_usec + t2.tv_usec - 1000000;
}
return tmp;
}
void timer_init(void)
{
struct timer *t;
for (t=timers;t<&timers[MAX_TIMERS];t++)
t->inuse = FALSE;
}
void timer_unregister(struct timer *t)
{
TIME tn;
sem_wait(semid, MUTEX);
disable_interrupt();
if (!t->inuse) {
enable_interrupt();
sem_signal(semid, MUTEX);
return;
}
t->inuse = FALSE;
/* check if we were waiting on this one */
if (t == timer_next) {
if (time_now(&tn) < 0) {
perror("time_now");
enable_interrupt();
sem_signal(semid, MUTEX);
return;
}
timers_update(subtract(tn, time_timer_set));
if (timer_next) {
set_timer(timer_next->time);
time_timer_set = tn;
}
}
enable_interrupt();
sem_signal(semid, MUTEX);
}
void timers_update(TIME time)
{
static struct timer timer_last = {
FALSE /* in use */,
VERY_LONG_TIME /* time */,
NULL /* callback pointer */,
NULL /* callback param */
};
struct timer *t;
timer_next = &timer_last;
for (t=timers;t<&timers[MAX_TIMERS];t++) {
if (t->inuse) {
if (time_after(t->time, time) > 0) { /* unexpired */
t->time = subtract(t->time, time);
if (time_after(timer_next->time, t->time) > 0)
timer_next = t;
} else { /* expired */
t->callback(t->param);
t->inuse = FALSE; /* remove timer */
}
}
}
/* reset timer_next if no timers found */
if (!timer_next->inuse)
timer_next = NULL;
}
struct timer *timer_register(TIME time, void (*callback)(void *), void *param)
{
struct timer *t;
TIME tn;
sem_wait(semid, MUTEX);
disable_interrupt();
for (t=timers;t<&timers[MAX_TIMERS];t++) {
if (!t->inuse)
break;
}
/* out of timers? */
if (t == &timers[MAX_TIMERS]) {
enable_interrupt();
sem_signal(semid, MUTEX);
return NULL;
}
/* install new timer */
t->time = time;
t->callback = callback;
t->param = param;
if (time_now(&tn) < 0) {
perror("time_now");
enable_interrupt();
sem_signal(semid, MUTEX);
return NULL;
}
if (!timer_next) {
/* no timers set at all, so this is shortest */
time_timer_set = tn;
timer_next = t;
set_timer(timer_next->time);
} else {
TIME new_time, last_time;
new_time = add(time, tn);
last_time = add(timer_next->time, time_timer_set);
if (time_after(last_time, new_time) > 0){
/* new timer is shorter than current one */
timers_update(subtract(tn, time_timer_set));
time_timer_set = tn;
timer_next = t;
set_timer(timer_next ->time);
} else {
/* new timer is longer than current one */
}
}
t->inuse = TRUE;
enable_interrupt();
sem_signal(semid, MUTEX);
return t;
}
void set_timer(TIME time)
{
struct itimerval itv, oldtv;
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = time.tv_sec;
itv.it_value.tv_usec = time.tv_usec;
setitimer(ITIMER_REAL, &itv, &oldtv);
}
void timer_interrupt_handler(int sig)
{
TIME tn;
printf("interrupt--------/n");
if (time_now(&tn) < 0) {
perror("time_now");
return;
}
timers_update(subtract(tn, time_timer_set));
/* start physical timer for next shortest time if one exists */
if (timer_next) {
time_timer_set = tn;
set_timer(timer_next ->time);
}
}
void timer_callback(void * param)
{
printf("%s callback/n", param);
}
int main( )
{
TIME time1 = {3,0},time2 = {5,0};
struct timer *p;
signal(SIGALRM, timer_interrupt_handler);
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
semid = sem_init(1, S_IRUSR|S_IWUSR);
if (semid == -1)
exit(-1);
sem_setval(semid, MUTEX, 1);/*protect critical region*/
timer_init( );
timer_register(time1, timer_callback, "timer1");
p = timer_register(time1, timer_callback, "timer2");
timer_unregister(p);
timer_register(time2, timer_callback, "timer3");
getchar();
sem_delete(semid, MUTEX);
exit(0);
}
参考文献:
Title: Implementing Software Timers
By: Don Libes
Originally appeared in the Nov. 1990 "C User"s Journal" and is also
reprinted as Chapter 35 of "Obfuscated C and Other Mysteries",
John Wiley & Sons, 1993, ISBN 0-471-57805-3.
http://www.wiley.com/compbooks/m3.html.