1.Overview

常用的定时器实现算法有两种:红黑树和时间轮(timing wheel)。

在Linux2.6的代码中,kernel/timer.c文件实现了一个通用定时器机制,使用的是时间轮算法。

每一个CPU都有一个struct tvec_base结构,代表这个CPU使用的时间轮。

struct tvec_base

{

spinlock_t lock;//同步锁

struct timer_list * running_timer;//当前正在运行的定时器

unsigned long timer_jiffies;//当前运行到的jiffies

struct tvec_root tv1;

struct tvec tv2;

struct tvec tv3;

struct tvec tv4;

struct tvec tv5;

}

struct tvec_root与struct tvec都是数组,数组中的每一项都指定一个链表。struct tvec_root定义的数组大小是256(2的8次方);struct tvec_root定义的数组大小是64(2的6次方)。所以,tv1~6定义的数组总大小是2的(8 + 4*6 = 32)次方,正好对应32位处理器中jiffies的定义(unsigned long)。

因为使用的是wheel算法,tv1~5就代表5个wheel。

tv1是转速最快的wheel,所有在256个jiffies内到期的定时器都会挂在tv1的某个链表头中。

tv2是转速第二快的wheel,里面挂的定时器超时jiffies在2^8 ~ 2^(8+6)之间。

tv3是转速第三快的wheel,超时jiffies在2^(8+6) ~ 2^(8+2*6)之间。

tv4、tv5类似。

2.增加timer

增加timer分两步,先是调用init_timer初始化一个struct timer_list结构,然后调用add_timer把timer挂到5个wheel中包含的某一个定时器队列中。

init_timer函数初始化timer_list中的一些域。

static void __init_timer(struct timer_list *timer)

{

timer->entry.next = NULL;

timer->base = __raw_get_cpu_var(tvec_bases);

#ifdef CONFIG_TIMER_STATS

timer->start_site = NULL;

timer->start_pid = -1;

memset(timer->start_comm, 0, TASK_COMM_LEN);

#endif

}

add_timer是__mod_timer函数的一个封装,__mod_timer最终会调用internal_add_timer。

static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)

{

unsigned long expires = timer->expires;

unsigned long idx = expires - base->timer_jiffies;// idx为定时器距离超时还有多少jiffies

struct list_head *vec;

//如果idx少于TVR_SIZE(256),把定时器加入tv1

if (idx < TVR_SIZE) {

int i = expires & TVR_MASK;

vec = base->tv1.vec + i;

}

//如果idx少于2^(8+6)次方,加入tv2

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

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

vec = base->tv2.vec + i;

}

//类推,加入tv3

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

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

vec = base->tv3.vec + i;

}

//类推,加入tv4

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

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

vec = base->tv4.vec + i;

}

//如果idx < 0,说明定时器已经超时,应该马上被调度,把它加入base->timer_jiffies,//即base的当前jiffies对应的队列。这里要注意的是,这个函数在spin_lock_irq被调//用之后调用,所以不会与定时器调度函数产生竞态。

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 = base->tv1.vec + (base->timer_jiffies & TVR_MASK);

}

//加入tv5队列。

else {

int i;

/* If the timeout is larger than 0xffffffff on 64-bit

* architectures then we use the maximum timeout:

*/

if (idx > 0xffffffffUL) {

idx = 0xffffffffUL;

expires = idx + base->timer_jiffies;

}

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

vec = base->tv5.vec + i;

}

trace_timer_set(timer);

/*

* Timers are FIFO:

*/

list_add_tail(&timer->entry, vec);

}

3.timer的调度

timer的调度在软中断中完成,在init_timers函数中调用

open_softirq(TIMER_SOFTIRQ, run_timer_softirq)

注册定时器对应的软中断处理函数。

static void run_timer_softirq(struct softirq_action *h)

{

struct tvec_base *base = __get_cpu_var(tvec_bases);

hrtimer_run_pending();              // 高精度时钟的处理

//如果jiffies超过了base->timer_jiffies,那么调用时钟处理函数

if (time_after_eq(jiffies, base->timer_jiffies))

__run_timers(base);

}

static inline void __run_timers(struct tvec_base *base)

