• 【RTOS训练营】定时器的机制、源码分析和晚课提问


    一:定时器的机制

    我们使用手机,设置一个有效的闹钟,需要设置的内容如下:

    请添加图片描述

    定时器也有三要素:

    • 超时时间

    • 超时函数

    • 是否重复

    我们使用队列的时候创建了一个结构体

    我们使用信号量的时候,创建了一个信号量结构

    我们使用事件组的时候创建了一个结构体

    我们使用定时器的时候也要去创建一个结构体

    用这样的函数来创建定时器:

    xMyTimeHandle = xTimerCreate("mytimer",2000,pdFALSE,NULL,MyTimerCall)
    
    • 1

    这个就是它的结构体:

    请添加图片描述

    定时器初始化如下:

    请添加图片描述

    如果让你设计定时器的话,你会怎么做?

    定时器可能存在以下情况:

    1.有多个定时器

    2.每个定时器的超时时间可能不一样

    我们肯定会用一个链表管理这些定时器:

    链表里面:根据超时时刻排序

    请添加图片描述

    超时时刻,就是某个tick

    FreeRTOS中每1ms产生一次tick中断, tick计数累加

    当tick值等于定时器的超时时间,定时器的超时函数就被调用

    无论是什么操作系统,定时器的原理肯定是这样的

    问题来了:

    定时器的超时函数就被调用,被谁调用?

    1.Tick中断?

    2.某个任务?

    都可以

    Linux里:在Tick中断里处理定时器

    FreeRTOS里:在某个任务里处理定时器

    FreeRTOS为什么不在tick中断里处理定时器?

    为了实时性。

    中断的优先级,永远比任务高。

    即使最低优先级的中断,它也比最高优先级的任务先运行

    如果你在中断里面调用定时器的处理函数:

    1.这些定时器函数可能并不需要那么优先地执行

    2.这些定时器函数可能执行时间很长

    3.都会影响到其他中断、所有任务

    所以为了实时性:我们不在tick中断里面处理定时器

    既然不在中断里处理定时器,那么就需要在应用里处理定时器

    我们现在引入了一个任务:定时器任务

    请添加图片描述

    具体怎么管理呢?分为两类:

    • 第一类:启动、停止、设置定时器:

    请添加图片描述

    在上图里面,左边的各种函数:实质都是写一个队列

    右边的任务,去读这队列,根据队列的数据:来启动、停止、设置定时器

    • 第二类:

    怎么判断定时器是否超时?怎么去运行定时器函数?

    请添加图片描述

    我们在假设有三个定时器:timer0、1、2

    他们的超时时间分别是:tA, tB, tC

    超时间还没有到,这个定时器任务:从ReadyList放入DelayList

    他放在哪个位置呢?根据tA决定

    当Tick中断累加Tick值,到达tA的时候,就会把定时器任务从DelayList放到ReadyList

    如果定时器任务的优先级最高,他就可以执行

    我们分开讲了两种情况:处理队列、处理定时器

    也就是说这个任务,他有两种唤醒源

    1.队列有数据

    2.时间到

    怎么统一起来?

    一个任务:怎么即等待队列有数据,又等待时间到?、

    我们等待队列的时候,不是可以指定超时时间吗?这就是关键

    等在队列的时候,我们可以指定:最多等待多少个Tick

    请添加图片描述

    我们再来总结一下机制:

    1.有一个定时器任务

    2.我们要去启动定时器的时候:xTimerStart(这个函数会写数据到 timer队列 里去)

    3.定时器任务: 平时xNextExpireTime-xTimeNow

    4.步骤2里面调用了xTimerStart写了队列,定时器任务马上被唤醒

    5.定时器任务读队列、处理"start命令"

    6.这个被启动的定时器,放入链表:

    请添加图片描述

    7.1 如果在等待队列时,队列再次得到数据,从上面的vQueueWaitForMessageRestricted函数退出,然后读队列、处理队列

    7.2如果队列一直没有数据,那么上面的vQueueWaitForMessageRestricted超时退出,表示timer0到时间了,执行它的函数

    二:源码分析

    请添加图片描述

    看看上面的图:

    1.什么时候创建队列?创建第1个定时器时就会创建队列

    2.什么时候创建定时器任务?启动调度器时就会创建定时器任务

    请添加图片描述

    main启动调度器,启动调度器时,先创建定时器任务

    请添加图片描述

    定时器的超时时间怎么确定?

    1.创建定时器的时候,指定有一个参数:周期

    2.启动定时器的时候:可以读取当前的tick值

    3.超时时间 = 调用xTimerStart时的tick值 + 周期

    请添加图片描述

    前面的分析:

    1.创建定时器导致创建队列

    2.启动调度器,导致创建了定时器任务

    我们再来看看怎么启动定时器:xTimerStart

    请添加图片描述

    请添加图片描述

    启动定时器,是通过:xTimerStart

    他的本质是:

    请添加图片描述

    定时器任务一直在等待队列的数据:

    请添加图片描述

    上图右边第2点:xNextExpireTime-xTimeNow

    平时在等待队列的数据,他会等待多长时间呢?这个时间由第1个即将超时的定时器来决定

    从队列里得到数据之后就会处理:

    请添加图片描述

    他会根据队列的数据来处理:

    xTimerStartxTimerStop等函数 ,写入队列的数据时不一样的
    请添加图片描述

    请添加图片描述

    怎么启动定时器我们也看到了:

    1.其他任务调用xTimerStart,实际上是写队列

    2.定时器任务:读队列

    3.根据队列数据,把定时器放入链表

    定时器的函数什么时候被调用?怎么被调用?

    1.定时器任务,平时在等待队列的数据:有一个超时时间

    这个超时时间,由即将到时的定时器决定

    请添加图片描述

    2.等待过程中,超时时间到了

    3.处理定时器

    请添加图片描述

    4.怎么处理?

    请添加图片描述

    三. 晚课学员提问

    1. 问: 这个管理定时器任务的任务 优先级是多少?

    答: 可以设置,是一个配置项

    2. 问: 调用"start"函数 执行"start"函数写队列 唤醒定时器任务 处理"start命令"启动定时器吗? 还是说执行"start"函数定时器立即启动?

    答: 1.什么叫做启动定时器?就是把它放入链表

    2.什么时候把它放入链表?看下图:

    请添加图片描述

    执行"start"函数定时器立即启动:执行xTimerStart一定可以启动定时器吗?

    失败的可能:

    1.队列满,你不愿意等待

    2.定时器任务优先级不够,没机会执行

    所以:

    1.xTimerStart可能失败,因为队列满

    2.xTimerStart成功了,但是定时器不一定真正启动了

    3. 问: 如果 某个定时器的任务 死循环了,其他定时器的任务也 嗝屁了?

    答: 是的,但不完全对

    准确的说法是这样:如果某个定时器的函数(不是任务)死循环了,那么定时器任务也就不能做其他事了

    其他定时器的函数就无法执行了。

    只有一个定时器任务,管理所有 定时函数。

    请添加图片描述

    4. 问:老师,这一个定时器任务,管理所有定时函数,也就是轮循所有定时器是否到时间?

    答: 不是轮询

    假设有三个定时器,他们的超时时间分别是:tA, tB, tC

    需要轮询吗?

    第1次:delay到tA

    第2次:delay到tB

    第3次:delay到tC

    不需要轮询,只需要等待

    5. 问: 老师,可以问一下么,定时器任务读链表的周期是多少?就是定时器任务的执行周期。

    答: 读链表?还是读队列?

    你没有理解到多任务的实质:你仍然以轮询的方式来思考

    1.读队列:读、等待、有数据就返回,或者超时返回

    2.读链表?不读链表,定时器插入链表时,根据超时时刻排序

    在FreeRTOS中,都是阻塞-唤醒,很少用轮询

    6. 问: 老师,我想问,如果其它任务一直在不间断的执行,如果Timer0时间到了,定时器任务不会处理timer0的到时间动作了是么?

    答: 这就是优先级的问题:定时器任务也只是一个普通的任务

    如果一直运行的其他任务,优先级比较高

    那么定时器任务就没有办法执行

    所以我们一般来说都会把定时器的任务设置得比较高

    7. 问: 老师反过来,定时器任务一直运行是不是会使其它任务没有时间运行?

    答: 这还是优先级的问题:定时器任务一直运行 ,它运行期间如果有其他更高优先级的任务就绪了,更高优先级的应用就会立刻执行

    8. 问: 管理定时器的函数是任务它进行写队列操作,

    定时器函数的执行需要另一个任务(定时器任务)进行读队列进行按照规定好的时间(类似时间片轮转)执行定时器函数,

    定时器函数只在一个任务里面执行,

    这个任务可以切换定时器函数。

    老师我可以这样理解吗

    答: 管理定时器的函数是任务:比如xTimerStart只是函数,不是任务

    xTimerStart进行写队列操作,它只是写队列。

    后续的工作需要另一个任务(定时器任务)进行:先读队列,进行按照规定好的

    时间(等待时间,不是轮询)执行定时器函数,

    定时器函数只在一个任务里面执行,这个任务可以处理多个定时器

    9. 问: 如果配置了多个定时器,那定时器任务是如何设置自己的超时的时间呢?

    是在启动新的定时器后会处理一下每个定时器的时间,然后把最短的时间拿来设置成定时器任务的超时时间吗?

    答: 1.新增加一个定时器时,根据它的超时时间插入链表

    2.新增加一个定时器时,根据它的超时时间插入链表

    3.根据第1个定时器的超时时间,来设置定时器任务自己的超时时间

    只需要根据链表第1个定时器的超时时间,来决定自己的超时时间

    不需要遍历所有的定时器

    10. 问: 老师,定时器任务的超时时间是从执行"start"时的tick+周期确定的?

    如果创建了多个定时器 这个超时时间就是有最早的周期确定吗?

    这个超时时间什么时候更新啊?

    答: 1.每个定时器都有自己的周期,启动某个定时器的时候都有一个当前时间,这个定时器的超时时间就确定了

    2.对于重复执行的定时器,他当前的时间到了,就会更新它的超时时间

    11. 问: 1.老师有没有这种情况,定时器任务的超时时间到了 但是定时器还没有到时间 这个情况会怎么处理?(晚课里说定时器任务的超时时间到了会退出,表示定时器时间到了)。

    2.老师假如我同时创建三个定时器(timer0 timer1 timer2),三个定时器的定时时间不同(timer0> timer1> timer2),之后调用"xstart"函数同时启动三个定时器,此时这定时器任务的超时时间由谁决定,如果一个定时器到时间了 其他两个没有到时间 这时定时器任务的超时时间怎么更新,老师这里有些蒙,在晚课您说现有tA设置 再有tB设置 怎么个设置顺序啊。

    3.老师您课上说这个超时时间由即将到时的定时器决定,假如上面三个定时器timer2时间最短,那么一开始超时时间由t2决定 等到t2到时间后 这个超时时间就会有即将到时间的timer1决定吗 老师系统中有多个定时器的时候这个超时时间这块有点不太理解

    答: 1.这种情况经常发生,定时器任务读取队列的时候会阻塞,阻塞时间由第1个定时器决定

    在它阻塞的过程中,如果有其他任务调用了定时器的函数,就相当于写了队列

    这个时候,定时器任务马上就会被唤醒

    唤醒之后会做什么事情:读出队列里面的数据、根据里面的数据来操作定时器(比如说启动它,停止它)

    1.1 处理完队列的数据之后,如果第1个定时器的时间还没到,被再次读队列并阻塞(会根据第1个定时器调整阻塞的时间)

    1.2 处理完队列的数据之后,如果第1个定时器的时间已经到了,就去执行第1个定时器的函数;
    执行完函数之后,再次读队列并阻塞(阻塞时间由第2个定时器决定)

    2.定时器任务的超时时间由:最近的定时器决定,也就是timer2

    注意这个函数:
    vQueueWaitForMessageRestricted( xTimerQueue, ( xNextExpireTime - xTimeNow ), xListWasEmpty );

    上面这个函数的第2个参数:表示最多等待多少个tick

    2.1 第1次等待队列: 最多等待tick = timer2的时间 - 当前tick

    2.2 第2次等待队列: 最多等待tick = timer1的时间 - 当前tick

    2.3 第3次等待队列: 最多等待tick = timer0的时间 - 当前tick

    你要注意到当前ticket是不断变化的

    问题3在问题2里面已经回答了

  • 相关阅读:
    Linux_day12_LVM
    浅析程序员的中秋之夜
    RocketMQ(4.9.4)学习笔记 - 安装部署
    LeetCode770之基本计算器IV(相关话题:波兰表达式,多项式运算)
    【C++语法讲解】 | 运算符重构 | 三种运算符的重构方式 |代码演示
    无人机技术,无人机动力系统知识,电机、电调、桨叶技术详解
    8086/8088 存储器分段概念
    激光共聚焦如何选择荧光染料
    ASPICE标准快速掌握「3.1. 实践示例」
    深入浅出C#消息
  • 原文地址:https://blog.csdn.net/thisway_diy/article/details/125999825