rtthread学习笔记系列(10/11) -- 系统定时器
文章目录
- 10. 系统定时器
- 10.1 跳跃表
- [定时器跳表 (Skip List) 算法](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/timer/timer?id=定时器跳表-skip-list-算法)
- 10.2 硬件定时器
- 10.2.1 初始化&&删除
- 10.2.2 start
- 10.2.2.1 线程定时器
- 10.2.2.2 定时器开始处理
- 10.2.3 删除定时器&&STOP
- 10.2 软件定时器
- 10.2.1 系统定时器线程
- 10.2.1.1 _timer_thread_entry
- 10.2.3软件定时器启动
- 10.3 rt_timer_check 定时器链表检测
- 10. 3 其他定时器管理算法
- 10.3.1 红黑树定时器
- 11 数据结构
https://github.com/wdfk-prog/RT-Thread-Study
10. 系统定时器
10.1 跳跃表
- 跳跃表是一种“随机”的数据结构,在很大的可能性下,它会得到O(log(N))的时间复杂度,而旧的列表得到O(N)。此外,当将RT_TIMER_SKIP_LIST_LEVEL设置为1时,它将与旧的双链表相同,无论是时间还是空间复杂性。基准测试表明,当RT_TIMER_SKIP_LIST_LEVEL为3时,当有100个计时器时,随机插入新计时器的平均时间比旧计时器快2倍,当有200个计时器时快3倍。但是,它恢复已弃用的函数rt_system_timer_init。bsp必须在系统启动时调用它。
定时器跳表 (Skip List) 算法
在前面介绍定时器的工作方式的时候说过,系统新创建并激活的定时器都会按照以超时时间排序的方式插入到 rt_timer_list 链表中,也就是说 t_timer_list 链表是一个有序链表,RT-Thread 中使用了跳表算法来加快搜索链表元素的速度。
跳表是一种基于并联链表的数据结构,实现简单,插入、删除、查找的时间复杂度均为 O(log n)。跳表是链表的一种,但它在链表的基础上增加了 “跳跃” 功能,正是这个功能,使得在查找元素时,跳表能够提供 O(log n)的时间复杂度,举例如下:
一个有序的链表,如下图所示,从该有序链表中搜索元素 {13, 39},需要比较的次数分别为 {3, 5},总共比较的次数为 3 + 5 = 8 次。

使用跳表算法后可以采用类似二叉搜索树的方法,把一些节点提取出来作为索引,得到如下图所示的结构:

在这个结构里把 {3, 18,77} 提取出来作为一级索引,这样搜索的时候就可以减少比较次数了, 例如在搜索 39 时仅比较了 3 次(通过比较 3,18,39)。当然我们还可以再从一级索引提取一些元素出来,作为二级索引,这样更能加快元素搜索。

