0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

任务状态机的相关概念

玩转嵌入式 来源:嵌入式客栈 2023-11-13 09:39 次阅读

[导读] 大家好,我是逸珺。

前面一文利用FreeRTOS点灯,算是将FreeRTOS给跑起来了,要用好RTOS,从黑盒角度去理解一下调度器是怎么工作的是很必要的,当然如果想研究其内部实现原理,可以去读其内部实现代码,但是个人感觉如果是从用的角度,把内核看成黑盒,跳出来梳理一下概念也很有用。

所以本文不切入内核代码,仅从用户视角来学习一下任务状态机相关的概念,以及相应API的作用。

RTOS核的作用

前面一文分析FreeRTOS框架的时候,曾给出这样一个理解图:

41930b1e-81c2-11ee-939d-92fbcf53809c.png

对于单片机而言,一般只有一个核,RTOS的主要作用是将用户多任务进行管理,在物理CPU核上调度管理。所以为了方便理解,可以将RTOS的调度管理器,看成是将硬件CPU核通过软件的办法为每一个应用任务虚拟出一个软核。这样就使每个任务看起来都拥有一个CPU核,这样从时间维度上看起来多任务是并行的,而事实上这种并行是伪并行。

一般单片机只有一个硬件核,那么在任意时刻,则只可能有一个任务在运行。其实这样理解还不全面,能够获取CPU时间的,从应用编程的视角,还有一个主角是不能忽略的,那就是中断程序。

41ac5aba-81c2-11ee-939d-92fbcf53809c.png

任务状态

状态概念

对于FreeRTOS的状态概念有必要先好好理解一下,理解了才能正确的使用API进行正确的应用,才知道调用了某一个API究竟会有怎样的行为表现。

<>在任务管理章节,首先给出任务的一个顶层状态机视图:

41c31a7a-81c2-11ee-939d-92fbcf53809c.png

对于单内核的芯片而言,任一任务要么处于运行态,要么处于非运行态。但同一时刻只能有一个任务处于运行态。这也是为什么这个图中①画的任务框是多个叠起来的,而②所示的任务只有一个框的原因。那么事实上,对于非运行态其内部又被划分出了几个子状态:

41d93ca6-81c2-11ee-939d-92fbcf53809c.png

Suspended: 挂起态,什么叫挂起呢?简单讲就是任务进入了挂起态后,调度器就不会对其进行调度了,也就是它不会被调度器装载到CPU核中运行,任务状态始终保持在进入挂起态时刻的现场。

就好比看一个修仙剧,内核调度器是一个法术高手,会时间静止法术,啪一个法术,这个任务就被定住了,不能再动了。但任务还在,只是不动了。直到法术解除。那么这里所谓的现场,就是该任务的TCB任务控制数据结构,将暂停时刻的物理CPU相关寄存器保存了。

Ready: 就绪态,就是指任务可以被调度器装载进CPU核运行的状态,但是还没有被装载进CPU核。为什么有这样一个就绪态呢?前面说了,RTOS主要作用就是多任务的调度管理。那么就绪的任务就有可能是多个,也就是说在同一时刻,多个任务有可能都就绪了,至于调度器究竟让哪一个任务先运行呢,这就是调度器调度算法的职责了,根据其内部的调度算法策略进行调度管理。

FreeRTOS支持的调度算法有:

时间片调度策略:也称为Round Robin调度算法,Round Robin调度算法不保证同等优先级的任务之间平均分配时间,只保证同等优先级的Ready状态任务会依次进入Running状态。

这可能让人费解,首先时间片Time Slice是指两个Tick中断间的时间间隔,每次新的Tick中断时,调度器会检查任务队列中是否有与正在运行的任务优先级相同的就绪态任务,如果有,就将正在运行的任务换出CPU,将新任务换入CPU。所以该机制并不保证相同优先级就绪态任务获得的CPU时间片相等。

固定优先级抢占式调度:这种调度算法根据任务的优先级选择任务进行装载。换句话说,高优先级任务总是在低优先级任务之前获得CPU。只有当没有处于就绪状态的高优先级任务时,低优先级任务才能执行。

