【RTOS训练营】上节回顾、内部机制、中断管理和晚课提问

一:上节回顾

上节课我们详细的讲了定时器的内部机制:

我们分为两条线来看定时器:

1.上图里面左边的那些函数,都是去写队列:写不同的命令

2.右边的定时器任务:他平时是阻塞状态,他怎样阻塞?

他会调用这个函数:等待队列有数据,但是会指定最多等待多长时间

vQueueWaitForMessageRestricted(xTimerQueue, (xNextExpireTime - xTimeNow),xListWasEmpty);

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

二:内部机制

在定时器任务阻塞的期间,

1.如果别的任务发来了定时器的各种命令:定时器任务会即刻被唤醒、去处理

2.如果一直没有别的任务发来定时器的各种命令,超时时间到了,定时器任务也被唤醒

这时候他就会去调用超时的、定时器的、函数

这个机制也不算很复杂

我觉得这种机制不够好,就比如说:调用xTimerStart

这个函数完全可以直接去操作定时器,也不是很花时间,没有必要去写队列

下面是RT-Thread的代码:

RT-Thread里面:它启动定时器的时候,就直接把定制器放入某个链表

FreeRTOS里面:启动定时器时,先写队列;由定时器任务读队列、放链表

实际上,一些汽车电子行业的人跟我说,

他们基本上不用自带的定时器,都是自己在中断里面直接处理定时器。

  • RT-Thread:在这个tick中断里调用定时器函数
  • Linux:在这tick中断里调用定时器函数
  • 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中断让它继续运行?

这是就非常乱了。

我们来比较这两个函数:xTimerResetxTimerResetFromISR

在视频里有这个图,如果看不清,看下视频。

我们现在逐个来分析代码:

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中断马上执行,马上切换任务

所以我们来总结一下,这个图就是今晚的精华:

四:晚课学员提问

1. 问: 老师,假如定时器超时时间没到,也没有队列写数据, 之后定时器任务的等待时间到了 这是定时器任务会怎么样啊 他会重新进入休眠吗

答: 它会执行定时器的函数,然后再次调用:

vQueueWaitForMessageRestricted(xTimerQueue, (xNextExpireTime - xTimeNow),xListWasEmpty);

等待时间xNextExpireTime - xTimeNow会重新计算

2. 问: systick中断里检查队列是否超时唤醒任务,有数据读队列也会唤醒任务,不管哪种唤醒都会检查一下是否到时间了,然后执行回调函数对吧?

答: 有数据就会去读数据,没数据而被唤醒时就去执行定时器的函数

被唤醒的原因,是因为有数据,那么就不会去调用定时器的函数

3. 问: 老师,想问您一下。GPIO中断优先级高,GPIO中断阻塞的时候,tick中断是不是发生不了?

答: 可以发生,但是不会被处理:处于pending状态

4. 问 :tick中断 比 exti0的优先级更高,能够运行吗?

答: tick可以发生、可以被处理, 也可以切换任务,但是tick中断函数执行完后,会重新进入exti0的中断函数

但任务函数根本没机会执行

5. 问: 中断自己阻塞是什么?死循环吗?还是被别的中断打断

答: 中断不会自己阻塞,之所以这样说是为了跟任务做一个对比

任务:可以自己阻塞

中断:不可以自己阻塞,没这个功能

6. 问: 老师是不是只有pendsv中断有这种Pending的功能?

答: pendsv只是名字叫pend,所有的中断都有pending功能

pending:中断发生了,正在等待处理