一:任务调度(续)
上一节课讲调度,我们还没有讲完,
调度是理解后续所有知识的基础,可以说调度是RTOS中最重要的部分。
今天有一个同学提了这么一个问题,实时性怎么体现?
实时性,就体现在下面3点:
- 中断优先级,高于 任务优先级。“中断优先级最低”的中断,高于“任务优先级最高”的任务
- 中断里,高优先级的中断,会抢占低优先级的中断
- 任务里,高优先级的任务,会抢占低优先级的任务
对于中断先不管,
常用的调度策略是这样的:可抢占,时间片轮转,空闲任务礼让。
高优先级的任务一旦就绪,他马上就可以执行。
他一直独占CPU,一直执行,直到他休眠。
高优先级的任务,他只会跟同级的任务轮流执行,如果高优先级的任务只有他一个,他会一直独霸CPU。
如果高优先级的任务只有他一个,这时候就不存在什么时间片轮转。
就绪的任务,大家的优先级都相同,这时候才会使用时间片轮转。
那如果我们配置内核不使用时间片轮转:既然不轮流执行,那就是独占。
- 有高优先级的任务就绪了,他可以马上执行
- 如果大家的优先级都相同,只有当前任务主动放弃运行,别的任务才可以运行
- 假设任务一,任务二的优先级都是1,任务3的优先级是2。 任务3平时休眠,任务3一旦就绪,他马上就可以执行。 任务3再次放弃CPU的时候,就会导致一次调度
我们来看看一个图:
大家可以看到,在任务三休眠的那一瞬间,会触发调度。
第1个红色箭头:切换到任务1
第2个红色箭头,切换到任务2
第3个红色箭头,切换到任务1
第4个红色箭头,切换到任务2
第5个红色箭头,切换到空闲任务
大家可以看到,如果不轮流执行,只有两种情况:
- 贪婪的任务,可以一直霸占CPU
- 优先级高的任务,可以抢占CPU
- 当优先级高的任务休息了,底下那些贪婪的任务才会重新分配CPU
大家一定要注意,轮转,只发生在同级的任务之间。
二:任务礼让
接下来再讲讲礼让。
礼让,也只是发生在同级任务之间:空闲任务、其他优先级也是0的任务。
如果有任务的优先级大于0,他一旦就绪,根本就轮不到空闲任务执行:这时候谈何礼让?
在上一节课里我们布置了作业,作业1提到“task1、task2都执行了2次之后,为什么空闲任务推迟那么久才执行?”
这就是因为礼让。
再来分析一下上节课的作业,这个作业有助于大家理解调度。
我们创建了三个任务,启动调度器的时候又创建了空闲任务。
任务1、2,空闲任务,优先级都是0,他们被放在同一个链表。
- 任务3优先级最高,他先运行,然后主动调用vTaskDelay,放弃了CPU,这会触发一次调度
- 从优先级为0的那个链表里, 取出任务一来运行,任务一被放到队列的后面
- 一毫秒到了之后,从队列里取出第1个任务也就是任务2。他开始运行,并且也被放到了队列后面。
- 一毫秒到了之后,从队列里面取出第1个任务也就是空闲任务,他开始运行
大家可以看到,空闲任务实际上也是在运行的。
运行顺序是这样:task3, task1, task2, idle task
我们在逻辑分析仪里看不到,是因为还没有运行到钩子函数,空闲任务就主动放弃运行。
我们是在下面的钩子函数里,设置那个变量:
主动放弃之后,链表是这样的:
礼让之后,task1运行1ms、task2运行1ms
然后,空闲任务再次运行:从哪里开始运行?
对于空闲任务,他是一个死循环,把这个死循环精简了一下:
从代码可以得知空闲任务做的所有事情:
- 清理工作很重要,礼让之前我先清理
- 什么情况下礼让?
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从不休眠,空闲任务有没有机会执行? 答案:没有
- 高优先级任务会抢占低优先级的任务
- 同优先级的任务,轮流执行
- 空闲任务,跟其他同级的任务,也是轮流执行:只不过空闲任务执行的时间比较短,他会主动放弃CPU
- 中断,比所有的任务优先级都更高
理解这些队列,关键在于:
- 调度的时候,从
pxReadyTasksLists[31]
、pxReadyTasksLists[30]
、……、pxReadyTasksLists[0]
,按照优先级从高往下查找这些队列 - 找出优先级最高的任务,来运行
- 最高优先级的任务,如果有多个的话,就轮流执行
- 有更高优先级的任务就绪任务时,低优先级的任务没有机会执行
- 任务调度的核心:当前任务执行完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有没有划分可抢占的区间,不可抢占的区间?
答: 当然有,比如:
- 关闭中断
- 执行代码
- 开启中断 在步骤2里,就是不可抢占的区间
3. 问: 那tick的中断优先级是最高的吗?
如果有比tick中断优先级更高的中断在运行是不是就要等高优先级中断执行完?
freertos有哪些情况下是要关闭中断再执行代码的?
答: 1.并不是tick中断优先级最高,它并不高,哪个中断优先级最高,由设计者决定。比如防火系统中,当然是烟雾报警的中断优先级最高。
2.高优先级的中断先运行,低优先级的中断当然要等
3.哪些情况下关闭中断?这也是由设计者决定,在中断A的处理函数里,它不想被高优先级的中断B抢占,A的函数就可以先关闭中断
4. 问: 老师我有一个问题 如果我有一个双核处理器,rtos是不是会自动同时运行两个同优先级的任务?
答: 你这样想问题:
- 双核处理器:看到的队列时一样的
- 如果队列里有3个任务:task3优先级最高,task1,2优先级相同
- 你觉得怎样运行更合理?
无论哪个CPU核,都是去队列里找到任务来运行
a.CPU1从队列里找到最高优先级的task3,运行
b.CPU2也不能闲着啊,它也去队列里找任务:task3? 不是,已经在运行了。所以它找到task1或task2,运行task1或task2
c.假设CPU2运行的是task1,1ms后从队列里挑出task2
5. 问: 常用的任务调度策略有:可抢占,时间片轮转,空闲任务礼让。
这是所有实时系统的常用策略吗?还是只是freertos?
答: 是常用策略,前面两个:可抢占时间片轮转, 都是相同的。
空闲任务礼让:有些RTOS并没有这种说法
6. 问: 钩子函数是在空闲任务的时间段里周期的运行?
答: 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。