spin_lock_irq加锁,关中断,保证与定时器添加函数之间不会产生竞态

while (time_after_eq(jiffies, base->timer_jiffies))

// index代表这个循环需要处理的定时器在数组tv1中的下标

int index = base->timer_jiffies & TVR_MASK;

//定时器的重新排列,下文详细讨论

if (!index &&

(!cascade(base, &base->tv2, INDEX(0))) &&

(!cascade(base, &base->tv3, INDEX(1))) &&

!cascade(base, &base->tv4, INDEX(2)))

cascade(base, &base->tv5, INDEX(3));

++base->timer_jiffies;

//取出这次循环需要处理的定时器队列

list_replace_init(base->tv1.vec + index, &work_list);

//处理队列中的每一个定时器。注意在调用定时器中的timer->function之前,会先//调用spin_unlock_irq,调用完之后再重新用spin_lock_irq加锁。这是为了提高CPU的效率。另外,在处理某个定时器时,//会调用set_running_timer把当前正在运行的定时器标记到base->running_timer//中。

……

//函数退出,标记现在base中没有正在运行的定时器

set_running_timer(base, NULL);

spin_unlock_irq//解锁

4.定时器的重新排列

时间轮算法中,随着时间推移,定时器到期的时间越来越近,时间轮也随着时间不停转动。当最快的轮转到某一个触发点,就将次快轮的某一队列搬到最快轮。次块

轮转到某一触发点,把下一级时间轮的某一队列再往上搬到次快轮。依此类推。所以排队中的定时器就是这样一级一级往上,直到在最快轮中被处理。

在这里,这些搬移由上节描述的__run_timers函数的下列代码体现:

// index代表这个循环需要处理的定时器在数组tv1中的下标

int index = base->timer_jiffies & TVR_MASK;

//定时器的重新排列

if (!index &&

(!cascade(base, &base->tv2, INDEX(0))) &&

(!cascade(base, &base->tv3, INDEX(1))) &&

!cascade(base, &base->tv4, INDEX(2)))

cascade(base, &base->tv5, INDEX(3));

当jiffies的低8位为0时,tv1到了触发点,调用cascade函数把tv2的某一格定时器搬到tv1。tv2的cascade函数返回0时,代表tv2也到了触发点,于是又触发下一级时间轮tv3。依此类推。

这里INDEX的定义为:

#define INDEX(N) ((base->timer_jiffies >> (TVR_BITS+ (N) * TVN_BITS)) & TVN_MASK)

可以看出这个宏返回的是时间轮中某一格的索引:

INDEX(0)返回tv2的索引。

INDEX(1)返回tv3的索引。

INDEX(2)返回tv4的索引。

INDEX(3)返回tv5的索引。

static int cascade(struct tvec_base *base, struct tvec *tv, int index)

//将tv数组中以index为下标的定时器队列取出,准备搬移

list_replace_init(tv->vec + index, &tv_list);

//根据超时时间,将队列中的定时器重新排队。定时器往上一级时间轮排队的过程就//在这里发生。

list_for_each_entry_safe(timer, tmp, &tv_list, entry) {

BUG_ON(tbase_get_base(timer->base) != base);

internal_add_timer(base, timer);

}

