一:定时器的机制
我们使用手机,设置一个有效的闹钟,需要设置的内容如下:
定时器也有三要素:
-
超时时间
-
超时函数
-
是否重复
我们使用队列的时候创建了一个结构体
我们使用信号量的时候,创建了一个信号量结构
我们使用事件组的时候创建了一个结构体
我们使用定时器的时候也要去创建一个结构体
用这样的函数来创建定时器:
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个即将超时的定时器来决定
从队列里得到数据之后就会处理:
他会根据队列的数据来处理:
xTimerStart
和xTimerStop
等函数 ,写入队列的数据时不一样的
怎么启动定时器我们也看到了:
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里面已经回答了