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

7.6.3.4 把一个定时器插入到链表中

函数add_timer()用来把参数timer指针所指向的定时器插入到一个合适的定时器链表中。它首先调用timer_pending()函数判断所指定的定时器是否已经位于在某个定时器向量中等待执行。如果是,则不进行任何操作,只是打印一条内核告警信息就返回了;如果不是,则调用internal_add_timer()函数完成实际的插入操作。其源码如下(kernel/timer.c):

void add_timer(struct timer_list *timer)

{

unsigned long flags;

spin_lock_irqsave(&timerlist_lock, flags);

if (timer_pending(timer))

goto bug;

internal_add_timer(timer);

spin_unlock_irqrestore(&timerlist_lock, flags);

return;

bug:

spin_unlock_irqrestore(&timerlist_lock, flags);

printk("bug: kernel timer added twice at %p.\n",

__builtin_return_address(0));

}

函数internal_add_timer()用于把一个不处于任何定时器向量中的定时器插入到它应该所处的定时器向量中去(根据定时器的expires值来决定)。如下所示(kernel/timer.c):

static inline void internal_add_timer(struct timer_list *timer)

{

/*

* must be cli-ed when calling this

*/

unsigned long expires = timer->expires;

unsigned long idx = expires - timer_jiffies;

struct list_head * vec;

if (idx < TVR_SIZE) {

int i = expires & TVR_MASK;

vec = tv1.vec + i;

} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {

int i = (expires >> TVR_BITS) & TVN_MASK;

vec = tv2.vec + i;

} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {

int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;

vec = tv3.vec + i;

} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {

int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;

vec = tv4.vec + i;

} else if ((signed long) idx < 0) {

/* can happen if you add a timer with expires == jiffies,

* or you set a timer to go off in the past

*/

vec = tv1.vec + tv1.index;

} else if (idx <= 0xffffffffUL) {

int i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;

vec = tv5.vec + i;

} else {

/* Can only get here on architectures with 64-bit jiffies */

INIT_LIST_HEAD(&timer->list);

return;

}

/*

* Timers are FIFO!

*/

list_add(&timer->list, vec->prev);

}

对该函数的注释如下:

(1)首先,计算定时器的expires值与timer_jiffies的插值(注意!这里应该使用动态定时器自己的时间基准),这个差值就表示这个定时器相对于上一次运行定时器机制的那个时刻还需要多长时间间隔才到期。局部变量idx保存这个差值。

(2)根据idx的值确定这个定时器应被插入到哪一个定时器向量中。其具体的确定方法我们在7.6.2节已经说过了,这里不再详述。最后,定时器向量的头部指针vec表示这个定时器应该所处的定时器向量链表头部。

(3)最后,调用list_add()函数把定时器插入到vec指针所指向的定时器队列的尾部。

7.6.3.5 修改一个定时器的expires值

当一个定时器已经被插入到内核动态定时器链表中后,我们还可以修改该定时器的expires值。函数mod_timer()实现这一点。如下所示(kernel/timer.c):

int mod_timer(struct timer_list *timer, unsigned long expires)

{

int ret;

unsigned long flags;

spin_lock_irqsave(&timerlist_lock, flags);

timer->expires = expires;

ret = detach_timer(timer);

internal_add_timer(timer);

spin_unlock_irqrestore(&timerlist_lock, flags);

return ret;

}

该函数首先根据参数expires值更新定时器的expires成员。然后调用detach_timer()函数把该定时器从它原来所属的链表中删除。最后调用internal_add_timer()函数把该定时器根据它新的expires值重新插入到相应的链表中。

函数detach_timer()首先调用timer_pending()来判断指定的定时器是否已经处于某个链表中,如果定时器原来就不处于任何链表中,则detach_timer()函数什么也不做,直接返回0值,表示失败。否则,就调用list_del()函数把定时器从它原来所处的链表中摘除。如下所示(kernel/timer.c):

static inline int detach_timer (struct timer_list *timer)

{

if (!timer_pending(timer))

return 0;

list_del(&timer->list);

return 1;

}

7.6.3.6 删除一个定时器

函数del_timer()用来把一个定时器从相应的内核定时器队列中删除。该函数实际上是对detach_timer()函数的高层封装。如下所示(kernel/timer.c):

int del_timer(struct timer_list * timer)

{

int ret;

unsigned long flags;

spin_lock_irqsave(&timerlist_lock, flags);

ret = detach_timer(timer);

timer->list.next = timer->list.prev = NULL;

spin_unlock_irqrestore(&timerlist_lock, flags);

return ret;

}

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循环开始下一次定时器服务。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux内核是一个开源的操作系统内核,拥有非常丰富的配置选项。以下是对Linux内核配置选项的详细解释: 1. 进程管理:通过配置选项,可以选择支持多进程、多线程、多任务等特性。可以设置进程调度策略、锁定内存区域等。 2. 文件系统支持:Linux内核支持多种文件系统,包括Ext2、Ext3、Ext4、XFS等。配置选项中可以选择需要支持的文件系统类型。 3. 设备驱动支持:通过配置选项可以选择支持的硬件设备驱动,比如网卡驱动、声卡驱动、USB驱动等。 4. 内存管理:可以配置页面大小、内存映射方式、虚拟内存管理等相关选项,以提高内存的利用效率。 5. 网络支持:可以选择支持不同的网络协议栈,比如TCP/IP、UDP等。还可以通过配置选项设置网络参数,如MTU大小、网络连接数等。 6. 安全性配置:可以选择开启不同的安全特性,如SELinux、AppArmor等。还可以对访问控制进行细粒度的配置。 7. 调试支持:通过配置选项可以选择是否开启调试信息和调试功能,以便于开发和排查问题。 8. 电源管理:可以选择支持电源管理功能,以延长电池寿命或节约电能。 9. 定时器支持:可以配置内核定时器的精度和分辨率,以满足不同应用场景的要求。 10. 文件系统特性:可以选择开启各种文件系统的特性,如日志、快照、压缩等。 总而言之,Linux内核配置选项非常丰富,可以根据不同的需求和环境进行灵活配置,以获得最佳的性能和功能。 ### 回答2: Linux内核是一个自由开源的操作系统内核,可运行在各种计算机硬件平台上。内核配置是指根据特定需求对内核进行定制和编译,以满足用户对系统功能和性能的要求。 史上最全的Linux内核配置详解包括了众多的选项和参数,可以根据用户的需求进行选择。其中包括了文件系统支持、设备驱动、网络协议、性能优化等方面的配置。 在文件系统支持方面,内核提供了多个选项,如EXT4、XFS、Btrfs等,用户可以根据需要选择合适的文件系统。此外,还可以选择支持的文件系统功能,如日志系统、快照、压缩等。 设备驱动是Linux内核的一个重要组成部分,内核提供了大量的设备驱动选项,包括网络设备、声卡、USB设备、磁盘控制器等。用户可以根据自己的硬件配置选择相应的驱动。 网络协议是支持网络通信的关键,内核提供了TCP/IP、IPv6、IPSec等多种网络协议的支持。用户可以根据网络环境的需求选择启用相应的协议。 内核配置还包括了一些性能优化的选项,如预排定、缓存管理、中断处理等。用户可以根据系统的性能需求选择相应的优化选项。 另外,内核配置中还包括了调试和跟踪选项,可以帮助开发人员定位和解决问题。 总之,史上最全的Linux内核配置详解提供了众多选项和参数供用户选择和定制,以满足各种不同的需求。用户可以根据自己的需求选择适合自己的内核配置,以获得更好的系统性能和功能支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值