0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

定时器原理以及一般定时器实现的方式

开关电源芯片 来源:Linux内核那些事 作者:Linux内核那些事 2021-08-14 11:15 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

定时器原理一般定时器实现的方式有以下几种:

基于排序链表方式:

通过排序链表来保存定时器,由于链表是排序好的,所以获取最小(最早到期)的定时器的时间复杂度为 O(1)。但插入需要遍历整个链表,所以时间复杂度为 O(n)。如下图:

基于最小堆方式:

通过最小堆来保存定时器,在最小堆中获取最小定时器的时间复杂度为 O(1),但插入一个定时器的时间复杂度为 O(log n)。如下图:

基于平衡二叉树方式:

使用平衡二叉树(如红黑树)保存定时器,在平衡二叉树中获取最小定时器的时间复杂度为 O(log n)(也可以通过缓存最小值的方法来达到 O(1)),而插入一个定时器的时间复杂度为 O(log n)。如下图:

时间轮:

但对于Linux这种对定时器依赖性比较高(网络子模块的TCP协议使用了大量的定时器)的操作系统来说,以上的数据结构都是不能满足要求的。所以Linux使用了效率更高的定时器算法:时间轮。

时间轮 类似于日常生活的时钟

日常生活的时钟,每当秒针转一圈时,分针就会走一格,而分针走一圈时,时针就会走一格。而时间轮的实现方式与时钟类似,就是把到期时间当成一个轮,然后把定时器挂在这个轮子上面,每当时间走一秒就移动时针,并且执行那个时针上的定时器。

一般的定时器范围为一个32位整型的大小,也就是 0 ~ 4294967295,如果通过一个数组来存储的话,就需要一个元素个数为4294967296的数组,非常浪费内存。这个时候就可以通过类似于时钟的方式:通过多级数组来存储。

时钟通过时分秒来进行分级,当然我们也可以这样,但对于计算机来说,时分秒的分级不太友好,所以Linux内核中,对32位整型分为5个级别,第一个等级存储0 ~ 255秒 的定时器,第二个等级为 256秒 ~ 256*64秒,第三个等级为 256*64秒 ~ 256*64*64秒,第四个等级为 256*64*64秒 ~ 256*64*64*64秒,第五个等级为 256*64*64*64秒 ~ 256*64*64*64*64秒。

注意:第二级至第五级数组的第一个槽是不挂任何定时器的。

每级数组上面都有一个指针,指向当前要执行的定时器。每当时间走一秒,Linux首先会移动第一级的指针,然后执行当前位置上的定时器。当指针变为0时,会移动下一级的指针,并把该位置上的定时器重新计算一次并且插入到时间轮中,其他级如此类推。

当要执行到期的定时器只需要移动第一级数组上的指针并且执行该位置上的定时器列表即可,所以时间复杂度为 O(1),而插入一个定时器也很简单,先计算定时器的过期时间范围在哪一级数组上,并且连接到该位置上的链表即可,时间复杂度也是 O(1)。

Linux时间轮的实现那么接下来我们看看Linux内核是怎么实现时间轮算法的。

定义五个等级的数组

#define TVN_BITS 6#define TVR_BITS 8#define TVN_SIZE (1 《《 TVN_BITS) // 64#define TVR_SIZE (1 《《 TVR_BITS) // 256#define TVN_MASK (TVN_SIZE - 1)#define TVR_MASK (TVR_SIZE - 1)struct timer_vec {

int index;

struct list_head vec[TVN_SIZE];

};

struct timer_vec_root {

int index;

struct list_head vec[TVR_SIZE];

};

static struct timer_vec tv5;static struct timer_vec tv4;static struct timer_vec tv3;static struct timer_vec tv2;static struct timer_vec_root tv1;void init_timervecs (void)

{

int i;

for (i = 0; i 《 TVN_SIZE; i++) {

INIT_LIST_HEAD(tv5.vec + i);

INIT_LIST_HEAD(tv4.vec + i);

INIT_LIST_HEAD(tv3.vec + i);

INIT_LIST_HEAD(tv2.vec + i);

}

for (i = 0; i 《 TVR_SIZE; i++)

INIT_LIST_HEAD(tv1.vec + i);

}

上面的代码定义第一级数组为 timer_vec_root 类型,其 index 成员是当前要执行的定时器指针(对应 vec 成员的下标),而 vec 成员是一个链表数组,数组元素个数为256,每个元素上保存了该秒到期的定时器列表,其他等级的数组类似。

插入定时器

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) { // 0 ~ 255

int i = expires & TVR_MASK;

vec = tv1.vec + i;

} else if (idx 《 1 《《 (TVR_BITS + TVN_BITS)) { // 256 ~ 16191

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;

}

/*

* 添加到链表中

*/

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

}

internal_add_timer() 函数的主要工作是计算定时器到期时间所属的等级范围,然后把定时器添加到链表中。

执行到期的定时器

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);

internal_add_timer(tmp);

curr = next;

}

INIT_LIST_HEAD(head);

tv-》index = (tv-》index + 1) & TVN_MASK;

}

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() 函数完成,该函数首先比较当前时间与最后一次运行 run_timer_list() 函数时间的差值,然后循环这个差值的次数,并执行当前指针位置上的定时器。

每循环一次对第一级数组指针进行加一操作,当第一级数组指针变为0(即所有定时器都执行完),那么就移动下一个等级的指针,并把该位置上的定时器重新计算插入到时间轮中,重新计算定时器通过 cascade_timers() 函数实现。