linux现代时间轮算法,linux2.6定时器的时间轮算法分析相关推荐

  1. 孙玄辜教授:基于Linux内核的时间轮算法设计实现【附代码】

    文章目录 1.时间轮算法基本思想 2.定时器的添加 3.定时器到期处理 孙玄:毕业于浙江大学,现任转转公司首席架构师,技术委员会主席,大中后台技术负责人(交易平台.基础服务.智能客服.基础架构.智能运 ...

  2. .Net之时间轮算法(终极版)定时任务

    TimeWheelDemo 一个基于时间轮原理的定时任务 对时间轮的理解 其实我是有一篇文章(.Net 之时间轮算法(终极版)[1])针对时间轮的理论理解的,但是,我想,为啥我看完时间轮原理后,会采用 ...

  3. 面试官:知道时间轮算法吗?在Netty和Kafka中如何应用的?

    最近看 Kafka 看到了时间轮算法,记得以前看 Netty 也看到过这玩意,没太过关注.今天就来看看时间轮到底是什么东西. 为什么要用时间轮算法来实现延迟操作? 延时操作 Java 不是提供了 Ti ...

  4. 小顶堆时间复杂度_时间轮算法以及时间轮在Netty和Kafka中的应用的

    大家好,我是yes. 最近看 Kafka 看到了时间轮算法,记得以前看 Netty 也看到过这玩意,没太过关注.今天就来看看时间轮到底是什么东西. 为什么要用时间轮算法来实现延迟操作? 延时操作 Ja ...

  5. Linux网络编程 | 高性能定时器 :时间轮、时间堆

    文章目录 时间轮 时间堆 在上一篇博客中我实现了一个基于排序链表的定时器容器,但是其存在一个缺点--随着定时器越来越多,添加定时器的效率也会越来越低. 而下面的两个高效定时器--时间轮.时间堆,会完美 ...

  6. 心跳超时时间设置_定时器实现之时间轮算法

    前言 在看这篇文章的时候对其中超时控制一块儿有点好奇.通过时间轮来控制超时?啥是时间轮?怎么控制的?文章会先介绍常见的计时超时处理,再引入时间轮介绍及 netty 在实现时的一些细节,最后总结下实现的 ...

  7. Linux内核深入理解定时器和时间管理(4):定时器 timer

    Linux内核深入理解定时器和时间管理 定时器 timer rtoax 2021年3月 在原文基础上,增加5.10.13内核源码相关内容. 1. Timers This is fourth part ...

  8. 实现较低的计时器粒度以重传TCP(RTO):时间轮算法如何减少开销

    <TCP/IP协议栈:TCP超时重传机制> Table of Contents 计时器轮算法 使用计时器轮算法实现RTO 概要 AIX®传输控制协议(TCP)为每个连接维护七个计时器: 建 ...

  9. 原 荐 简单说说Kafka中的时间轮算法

    零.时间轮定义简单说说时间轮吧,它是一个高效的延时队列,或者说定时器.实际上现在网上对于时间轮算法的解释很多,定义也很全,这里引用一下 朱小厮博客 里出现的定义:参考下图,Kafka中的时间轮(Tim ...

最新文章

  1. python 四边形分割
  2. 网络相关的一些基本的命令的使用(ping、ifconfig、route、netstat)---Linux学习笔记
  3. bigfile.to服务器位置,Cloudera Manager 迁移服务器
  4. android工程jrr版本怎么改,ionic3 生成android 如何控制versionCode版本号
  5. java基础学习笔记(二)
  6. 让大家久等了,BERT推理加速终于开源了
  7. ubuntu14.04 下 mysql 存储目录迁移
  8. 电脑面上,在电脑桌面上添加文字_在电脑桌面上添加图片
  9. 网页与服务器时间不一致,js解决客户端与服务器时间不一致的问题
  10. 配置 OpenLDAP 使用 SSL/TLS 加密数据通信
  11. 李嘉诚、英特尔、比亚迪入股的雷蛇,上市后能玩把大的吗?
  12. Oracle基本语法及例子
  13. Centos 7 利用LVM实现动态扩容(1)
  14. Consul Sessions
  15. DNS服务未响应的简单解决办法
  16. CSDN如何公开私密博客
  17. xdoj 174-分配宝藏
  18. 2016年美国数学奥林匹克竞赛试题
  19. ubuntu下的android JNI入门DEMO
  20. codeforces_#242 (Div. 2)

热门文章

  1. 超实用web前端开发工具推荐(web开发+前端性能优化+浏览器兼容性测试+……)
  2. 编译原理--词法分析器(python语言实现)
  3. 百战程序员python资源_【百战程序员】Python 文件I/O
  4. 上位机与下位机都是个啥?
  5. stm8s103k3 周期 捕获_STM8S做输入捕获
  6. 小米笔记本待机系统崩溃怎么U盘重装系统?
  7. 单点登陆(SSO)协议简介:OpenID、OAuth2、SAML
  8. 新内核版本ioctl的变化 _IO, _IOR, _IOW, _IOWR 幻数的理解
  9. 2023年疫情开放,国内程序员薪资涨了还是跌了?大数据告诉你答案
  10. vim 设置变量、结构体成员及函数名不同颜色显示(c语言)