更准确地理解:如果优先级高于运行状态任务的任务进入就绪状态,抢占式调度算法将立即“抢占”运行状态的低优先级任务。被抢占意味着低优先级任务马上被调度器换出运行状态,并进入就绪状态,而高优先级任务被转载进CPU进行运行。需要注意的是,低优先级任务是进入就绪态而非挂起态,当高优先级任务完成运行,进入阻塞态后,原低优先级任务将有机会被调度运行。

Blocked: 阻塞态。所谓阻塞态,可以简单理解是任务被卡在了哪里,该任务不会继续往下运行,直到阻塞解除,被转入就绪态,然后被调度至运行态。需要注意区分的是:阻塞态与暂停态是两回事,暂停是被移除调度列表,除非被人为恢复进任务调度表。而阻塞态,当阻塞事件解除,会自动进入就绪态,从而有机会被调度器换入CPU进而运行。

阻塞事件基本可以分成两类:

时间事件:比如vTaskDelay调用,任务将延迟一定的时间,一旦该函数被调用,该任务就被阻塞,直到延迟的时间结束会进入就绪态。

同步事件:比如等待消息队列、获取信号量、获取互斥体等等。

上面说到抢占式调度算法,看下面这个图就比较好理解了,在图中所示的时间点,高优先级的任务一旦就绪则会马上抢占低优先级任务。

41f4c2dc-81c2-11ee-939d-92fbcf53809c.png

状态切换

前面将状态概念撸了一遍,状态机的理解需要从两个维度进行理解:1.有哪些状态,每个状态啥物理含义;2.状态的切换条件,什么条件会触发状态变化。

上面的任务状态图描述的比较清楚,这里总结一下这些状态究竟怎么切换的:

进入挂起态:在任务的任意状态下,一旦应用程序调用了vTaskSuspend这个API,就会将指定的任务设置挂起态。

voidvTaskSuspend(TaskHandle_tpxTaskToSuspend);
voidvTaskSuspendAll(void);

以上两个任务都可以用于将任务设置成挂起态,vTaskSuspend用于将指定的任务设置为挂起态,pxTaskToSuspend就是指定的任务描述符,而vTaskSuspendAll将所有任务设置成挂起态。

退出挂起态:当任务已经处于挂起态,如应用需要将其恢复,需要调用vTaskResume或者xTaskResumeAll,将某个任务或者全部任务恢复为就绪态。注意是就绪态而非运行态,进入运行态是调度器实现的。

voidvTaskResume(TaskHandle_tpxTaskToResume);
BaseType_txTaskResumeAll(void);

要让任务恢复运行,上面两个API必须要在非挂起态任务中调用,否则是不可能被恢复的,因为处于挂起态的任务是没有机会获得CPU使用权运行的。

对于挂起态的应用场景的思考,比如应用程序中检测到某个故障了,此时需要处理故障,就可以将某个任务挂起,或者全部挂起,直到故障消除。

进入阻塞态:阻塞的概念是相对于运行而言的,也就是说一个正在运行的任务由于OS API调用会卡住不往下运行,所以状态图中是运行态会被阻塞,也就是说该任务本来正在运行,但在这个调用之后就会被调度器换出CPU。

有哪些API会让一个正处于运行的任务阻塞呢?

1.时间事件API:

voidvTaskDelay(TickType_txTicksToDelay);
voidvTaskDelayUntil(TickType_t*pxPreviousWakeTime,TickType_txTimeIncrement);

这两个API是当任务希望主动出让CPU时使用,一旦调用该任务就被设置为阻塞态,直到需要等待的时间结束,调度器将相应的任务设置为就绪态。调度器再根据调度算法决定是否被装载进CPU核运行。

应用例子:比如某个需要固定周期执行的任务,就可以在任务应用代码执行完后调用这个延迟函数,出让CPU。让其他的任务有机会被转载运行。

vTaskDelayUntil一般会先获取当前Tick数,然后再延迟到某一个增加量。

