RTOS训练营上节回顾空闲任务定时器任务执行顺序调度策略和晚课提问

Posted 韦东山

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RTOS训练营上节回顾空闲任务定时器任务执行顺序调度策略和晚课提问相关的知识,希望对你有一定的参考价值。

一:上节回顾

在上一节课我们贴了这么一个图:

FreeRTOS里面有很多个链表,这些链表分为三类:就绪列表、暂停列表、Delay链表。

对于就绪列表,每一个优先级都有一个链表,比如我们有32个优先级,那么就有32个就绪链表。

就绪链表里面存放的是:就绪状态的任务、运行状态的任务。

同一时间,对于单核CPU,只能够有一个运行状态的任务。

对于这一段代码,系统里面有几个任务?

答案是:4个或者5个

第4个是空闲任务,第5个是定时器任务。

二:空闲任务

如果我们配置了支持定时器,那么就会有一个定时器任务,看看代码:

再提一个问题,能不能够去掉空闲任务?

答案是:不能。

空闲任务通常为自杀任务释放内存,但是如果编写的程序,所有的任务都不自杀。

假设有任务1,任务2,假设他们都进入到了暂停状态。任务是暂停了,那CPU还在运行, CPU运行谁的代码?

所以总得有一些代码让CPU来运行,总得有一些函数来运行,这个函数就是空闲任务的函数。

从这个角度来看, CPU总得去做点事情。

当所有我们自己创建的任务都不再运行,一定有一个任务在运行:这就是空闲任务。

从这个角度来说,空闲任务只有两种状态:就绪态,运行态。

空闲任务有什么作用?回收。

在使用vTaskDelete来删除别的任务后,就会自己清理。

怎么清除呢?释放栈、释放TCB。

去创建一个任务的时候,会为他分配栈,分配TCB结构。

去删除一个任务的时候,要去释放栈、释放TCB结构体。

上面讲的是使用vTaskDelete来删除别的任务,那对于自杀的任务,能否自己清理呢?

答案是:不能。运行程序需要栈,但释放自己的栈需要运行代码,运行代码又需要栈。

我们假设可以,要运行某个函数A,函数A用到栈,函数A要去释放栈,自相矛盾。

那么对于自杀的任务,他的清理工作,就有空闲任务来执行,怎么清理呢?

上面贴的图就是空闲任务的函数,函数名取得比较奇怪。

我们把那个宏展开,这就是一个名为 prvIdelTask的函数。

清理自杀的任务,这就是空闲任务的主要工作。

在视频里面我们有一个实验,故意不让空闲任务执行,然后不断地创建、删除任务,最后发现内存耗尽。

原因就是空闲任务不能够执行,他就不能够去释放自杀的任务。

我们再来看看空闲任务的其他作用,直接看代码就可以知道:

比如说你想统计一下系统的CPU占用率、内存占用情况,可以去提供上图里面的那个函数。

这个函数就是空闲任务的钩子函数,函数内容需要自己写。

三:定时器任务

再来看看第5个任务是怎样的:

在配置了这个内核确定说使用定时器的时候,他才会去帮你创建定时器任务。

定时器任务我们暂时用不到,先不细讲,对应配置项:configUSE_TIMERS

四:执行顺序

我们假设有4个任务:1、2、3、空闲任务。他们怎么执行呢?谁先运行呢?

首先任务3的优先级最高,他先运行。

如果任务三,不休眠的话,作为最高优先级的任务,他将会一直运行。

这跟Linux不一样,在Linux系统中,最高优先级的任务也会让路。

在FreeRTOS里,最高优先级的任务:优先执行,他不放弃的话,别的任务都没有机会执行。

即使时间片轮转打开,他也只是在同等优先级的任务里面轮流执行。时间片轮转,只适用于同等优先级的多个任务。

五:调度策略

上面讲的是默认的调度算法:可抢占、时间片轮转。

我们先概括介绍下调度策略:

从3个角度统一理解多种调度算法:

  • 可否抢占?高优先级的任务能否优先执行(配置项: configUSE_PREEMPTION)

    • 可以:被称作"可抢占调度"(Pre-emptive),高优先级的就绪任务马上执行,下面再细化。
    • 不可以:不能抢就只能协商了,被称作"合作调度模式"(Co-operative Scheduling)
      • 当前任务执行时,更高优先级的任务就绪了也不能马上运行,只能等待当前任务主动让出CPU资源。
      • 其他同优先级的任务也只能等待:更高优先级的任务都不能抢占,平级的更应该老实点
  • 可抢占的前提下,同优先级的任务是否轮流执行(配置项:configUSE_TIME_SLICING)

    • 轮流执行:被称为"时间片轮转"(Time Slicing),同优先级的任务轮流执行,你执行一个时间片、我再执行一个时间片
    • 不轮流执行:英文为"without Time Slicing",当前任务会一直执行,直到主动放弃、或者被高优先级任务抢占
  • 在"可抢占"+"时间片轮转"的前提下,进一步细化:空闲任务是否让步于用户任务(配置项:configIDLE_SHOULD_YIELD)

    • 空闲任务低人一等,每执行一次循环,就看看是否主动让位给用户任务
    • 空闲任务跟用户任务一样,大家轮流执行,没有谁更特殊

我们来举例说明:

是否可抢占,配置项为:configUSE_PREEMPTION

可抢占的意思,就是高优先级的任务就绪之后,可以去抢占低优先级的任务。

只要高优先级的任务就绪,他就可以马上、抢占别人。

那反过来,如果不允许抢占呢?

我们看一个对比图:

task1、task2在task3阻塞的时候为啥运行时间不一样呢,它们不是均分时间片吗?

大家沿着123来看:

①:task3运行vTaskDelay的时刻,有可能是在1ms边界的附近,也可能在1ms很远的地方

②:假设任务3休眠之后任务1运行,任务1能够运行的时间并不是足额的1ms

③:1ms的tick中断发生后,轮到任务2运行

大家可以看到,业务1运行的时间,是随机的。下图用红色的圆圈,绿色的圆圈框出了一些波形,大家感受一下。

前面讨论了抢占,可抢占,是默认配置。

不允许抢占,用的很少,基本没人这样去用它。

那如果不允许抢占的话,会发生什么事情?

在任务一运行的过程中,即使任务三休眠时间到了,因为他不能够抢占,他的优先级再高,也只能够等。

在代码上是怎么体现出来的呢?

看看这个图,这是可抢占的情况,如果我没有配置configUSE_PREEMPTION,这个图的代码就没有效果。

如果不抢占的话, 为什么大家不轮流执行呢?

这应该是FreeRTOS根本没考虑到这一点,我们来看看代码:

我认为,这是FreeRTOS的设计缺陷,它根本就没有考虑:不抢占的实用性。

六: 晚课学员提问

1. 问: 空闲任务是否可以空操作?

答: 可以是空操作,空操作就是:NOP汇编指令,那也得执行指令。

2. 问: 空闲任务应该是最低优先级的吧?不是最低的话,比他低的都不会执行?

答: 是最低的,但是其他的任务可以跟他并列最低。

3. 问: 如果高优先级的任务再主动放弃的过程中,又来了一个一个触发他运行的事件怎么办?

答: 高优先级的任务可以马上再次运行。

4. 问: 老师,高优先级的任务就绪以后自己会触发一个调度吗?还是通过硬件中断触发一个调度,然后再执行?

答: 自己触发一个调度?这句话有逻辑错误。之前是休眠状态,休眠的任务怎么可以触发调度呢?

休眠,意味着不执行,你都不执行了,你怎么能够触发调度。所以:是别人发起调度。

这个别人是谁?task3调用vTaskDelay休眠一段时间,Tick中断发现你的时间到了,会触发调度,是tick中断来触发调度。

