【RTOS训练营】任务调度(续)、任务礼让、调度总结、队列和晚课提问

一:任务调度(续)

上一节课讲调度,我们还没有讲完,

调度是理解后续所有知识的基础,可以说调度是RTOS中最重要的部分。

今天有一个同学提了这么一个问题,实时性怎么体现?

实时性,就体现在下面3点:

  1. 中断优先级,高于 任务优先级。“中断优先级最低”的中断,高于“任务优先级最高”的任务
  2. 中断里,高优先级的中断,会抢占低优先级的中断
  3. 任务里,高优先级的任务,会抢占低优先级的任务

对于中断先不管,

常用的调度策略是这样的:可抢占,时间片轮转,空闲任务礼让。

高优先级的任务一旦就绪,他马上就可以执行。

他一直独占CPU,一直执行,直到他休眠。

高优先级的任务,他只会跟同级的任务轮流执行,如果高优先级的任务只有他一个,他会一直独霸CPU。

如果高优先级的任务只有他一个,这时候就不存在什么时间片轮转。

就绪的任务,大家的优先级都相同,这时候才会使用时间片轮转。

那如果我们配置内核不使用时间片轮转:既然不轮流执行,那就是独占。

  1. 有高优先级的任务就绪了,他可以马上执行

  2. 如果大家的优先级都相同,只有当前任务主动放弃运行,别的任务才可以运行

  3. 假设任务一,任务二的优先级都是1,任务3的优先级是2。

    任务3平时休眠,任务3一旦就绪,他马上就可以执行。

    任务3再次放弃CPU的时候,就会导致一次调度

我们来看看一个图:

大家可以看到,在任务三休眠的那一瞬间,会触发调度。

第1个红色箭头:切换到任务1

第2个红色箭头,切换到任务2

第3个红色箭头,切换到任务1

第4个红色箭头,切换到任务2

第5个红色箭头,切换到空闲任务

大家可以看到,如果不轮流执行,只有两种情况:

  1. 贪婪的任务,可以一直霸占CPU
  2. 优先级高的任务,可以抢占CPU
  3. 当优先级高的任务休息了,底下那些贪婪的任务才会重新分配CPU

大家一定要注意,轮转,只发生在同级的任务之间。

二:任务礼让

接下来再讲讲礼让。

礼让,也只是发生在同级任务之间:空闲任务、其他优先级也是0的任务。

如果有任务的优先级大于0,他一旦就绪,根本就轮不到空闲任务执行:这时候谈何礼让?

在上一节课里我们布置了作业,作业1提到“task1、task2都执行了2次之后,为什么空闲任务推迟那么久才执行?”

这就是因为礼让。

再来分析一下上节课的作业,这个作业有助于大家理解调度。

我们创建了三个任务,启动调度器的时候又创建了空闲任务。

任务1、2,空闲任务,优先级都是0,他们被放在同一个链表。

  1. 任务3优先级最高,他先运行,然后主动调用vTaskDelay,放弃了CPU,这会触发一次调度
  2. 从优先级为0的那个链表里, 取出任务一来运行,任务一被放到队列的后面

  1. 一毫秒到了之后,从队列里取出第1个任务也就是任务2。他开始运行,并且也被放到了队列后面。

  1. 一毫秒到了之后,从队列里面取出第1个任务也就是空闲任务,他开始运行

大家可以看到,空闲任务实际上也是在运行的。

运行顺序是这样:task3, task1, task2, idle task

我们在逻辑分析仪里看不到,是因为还没有运行到钩子函数,空闲任务就主动放弃运行。

我们是在下面的钩子函数里,设置那个变量:

主动放弃之后,链表是这样的:

礼让之后,task1运行1ms、task2运行1ms

然后,空闲任务再次运行:从哪里开始运行?

对于空闲任务,他是一个死循环,把这个死循环精简了一下:

从代码可以得知空闲任务做的所有事情:

  1. 清理工作很重要,礼让之前我先清理
  2. 什么情况下礼让?

pxReadyTasksLists[ tskIDLE_PRIORITY ] 就是: pxReadyTasksLists[ 0 ]

它里面存放的是:优先级为0的就绪任务。

如果这个队列的长度大于1,是不是意味着:除了空闲任务,还有其他优先级为0的就绪任务?

如果有其他优先级为0的就绪任务,我就礼让一下:发起一次调度,空闲任务躲到最后,让你们先运行。

当任务一、任务二,都执行了1ms,就能到空闲任务再次运行:

task1运行1次,

task2运行1次,

