写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。
标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。
点击此处进入学习日记的总目录
2024.03.27:UCOSIII第二十四节:任务状态
- 三十八、UCOSIII:任务状态
- 1、任务的基本概念
- 2、任务调度器的基本概念
- 3、任务状态迁移
- 4、μC/OS的任务状态
三十八、UCOSIII:任务状态
1、任务的基本概念
从系统的角度看,任务是竞争系统资源的最小运行单元。
μC/OS是一个支持多任务的操作系统。
在μC/OS中,任务可以使用或等待CPU、使用内存空间等系统资源, 并独立于其他任务运行,任何数量的任务可以共享同一个优先级,处于就绪态的多个相同优先级任务将会以时间片切换的方式共享处理器。
简而言之:μC/OS的任务可认为是一系列独立任务的集合。
每个任务在自己的环境中运行。在任何时刻,只有一个任务得到运行,μC/OS调度器决定运行哪个任务。
调度器会不断的启动、停止每一个任务,宏观看上去所有的任务都在同时在执行。
作为任务,不需要对调度器的活动有所了解, 在任务切入切出时保存上下文环境(寄存器值、栈内容)是调度器主要的职责。
为了实现这点,每个μC/OS任务都需要有自己的栈空间。
当任务切出时,它的执行环境会被保存在该任务的栈空间中,这样当任务再次运行时,就能从栈中正确的恢复上次的运行环境,任务越多, 需要的栈空间就越大,而一个系统能运行多少个任务,取决于系统的可用的SRAM。
μC/OS的可以给用户提供多个任务单独享有独立的栈空间,系统可用决定任务的状态,决定任务是否可以运行,同时还能运用内核的IPC通信资源, 实现了任务之间的通信,帮助用户管理业务程序流程。这样用户可以将更多的精力投入到业务功能的实现中。
μC/OS中的任务是抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。 同时μC/OS也支持时间片轮转调度方式,只不过时间片的调度是不允许抢占任务的CPU使用权。
任务通常会运行在一个死循环中,也不会退出,如果一个任务不再需要,可以调用μC/OS中的任务删除API函数接口显式地将其删除。
2、任务调度器的基本概念
μC/OS中提供的任务调度器是基于优先级的全抢占式调度:
在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外, 系统的其他部分都是可以抢占的。
系统理论上可以支持无数个优先级(0 ~ N, 优先级数值越大的任务优先级越低,(OS_CFG_PRIO_MAX - 1u)为最低优先级, 分配给空闲任务使用,一般不建议用户来使用这个优先级。
一般系统默认的最大可用优先级数目为32。
在一些资源比较紧张的系统中, 用户可以根据实际情况选择只支持8个或自定义个数优先级的系统配置。
在系统中,当有比当前任务优先级更高的任务就绪时, 当前任务将立刻被切出,高优先级任务抢占处理器运行。
一个操作系统如果只是具备了高优先级任务能够“立即”获得处理器并得到执行的特点,那么它仍然不算是实时操作系统。
因为这个查找最高优先级任务的过程决定了调度时间是否具有确定性,例如一个包含n个就绪任务的系统中,如果仅仅从头找到尾, 那么这个时间将直接和n相关,而下一个就绪任务抉择时间的长短将会极大的影响系统的实时性。
μC/OS内核中采用两种方法寻找最高优先级的任务。
第一种是通用的方法,因为μC/OS防止CPU平台不支持前导零指令, 就采用C语言模仿前导零指令的效果实现了快速查找到最高优先级任务的方法。
而第二种方法则是特殊方法,利用硬件计算前导零指令CLZ, 这样子一次就能知道哪一个优先级任务能够运行,这种调度算法比普通方法更快捷,但受限于平台(在STM32中我们就使用这种方法)。
如果分别创建了优先级3、5、8和11这四个任务,任务创建成功后,调用CPU_CntLeadZeros()可以计算出OSPrioTbl[0]第一个置1的位前面有3个0, 那么这个3就是我们要查找的最高优先级,至于后面还有多少个位置1我们都不用管,只需要找到第一个1即可。
μC/OS内核中也允许创建相同优先级的任务。
相同优先级的任务采用时间片轮转方式进行调度(也就是通常说的分时调度器), 时间片轮转调度仅在当前系统中无更高优先级就绪任务存在的情况下才有效。为了保证系统的实时性,系统尽最大可能地保证高优先级的任务得以运行。
任务调度的原则是一旦任务状态发生了改变,并且当前运行的任务优先级小于优先级队列组中任务最高优先级时, 立刻进行任务切换(除非当前系统处于中断处理程序中或禁止任务切换的状态)。
3、任务状态迁移
μC/OS系统中的每一个任务都有多种运行状态,他们之间的转换关系是怎么样的呢?从运行态任务变成阻塞态,或者从阻塞态变成就绪态, 这些任务状态是如何进行迁移?下面就让我们一起了解任务状态迁移吧
- (1):创建任务→就绪态(Ready):任务创建完成后进入就绪态, 表明任务已准备就绪,随时可以运行,只等待调度器进行调度。
- (2):就绪态→运行态(Running):发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态。
- (3):运行态→就绪态:有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪列表中最高优先级任务变为运行态, 那么原先运行的任务由运行态变为就绪态,依然在就绪列表中, 等待最高优先级的任务运行完毕继续运行原来的任务(此处可以看作CPU使用权被更高优先级的任务抢占了)。
- (4):运行态→阻塞态(或者称为挂起态Suspended):正在运行的任务发生阻塞(挂起、延时、读信号量等待)时, 该任务会从就绪列表中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务。
- (5):阻塞态→就绪态:阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等), 此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态;如果此时被恢复任务的优先级高于正在运行任务的优先级, 则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。
- (6)(7)(8):就绪态、阻塞态、运行态→删除态(Delete): 任务可以通过调用OSTaskDel() API 函数都可以将处于任何状态的任务删除,被删除后的任务将不能再次使用,关于任务的资源都会被系统回收。
- (9):删除态→就绪态:这就是创建任务的过程,一个任务将会从无到有,创建成功的任务可以参与系统的调度。
注意:此处的任务状态只是大致的任务状态而并非μC/OS的所有任务状态,下面会具体介绍μC/OS中具体的任务的状态。
4、μC/OS的任务状态
μC/OS系统中的每一任务都有多种运行状态。系统初始化完成后,创建的任务就可以在系统中竞争一定的资源,由内核进行调度。
μC/OS的任务状态通常分为以下几种:
- 就绪(OS_TASK_STATE_RDY):该任务在就绪列表中,就绪的任务已经具备执行的能力, 只等待调度器进行调度,新创建的任务会初始化为就绪态。
- 延时(OS_TASK_STATE_DLY):该任务处于延时调度状态。
- 等待(OS_TASK_STATE_PEND):任务调用OSQPend()、OSSemPend()这类等待函数, 系统就会设置一个超时时间让该任务处于等待状态, 如果超时时间设置为0,任务的状态,无限期等下去,直到事件发生。如果超时时间为N(N>0),在N个时间内任务等待的事件或信号都没发生, 就退出等待状态转为就绪状态。
- 运行(Running):该状态表明任务正在执行,此时它占用处理器,ΜC/OS调度器选择运行的永远是处于最高优先级的就绪态任务, 当任务被运行的一刻,它的任务状态就变成了运行态,其实运行态的任务也是处于就绪列表中的。
- 挂起(OS_TASK_STATE_SUSPENDED):任务通过调用 OSTaskSuspend()函数能够挂起自己或其他任务, 调用 OSTaskResume()是使被挂起的任务回复运行的唯一的方法。挂起一任务意味着该任务再被恢复运行以前不能够取得CPU的使用权, 类似强行暂停一个任务。
- 延时+挂起(OS_TASK_STATE_DLY_SUSPENDED):任务先产生一个延时,延时没结束的时候被其他任务挂起, 挂起的效果叠加,当且仅当延时结束并且挂起被恢复了,该任务才能够再次运行。
- 等待+挂起(OS_TASK_STATE_PEND_SUSPENDED):任务先等待一个事件或信号的发生(无限期等待), 还没等待到就被其他任务挂起,挂起的效果叠加,当且仅当任务等待到事件或信号并且挂起被恢复了,该任务才能够再次运行。
- 超时等待+挂起(OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED):任务在指定时间内等待事件或信号的产生, 但是任务已经被其他任务挂起。
- 删除(OS_TASK_STATE_DEL):任务被删除后的状态,任务被删除后将不再运行,除非重新创建任务。