如果换另外一种方法进入休眠呢:假设task3在等待某个事件,谁来把他唤醒?事件的源头把它唤醒。

高优先级的任务就绪以后自己会触发一个调度吗?不会,由中断或者别的任务来触发调度。

5. 问: 老师,task3,delay后为什么没有继续执行被抢占的任务呢?

答: 假设:

  1. task1运行,它被放到的就绪队列的尾部
  2. task3就绪,抢占任务1
  3. task3再次休眠,从就绪队列的头部取出一个任务来执行,是task2

所以,task3抢占了task1,任务3再次休眠时,不一定是task1继续运行。

6. 问: 调度的时间占用任务的时间片吗?

答: 占用,调度也是需要花时间,会占用一些时间。

7. 问: 老师,假设高优先级的任务正在执行,这个时候tick时间到了,这个时候还要触发调度?

答: 高优先级的任务正在执行,可能高优先级的任务有多个。

所以,tick中断,他要去判断:有没有同优先级的其他任务?有的话就触发调度。

没有的话, 整个系统你最大, 当然就不用触发调度了。

8. 问: task1 里对两个全局变量a b 进行累加,a++ b++,那么一段时间后a 和b的值可能不同是吧。a++ 执行后,可能被高优先级任务抢占,b++没执行。

答: 是的。

9. 问: 某个任务被高优先级打断,剩下的就得不到执行了,感觉不太合理吧。会给设计带来困难啊。

答: 所以我们编写程序的时候,高优先级的任务,处理完紧急的事情之后就要休眠,不要让高优先级的任务一直执行。

高优先级的任务休眠之后,低优先级的任务可以再次运行:从被中断的地方再次运行。

只不过,低优先级能够再次运行的时间,取决于高优先级的任务什么时候休眠。

10. 问: 假设tick设置100ms,任务3目前已经从阻塞或暂停态恢复就绪态,此时tick未进入中断发生调度,那任务3是怎么进行调度的(它是抢占最高的),还有delay它是怎么被运行的(就是他要把task3从阻塞恢复成就绪态,也就是说谁把他恢复的),一切的一切是因为delay与tick绑定在一起,ms级别延时是吗?还有此时它是怎么抢占的,是谁把他调度的,一切的一切都是和tick绑定在一起的吗?抢占的意义还存在吗(delay是1ms,tick也是1ms,我怎么知道是否抢占,还不是利用tick吗?我没看源码分析,所以有这些问题)

答: 这个问题有一些概念错误。

假设tick设置100ms,任务3目前已经从阻塞或暂停态恢复就绪态,此时tick未进入中断发生调度

task3调用vTaskDelay,他能够恢复为就绪态,必定是发生了tick中断,tick计数值累加了。

还有delay它是怎么被运行的(就是他要把task3从阻塞恢复成就绪态,也就是说谁把他恢复的),一切的一切是因为delay与tick绑定在一起,ms级别延时是吗?

没错, vTaskDelay的基准就是Tick中断。

流程是这样:

  1. Task3调用vTaskDelay(5); 当前tick计数值假设是10
  2. 过了1ms,tick计数值累加为11
  3. 再过1ms,tick计数值累加为12
  4. ……
  5. tick计数值累加为15时,就把task3从delay链表移到就绪链表,并且检查发现,task3是优先级最高的就绪任务
  6. 发起调度

从这个流程,你发现:对于因为调用vTaskDelay而进入休眠的任务,是被tick中断导致的调度唤醒的。

11. 问: 请教一个问题 ,引起调度是不是有以下情况:

1.当前任务主动执行了 delay 或者supend的操作

2.TICK中断会触发一次调度

答: 有很多种情况,比如说队列操作:

一开始队列为空,task1去读队列,因为没有数据进入休眠;

task2写队列,还会去唤醒等待队列数据的task1

12. 问: 老师,这个时候把task3休眠,那么休眠的时间是从这个tick起点开始?

