Linux定时器的实现

时间:2020-11-26 23:30:57

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管理器。   Linux定时器的实现 

 

#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.  Linux定时器的实现