7.5 时钟中断的Bottom Half
与时钟中断相关的Bottom Half向两主要有两个:TIMER_BH和TQUEUE_BH。与TIMER_BH相对应的BH函数是timer_bh(),与TQUEUE_BH对应的函数是tqueue_bh()。它们均实现在kernel/timer.c文件中。
7.5.1 TQUEUE_BH向量
TQUEUE_BH的作用是用来运行tq_timer这个任务队列中的任务。因此do_timer()函数仅仅在tq_timer任务队列不为空 的情况才激活TQUEUE_BH向量。函数tqueue_bh()的实现非常简单,它只是简单地调用run_task_queue()函数来运行任务队列 tq_timer。如下所示:
void tqueue_bh(void)
{
run_task_queue(&tq_timer);
}
任务对列tq_timer也是定义在kernel/timer.c文件中,如下所示:
DECLARE_TASK_QUEUE(tq_timer);
7.5.2 TIMER_BH向量
TIMER_BH这个Bottom Half向量是Linux内核时钟中断驱动的一个重要辅助部分。内核在每一次对时钟中断的服务快要结束时,都会无条件地激活一个TIMER_BH向量,以 使得内核在稍后一段延迟后执行相应的BH函数——timer_bh()。该任务的源码如下:
void timer_bh(void)
{
update_times();
run_timer_list();
}
从上述源码可以看出,内核在时钟中断驱动的底半部分主要有两个任务:(1)调用update_times()函数来更新系统全局时间xtime; (2)调用run_timer_list()函数来执行定时器。关于定时器我们将在下一节讨论。本节我们主要讨论TIMER_BH的第一个任务——对内核 时间xtime的更新。
我们都知道,内核局部时间xtime是用来供用户程序通过时间syscall来检索或设置当前系统时间的,而内核代码在大多数情况下都引用 jiffies变量,而很少使用xtime(偶尔也会有引用xtime的情况,比如更新inode的时间标记)。因此,对于时钟中断服务程序 timer_interrupt()而言,jiffies变量的更新是最紧迫的,而xtime的更新则可以延迟到中断服务的底半部分来进行。
由于Bottom Half机制在执行时间具有某些不确定性,因此在timer_bh()函数得到真正执行之前,期间可能又会有几次时钟中断发生。这样就会造成时钟滴答的丢 失现象。为了处理这种情况,Linux内核使用了一个辅助全局变量wall_jiffies,来表示上一次更新xtime时的jiffies值。其定义如 下(kernel/timer.c):
/* jiffies at the most recent update of wall time */
unsigned long wall_jiffies;
而timer_bh()函数真正执行时的jiffies值与wall_jiffies的差就是在timer_bh()真正执行之前所发生的时钟中断次数。
函数update_times()的源码如下(kernel/timer.c):
static inline void update_times(void)
{
unsigned long ticks;
/*
* update_times() is run from the raw timer_bh handler so we
* just know that the irqs are locally enabled and so we don't
* need to save/restore the flags of the local CPU here. -arca
*/
write_lock_irq(&xtime_lock);
ticks = jiffies - wall_jiffies;
if (ticks) {
wall_jiffies += ticks;
update_wall_time(ticks);
}
write_unlock_irq(&xtime_lock);
calc_load(ticks);
}
(1)首先,根据jiffies和wall_jiffies的差值计算在此之前一共发生了几次时钟滴答,并将这个值保存到局部变量ticks中。 并在ticks值大于0的情况下(ticks大于等于1,一般情况下为1):①更新wall_jiffies为jiffies变量的当前值 (wall_jiffies+=ticks等价于wall_jiffies=jiffies)。②以参数ticks调用update_wall_time ()函数去真正地更新全局时间xtime。
(2)调用calc_load()函数去计算系统负载情况。这里我们不去深究它。
函数update_wall_time()函数根据参数ticks所指定的时钟滴答次数相应地更新内核全局时间变量xtime。其源码如下(kernel/timer.c):
/*
* Using a loop looks inefficient, but "ticks" is
* usually just one (we shouldn't be losing ticks,
* we're doing this this way mainly for interrupt
* latency reasons, not because we think we'll
* have lots of lost timer ticks
*/
static void update_wall_time(unsigned long ticks)
{
do {
ticks--;
update_wall_time_one_tick();
} while (ticks);
if (xtime.tv_usec >= 1000000) {
xtime.tv_usec -= 1000000;
xtime.tv_sec++;
second_overflow();
}
}
对该函数的注释如下:
(1)首先,用一个do{}循环来根据参数ticks的值一次一次调用update_wall_time_one_tick()函数来为一次时钟滴答更新xtime中的tv_usec成员。
(2)根据需要调整xtime中的秒数成员tv_usec和微秒数成员tv_usec。如果微秒数成员tv_usec的值超过106,则说明已经 过了一秒钟。因此将tv_usec的值减去1000000,并将秒数成员tv_sec的值加1,然后调用second_overflow()函数来处理微 秒数成员溢出的情况。
函数update_wall_time_one_tick()用来更新一次时钟滴答对系统全局时间xtime的影响。由于tick全局变量表示了 一次时钟滴答的时间间隔长度(以us为单位),因此该函数的实现中最核心的代码就是将xtime的tv_usec成员增加tick微秒。这里我们不去关心 函数实现中与NTP(Network Time Protocol)和系统调用adjtimex()的相关部分。其源码如下(kernel/timer.c):
/* in the NTP reference this is called "hardclock()" */
static void update_wall_time_one_tick(void)
{
if ( (time_adjust_step = time_adjust) != 0 ) {
/* We are doing an adjtime thing.
*
* Prepare time_adjust_step to be within bounds.
* Note that a positive time_adjust means we want the clock
* to run faster.
*
* Limit the amount of the step to be in the range
* -tickadj .. +tickadj
*/
if (time_adjust > tickadj)
time_adjust_step = tickadj;
else if (time_adjust < -tickadj)
time_adjust_step = -tickadj;
/* Reduce by this step the amount of time left */
time_adjust -= time_adjust_step;
}
xtime.tv_usec += tick + time_adjust_step;
/*
* Advance the phase, once it gets to one microsecond, then
* advance the tick more.
*/
time_phase += time_adj;
if (time_phase <= -FINEUSEC) {
long ltemp = -time_phase >> SHIFT_SCALE;
time_phase += ltemp << SHIFT_SCALE;
xtime.tv_usec -= ltemp;
}
else if (time_phase >= FINEUSEC) {
long ltemp = time_phase >> SHIFT_SCALE;
time_phase -= ltemp << SHIFT_SCALE;
xtime.tv_usec += ltemp;
}
}
阅读(1013) | 评论(0) | 转发(0) |