所以,定时器跳表可以通过上层的索引,在搜索的时候就减少比较次数,提升查找的效率,这是一种通过 “空间来换取时间” 的算法,在 RT-Thread 中通过宏定义 RT_TIMER_SKIP_LIST_LEVEL 来配置跳表的层数,默认为 1,表示采用一级有序链表图的有序链表算法,每增加一,表示在原链表基础上增加一级索引。
10.2 硬件定时器
-
HARD_TIMER 模式的定时器超时函数在中断上下文环境中执行,可以在初始化 / 创建定时器时使用参数 RT_TIMER_FLAG_HARD_TIMER 来指定。
在中断上下文环境中执行时,对于超时函数的要求与中断服务例程的要求相同:执行时间应该尽量短,执行时不应导致当前上下文挂起、等待。例如在中断上下文中执行的超时函数它不应该试图去申请动态内存、释放动态内存等。
RT-Thread 定时器默认的方式是 HARD_TIMER 模式,即定时器超时后,超时函数是在系统时钟中断的上下文环境中运行的。在中断上下文中的执行方式决定了定时器的超时函数不应该调用任何会让当前上下文挂起的系统函数;也不能够执行非常长的时间,否则会导致其他中断的响应时间加长或抢占了其他线程执行的时间。
10.2.1 初始化&&删除
-
初始化
- 动态分配,malloc定时器结构体 || 静态分配,外部分配
- 设置相关参数
- init tick;timeout = 0;
- 初始化跳跃表链表
-
删除
- 移除跳跃表链表
- 从系统移除该对象
10.2.2 start
- 获取链表
10.2.2.1 线程定时器
- 调度上锁
- 从定时器计算线程结构体地址
- 标识线程定时器启动
- 调度解锁
10.2.2.2 定时器开始处理
- 当前正在执行超时处理函数,退出;(超时函数执行后会再次运行开始函数)
- 移除跳跃表定时器
- 定时器状移除激活状态
- 计算超时tick
- 提取跳表索引到_timer_list中
row_head[0] = &timer_list[0];//遍历每一层的定时器列表for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++){//当前层中,遍历定时器列表,直到 row_head 指向当前层的最后一个定时器。for (; row_head[row_lvl] != timer_list[row_lvl].prev;row_head[row_lvl] = row_head[row_lvl]->next){struct rt_timer *t;rt_list_t *p = row_head[row_lvl]->next;/* 获取当前节点定时器的指针,并将其转换为 rt_timer 结构体类型。 */t = rt_list_entry(p, struct rt_timer, row[row_lvl]);//如果我们有两个定时器同时超时,最好是先插入的定时器被提前调用。因此,将新的计时器插入到some-timeout计时器列表的末尾。//当前节点定时器的超时时间和需要开启的定时器超时时间相同,继续遍历。if ((t->timeout_tick - timer->timeout_tick) == 0){continue;}//如果当前节点定时器比需要开启的定时器超时时间还长,退出elseif ((t->timeout_tick - timer->timeout_tick) < RT_TICK_MAX / 2){break;}}//将提取的索引更新至_timer_list中if (row_lvl != RT_TIMER_SKIP_LIST_LEVEL - 1)row_head[row_lvl + 1] = row_head[row_lvl] + 1;/*这个简单的计数器可以很好地均匀分布列表高度。通过使用tst_nr的最低有效位,它确保定时器被插入到不同的级别,从而实现平衡分布。目标是避免具有相似超时节拍的计时器聚类*///每次插入定时器时,计数器会递增。这个计数器的作用是在跳表中决定新的定时器节点应该插入到哪一层。random_nr++;tst_nr = random_nr;//将新的定时器节点插入到系统定时器列表的最后一层rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - 1],&(timer->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));//从第二层开始,遍历每一层的定时器列表。for (row_lvl = 2; row_lvl <= RT_TIMER_SKIP_LIST_LEVEL; row_lvl++){//在相应级别的节点后插入计时器。 //RT_TIMER_SKIP_LIST_MASK = 3;1~3 ,5~7,9~11这样添加if (!(tst_nr & RT_TIMER_SKIP_LIST_MASK))rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - row_lvl],&(timer->row[RT_TIMER_SKIP_LIST_LEVEL - row_lvl]));elsebreak;//tst_nr右移以测试下一位tst_nr >>= (RT_TIMER_SKIP_LIST_MASK + 1) >> 1;}}
10.2.3 删除定时器&&STOP
- 从定时器链表中剔除
- 从object对象中剔除
10.2 软件定时器
10.2.1 系统定时器线程
- 初始化跳表链表
- 信号量初始化,挂起线程按优先级排队恢复
- 线程初始化并启动,优先级4,时间片10;(FREERTOS的时间片长度不可设置,1个时间片执行一次切换)
10.2.1.1 _timer_thread_entry
-
进入前设置信号量最大获取次数为1,二值信号量
-
进入后 : 获取最近的超时时间
- 当前定时器链表为空,说明没有定时器启动; -> 信号量阻塞该线程,直到定时器启动释放信号量
- 不为空则返回最近的超时时间
-
获取当前tick
-
计算下一次定时器到达超时时间,使用信号量阻塞到该时间
-
阻塞结束后执行超时检测
10.2.3软件定时器启动
- 信号量在启动时释放;
- 目的:如果定时器线程发现当前没有正在执行的定时器链表,则会进入RT_WAITING_FOREVER阻塞状态;
- 此处释放信号量用于释放定时器线程,计算下一个唤醒时间
10.3 rt_timer_check 定时器链表检测
- systick irq中调用
- 获取当前tick
rt_list_init(&list);//初始化链表//最底层跳表不为空while (!rt_list_isempty(&_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1])){//获取定时器结构体t = rt_list_entry(_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,struct rt_timer, row[RT_TIMER_SKIP_LIST_LEVEL - 1]);//由于tick中断应该是很快进入的,所以判断超时时间<RT_TICK_MAX既可if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2){//从计时器列表中删除计时器_timer_remove(t);t->parent.flag |= RT_TIMER_FLAG_PROCESSING;//将计时器添加到临时列表rt_list_insert_after(&list, &(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));/* call timeout function */t->timeout_func(t->parameter);/* re-get tick */current_tick = rt_tick_get();t->parent.flag &= ~RT_TIMER_FLAG_PROCESSING;//检查定时器对象是否被分离或重新启动//由于没有屏蔽中断,可能在执行过程中,其他中断停止该定时器;如果不直接退出,t的指向将会出问题if (rt_list_isempty(&list)){continue;}rt_list_remove(&(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&(t->parent.flag & RT_TIMER_FLAG_ACTIVATED)){/* start it */t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;_timer_start(_timer_list, t);}}elsebreak;}
10. 3 其他定时器管理算法
https://zhuanlan.zhihu.com/p/549450132
有序链表:插入O(n),删除O(1),过期expire执行O(1)最小堆:插入O(logn),删除O(logn),过期expire执行O(1)红黑树:插入O(logn),删除O(logn),过期expire执行O(logn)哈希表+链表(时间轮):插入O(1),删除O(1),过期expire平均执行O(1)(最坏为O(n))
10.3.1 红黑树定时器
Nginx 定时器 :https://blog.csdn.net/dearQiHao/article/details/102878306
- 红黑树与AVL(二叉平衡树) 红黑树比 AVL 树具体更高效在哪里?
因为红黑树利用了缓存。
Robert Sedgewick, 红黑树的发明人,在《算法(第4版)》 中说过, 红黑树等价于2-3树。

