【转】基于Linux内核的时间轮算法设计实现【附代码】

带你走进linux 内核 定时器(timer)实现机制 定时器层是基于tick层(高精度定时器)之上的,是根据系统jiffies来触发的,精度相对比较低。利用定时器,我们可以设定在未来的某一时刻,触发一个特定的事件。经常,也会把这种低精度定时器称作时间轮(Timer Wheel)。在内核中,一个定时器是使用 timer_list 结构体来表示的: entry:所有的定时器都会根据到期的时间被分配到一组链表中的一个中,该字段是链表的节点成员。 expires:字段指出了该定时器的到期时刻,也就是期望定时器到期时刻的jiffies计数值。这是一个绝对值,不 阅读详情

首先声明,本文内容参考了以下博客文章,向这三篇文章的作者表示感谢。

  1. https://www.cnblogs.com/arnoldlu/p/7078262.html
  2. https://blog.youkuaiyun.com/HELPLEE601276804/article/details/36717979
  3. https://www.cnblogs.com/lsgxeva/p/8072468.html

1. 时间轮算法基本思想

对于一个复杂的软件系统,定时器的对任务的管理和调度至关重要,通常定时器的管理已成为一个复杂系统的重要基础设施。

定时器有很多种(一文完全理解定时器实现技术),基于升序的定时器时间链表是一种最直接的实现方式:即按照定时器时间到的时间顺序依次存放在一个链表中进行管理。但是这种链表存在效率的不足,就是当插入定时器的时候时间复杂度是O(n). 因此需要一种更高效地管理定时器的数据结构和算法,这里结合Linux内核中基于时间轮的定时器管理器的具体实现,介绍一种基于时间轮的定时器管理算法。图1为时间轮的基本结构:

图1 定时器基本结构

图1所示的是一个时间轮的基本结构。时间轮分为N个(例如8个)时间槽slot,每时间槽指向一个定时器链表,这个链表里包含多个定时器,这个链表的定时器Timeout时间相同,因此链表里的定时器无须排序。

时间轮每一个滴答时间转动一格,会指向下一个时间槽。这里的滴答时间取决于时间轮的具体实现,可以是系统的一个时钟时间,也可以是一个毫秒,一秒钟等。

如果记时间轮的一个滴答时间为si(slot interval),即时间轮每转动一个槽的时间为si,如果有N个槽,那么时间轮转动一圈的时间为N * si。

如果时间轮开始转动的起始时间为ts,那么当有个定时器Timeout时间为t的定时器要加入到时间轮,那么应该将这个定时器放到哪个时间槽对应的链表呢?可以用下面的公式计算:

((t - ts)/ si) % N

以图1为例,如果时间轮一个滴答时间为1秒,假设时间轮开始转动时间为0,那么一个定时器Timeout=6s的定时器应该加到6号时间槽对应的链表里,一个定时器Timeout=7s的定时器应该加到7号槽对应的链表里。

那么时间轮该如何检查定时器是否时间到呢?同样地,如果时间轮开始转动的起始时间为ts,当前时间为tc,则计算

((tc - ts)/ si) % N

计算结果则为定时器时间到的那个时间槽对应编号,这个时间槽对应的链表里的定时器全部时间到。

聪明的读者马上会想到一个问题:那么一个定时器Timout=8s的定时器会加到0号槽,岂不是和定时器Timeout=0s(马上时间到)的定时器放到一个槽里了?这是因为图1所示的时间轮刻度只要8个(即只能管理8种不同Timout的定时器),因此为了解决这种问题,需要增加N的值。

增加N的值更聪明的办法是采用多级时间轮,即在图1所示的时间轮外面再环绕一个时间轮,假设外面时间轮的刻度为8,即外轮的时间槽也是8个,每个时间槽也对应一个链表。同时定义时间轮的转动规则:当里面的时间轮转动1圈(8格),外面的时间轮转动1格。可以看到,采用这种方式,二级时间轮可以管理(8*8=64)种不同Timeout的定时器。

