2012年(4)
分类: LINUX
2012-02-13 23:43:44
Linux 内核定时器及使用方法
一.度量时间差
时钟中断是由系统的定时硬件以周期性的时间间隔产生,这个间隔(即频率)由内核根据HZ来确定,HZ是一个与体系结构无关的常量(定义在
每当时钟中断发生时,全局变量jiffies(一个32位的unsigned long 变量,定义在
内核提供了一组宏用来比较时间量:
这几个宏可以理解为 a 宏名 b? 1:0.
获取当前时间:
二.内核定时器
内核定时器用于控制某个函数(定时器处理函数)在未来的某个特定时间执行.内核定时器注册的处理函数只执行一次.处理过后即失效.
当内核定时器被调度运行时,几乎可以肯定其不会在注册这些函数的进程正在执行时.相反,它会异步的执行.这种异步类似于硬件中断发生时的情景.实际上,内核定时器是被"软件中断"调度运行的.因此,其运行于原子上下文中.这点和tasklet很类似.处于原子上下文的进程有一些运行时的限制:
1. 不能访问用户空间.因为没有进程上下文.无法与特定的进程与用户关联
2. 不能执行调度或休眠.
3. Current指针在原子模式下无意义.
内核定时器被组织成双向链表,使用struct timer_list
这3个字段表示,当jiffies等于expires时,内核会调度function函数运行.data是传递给function的参数的指针,如果function函数需要不止一个参数,那么可以将这几个参数组成一个结构体,并将结构体的指针赋值给data.
三.管理定时器的接口
void init_timer(struct time_list *timer);
初始化定时器队列结构.timer_list结构在使用前必须初始化,这是要保证结构体中其他的成员能正确赋值.
void add_timer(struct time_list *timer);
启动定时器.
int del_timer(struct time_list *timer);
在定时器超时前将定时器删除.当定时器超时后,系统会自动将其删除.
四.内核定时器的使用方法
当删除定时器时,必须小心一个潜在的竞争条件。当del_timer()返回后,可以保证的只是: 定时器不会再被激活(也就是,将来不会执行),但是在多处理机器上定时器中断可能已经在其他处理器上运行了,所以删除定时器时需要等待可能在其他处理器上 运行的定时器处理程序都退出,这时就要使用del_timer_sync()函数执行删除工作:
del_timer_sync(&my_timer);
和del_timer()函数不同,del_timer_sync()函数不能在中断上下文中使用.(这点不是很理解)
五. 内核定时器的实现
...待完善
附:内核中的中断上下文:
内核的一个基本原则就是:在中断或者说原子上下文中,内核不能访问用户空间,而且内核是不能睡眠的。也就是说在这种情况下,内核是不能调用有可能引起睡眠的任何函数。一般来讲原子上下文指的是在中断或软中断中,以及在持有自旋锁的时候。内核提供了四个宏来判断是否处于这几种情况里:
这四个宏所访问的count都是thread_info->preempt_count。这个变量其实是一个位掩码。最低8位表示抢占计数,通常由spin_lock/spin_unlock修改,或程序员强制修改,同时表明内核容许的最大抢占深度是256。
8-15位表示软中断计数,通常由local_bh_disable/local_bh_enable修改,同时表明内核容许的最大软中断深度是256。
位16-27是硬中断计数,通常由enter_irq/exit_irq修改,同时表明内核容许的最大硬中断深度是4096。
第28位是PREEMPT_ACTIVE标志。用代码表示就是:
PREEMPT_MASK: 0x000000ff
SOFTIRQ_MASK: 0x0000ff00
HARDIRQ_MASK: 0x0fff0000
凡是上面4个宏返回1得到地方都是原子上下文,是不容许内核访问用户空间,不容许内核睡眠的,不容许调用任何可能引起睡眠的函数。而且代表thread_info->preempt_count不是0,这就告诉内核,在这里面抢占被禁用。
但是,对于in_atomic()来说,在启用抢占的情况下,它工作的很好,可以告诉内核目前是否持有自旋锁,是否禁用抢占等。但是,在没有启用抢占的情况下,spin_lock根本不修改preempt_count,所以即使内核调用了spin_lock,持有了自旋锁,in_atomic()仍然会返回0,错误的告诉内核目前在非原子上下文中。所以凡是依赖in_atomic()来判断是否在原子上下文的代码,在禁抢占的情况下都是有问题的。