其中2-节点 等价于普通平衡二叉树的节点,3-节点 本质上是非平衡性的缓存。
当需要再平衡(rebalance)时,增删操作时,2-节点 与 3-节点间 的 转化会吸收不平衡性,减少旋转次数,使再平衡尽快结束。
在综合条件下,增删操作相当时,数据的随机性强时,3-节点的非平衡性缓冲效果越明显。因此红黑树的综合性能更优。
继续追根溯源,红黑树的性能优势,本质上是用空间换时间。
11 数据结构
- 二叉搜索树、B树、B+树、AVL树、红黑树
- 树堆(Treap)和红黑树(RB-Tree)各有哪些优劣?
相关文章:
rtthread学习笔记系列(10/11) -- 系统定时器
文章目录 10. 系统定时器10.1 跳跃表[定时器跳表 (Skip List) 算法](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/timer/timer?id定时器跳表-skip-list-算法) 10.2 硬件定时器10.2.1 初始化&&删除10.2.2 sta…...
mock服务-通过json定义接口自动实现mock服务
go-mock介绍 不管在前端还是后端开发过程中,当我们需要联调其他服务的接口,而这个服务还没法提供调用时,那我们就要用到mock服务,自己按接口文档定义一个临时接口返回指定数据,以供本地开发联调测试。 怎么快速启动一…...
像JSONDecodeError: Extra data: line 2 column 1 (char 134)这样的问题怎么解决
问题介绍 今天处理返回的 JSON 的时候,出现了下面这样的问题: 处理这种问题的时候,首先你要看一下当前的字符串格式是啥样的,比如我查看后发现是下面这样的: 会发现这个字符串中间没有逗号,也就是此时的J…...
C#版 软件开发6大原则与23种设计模式
开发原则和设计模式一直是软件开发中的圣经, 但是这仅仅适用于中大型的项目开发, 在小型项目的开发中, 这些规则会降低你的开发效率, 使你的工程变得繁杂. 所以只有适合你的才是最好的. 设计模式六大原则1. 单一职责原则(Single Responsibility Principle࿰…...
java8 springboot 集成javaFx 实现一个客户端程序
1. 先创建一个springboot 程序(此步骤不做流程展示) 2. 更改springboot的版本依赖和导入所需依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.7</versio…...
MySQL(高级特性篇) 06 章——索引的数据结构
一、为什么使用索引 索引是存储引擎用于快速找到数据记录的一种数据结构,就好比一本教科书的目录部分,通过目录找到对应文章的页码,便可快速定位到需要的文章。MySQL中也是一样的道理,进行数据查找时,首先查看查询条件…...
PanWeidb-使用BenchmarkSQL对磐维数据库进行压测
本文提供PanweiDb使用BenchmarkSQL进行性能测试的方法和测试数据报告。 BenchmarkSQL,一个JDBC基准测试工具,内嵌了TPC-C测试脚本,支持很多数据库,如PostgreSQL、Oracle和Mysql等。 TPC-C是专门针对联机交易处理系统(OLTP系统)的规范,一般情况下我们也把这类系统称为业…...
AR 在高校实验室安全教育中的应用
AR应用APP可以内置实验室安全功能介绍,学习并考试(为满足教育部关于实验室人员准入条件),AR主模块。其中AR主模块应该包括图形标识码的扫描,生成相应模型,或者火灾、逃生等应急处置的路线及动画演示。考试采…...
微信小程序实现个人中心页面
文章目录 1. 官方文档教程2. 编写静态页面3. 关于作者其它项目视频教程介绍 1. 官方文档教程 https://developers.weixin.qq.com/miniprogram/dev/framework/ 2. 编写静态页面 mine.wxml布局文件 <!--index.wxml--> <navigation-bar title"个人中心" ba…...
Spring Boot中的配置文件有哪些类型
在 Spring Boot 中,配置文件用于管理应用程序的设置和参数,通常存放在项目的 src/main/resources 目录下。Spring Boot 支持多种类型的配置文件,并通过这些文件来控制应用的行为和环境配置。 1. application.properties application.proper…...
Spring Boot 项目启动后自动加载系统配置的多种实现方式
Spring Boot 项目启动后自动加载系统配置的多种实现方式 在 Spring Boot 项目中,可以通过以下几种方式实现 在项目启动完成后自动加载系统配置缓存操作 的需求: 1. 使用 CommandLineRunner CommandLineRunner 是一个接口,可以用来在 Spring…...
如何在 CentOS 中生成 CSR
在本教程中,我们将向您展示如何在CentOS 7和6中生成CSR。您可以直接从服务器生成 CSR。 只需按照以下步骤操作: 第 1 步:使用安全外壳 (SSH) 登录您的服务器 步骤 2:创建私钥和 CSR 文件 在提示符处键入以…...
qml XmlListModel详解
1、概述 XmlListModel是QtQuick用于从XML数据创建只读模型的组件。它可以作为各种view元素的数据源,比如ListView、GridView、PathView等;也可以作为其他和model交互的元素的数据源。通过XmlRole定义角色,如name、age和height,并…...
C++并发编程之跨应用程序与驱动程序的单生产者单消费者队列
设计一个单生产者单消费者队列(SPSC队列),不使用C STL库或操作系统原子操作函数,并且将其放入跨进程共享内存中以便在Ring3(用户模式)和Ring0(内核模式)之间传递数据,是一…...
PostgreSQL技术内幕22:vacuum full 和 vacuum
文章目录 0.简介1.概念及使用方式2.工作原理2.1 主要功能2.2 清理流程2.3 防止事务id环绕说明 3.使用建议 0.简介 在之前介绍MVCC文章中介绍过常见的MVCC实现的两种方式,一种是将旧数据放到回滚段,一种是直接生成一条新数据(对于删除是不删除…...
【网络】:网络编程套接字
目录 源IP地址和目的IP地址 源MAC地址和目的MAC地址 源端口号和目的端口号 端口号 VS 进程ID TCP协议和UDP协议 网络字节序 字符串IP和整数IP相互转换 查看当前网络的状态 socket编程接口 socket常见API 创建套接字(socket) 绑定端口号&…...
java基础概念55-不可变集合
一、定义 不可变集合:不可以被修改的集合,只能查询。(长度、内容均不可被修改) 二、创建不可变集合 【注意】: 此方法是在JDK9中引入的。 2-1、list不可变集合 示例: import java.util.List;public cla…...
深入理解 C++ 函数重载
在 C 中, 函数重载是一个非常强大的特性, 允许多个函数使用相同的名称, 但具有不同的参数类型. 重载解析决定了在给定的调用中, 编译器应选择哪个版本的重载函数. 本文将深入探讨 C 重载解析的工作原理, 帮助你在实际编程中更好地理解这一机制. 重载(Overload) vs 重写(Overri…...
相机和激光雷达的外参标定 - 无标定板版本
1. 实现的效果 通过本软件实现求解相机和LiDAR的外参,即2个传感器之间的三维平移[x, y, z]和三维旋转[roll, pitch, yaw]。完成标定后,可将点云投影到图像,效果图如下: 本软件的优势:(1)无需特…...
Redis 知识速览
文章目录 1. Redis 简介2. Redis 优缺点3. Redis 高性能4. Redis VM 机制5. Redis 数据类型6. 应用场景7. 持久化8. 过期策略9. 内存相关10. 线程模型11. 事务12. 集群 1. Redis 简介 定义:Redis 是一个用 C 语言编写的高性能非关系型(NoSQL)…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
