内核中的时间概念
硬件为内核提供了一个系统定时器用以计算流逝的时间。系统定时器是一种可编程硬件芯片,它能以固定频率产生中断。该频率可以通过编程预定,称作节拍率(tick rate)。该中断就是所谓的定时器中断,它所对应的中断处理程序负责更新系统时间,也负责执行需要周期性运行的任务。
节拍率Hz
系统定时器频率(节拍率)是通过静态预处理定义的,也就是HZ(赫兹),在系统启动时按照HZ值对硬件进行设置。体系结构不同,HZ的值也不同。
内核在asm/param.h文件中定义了这个值。节拍率有一个HZ频率,一个周期为1/HZ秒。x86体系结构中,系统定时器频率默认为100。
jiffies
全局变量jiffies用来记录自系统启动以来产生的节拍的总数。
jiffies定义于文件 linux/jiffies.h 中:
extern unsigned long volatile jiffies;
将以秒为单位的时间转化为jiffies: (seconds * HZ)
将jiffies转换为以秒为单位的时间:(jiffies/HZ)
用户空间和HZ
在2.6版以前的内核中,如果改变内核中HZ的值,会给用户空间中某些程序造成异常结果。为了避免这个错误,内核定义了USER_HZ来代表用户空间看到的HZ值。
内核使用函数jiffies_to_clock_t()或jiffies_64_to_clock_t()将jiffies值的单位从HZ转换为USER_HZ。
定时器
内核定时器是管理内核流逝的时间的基础,与下半部将工作推后执行不同,内核定时器能够使工作在指定时间点上执行。
内核定时器也称为动态定时器,因为这种定时器超时之后就自动撤销,如果需要周期运行,就必须得不断地创建和撤销。
1. 使用定时器
定时器由结构体timer_list表示,定义在文件 linux/timer.h 中。
struct timer_list {
struct list_head entry; /* 定时器链表的入口 */
unsigned long expires; /* 以jiffies为单位的定时值 */
void (*function)(unsigned long); /* 定时器处理函数 */
unsigned long data; /* 传给处理函数的长整型参数 */
struct tvec_t_base_s *base; /* 定时器内部值,用户不要使用 */
};
创建定时器时需要定义它:
struct timer_list my_timer;
然后初始化:
init_timer(&my_timer);
然后填充结构中需要的值:
my_timer.expires = jiffies + delay; /* 定时器超时时的节拍数 */
my_timer.data = 0; /* 给定时器处理函数传入0值 */
my_timer.function = my_function; /* 定时器超时时调用的函数 */
处理函数必须符合下面的函数原型:
void my_timer_function(unsigned long data);
最后激活定时器:
add_timer(&my_timer);
其他定时器相关函数:
/**
* 修改定时器
*/
int mod_timer(struct timer_list *timer, unsigned long expires);
/**
* 删除定时器
*/
int del_timer(struct timer_list *timer);
/**
* 删除定时器,删除前,等待多处理器上的定时器处理程序都退出
*/
int del_timer_sync(struct timer_list *timer);
2. 定时器处理函数
内核在时钟中断发生后执行定时器,定时器作为软中断在下半部的上下文中执行。具体来说,时钟中断处理程序会执行update_process_times函数,该函数随机调用run_local_timers函数:
void run_local_timers(void)
{
hrtimer_run_queues();
raise_softirq(TIMER_SOFTIRQ); /* 执行定时器软中断 */
softlockup_tick();
}
延迟执行
1. 忙等待
最简单的延迟方法是忙等待(或者说忙循环),例如:
unsigned long delay = jiffies + 2*HZ; /* 延迟2秒 */
while (time_before(jiffies, delay));
这是一种非常非常低效率的方法,稍微好一点的,是在代码等待时,允许内核重新调度执行其他任务:
unsigned long delay = jiffies + 2*HZ; /* 延迟2秒 */
while (time_before(jiffies, delay))
cond_resched();
2. 短延迟
内核提供了三个可以处理ms、ns和ms级别的延迟函数,它们定义在文件 linux/delay.h 和 asm/delay.h 中,
void udelay(unsigned long usecs);
void ndelay(unsigned long nsecs);
void mdelay(unsigned long msecs);
udelay()函数依靠执行数次循环达到延迟效果,而mdelay()函数又是通过udelay()函数实现的。
3. schedule_timeout()
更理想的延迟执行方法是使用schedule_timeout()函数,该函数会让需要延迟执行的任务睡眠到指定的延迟时间耗尽后再重新运行。schedule_timeout()函数是内核定时器的一个简单应用。