2.同步事件API:

uint32_tulTaskNotifyTake(BaseType_txClearCountOnExit,TickType_txTicksToWait);
BaseType_txTaskNotifyWait(uint32_tulBitsToClearOnEntry,
uint32_tulBitsToClearOnExit,
uint32_t*pulNotificationValue,
TickType_txTicksToWait);

//消息队列相关
BaseType_txQueueReceive(QueueHandle_txQueue,
void*pvBuffer,
TickType_txTicksToWait);
BaseType_txQueueReceiveFromISR(QueueHandle_txQueue,
void*pvBuffer,
BaseType_t*pxHigherPriorityTaskWoken);
BaseType_txQueuePeek(QueueHandle_txQueue,
void*pvBuffer,TickType_t
xTicksToWait);
BaseType_txQueuePeekFromISR(QueueHandle_txQueue,void*pvBuffer);

//信号量相关
BaseType_txSemaphoreTake(SemaphoreHandle_txSemaphore,TickType_txTicksToWait);
BaseType_txSemaphoreTakeFromISR(SemaphoreHandle_txSemaphore,
signedBaseType_t*pxHigherPriorityTaskWoken);
BaseType_txSemaphoreTakeRecursive(SemaphoreHandle_txMutex,
TickType_txTicksToWait);

//stream相关
size_txStreamBufferReceive(StreamBufferHandle_txStreamBuffer,
void*pvRxData,
size_txBufferLengthBytes,
TickType_txTicksToWait);
size_txStreamBufferReceiveFromISR(StreamBufferHandle_txStreamBuffer,
void*pvRxData,
size_txBufferLengthBytes,
BaseType_t*pxHigherPriorityTaskWoken);
//Event相关
EventBits_txEventGroupWaitBits(constEventGroupHandle_txEventGroup,
constEventBits_tuxBitsToWaitFor,
constBaseType_txClearOnExit,
constBaseType_txWaitForAllBits,
TickType_txTicksToWait);
EventBits_txEventGroupSync(EventGroupHandle_txEventGroup,
constEventBits_tuxBitsToSet,
constEventBits_tuxBitsToWaitFor,
TickType_txTicksToWait);

//message相关
size_txMessageBufferReceive(MessageBufferHandle_txMessageBuffer,
void*pvRxData,
size_txBufferLengthBytes,
TickType_txTicksToWait);
size_txMessageBufferReceiveFromISR(MessageBufferHandle_txMessageBuffer,
void*pvRxData,
size_txBufferLengthBytes,
BaseType_t*pxHigherPriorityTaskWoken);

此类任务主要用于任务间,或者任务与中断间同步或通讯的目的,在等待某一个消息或者事件的时候,将该任务阻塞而不是裸奔的查询等待,本质上就是为了提高CPU的利用率的。

需要注意的是,有的API是不能用于等待来自中断的消息或者事件的,如果需要与中断程序同步或者通信,需要使用相应的中断版本API。

总结一下

将FreeRTOS任务相关的状态梳理一下,其他的RTOS其实也是类似的,只不过实现细节会略有差异,从概念上大体上是相通的。要正确的使用RTOS,清楚正确的理解其任务状态相关概念是必要的。相关的API并不需要记忆,只需要理解概念就可以了,用的时候查一查就好了。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6032

    文章

    44513

    浏览量

    632788
  • 内核
    +关注

    关注

    3

    文章

    1362

    浏览量

    40221
  • FreeRTOS
    +关注

    关注

    12

    文章

    483

    浏览量

    61995
  • 状态机
    +关注

    关注

    2

    文章

    492

    浏览量

    27471

原文标题:使用FreeRTOS要好好理解任务状态机

