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

一:定时器的机制

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

定时器也有三要素:

  • 超时时间

  • 超时函数

  • 是否重复

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

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

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

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

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

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

这个就是它的结构体:

定时器初始化如下:

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

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

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里面已经回答了