一、提要      

        计时器属于操作系统中的基础组件,不管是用户空间的程序开发,还是内核空间的程序开发,很多时候都需要有定时器作为基础组件的支持。使用定时器的目的无非是为了周期性的执行某一任务,或者是到了一个指定时间去执行某一个任务。

        本文首先讨论了在 Linux 环境下,计时器的分类与实现,并对相应的接口函数进行使用。


二、计时器的种类

RTC(Real Time Clock)

实时时钟,独立于CPU和其他所有芯片,能够在IRQ8上发出周期性的中断,频率在2Hz-8192Hz之间。


TSC(Time Stamp Counter)

时间戳计时器,主体是位于CPU里面的一个64位的TSC寄存器。每个CPU时钟周期其值加一。可以通过汇编语言指令rdtsc读这个寄存器。


PIT(Programmable Interval Timer)

可编程间隔定时器,通过发出一个特殊的中断来通知内核一个时间间隔过去了,PIT永远以内核确定的固定频率不停地发出中断。


APIC - CPU本地定时器


HPET - 高精度事件定时器


ACPI 电源管理定时器


三、计时体系的结构

内核会周期性地做下面的事:

1)更新系统启动以来所经过的时间;

2)更新时间和日期;

3)处理时间片的分配;

4)更新资源使用统计数;

5)检查每个软定时器的时间间隔是否已到。


计时器的初始化

1)初始化xtime变量(存放当前时间和日期);

2)初始化wall_to_monotonic变量;

3)如果内核支持HPET,它将调用hpet_enable函数来确认ACPI固件是否探测到了该芯片并将它的寄存器映射到了内存地址空间中;

4)调用select_timer()来挑选系统中可利用的最好的定时器资源(精度优先),设置cur_timer变量指向该定时器资源对应的定时器对象的地址;

5)调用setup_irq(0,&irq0)来创建与IRQ0相应的中断门,IRQ0引脚连接着系统时钟的中断源(PIT或者HPET).


时钟终端处理程序的执行

1)在xtime_lock顺序锁上产生一个write_seqlock()来保护与定时器相关的内核变量;

2)执行cur_timer定时器对象的mark_offset方法。cur_timer指向的定时器在计时器的初始化已确定;

3)调用do_timer_interrupt()函数;

4)调用write_sequnlock释放xtime_lock顺序锁;

5)返回1,报告中断已经有效处理了。


Linux考虑两种类型的定时器,动态定时器(dynamic timer)和间隔定时器(interval timer)。第一种类型由内核使用,后者可以由进程在用户态创建。


创建动态定时器的步骤

1)创建一个新的timer_list对象(静态全局变量、定义局部变量、动态分配);

2)调用init_timer(&t)初始化这个对象;

3)把定时器到期时要激活的函数地址放入function字段;

4)如果动态定时器还没有被插入到链表中,如expires字段赋一个合适的值并调用add_timer(&t)把t插入链表;

5)如已插入,调用mod_timer()来更新expires字段。


三、计时器的使用

unsigned int alarm(unsigned int seconds);
函数说明: alarm()用来设置信号SIGA
LRM在经过参数seconds指定的秒数后传送给目前的进程。如果参数seconds为0,则之前设置的闹钟会被取消,并将剩下的时间返回。

返回值: 返回之前闹钟的剩余秒数,如果之前未设闹钟则返回0。
alarm()执行后,进程将继续执行,在后期(alarm以后)的执行过程中将会在seconds秒后收到信号SIGALRM并执行其处理函数。

例子:

#include <stdio.h> #include <unistd.h> #include <signal.h>  void alarm_handler(int a){ 	printf("Timer is up!\n"); 	alarm(3); }  int main() { 	signal(SIGALRM,alarm_handler); 	alarm(1); 	while(1) {}; 	return 1; }

执行 结果:




int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
setitimer()比alarm功能强大,支持3种类型的定时器:

ITIMER_REAL : 以系统真实的时间来计算,它送出SIGALRM信号。
ITIMER_VIRTUAL : -以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
ITIMER_PROF : 以该进程在用户态下和内核态下所费的时间来计算,它送出SIGPROF信号。
setitimer()第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例;第三个参数可不做处理。
setitimer()调用成功返回0,否则返回-1。


相关结构体:


struct itimercal:
           struct itimerval {
           struct timeval it_interval; /* timer interval */
           struct timeval it_value; /* current value *
};

struct timeval {
    long tv_sec;
    long tv_usec;
};

itimerval结构中的it_value是减少的时间,当这个值为0的时候就发出相应的信号了. 然后再将it_value设置为it_interval值.这样就实现了轮询的定时,而不是想alarm那样只能定时一次,而且其精确度也很高。


例子:

#include <stdio.h> #include <unistd.h> #include <signal.h> #include <sys/time.h>  void alarm_handler(int sig){ 	switch(sig) 	{ 		case SIGALRM: 			printf("Catch a SIGALRM!\n"); 			break; 		case SIGVTALRM: 			printf("Catch a SIGVTALRM!\n"); 			break; 	} 	return; }  int main() { 	struct itimerval value, ovalue, value2;               signal(SIGALRM, alarm_handler);     signal(SIGVTALRM, alarm_handler);     value.it_value.tv_sec = 1;     value.it_value.tv_usec = 0;     value.it_interval.tv_sec = 1;     value.it_interval.tv_usec = 0;     setitimer(ITIMER_REAL, &value, &ovalue);     value2.it_value.tv_sec = 0;     value2.it_value.tv_usec = 500000;     value2.it_interval.tv_sec = 0;     value2.it_interval.tv_usec = 500000;     setitimer(ITIMER_VIRTUAL, &value2, &ovalue);     while(1); 	return 1;  }

执行:



四、参考

understanding the kernel  3rd Edith

linux应用层定时器与休眠 - http://blog.csdn.net/max415/article/details/2315977