文章出处:【微信号:玩转嵌入式,微信公众号:玩转嵌入式】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Verilog状态机+设计实例

    在verilog中状态机的一种很常用的逻辑结构,学习和理解状态机的运行规律能够帮助我们更好地书写代码,同时作为一种思想方法,在别的代码设计中也会有所帮助。 一、简介 在使用过程中我们常说
    的头像 发表于 02-12 19:07 3824次阅读
    Verilog<b class='flag-5'>状态机</b>+设计实例

    raw os 之状态机编程

    hsm 状态机概念读者自行参考有关概念学习,这里不再表述。推荐看Practical UML Statecharts in c/c++这本书。raw os的状态机理念,很大部分参考了它
    发表于 02-27 14:35

    如何写好状态机

    如何写好状态机:状态机是逻辑设计的重要内容,状态机的设计水平直接反应工程师的逻辑功底,所以许多公司的硬件和逻辑工程师面试中,状态机设计几乎是必选题目。本章在引入
    发表于 06-14 19:24 97次下载

    状态机思路在单片程序设计中的应用

    状态机思路在单片程序设计中的应用 状态机概念       状态机是软件编程中的一个
    发表于 03-18 15:00 1248次阅读
    <b class='flag-5'>状态机</b>思路在单片<b class='flag-5'>机</b>程序设计中的应用

    状态机举例

    状态机举例 你可以指定状态寄存器和状态机状态。以下是一个有四种状态的普通状态机。 // Th
    发表于 03-28 15:18 972次阅读

    两段式状态机不可能完成的任务

    最近折腾 状态机 ,发现一个小任务对于两段式状态机写法是不可能完成的。这个小任务很简单,先看用一段式状态机实现的代码: module tes
    发表于 05-16 15:44 7894次阅读
    两段式<b class='flag-5'>状态机</b>不可能完成的<b class='flag-5'>任务</b>

    状态机代码生成工具

    状态机代码生成工具状态机代码生成工具状态机代码生成工具状态机代码生成工具
    发表于 11-19 15:12 9次下载

    状态机原理及用法

    状态机原理及用法状态机原理及用法状态机原理及用法
    发表于 03-15 15:25 0次下载

    状态机原理进行软件设计

    组成部分。 不过,状态机理论的发展却很缓慢。在众多原因中,状态机只是做为编程的实现工具而不是设计工具是一个最重要的原因。 本文的重点就在于,怎样利用状态机原理进行程序设计。本文会先给出普通的、一个平面上的FSM(有限
    发表于 12-02 15:03 534次阅读

    状态机概述 如何理解状态机

    本篇文章包括状态机的基本概述以及通过简单的实例理解状态机
    的头像 发表于 01-02 18:03 1w次阅读
    <b class='flag-5'>状态机</b>概述  如何理解<b class='flag-5'>状态机</b>

    FPGA:状态机简述

    本文目录 前言 状态机简介 状态机分类 Mealy 型状态机 Moore 型状态机 状态机描述 一段式
    的头像 发表于 11-05 17:58 7296次阅读
    FPGA:<b class='flag-5'>状态机</b>简述

    基于状态相关字段的二进制私有协议状态机推断

    基于状态相关字段的二进制私有协议状态机推断
    发表于 06-27 15:30 6次下载

    什么是状态机状态机5要素

    玩单片还可以,各个外设也都会驱动,但是如果让你完整的写一套代码时,却无逻辑与框架可言。这说明编程还处于比较低的水平,你需要学会一种好的编程框架或者一种编程思想!比如模块化编程、状态机编程、分层思想
    的头像 发表于 07-27 11:23 2w次阅读
    什么是<b class='flag-5'>状态机</b>?<b class='flag-5'>状态机</b>5要素

    状态模式(状态机)

    以前写状态机,比较常用的方式是用 if-else 或 switch-case,高级的一点是函数指针列表。最近,看了一文章《c语言设计模式–状态模式(状态机)》(来源:embed linux
    发表于 12-16 16:53 9次下载
    <b class='flag-5'>状态</b>模式(<b class='flag-5'>状态机</b>)

    什么是状态机状态机的种类与实现

    状态机,又称有限状态机(Finite State Machine,FSM)或米利状态机(Mealy Machine),是一种描述系统状态变化的模型。在芯片设计中,
    的头像 发表于 10-19 10:27 8975次阅读