答:

我们在中间调用vTaskDelay,那么他什么时候被唤醒?

中间时刻+5 吗 ?不是的,我们的最小时间精度就是Tick,对于中间的时间,他没有办法记录。

那么他什么时候被唤醒?左边的Tick + 5。

13. 问: 老师,如果task3由于调用vTaskDelay后进入休眠。休眠时间还没有到的话,能不能用其他方式把他唤醒成就绪状态?

答: 一个任务调用vTaskDelay后,就被放入了delay list。我们怎样才能够把它从delay list,移到就绪链表?

  1. Tick中断函数判断时间到了
  2. 我找到一个函数,我认为是可以的,即使时间没到,别的任务也可以把它唤醒,这个没有做过实验,我会把它作为作业留给大家。

14. 问: 一个任务执行到A位置被打断了,未来某个时刻该任务还会被执行,接着从A位置执行,那这个A位置保存在这个任务的栈里?

在栈某个什么位置,这个位置有什么说法,为什么能找到他?

答:

大家沿着12345来看,假设任务1,调用函数A,A调用B, B调用C。

123:分别在栈里面画出了函数ABC的栈空间,

在函数C的运行过程中,假设是在X位置,被切换出去了。

X的值保存在PC寄存器里,PC寄存器的值保存在图中4的位置, 所有的寄存器都会保存起来。

并且,栈的当前位置SP也会记录在TCB结构体里。

以后,task1能够再次运行时,从TCB终找到栈,回复各个寄存器,也就回复了PC寄存器,也就从X位置继续运行了。

15. 问: 老师,X的值不是保存在C的栈里面吗?

答: 不是,在函数C里,你当前运行的什么位置,根本不是保存在函数C的栈里。

函数C的栈,保存的是C的局部变量等。

16. 问: 老师,这些宏配置的抢站或不抢占,轮转或不轮转,礼让或不礼让,这些宏配置在程序运行中还可以更改配置状态么?

答: 宏开关是用来决定某一段代码是不是要启用它,一旦编译程序之后,得到的可执行程序就没有办法再去改宏开关了。

一旦改宏开关,就要重新编译程序,重新烧写程序.

17. 问: 老师,当前任务是链表头的任务么,这个TCB指针是指向哪里的呢,能用图像的方法表示下任务是如何在链表中替换的么?

答:

1.创建任务一

当前tcb,指向任务一

  1. 创建任务二

当前tcb,指向任务二

  1. 创建任务三

当前tcb,指向任务三

  1. 启动调度,会创建空闲任务

当前tcb,还是指向任务三

  1. task3运行vTaskDelay后:

5.1 当前tcb,指向队列里原来的、最前面的任务1, 任务1,移到队列的最后面

  1. Tick中断里,轮到Task2运行:

当前tcb,指向队列里原来的、最前面的任务2,任务2,移到队列的最后面.

整个调度过程就这样的.

18. 问: 空闲函数执行一次只能清理一个任务,如果有两个任务需要清理就不可以了?

**答:**执行一次,清理所有任务.

19. 问: 韦老师,FreeRTOS里讲到的任务调度方式和RT-thread等其他RTOS一样吗?您讲过RT-thread里创建任务会有返回值,这个会不会引起任务调度方法的差异?

答: 基本是类似,
FreeRTOS里每一个Tick会判断是否切换 ,每个任务默认时间是一个Tick,RTT的任务可以指定能运行多少个Tick

以上是关于RTOS训练营上节回顾空闲任务定时器任务执行顺序调度策略和晚课提问的主要内容,如果未能解决你的问题,请参考以下文章

RTOS训练营上节回顾空闲任务定时器任务执行顺序调度策略和晚课提问

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

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

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

RTOS训练营上节回顾轻量级队列轻量级事件组和晚课提问

RTOS训练营上节回顾轻量级队列轻量级事件组和晚课提问