在二级时间轮的结构下,一个定时器Timeout=t的定时器怎么加入时间轮呢?还是假设二级时间轮都有8个槽,假设时间轮的起始时间为ts,则采用如下算法将Timeout=t的定时器加入时间轮:

1)计算t-ts/si;

2)如果t-ts/si < 8,则以t-ts/si的低3位作为索引加入内轮;

3)如果t-ts/si>= 8(当然要小于64,否则又溢出),则以t-ts/si的高3位作为索引加入外轮;

二级时间轮检查时间到的算法与单机时间轮类似,不同的地方就是当内轮的所有时间槽都时间到后,要把外轮的时间槽链表迁移到内轮。

综上所述:基于排序链表的定时器使用唯一的链表来管理所有定时器,所以插入定时器的数目越多,效率就会越低,而时间轮则是利用哈希表的思想,将定时器散列到不同的链表中,这样每条链表上的数据就会显著少于原来排序链表的数目。插入操作的效率基本不受定时器数目的影响(不需要排序,直接散列到一个链表上)。因此插入定时器的时间复杂度和定时器数量n无关,为O(1)。

显然要提高时间轮的精度,就要使si(slot interval)足够小,要提高其执行效率则要N要足够大。如果最里面一级时间轮的槽采用n1为二进制编码,外面一级时间轮采用n2位二进制编码,则总共可以管理的时间范围为0 ~ 2(n1+n2) – 1。以上面的例子为例,如果二级时间轮都是3位二进制编码(8个时间槽),那么总共可以管理的时间范围为0 ~ 63,即64种Timeout的定时器。

Linux内核采用多级时间轮。定义了5个链表数组(每个数组里面包含多个定时器链表):tv1-tv5都是一个链表数组,其中tv1的数组大小为TVR_SIZE(256,8位编码), tv2、tv3、tv4、tv5的数组大小为TVN_SIZE(64,6位编码)。可以看到一共是32位编码,总共可以管理的时间范围为0 ~ 232 – 1。

这5个数组就好比是5个齿轮,它们随着滴答时间的增长而不停地转动,每次只需处理第一个齿轮的某一个齿节,低一级的齿轮转动一圈,高一级的齿轮转动一个齿,同时自动把即将到期的定时器迁移到上一个齿轮中,所以低分辨率定时器通常又被叫做时间轮:time wheel。事实上,它的实现是一个很好的空间换时间软件算法。参考Linux的实现,具体代码如下:

首先定义如下宏:

2. 定时器的添加

要加入一个新的定时器,按以下步骤进行处理:

1)计算定时器到期时间和当前cpu定时器所经历过的毫秒数的差值,记为idx

2)根据idx的值,选择该定时器应该被放到tv1--tv5中的哪一个链表数组中,可以认为tv1-tv5分别占据一个32位数的不同比特位,tv1占据最低的8位,tv2占据紧接着的6位,然后tv3再占位,以此类推,最高的6位分配给tv5。最终的选择规则如下表所示:

确定链表数组后,接着要确定把该定时器放入数组中的哪一个链表中,如果时间差idx小于256,按规则要放入tv1中,因为tv1包含了256个链表,所以可以简单地使定时器的expires的低8位作为数组的索引下标,把定时器链接到tv1中相应的链表中即可。如果时间差idx的值在256--18383之间,则需要把定时器放入tv2中,同样的,使用定时器的expires的8--14位作为数组的索引下标,把定时器链接到tv2中相应的链表中,。定时器要加入tv3tv4 tv5使用同样的原理。经过这样分组后的定时器,在后续的tick事件中,系统可以很方便地定位并取出相应的到期定时器进行处理。代码如下:

 

3. 定时器到期处理