编辑:jq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 计算机
    +关注

    关注

    19

    文章

    7764

    浏览量

    92677
  • 定时器
    +关注

    关注

    23

    文章

    3360

    浏览量

    121734
  • TCP协议
    +关注

    关注

    1

    文章

    101

    浏览量

    12709

原文标题:一文读懂:Linux定时器实现

文章出处:【微信号:gh_3980db2283cd,微信公众号:开关电源芯片】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    CW32L010+定时器介绍

    篇介绍一下定时器的内容,从了解CW定时器到功能实现定时器类型 CW32L010系列M
    发表于 12-01 07:53

    CW32定时器及中断介绍

    ,LPTIM 还可以与正交编码连接,自动 实现递增计数和递减计数。 通用定时器:CW32L083 内部集成 4 个通用定时器(GTIM),每个 GTIM 完全独立且功能完全相同,各
    发表于 12-01 07:08

    单片机定时器中断

    定时器/计数的工作方式寄存,确定工作方式和功能;TCON是控制寄存,控制T0,T1的启动
    发表于 11-24 06:22

    PWM、定时器、SysTick 区别及应用场景

    。下面我们来梳理清楚。、基本概念定时器(Timer)MCU内最基础的计数外设,通过计数时钟周期实现定时、计数功能。多数MCU内部有多个通用定时器
    的头像 发表于 11-17 10:53 219次阅读
    PWM、<b class='flag-5'>定时器</b>、SysTick 区别及应用场景

    ‌TLC551 LinCMOS™ 定时器芯片技术文档总结

    TLC551 是使用 TI LinCMOS 制造的单片定时电路^TM的^过程。这定时器与 CMOS、TTL 和 MOS 逻辑完全兼容,工作频率高达 2 MHz。与 NE555 定时器相比,该器件由于输入阻抗高,因此使用更小的
    的头像 发表于 09-24 09:16 638次阅读
    ‌TLC551 LinCMOS™ <b class='flag-5'>定时器</b>芯片技术文档总结

    SysTick系统滴答定时器简介

    SysTick—系统定时器是属于CM33内核中的个外设,内嵌在NVIC中。系统定时器个24bit的向下递减的计数,计数
    的头像 发表于 09-23 09:50 1328次阅读
    SysTick系统滴答<b class='flag-5'>定时器</b>简介

    ‌TPL5010-Q1 纳米功耗系统定时器(带看门狗功能)技术文档摘要

    TPL5010-Q1 Nano 定时器款低功耗、符合 AEC-Q100 标准的定时器,带有看门狗 该功能非常适合占空比或电池供电应用中的系统唤醒。在这样的系统中 微控制
    的头像 发表于 09-13 10:01 1256次阅读
    ‌TPL5010-Q1 纳米功耗系统<b class='flag-5'>定时器</b>(带看门狗功能)技术文档摘要

    大彩讲堂:VisualHMI-LUA教程-定时器的使用指南

    定时器的使用
    的头像 发表于 08-31 16:59 848次阅读
    大彩讲堂:VisualHMI-LUA教程-<b class='flag-5'>定时器</b>的使用指南

    TPS3435 Nano IQ精密超时看门狗定时器技术解析与应用指南

    Texas Instruments TPS3435/TPS3435-Q1 Nano I~Q~ 精确超时看门狗定时器款250nA(典型值)超低功耗器件,具有可编程超时看门狗定时器。该看门狗
    的头像 发表于 08-26 16:20 713次阅读
    TPS3435 Nano IQ精密超时看门狗<b class='flag-5'>定时器</b>技术解析与应用指南

    基于 AS32X601 微控制定时器模块(TIM)技术研究与应用实践

    摘要: 本文全面介绍了国科安芯推出的AS32X601系列微控制定时器模块(TIM),包括其系统架构、功能特性、应用场景以及工程实践要点。通过对芯片的详细分析,揭示了其高性能运行的基础。本文详细
    的头像 发表于 08-19 16:44 595次阅读

    高电流、高效率电荷泵,具有自动定时器 skyworksinc

    电子发烧友网为你提供()高电流、高效率电荷泵,具有自动定时器相关产品参数、数据手册,更有高电流、高效率电荷泵,具有自动定时器的引脚图、接线图、封装手册、中文资料、英文资料,高电流、高效率电荷泵,具有自动定时器真值表,高电流、高效
    发表于 07-29 18:32
    高电流、高效率电荷泵,具有自动<b class='flag-5'>定时器</b> skyworksinc

    第二十章 TIM——基本定时器

    本章介绍了W55H32基本定时器TIM6、TIM7,16位向上计数,含时钟源、预分频等,讲解定时计算及初始化结构体。
    的头像 发表于 06-20 13:51 898次阅读
    第二十章 TIM——基本<b class='flag-5'>定时器</b>

    第十二章 SysTick——系统定时器

    本章介绍了W55MH32的SysTick系统定时器,它是24位递减计数,含4个寄存,可配置定时、中断,用于产生时基 等。
    的头像 发表于 05-22 17:16 827次阅读
    第十二章 SysTick——系统<b class='flag-5'>定时器</b>

    MCU定时器/计数

    RISC-V核低功耗MCU通过灵活的定时器架构、低功耗模式适配及硬件级中断优化,在工业控制、智能家居等场景中实现高精度计时与能耗控制的协同设计,满足复杂任务调度与实时响应的双重需求‌。 、‌硬件
    的头像 发表于 04-27 13:54 589次阅读