2012年(11)
分类: LINUX
2012-09-15 23:09:35
Dynamic timer或者kernel timer(通常所说的定时器)是软件的,用于定时,比如过一些时间之后发生某个事件,但dynamic timer的实现是依赖于system timer的。
• Tick和HZ
两个system timer中断之间的间隔是一个tick,一秒钟有多少个tick称为HZ。通常HZ的大小为100或者1000(通常linux的时钟分辨率也是毫秒级)。系统每秒钟产生HZ个时钟中断。
HZ(也就是系统时钟)可以配置,尽管越高值代表着更高的时钟分辨率,但是伴随而来的弊端就是增大系统开销(因为有了更多的中断),而且会增加系统功耗(系统功耗和时钟频率很有关系)。用户空间有相应的USER_HZ可以使用。
• Jiffies
内核会对system timer中断进行计数,在系统启动的时候,计数值为0。Jiffies就是这么一个计数值,它是一个全局变量。在32位的平台上,它其实是另一个变量jiffies_64的低32位,jiffies_64每过一个tick递增1。之所以jiffies_64要是64位的原因是防止溢出,只要HZ设置的合理,长达几十年的时间里jiffies_64都不会溢出。而在64位平台上,jiffies_64 jiffies是一样的。32位平台上更为广泛的使用jiffies是因为访问它是原子化的,而访问jiffies_64则不一定。
很多时候,使用jiffies来衡量某个操作是否超时,比如:
若是考虑到溢出的风险,使用大于符号“>”不够理想,系统提供了专门的比较函数:
diff = (long)t2 - (long)t1;//两个时间点的差异,不管有没有溢出,都可以比较
获取jiffies_64的方法是:
u64 get_jiffies_64(void);
• 精确计时
Jiffies的时间分辨率不高(毫秒级),若需要精确的比较两个时间点,可以利用CPU本身的寄存器。CPU的工作频率很高(MHz级别),现代CPU都有一个计数寄存器,以CPU时钟的时间频率自我累加,所以分辨率非常高,可以用于需要计时精确的场合:
#define rdtscl(dest) \
__asm__ __volatile__("mfc0 %0,$9; nop" : "=r" (dest))
• Wall time
一般存在两个跟时钟有关的硬件,一个是前面提到的系统时钟(system timer),另外一个是RTC(real-time clock)。RTC在系统掉电时维护时间(wall time)的更新,在系统启动时,从RTC中读取当前时间,之后通过system timer来维护时间,并周期性更新至RTC。
系统中有两种数据类型来描述wall time,一个是timeval,另一个是timespec,前者用秒和毫秒描述,后者用秒和纳秒描述,它们与jiffies之间可以互相转换:
unsigned long timespec_to_jiffies(struct timespec *value);
void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);
unsigned long timeval_to_jiffies(struct timeval *value);
void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);
获得当前的wall time的方法有:
do_gettimeofday() — 用timeval描述
current_kernel_time() — 用timespec描述
• 系统时钟ISR
system timer的作用是提供周期性中断,在system timer的中断处理程序中主要处理这么一些工作:
更新jiffies_64,wall time
进程调度——用于减少处于运行状态进程的timeslice count,当count为0时,need_resched标志置位,于是就开始调度新的进程。
处理定时器(dynamic timer),若有定时器expire了,则触发softirq来调用其handler
在系统时钟ISR中见过run_local_timers,它触发TIMER_SOFTIRQ软中断,并处理所有expired timers:
struct timer_list my_timer;//定义数据结构
init_timer(&my_timer);//初始化
my_timer.expires = jiffies + delay; /* timer expires in delay ticks */
my_timer.data = 0; /* zero is passed to the timer handler */
my_timer.function = my_function; /* function to run when timer expires */
add_timer(&my_timer);//激活定时器
此外,还有以下操作函数:
int mod_timer(struct timer_list *timer, unsigned long expires);
int del_timer_sync(struct timer_list *timer);
前面说的TIMER_SOFTIRQ软中断中会“处理”超时的定时器,其实指的是会调用timer_list中的function,也就是上述代码中的my_function。需要注意的是my_function是运行在中断上下文的,也就是不能sleep!
定时器总是在激活它的CPU上运行。
定时器的简单实现是所有定时器都挂在一个链表上,每次系统时间中断来临之后,链表上的所有定时器的expire值都减一。之前所述,在系统时间中断的ISR中会触发softirq去处理超时的定时器,但在softirq若是将链表上的定时器都检查一边是否超时,就太过于复杂了。所以,内核将定时器按照不同的超时期限来分类,比如未来5秒超时的放在一个链表上,未来50秒超时的放在另一个链表上,这样softirq只要检查一个小巧的链表就可以了!
• 延时
1.忙等延时—如果在忙等之前,内核禁用了抢占,忙等的时候系统就像死机了一样。更为值得注意的是,如果在忙等之前,系统已经禁用了中断,那么jiffies是不会被更新的,也就是忙等永远永远不会结束!
unsigned long timeout = jiffies + 10; /* ten ticks */
while (time_before(jiffies, timeout))
;
2.调度延时—这个方法的问题是不够精确,因为系统可能存在优先级更高的进程,等到原来的进程获得执行机会的时候,可能已经过了设定的时间了。
unsigned long delay = jiffies + 5*HZ;
while (time_before(jiffies, delay))
schedule()//或者cond_resched();//若need_resched设置了,就会调度
3.挂起延时—进程被挂起也就是处于sleep状态,直到某个事件来唤醒或者超时
long wait_event_timeout(wait_queue_head_t q, condition, long timeout);
long wait_event_interruptible_timeout(wait_queue_head_t q,condition, long timeout);
在这种情况下,没有事件(event)会唤醒进程,只能靠超时,所以有特别的函数:
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout (s * HZ);
其实,上述XXX_timeout靠的是之前所的定时器方法来实现的!
4.小延迟
Jiffies是毫秒级的分辨率,若需要更精准的延迟,采用如下方案:
void udelay(unsigned long usecs)
void ndelay(unsigned long nsecs)
void mdelay(unsigned long msecs)
它们依靠CPU本身的loop运算(_nop_空指令吧?)来完成。
有个BogoMIPS的概念,指的是一个jiffies中CPU可以loop运算几次,内核通过calibrate_dela()计算loops_per_jiffy变量。