系统中的定时器按到期时间有规律地放置在tv1--tv5各个链表数组中,其中tv1中放置着在接下来的256个滴答时间(如毫秒)即将到期的定时器列表。系统滴答值一直在随着系统的运行而动态地增加,原则上是每个tick事件会加1,定时器加入tv1中使用的下标索引是定时器到期时间expires的低8位,所以假设当前的滴答值是0x34567826,则马上到期的定时器是在tv1.vec[0x26]中,如果这时候系统加入一个在滴答值0x34567828到期的定时器,他将会加入到tv1.vec[0x28]中,运行两个tick后,系统滴答的值会变为0x34567828,很显然,在每次tick事件中,定时器系统只要以当前滴答值的低8位作为索引,取出tv1中相应的链表,里面正好包含了所有在该滴答值到期的定时器列表。

那什么时候处理tv2--tv5中的定时器?每当当前系统滴答值的低8位为0值时,这表明当前系统滴答值的第8-13位有进位发生,这6位正好代表着tv2,这时只要按当前系统滴答值的第8-13位的值作为下标,移出tv2中对应的定时器链表,然后用第2节的步骤把它们重新加入到定时器系统中来,因为这些定时器一定会在接下来的256个tick期间到期,所以它们肯定会被加入到tv1数组中,这样就完成了tv2往tv1迁移的过程。同样地,当当前系统滴答值的第8-13位为0时,这表明当前系统滴答值的第14-19位有进位发生,这6位正好代表着tv3,按当前系统滴答值的第14-19位的值作为下标,移出tv3中对应的定时器链表,然后用第2节的步骤把它们从新加入到定时器系统中来,显然它们会被加入到tv2中,从而完成tv3到tv2的迁移,tv4,tv5的处理可以以此作类推。具体地,定时器时间到需要实现下面二个函数:check和cascade,其中cascade完成时间轮的从外轮向里轮的进位。

基于Linux内核的时间轮实现代码,可以在应用程序层面实现一个基于时间轮的管理器。部分代码如下所示:

  

TimerManager 类的定义如下:

 

本文分享自微信公众号 - 架构之美(beautyArch),作者:辜希武

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-12-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

基于Linux内核定时器时间轮实现方式 改进的单时间轮其实是一个对时间和空间折中的思路,即不会像单时间轮那样有O(1)的时间复杂度,但也不会像单时间轮那样对bucket个数有巨大的需求。在 Linux 系统中,我们可以设置slot为1个jiffy(1/HZ)的定时器,假设最大的到期时间范围要达到 2^32个 jiffies,如果采用上面这样的单时间轮,我们就需要2^32个 bucket,这会带来巨大的内存消耗,显然是需要优化改进的。相比简单的单时间轮,时间上仅仅多了1/256次(为约等于值,忽略了tv2以上产生的进位操作)的链表迁移操作耗时。 阅读详情

相关推荐

基于 golang 从零到一实现时间轮算法 (一)

时间轮是用来解决海量百万级定时器(或延时)任务的最佳方案,linux 的内核定时器就是采用该数据结构实现

qq_40318498的博客 727

linux2.6定时器的时间轮算法分析