第1次礼让:task1运行1次,task2运行1次,idle任务运行1次, idle任务马上礼让

第2次task1运行1次,task2运行1次,idle任务继续运行

第一次礼让时不是空闲任务也执行了吗,为什么说没执行呢?

运行了,只是还没执行到钩子函数,我们在示波器上看不出来。

三:调度总结

我们再来总结一下调度:

1.默认了调度策略:可抢占、时间片轮转、空闲任务礼让

2.提几个问题:

假设 task1, task2优先级都是0,task3优先级是2

a. task3不休眠的话, task1, task2, idle任务都无法执行:对不对? 答案:对

b. task3不休眠的话, 中断也无法执行:对不对? 答案:不对

c. 高优先级的任务,应该尽快执行,然后让出CPU:对不对?答案:对

d. task1, task2从不休眠,空闲任务有没有机会执行?答案:有,轮转

e.task3从不休眠,空闲任务有没有机会执行? 答案:没有

  1. 高优先级任务会抢占低优先级的任务
  2. 同优先级的任务,轮流执行
  3. 空闲任务,跟其他同级的任务,也是轮流执行:只不过空闲任务执行的时间比较短,他会主动放弃CPU
  4. 中断,比所有的任务优先级都更高

理解这些队列,关键在于:

  1. 调度的时候,从 pxReadyTasksLists[31]pxReadyTasksLists[30]、……、pxReadyTasksLists[0],按照优先级从高往下查找这些队列

  2. 找出优先级最高的任务,来运行

  3. 最高优先级的任务,如果有多个的话,就轮流执行

  4. 有更高优先级的任务就绪任务时,低优先级的任务没有机会执行

  5. 任务调度的核心:当前任务执行完1tick,就乖乖的到后面去排队

    调度时,取出队列的第1个任务,让他运行。

四:队列

多个任务之间传递信息,非常简单,用全局变量就可以。

如果你简单的使用全局变量来传递信息,会有一些缺点。

我们前面的程序,每个任务里面故意打印很短的字符串。

你们可以试验一下,你把这些字符串拉长,你会发现这些字符串会混杂在一起打印。

为什么呢?因为每个任务只能够运行一个tick,你打印很长的字符串的话,打印到中间的时候就被切换出去了,轮到别人打印了。

我们想写一个打印函数:
我打印之前,我会判断一下:如果有别的任务在使用串口,我就先不打印了,不去破坏别人。

来看看使用全局变量来怎么写代码:

这种方法行不行?我有一个全局变量,每个人都想去调用这个函数的话,都先判断一下。

大家一定要有一个概念,多任务:
假设有两个任务a和B,任务A执行的过程中,随时可能被任务B打断。

因此,可能出现这种情况:

task1执行到①的时候,它读入这个变量,发现是1。

在红线位置,被切换出去了,轮到task2运行,task2打印部分后,切换task1打印,就跳过了task1的判断,导致两个task打印交叉在一起。

再举一个例子:

我们可以直接上来就把这个变量减1,但是,减1的操作分为:读、减、写。

假设你刚读进来,就被切换出去了,

任务一和任务二,按照上面的黑色箭头运行,结果这两个任务还可以同时使用串口。

这种现象,是因为这两个任务都想去写同一个变量。

那如果这个变量,一个任务读,一个任务写,是不是就可以解决这种冲突的问题?

我们现在假设有两个任务,任务一做一个复杂的计算,任务2在等待他计算完成。

使用一个全局变量g_cal_ok来同步,任务1计算完之后,设置这个变量等于1,任务2循环检测这个变量,死等这个变量等于1。

上面的代码没有问题,可以正确运行,但是有什么缺点?

大家看到了,没有休眠-唤醒 机制。

使用全局变量,确实可以协调这些任务,但是没有休眠唤醒机制,task2一直在死循环等待。

如果我能够让任务2休眠,等任务一运行完毕,再让任务二重新运行,任务1就可以独占CPU,计算的更快。

任务之间可以有同步、互斥这样的操作,同步、互斥,怎么理解呢?

同步、 互斥,相辅相成。

一句话理解同步与互斥:我等你用完卫生间,我再用卫生间。

什么叫同步?就是:哎哎哎,我正在用卫生间,你等会。

什么叫互斥?就是:哎哎哎,我正在用卫生间,你不能进来。

同步与互斥经常放在一起讲,是因为它们之的关系很大,而且“互斥”操作可以使用“同步”来实现。

我“等”你用完卫生间,我再用卫生间,这就是用“同步”来实现“互斥”。

分别举个应用的例子:

同步: Gui界面,在等待按键。这就是同步,同步的意思“等待”。

互斥:有两个程序都想去做全屏的屏幕显示,如果他们同时去使用屏幕,屏幕就是乱糟糟了。这个时候,我用屏幕时你不能够用,你用屏幕时我不能够用,这就是互斥。

同步强调先后,前后有依赖;互斥强调独占。

五:晚课学员提问

1. 问: 老师,有两个问题,FreeRTOS是实时系统体现在那个方面啊?

另外Task的调度抢占是可以发生在任意时刻吗?

比如正在执行与调度中断同优先级的其他中断,那么Tick中断来了也会去调度Task去执行吗??

答: 1. 中断优先级,高于 任务优先级。“中断优先级最低”的中断,高于“任务优先级最高”的任务。

2.中断里,高优先级的中断,会抢占低优先级的中断

3.任务里,高优先级的任务,会抢占低优先级的任务

2. 问: FreeRTOS有没有划分可抢占的区间,不可抢占的区间?

答: 当然有,比如:

  1. 关闭中断

  2. 执行代码

  3. 开启中断

    在步骤2里,就是不可抢占的区间

3. 问: 那tick的中断优先级是最高的吗?

如果有比tick中断优先级更高的中断在运行是不是就要等高优先级中断执行完?

freertos有哪些情况下是要关闭中断再执行代码的?

答: 1.并不是tick中断优先级最高,它并不高,哪个中断优先级最高,由设计者决定。比如防火系统中,当然是烟雾报警的中断优先级最高。

2.高优先级的中断先运行,低优先级的中断当然要等

3.哪些情况下关闭中断?这也是由设计者决定,在中断A的处理函数里,它不想被高优先级的中断B抢占,A的函数就可以先关闭中断

4. 问: 老师我有一个问题 如果我有一个双核处理器,rtos是不是会自动同时运行两个同优先级的任务?

答: 你这样想问题:

  1. 双核处理器:看到的队列时一样的

  2. 如果队列里有3个任务:task3优先级最高,task1,2优先级相同

  3. 你觉得怎样运行更合理?

无论哪个CPU核,都是去队列里找到任务来运行

a.CPU1从队列里找到最高优先级的task3,运行

b.CPU2也不能闲着啊,它也去队列里找任务:task3? 不是,已经在运行了。所以它找到task1或task2,运行task1或task2

c.假设CPU2运行的是task1,1ms后从队列里挑出task2

5. 问: 常用的任务调度策略有:可抢占,时间片轮转,空闲任务礼让。

这是所有实时系统的常用策略吗?还是只是freertos?

答: 是常用策略,前面两个:可抢占\时间片轮转, 都是相同的。

空闲任务礼让:有些RTOS并没有这种说法

6. 问: 钩子函数是在空闲任务的时间段里周期的运行?

答: 1. 空闲任务:它里面有一个死循环,循环里面会调用钩子函数

  1. 但是执行的时间并不是周期的,空闲任务地位很低,执行时间没有保障了

7. 问: 礼让的条件是什么呀?

答: 我们配置了:可抢占、礼让,并且有其他“优先级为0的任务”就绪了。

8. 问: 那如果任务执行时间超过一个tick,且当前只有另外一个同级的任务,那么是会切换执行另一个同级任务的吧?

答: 一定会切换的。一个任务一般来说都是一个死循环,它期待的执行时间是“永远”。

9. 问: 老师。假设现在有task1 task2 task3三个任务。

task1为算法处理任务,task2 task3为传感器数据采集任务都是100hz,

task1要分别拿到task2和task3发过来的数据才去做算法的处理,

我现在做法是创建两个队列,用作task2 task3传输数据给task1,然后task1分别顺序等待两个队列都成功返回再往下处理算法。

这种方式可以吗?还有更好的方式吗?

答: 每次处理,都必须得到任务二、任务三的数据,顺序地、分别等待当然没问题

10. 问: 老师,普通任务会执行taskYIELD()函数主动放弃CPU吗?

答: 会的。

11. 问: 老师,如果是一个比较复杂的系统。有很多显示界面。那么所有的显示界面在一个任务里处理呢?还是各个任务分别调用GUI函数做显示?

答: 一般来说会有一个统一管理界面的任务。

12. 问: 老师,那Linux或安卓也也是显示有一个单独的任务来处理吗?只有显示任务里可以调用GUI函数?

答: 是的,有一个窗口管理的程序,其他任务都有自己的虚拟的显存,窗口管理的程序会根据他们的叠加关系组装出数据,再写LCD。