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

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

  1. linux内核培训广州,嵌入式Linux驱动开发高级培训班-华清远见嵌入式培训中心

    课程目标 本课程以案例教学为主,系统地介绍Linux下有关FrameBuffer.MMC卡.USB设备的驱动程序开发.参加本课程学习的学员,因为具备了Linux设备驱动开发基础,所以本课程针对性较强, ...

  2. linux内核启动分析 三,Linux内核分析 实验三:跟踪分析Linux内核的启动过程

    贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 ...

  3. Linux内核开发_1_编译LInux内核

    目录 1. 准备工作 1.1 学习环境 1.2 下载Linux内核源码 1.3 解压Linux内核 1.4 目录结构介绍 2. Linux内核配置 2.1 配置选项 1. make config 2. ...

  4. 一文了解linux内核,一文了解Linux的系统结构

    什么是 Linux ? 如果你以前从未接触过Linux,可能就不清楚为什么会有这么多不同的Linux发行版.在查看Linux软件包时,你肯定被发行版.LiveCD和GNU之类的术语搞晕过.初次进入Li ...

  5. Linux内核入门-如何获取Linux内核源代码、生成配置内核

    如何获取Linux内核源代码 如何获取Linux内核源代码 下载Linux内核当然要去官方网站了,网站提供了两种文件下载,一种是完整的Linux内核,另一种是内核增量补丁,它们都是tar归档压缩包.除 ...

  6. 查看linux内核的编译时间,linux内核编译步骤

    linux内核编译步骤 对于linux新手来说,编译内核相对有一些难度,甚至不知道如何入手,我通过在网上收集这方面的资料,最终编译成功.现在我归纳了一下,写出这一篇还算比较详细的步骤,希望能对各位新手 ...

  7. 【Linux 内核 内存管理】Linux 内核堆内存管理 ① ( 堆内存管理 | 内存描述符 mm_struct 结构体 | mm_struct 结构体中的 start_brk、brk 成员 )

    文章目录 一.堆内存管理 二.内存描述符 mm_struct 结构体 三.mm_struct 结构体中的 start_brk.brk 成员 一.堆内存管理 Linux 操作系统中的 " 堆内 ...

  8. 【Linux 内核】进程管理 ( Linux 内核中的进程状态 | TASK_RUNNING | TASK_INTERRUPTIBLE | __TASK_STOPPED | EXIT_ZOMBIE )

    文章目录 一.Linux 内核中的进程状态 二.TASK_RUNNING 状态 三.TASK_RUNNING 状态 四.TASK_UNINTERRUPTIBLE 状态 五.__TASK_STOPPED ...

  9. linux内核学习之三:linux中的32位与64位

    linux内核学习之三:linux中的"32位"与"64位" 在通用PC领域,不论是windows还是linux界,我们都会经常听到"32位" ...

  10. 在win10查看本机linux的文件,Windows 10变身开发者利器:内置Linux内核,轻松查看Linux子系统文件...

    原标题:Windows 10变身开发者利器:内置Linux内核,轻松查看Linux子系统文件 来源:创事记 终于!在Windows里可以访问Linux文件了. 这表明,微软插入开源界的触角,越来越深入 ...

最新文章

  1. 程序员成熟的几个标志
  2. SQL基础---增删查询操作
  3. 公开羞辱邻座大码乘客 美国一女乘客被逐下客机
  4. 双11稳定性负责人叔同讲述:九年双11的云化架构演进和升级
  5. 【Nginx】Nginx配置文件参数/启动参数详解;启动/停止/重新加载配置命令
  6. codeforces 337D Book of Evil(dp)
  7. mysql的几种模式_MYSQL复制的几种模式
  8. POJ3979 分数加减法【水题】
  9. 执行数据库命令Command对象——ADO.NET学习应用笔记之三
  10. Linux中安装WPS
  11. WPF开发之dll文件创建与调用
  12. 操作系统--进程调度实验报告
  13. python列表查找值_查找列表中某个值的位置(python)
  14. MATLAB r2014a 下载+安装+激活
  15. B站陈睿说:“B站也是个学习APP”!亲测还很好学
  16. Django_Model详解
  17. python评估函数_python绘制评估优化算法性能的测试函数
  18. 揭秘无领导小组面试时面试官的选人角度
  19. 学习笔记:unity通过Mesh网格绘制图形:三角形正方体圆柱
  20. 毕业论文怎么写?一搏教你拆解论文撰写8大模块

热门文章

  1. pythonchallenge之C++学习篇-01
  2. 【中级03】class loader subsystem详解
  3. python GIL 全局解释器锁详解
  4. bigdecimal 科学计数转普通计数_LoaRunner性能测试教程:Windows计数器(2)
  5. 30 秒速成好莱坞黑客 -- 在 Linux 终端中伪造好莱坞黑客屏幕
  6. Glib2版本差异初始化(三)
  7. 声压级和灵敏度的关系
  8. 深度学习与自然语言处理 主要概念一览
  9. Linux基于v4l2的视频采集(可用)
  10. webgis之Openlayer加载wmts服务