上节课我们详细的讲了定时器的内部机制:
我们分为两条线来看定时器:
1.上图里面左边的那些函数,都是去写队列:写不同的命令
2.右边的定时器任务:他平时是阻塞状态,他怎样阻塞?
他会调用这个函数:等待队列有数据,但是会指定最多等待多长时间
vQueueWaitForMessageRestricted(xTimerQueue, (xNextExpireTime - xTimeNow),xListWasEmpty);
这个时间:由即将超时的定时器决定
在定时器任务阻塞的期间,
1.如果别的任务发来了定时器的各种命令:定时器任务会即刻被唤醒、去处理
2.如果一直没有别的任务发来定时器的各种命令,超时时间到了,定时器任务也被唤醒
这时候他就会去调用超时的、定时器的、函数
这个机制也不算很复杂
我觉得这种机制不够好,就比如说:调用xTimerStart
这个函数完全可以直接去操作定时器,也不是很花时间,没有必要去写队列
下面是RT-Thread的代码:
RT-Thread里面:它启动定时器的时候,就直接把定制器放入某个链表
FreeRTOS里面:启动定时器时,先写队列;由定时器任务读队列、放链表
实际上,一些汽车电子行业的人跟我说,
他们基本上不用自带的定时器,都是自己在中断里面直接处理定时器。
RT-Thread效率更高,但是必须约定:定时器函数要高效、不能阻塞
FreeRTOS效率低,但是绝对不会影响到中断性能
我们再简单的看看两个例子
这个例子非常简单,注意创建定时器是第3个参数:pdTRUE表示它是周期性的任务
创建完之后还要去启动它
以后定时器任务就会周期性地执行定时器的函数
这个是第2个例子:
创建的时候第3个参数是 pdFALSE,表示一次性定时器
也就是你启动它之后,时间到了会执行一次;然后就再也不会运行了
我觉得这个例子是用来消除抖动,消除抖动应该都很熟悉了
我们开始讲中断里面用到的函数
现在这里也没有错,是因为我们恰巧把第2个参数设置为0
我们假设:
1.定时器的队列满了
2.上面那个函数超时时间不等于0
会发生什么事情?
中断的优先级比定时器任务优先级高,定时器本身并没有什么优先级
我们看看这个图:
假设有三个任务在轮流运行
你什么时候按下按键,根本就是一个随机的事情
如果队列满了、你调用xTimerReset时指定阻塞时间不为0
他会使得当前任务阻塞。
是谁阻塞?中断阻塞?我们看代码:
再问一下:中断函数里调用xTimerReset
导致阻塞,谁阻塞?
从代码里面我们可以看到:pxCurrentTCB
被阻塞
pxCurrentTCB 是谁?定时器任务。
task1正在运行,中断发生了,当前任务仍然是task1
task2正在运行,中断发生了,当前任务仍然是task2
再问一下:中断函数里调用xTimerReset导致阻塞,谁阻塞?
也就是我这个被中断的任务,跟你这个GPIO没有任何关系
大家看到了吧:在中断函数里面,你调用的函数,不能够导致阻塞
我们假设这么一种情况:
1.GPIO中断优先级比tick优先级高
2.GPIO中断函数卡主了,GPIO中断没处理完
3.那么tick中断无法产生、时间片轮转无法实现、定时器无法实现
所以中断函数要尽快执行完
在中断函数执行的期间,任务是无法执行的
不论从哪一个角度来看,中断函数都要尽快执行完
我们从头来讲吧,从头讲中断的处理过程:
1.task1正在运行,pxCurrentTCB
执向task1
2.按下GPIO按键,产生中断
3.task1的现场,被保存在task1的栈里
4.CPU使用另一个栈,就是中断的栈,开始执行中断函数
5.假设这个中断函数是:
6.xTimeReset
就是写队列,假设队列满了
7.xTimerReset
导致 当前任务,也就是task1阻塞
对于task1来说,是不是太不公平了?
所以,中断函数里不能调用xTimerReset
, 因为它会导致不相干的任务阻塞,
而是调用xTimerResetFromISR
,因为它不会阻塞任何任务
我们再来讲另一种情况,中断本身能否阻塞?
1.task1正在运行,pxCurrentTCB
执向task1
2.按下GPIO按键,产生中断
3.task1的现场,被保存在task1的栈里
4.CPU使用另一个栈,就是中断的栈,开始执行中断函数
5.假设这个中断函数是:
6.我们来看看会发生什么事情:
我们假设中断函数也可以阻塞
阻塞瞬间的寄存器,被保存在栈里:MSP
然后再次发生了同一个GPIO中断,也阻塞,阻塞瞬间寄存器也保存在MSP里
也就是说:
7.接着再次发生了Tick中断
8.在Tick中断执行过程中,GPIO中断被唤醒了,怎么办?当前栈被Tick中断使用了,你怎么恢复GPIO中断让它继续运行?
这是就非常乱了。
我们来比较这两个函数:xTimerReset
、xTimerResetFromISR
在视频里有这个图,如果看不清,看下视频。
我们现在逐个来分析代码:
1.xQueueSendToBack
: 写队列,队列满则阻塞
2.xQueueSendToBackFromISR
: 写队列,无论是否成功都马上返回
FromISR
函数:绝对不会阻塞
我们先来总结一下:FromISR函数:
1.不会阻塞
2.会唤醒别的任务
3.但是不会即刻调度,也就是不会让别的任务马上运行
我们先来讲讲理论:
1.不会阻塞:因为中断要迅速执行,它自己不能阻塞,也不能阻塞当前任务(当前任务跟中断没关系)
2.会唤醒别的任务:
我按下了按键,产生了中断,在中段函数里写了队列
如果有任务在等待队列,那么这个任务会被唤醒
所谓唤醒:只是把它从delaylist放到ready list
这个被唤醒的任务,即使它的优先级最高,也不会马上被执行的:因为当前正在处理中断
3.既然在中断的处理过程中,不会运行任何任务,那么自然就没有必要去调度
调度就是切换任务、切换栈
如果你在中断函数的处理过程中:切换任务、切换栈,完全是浪费时间
比如:
上图我们用反例来说明:在FromISR里不会调度,也就是不会切换任务
因为效率太低
上面是理论讲解,我们来看看代码:
上面这个图,是普通的函数,没有FromISR后缀
下面这个有FromISR后缀:
在看上图蓝色的箭头
1.`FromISR函数内部,会唤醒任务,但是不会切换任务:如果被唤醒的任务优先级更高,就记录下来
2.portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
如果xHigherPriorityTaskWoken
等于pdTRUE
,就表示需要调度,这个函数就会触发调度
以写队列为例:
xQueueSendToBack | xQueueSendToBackFromISR | |
---|---|---|
参数不同 | xTicksToWait: 队列满的话阻塞多久 | 没有xTicksToWait |
唤醒等待的任务 | 写队列后,会唤醒等待数据的任务 | 写队列后,会唤醒等待数据的任务 |
调度 | 如果被唤醒的任务优先级更高,即刻调度 | 如果被唤醒的任务优先级更高,不会调度 只是记录下来表示:需要调度 |
阻塞 | 如果队列满,可以阻塞 | 如果队列满,不能阻塞 |
什么叫做调度?
1.当前task1在运行,中断把task2唤醒了,task2优先级更高
需要让task2运行
怎么让task2运行?怎切换任务:
a. 把task1的寄存器保存进task1的栈里
b. 让pxCurrentTCB = task2
c. 从task2的栈里,把保存的值恢复到CPU寄存器里
他怎么调度呢?
他只是去设置一个中断,以后,注意了:我说的是以后
由这个中断来调度
PENDSV的中断优先级最低
1.在中断里触发PENDSV中断:当前中断执行完,才会执行PENDSV中断,才会切换任务
2.在任务里触发PENDSV中断:PENDSV中断马上执行,马上切换任务
所以我们来总结一下,这个图就是今晚的精华:
答: 它会执行定时器的函数,然后再次调用:
vQueueWaitForMessageRestricted(xTimerQueue, (xNextExpireTime - xTimeNow),xListWasEmpty);
等待时间xNextExpireTime - xTimeNow
会重新计算
答: 有数据就会去读数据,没数据而被唤醒时就去执行定时器的函数
被唤醒的原因,是因为有数据,那么就不会去调用定时器的函数
答: 可以发生,但是不会被处理:处于pending状态
答: tick可以发生、可以被处理, 也可以切换任务,但是tick中断函数执行完后,会重新进入exti0的中断函数
但任务函数根本没机会执行
答: 中断不会自己阻塞,之所以这样说是为了跟任务做一个对比
任务:可以自己阻塞
中断:不可以自己阻塞,没这个功能
pendsv
中断有这种Pending
的功能?答: pendsv
只是名字叫pend
,所有的中断都有pending
功能
pending
:中断发生了,正在等待处理