1.     Overview常用的定时器实现算法有两种:最小堆和时间轮(timing wheel)。在Linux2.6的代码中,kernel/timer.c文件实现了一个通用定时器机制,使用的是时间轮算法。每一个CPU都有一个struct tvec_base结构,代表这个CPU使用的时间轮。struct tvec_base{       spinlock_t lock;                                  // 同步锁       struct timer_list * ru

walkingman321的专栏 1万+

Linux内核都在用的时间轮算法,我用C++实现了分布式版本——附完整源码

│ (集群协调器) ││ ││ │ (一致性哈希环) │ ││ ││ │ (心跳检测) │ ││ │ │▼ ▼ ▼层级组件解决的问题数据结构层O(1)的插入/删除/触发内存管理层ObjectPool消除malloc开销和内存碎片并发安全层单线程tick + 无锁队列跨线程通信分布式层任务分片 + 节点管理高可用层故障检测 + 任务迁移数据结构层——层级时间轮。这是整个系统的根基。

weixin_45715405的博客 155

linux的动态定时器--时间轮

定时器—有时也称为动态定时器或内核定时器—是管理内核时间的基础。定时器是一种软件功能,即允许在将来的某个时刻,函数在给定的时间间隔用完时被调用。注意的是定时器并不会周期运行,它在超时后就自行销毁,这也是定时器被称为动态定时器的一个原因。动态定时器不断地创建和销毁,而且它的运行次数也不受限制。 定时器在内核代码中属于一个基础组件。要想完全弄清楚linux2.6中内核定时器的实现,得......

BaikalHU 8377

Proxmox VE网络配置踩坑实录:从Web界面无法访问到完美修复的全过程

本文详细记录了Proxmox VE服务器Web界面无法访问的完整排查与修复过程。作者从基础网络连通性测试入手,逐步排查了服务状态、SSL证书、端口监听、防火墙规则及网关配置,最终定位并解决了问题。文章提供了系统性的故障排查思路和实用的修复命令,对管理Proxmox VE网络具有重要参考价值。

weixin_29231221的博客 889

Android wifi源码分析(二) Wifi关闭流程

接着上一篇,这篇说一下Wifi的关闭流程。 由上一篇可以知道,framework层提供的wifi开关接口都是**setWifiEnabled**,只是参数不同而已。*true*表示开启wifi、*false*表示关闭wifi。

奋斗的菜鸟ing 3297

linux现代时间轮算法,简谈一上时间轮(Time Wheel)

简谈一下时间轮(Time Wheel)如果一个程序员不知道Time Wheel,那么那个程序员一定不是个合格的程序员。timer对于操作系统还是一个虚拟机语言或大型中间件都起着重要的作用,同时timer算法的选择也直接影响着性能。Time Wheel翻译为时间轮,是用于实现定时器timer的经典算法,算法细节就不多说了,这里主要是看看Erlang中和Linux kernel的time wheel实...

weixin_33680112的博客 680

hi3519dv500 os04a10输入调试

因该sensor接在soc端的iic6,在驱动中(Hi3519DV500_SDK_V2.0.1.1\smp\a55_linux\source\mpp\cbb\isp\user\sensor\hi3519dv500\omnivision_os04a10\os04a10_sensor_ctl.c)注意总线的选择。mem_process_isolation=1 改为 mem_process_isolation=0。2、OT_MIPI_SET_HS_MODE配置为LANE_DIVIDE_MODE_1。

linus 3167

基于Linux内核的时间轮算法设计实现【附代码】

首先声明,本文内容参考了以下博客文章,向这三篇文章的作者表示感谢。https://www.cnblogs.com/arnoldlu/p/7078262.htmlhttps://blog....

musicml的博客 1282

VS2022+Qt6保姆级配置指南:从插件安装到项目互通全流程(附常见错误解决)

本文提供了一份详细的Visual Studio 2022与Qt6集成开发环境配置指南。从插件安装、环境搭建到项目创建与互通,涵盖了全流程操作步骤,并针对常见的配置错误和调试问题提供了实用的解决方案,帮助开发者高效构建企业级桌面应用。

nginx7reverse的博客 1205

游戏开发-GM系统的设计

游戏管理员(gm)作为游戏中拥有更高权限的账号,在游戏开发的过程中,我们需要很早的将gm系统做出来,作为测试游戏逻辑具有不可替代的作用。 内容目录 服务端与客户端通讯基本流程 gm命令格式设计 gm系统的实现 服务端与客户端通讯基本流程 服务端与客户端通信,如果基本的网络框架搭建完成,那么我们只需要写好命令就可以了 服务器完成一条命令的添加需要3个过程。 (1)命令格式的定义

微不足道 4616

java时间轮定时器_基于Linux内核的时间轮算法设计实现【附代码】

首先声明,本文内容参考了以下博客文章,向这三篇文章的作者表示感谢。https://www.cnblogs.com/arnoldlu/p/7078262.htmlhttps://blog.youkuaiyun.com/HELPLEE601276804/article/details/36717979https://www.cnblogs.com/lsgxeva/p/8072468.html1. 时间轮...

weixin_39999859的博客 147

企业数字化建设之——品牌零售全渠道运营中台解决方案

OMS审单 --> 订单进入OMS待发货列表区域 --> 备货(线下)-->拣货((线下 )-->质检(线下)--> 包装(线下)--> 快递员取货(线下)(物品 + 客户收货地址清单 )--> 快递员发货(2.2.5 正常退货,客户发起 --> 客服助理审核 --> 评审组 --> ( 货物运输 ) -->质检 ->仓库收货入 正常仓库 -->财务退款,记账。OMS审单 --> 订单进入物流公司系统(如:*丰),--> 快递员发货 --> 物流状态同步OMS --> OMS同步物流状态到销售平台。

java_zhong1990的专栏 612

线性规划算法设计代码_基于Linux内核的时间轮算法设计实现【附代码】

首先声明,本文内容参考了以下博客文章,向这三篇文章的作者表示感谢。https://www.cnblogs.com/arnoldlu/p/7078262.htmlhttps://blog.youkuaiyun.com/HELPLEE601276804/article/details/36717979https://www.cnblogs.com/lsgxeva/p/8072468.html1. 时间轮...

weixin_29343807的博客 159

图解联合概率密度、边缘概率密度、条件概率密度之间的关系

简要图解联合概率密度、边缘概率密度、条件概率密度之间的关系

一个搬运知识的笨小孩 2万+

STM32智能小车控制舵机转向

文章目录前言一、舵机简单介绍及接线方式二、cubeMX配置三、代码解析总结 前言 本文将带大家学习如何驱动舵机转向 一、舵机简单介绍及接线方式 信号线(黄线)红线(电源线)棕色(地线) 舵机的控制一般需要一个20ms左右的时基脉冲,该脉冲的高电平部分一般为0.5ms-2.5ms范围内的角度控制脉冲部分,总间隔为2ms。以180度角度伺服为例,那么对应的控制关系是这样的: 0.5ms--------------0度; 1.0ms------------45度; 1.5ms------------90度; 2

m0_49476241的博客 8613

内核定时底层核心:时间轮算法

因为很多文章会让你误以为“时间轮天下无敌”,其实并不是。

hw5230的博客 465

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

内核定时器 —— 时间轮算法

内核定时器 2.6 的内核 概述 内核通过时间轮算法实现一个精度固定的定时器,这个精度由时钟中断和软频率 HZ 决定。定时器运作的基本流程: 在每次(时间)中断触发后,执行各种软中断,其中包括定时器软中断。 检查时间轮中的定时器是否超时,如果超时则运行其回调。 初始化 内核在 start_kernel() 中初始化定时器子系统: void __init init_timers(void) ...

CrazyHeroZK的专栏 829

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

时间轮python_时间轮定时器的实现(参考Linux源码)

时间轮 (Timing-Wheel) 算法类似于一以恒定速度旋转的左轮手枪,枪的撞针则撞击枪膛,如果枪膛中有子弹,则会被击发;与之相对应的是:对于 PerTickBookkeeping,其最本质的工作在于以 Tick 为单位增加时钟,如果发现有任何定时器到期,则调用相应的 ExpiryProcessing。设定一个循环为 N 个 Tick 单元,当前时间是在 S 个循环之后指向元素 i (i&gt...

weixin_33739480的博客 847

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

定时器设计:传统方法和时间轮算法

组成 触发“时针”pointer转动的Ticker:一般使用定时循环(一般使用死循环,每次循环sleep小段时间) 存放定时任务的环形哈希表 工作过程 当Ticker触发时,pointer进行自增,在每一次自增中都会对pointer对应的槽中任务链表进行扫描,执行并删除过期的任务,直至pointer的时间与当前时间一致。 核心问题 如何设计高效哈希表以适应不同的定时任务? Ticker的设计 这两个问题都会通过分析下面的示例后进行解答。 示例 FastDFS FastDFS的使用时间轮实现超时控制以及.

lidelin10的博客 1085

时间轮算法设计实现

时间轮算法基本思想 参照时钟运转的思路,每个相同刻度下的时间是相同的,将所有相同时间的时间放在一个槽中,通过用一个指针每过一个刻度指向时间轮的槽,取出槽中链表,循环遍历相同刻度下的事件。(liunx内核就是这么做的)。 如图所示的是一个时间轮的基本结构。时间轮分为N个时间槽slot,每时间槽指向一个定时器链表,这个链表里包含多个定时器,这个链表的定时器Timeout时间相同,因此链表里的定时器无须排序。 简单时间轮的实现 #include <unistd.h> #include <i

G_Super_Mouse的博客 495

浅谈linux定时器时间轮算法

以下内容转载自https://www.toutiao.com/i6914186976950223367/ Linux定时器分为低精度定时器和高精度定时器两种类型,内核对其均有实现。本文讨论的是我们在应用程序开发中比较常见的低精度定时器。作为常用的基础组件,定时器常用的几种实现方法包括:基于排序链表实现、基于小根堆实现、基于红黑树实现、基于时间轮实现。本文讲解的是时间复杂度最优,也是linux内核采用的基于时间轮的实现方式。 采用有序链表实现的定时器,添加定时器的时间复杂度为O(n);采用小根堆或红黑树.

Shanks 455

使用c++实现时间轮算法(Timing-Wh…

前段时间写了篇blog描述了时间轮算法(http://blog.sina.com.cn/s/blog_48d4cf2d0100pq9o.html),昨天终于动手用c++将其实现了,并通过了单元测试。我没有看Linux内核的实现代码,只是看了资料凭自己的理解来实现,在效率方面也没有尽量优化。所以下面的代码用来帮助理解算法可以,拿来实际用就不一定靠谱了。     另外我没有实现删除函数,因为懒得自己

runyon的博客 2794

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

1.Overview常用的定时器实现算法有两种:红黑树和时间轮(timing wheel)。在Linux2.6的代码中,kernel/timer.c文件实现了一个通用定时器机制,使用的是时间轮算法。每一个CPU都有一个struct tvec_base结构,代表这个CPU使用的时间轮。struct tvec_base{spinlock_t lock;//同步锁struct timer_list * ...

weixin_42351189的博客 257

C++ 实现定时器的两种方法(线程定时和时间轮算法修改版)

定时器要求在固定的时间异步执行一个操作,比如boost库中的boost::asio::deadline_timer,以及MFC中的定时器。可以利用c++11的thread, mutex, condition_variable 来实现一个定时器。使用static以免被析构,可在cpp类外全局声明,第一个参数为任务容量最大数量,第二个参数为定时判断的毫秒数即最低检测时间单位。使用C++11中的thread, mutex, condition_variable来实现一个定时器。在要使用的地方,启动并添加任务。

Leckun的专栏 1万+

64位 atol c linux_浅谈linux定时器时间轮算法

相关视频解析linux后台开发之海量、高并发场景下,定时器如何设计时间轮实现Linux定时器分为低精度定时器和高精度定时器两种类型,内核对其均有实现。本文讨论的是我们在应用程序开发中比较常见的低精度定时器。作为常用的基础组件,定时器常用的几种实现方法包括:基于排序链表实现、基于小根堆实现、基于红黑树实现、基于时间轮实现。本文讲解的是时间复杂度最优,也是linux内核采用的基于时间轮的实现方式。采用...

weixin_33657090的博客 137
上一篇: 文件IO零拷贝
下一篇: Bazel BUILD文件格式化及命名
LinuxG_G
博客等级 码龄15年 58粉丝 176原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值