热门关键字:  ubuntu  分区  Fedora  linux系统进程  函数

当前位置 :| 主页>Linux教程>内核研究>

Linux系统内核定时器机制详解(下)

来源: 作者: 时间:2007-05-20 Tag: 点击:

7.6.3.7 定时器迁移操作

由于一个定时器的interval值会随着时间的不断流逝(即jiffies值的不断增大)而不断变小,因此那些原本到期紧迫程度较低的定时器会随着jiffies值的不断增大而成为既将马上到期的定时器。比如定时器向量tv2.vec [0]中的定时器在经过256个时钟滴答后会成为未来256个时钟滴答内会到期的定时器。因此,定时器在内核动态定时器链表中的位置也应相应地随着改变。改变的规则是:当tv1.index重新变为0时(意味着tv1中的256个定时器向量都已被内核扫描一遍了,从而使tv1中的256个定时器向量变为空),则用tv2.vec[index]定时器向量中的定时器去填充tv1,同时使tv2.index加1(它以64为模)。当tv2.index重新变为0(意味着tv2中的64个定时器向量都已经被全部填充到tv1中去了,从而使得tv2变为空),则用tv3.vec[index]定时器向量中的定时器去填充tv2。如此一直类推下去,直到tv5。

函数cascade_timers()完成这种定时器迁移操作,该函数只有一个 timer_vec结构类型指针的参数tv。这个函数将把定时器向量tv->vec[tv->index]中的所有定时器重新填充到上一层定时器向量中去。如下所示(kernel/timer.c):

static inline void cascade_timers(struct timer_vec *tv) 
{ 
/* cascade all the timers from tv up one level */ 
struct list_head *head, *curr, *next; 

head = tv->vec + tv->index; 
curr = head->next; 
/* 
* We are removing _all_ timers from the list, so we don't have to 
* detach them individually, just clear the list afterwards. 
*/ 
while (curr != head) { 
struct timer_list *tmp; 

tmp = list_entry(curr, struct timer_list, list); 
next = curr->next; 
list_del(curr); // not needed 
internal_add_timer(tmp); 
curr = next; 
} 
INIT_LIST_HEAD(head); 
tv->index = (tv->index + 1) & TVN_MASK; 
}

对该函数的注释如下:

(1)首先,用指针head指向定时器头部向量头部的list_head结构。指针curr指向定时器向量中的第一个定时器。

(2)然后,用一个while{}循环来遍历定时器向量tv->vec[tv ->index]。由于定时器向量是一个双向循环队列,因此循环的终止条件是curr=head。对于每一个被扫描的定时器,循环体都先调用 list_del()函数将当前定时器从链表中摘除,然后调用internal_add_timer()函数重新确定该定时器应该被放到哪个定时器向量中去。

(3)当从while{}循环退出后,定时器向量tv->vec[tv- >index]中所有的定时器都已被迁移到其它地方(到它们该呆的地方:-),因此它本身就成为一个空队列。这里我们显示地调用 INIT_LIST_HEAD()宏来将定时器向量的表头结构初始化为空。

(4)最后,将tv->index值加1,当然它是以64为模。

7.6.4.8 扫描并执行当前已经到期的定时器

函数run_timer_list()完成这个功能。如前所述,该函数是被 timer_bh()函数所调用的,因此内核定时器是在时钟中断的Bottom Half中被执行的。记住这一点非常重要。全局变量timer_jiffies表示了内核上一次执行run_timer_list()函数的时间,因此 jiffies与timer_jiffies的差值就表示了自从上一次处理定时器以来,期间一共发生了多少次时钟中断,显然run_timer_list ()函数必须为期间所发生的每一次时钟中断补上定时器服务。该函数的源码如下(kernel/timer.c):

static inline void run_timer_list(void) 
{ 
spin_lock_irq(&timerlist_lock); 
while ((long)(jiffies - timer_jiffies) >= 0) { 
struct list_head *head, *curr; 
if (!tv1.index) { 
int n = 1; 
do { 
cascade_timers(tvecs[n]); 
} while (tvecs[n]->index == 1 && ++n < NOOF_TVECS); 
} 
repeat: 
head = tv1.vec + tv1.index; 
curr = head->next; 
if (curr != head) { 
struct timer_list *timer; 
void (*fn)(unsigned long); 
unsigned long data; 

timer = list_entry(curr, struct timer_list, list); 
fn = timer->function; 
data= timer->data; 

detach_timer(timer); 
timer->list.next = timer->list.prev = NULL; 
timer_enter(timer); 
spin_unlock_irq(&timerlist_lock); 
fn(data); 
spin_lock_irq(&timerlist_lock); 
timer_exit(); 
goto repeat; 
} 
++timer_jiffies; 
tv1.index = (tv1.index + 1) & TVR_MASK; 
} 
spin_unlock_irq(&timerlist_lock); 
}

函数run_timer_list()的执行过程主要就是用一个大while{}循环来为时钟中断执行定时器服务,每一次循环服务一次时钟中断。因此一共要执行(jiffies-timer_jiffies+1)次循环。循环体所执行的服务步骤如下:

(1)首先,判断tv1.index是否为0,如果为0则需要从tv2中补充定时器到tv1中来。但tv2也可能为空而需要从tv3中补充定时器,因此用一个do{}while循环来调用cascade_timer()函数来依次视需要从tv2中补充tv1,从tv3中补充tv2、…、从tv5中补充tv4。显然如果tvi.index=0(2≤i≤5),则对于tvi执行 cascade_timers()函数后,tvi.index肯定为1。反过来讲,如果对tvi执行过cascade_timers()函数后 tvi.index不等于1,那么可以肯定在未对tvi执行cascade_timers()函数之前,tvi.index值肯定不为0,因此这时tvi 不需要从tv(i+1)中补充定时器,这时就可以终止do{}while循环。

(2)接下来,就要执行定时器向量tv1.vec[tv1.index]中的所有到期定时器。因此这里用一个goto repeat循环从头到尾依次扫描整个定时器对列。由于在执行定时器的关联函数时并不需要关CPU中断,所以在用detach_timer()函数将当前定时器从对列中摘除后,就可以调用spin_unlock_irq()函数进行解锁和开中断,然后在执行完当前定时器的关联函数后重新用 spin_lock_irq()函数加锁和关中断。

(3)当执行完定时器向量tv1.vec[tv1.index]中的所有到期定时器后,tv1.vec[tv1.index]应该是个空队列。至此这一次定时器服务也就宣告结束。

(4)最后,将timer_jiffies值加1,将tv1.index值加1,当然它的模是256。然后,回到while循环开始下一次定时器服务。




相关文章:
精通initramfs构建step by step
Linux利用kexec迅速切换内核
进程上下文VS中断上下文
内核通知链 学习笔记
linux spi子系统驱动分析
menuconfig 配置选项
《Linux操作系统内核实习》之练习一
udev详解
什么叫微内核,宏内核?
Linux 信号signal处理机制
开发简单的 Linux2.6 内核模块
删除内核的perl脚本
Linux2.6内核usb gadget驱动移植
GCC hacks in the Linux kernel
iomem
kernel学习的想法
让自己的驱动支持udev
linux内核编译步骤
内核的等待队列
Linux内核wait_queue深入分析
升级和删除内核
SD卡驱动分析2
Linux Kernel VDSO本地权限提升漏洞
内核中的TCP的追踪分析-15-TCP(IPV4)的客户端与
linux 2.6内核可加载模块的编译
内核模块HelloWorld
在环回接口上发送一个数据报
ARP初始化
1分钟编译FreeBSD内核
linux设备模型之uart驱动架构分析