• 【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

  • 相关阅读:
    计算机基础 CMOS
    java: 无效的目标发行版: 17 问题解决
    从 QFramework 重新开始
    闪存 64TQFP CY8C6244AZI-S4D92 32 位双核微控制器
    【Java SE】程序逻辑控制
    codeforces每日5题(均1600)-第三十三天
    javascript高级篇之原型和原型链
    精心整理高频js手写题请查收
    【10. 信号量和管程】
    【华为机试真题 JAVA】字符串加密-100
  • 原文地址:https://blog.csdn.net/thisway_diy/article/details/125978530