一、提要
计时器属于操作系统中的基础组件,不管是用户空间的程序开发,还是内核空间的程序开发,很多时候都需要有定时器作为基础组件的支持。使用定时器的目的无非是为了周期性的执行某一任务,或者是到了一个指定时间去执行某一个任务。
本文首先讨论了在 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()用来设置信号SIGALRM在经过参数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