• 【freertos】009-任务控制



    前言

    本节描述任务相关的控制。

    主要讲解使用,源码分析后面对应章节会有。

    学习本节前,建议同学们往前回忆下任务控制块的内容。

    参考:

    任务控制主要是对任务控制块的处理。

    比如任务延时、重置任务优先级、任务挂起与恢复。

    对于延时相关的代码细节,可以参考前面的【freertos】007-系统节拍和系统延时管理实现细节章节详细分析。

    9.1 相对延时

    9.1.1 函数原型

    9.1.2 函数说明

    • vTaskDelay()用于相对延时,是指每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束。
    • xTicksToDelay参数用于设置延迟的时钟节拍个数。
    • 延时的最大值宏在portmacro.h中有定义:#define portMAX_DELAY (TickType_t )0xffffffffUL

    9.1.3 参考例子

    9.2 绝对延时

    该功能可用于周期性任务,保证执行频率不变。

    9.2.1 函数原型

    9.2.2 函数说明

    • vTaskDelayUntil()用于绝对延时,也叫周期性延时。想象下精度不高的定时器。
    • pxPreviousWakeTime参数是存储任务上次处于非阻塞状态时刻的变量地址。
    • xTimeIncrement参数用于设置周期性延时的时钟节拍个数。
    • 返回:pdFALSE 说明延时失败。
    • 使用此函数需要在FreeRTOSConfig.h配置文件中开启:#defineINCLUDE_vTaskDelayUntil 1
    • 需要保证周期性延时比任务主体运行时间长。
    • 相对延时的意思是延时配置的N个节拍后恢复当前任务为就绪态。
    • 绝对延时的意思是延时配置的N个节拍后该任务跑回到当前绝对延时函数。

    9.2.3 参考例子

    9.3 获取任务优先级

    9.3.1 函数原型

    9.3.2 函数说明

    • 用于获取任务的优先级。
    • xTask参数为任务句柄。传入NULL,表示获取当前调用该API的任务的优先级。
    • 使能方法:在FreeRTOSConfig.h中配置INCLUDE_vTaskPriorityGet为1。
    • 返回:任务优先级。

    9.3.3 uxTaskPriorityGet()源码分析

    • 通过句柄获取任务控制块。
    • 通过任务控制块获取任务优先级并返回。

    9.3.4 例子参考代码

    9.4 设置任务优先级

    任务优先级除了在创建时设置外,也可以在系统启动后重置,毕竟任务优先级的本质也只是任务控制块里面的一直成员值。

    但是修改优先级时需要维护优先级继承机制。

    9.4.1 函数原型

    9.4.2 函数说明

    作用:

    • 该函数用于重置任务优先级。
    • 如果设置的优先级高于当前正在执行的任务,则会在函数返回之前进行上下文切换。

    参数:

    • xTask:需要修改任务优先级的任务句柄。NULL时,表示修改当前任务的任务优先级。
    • uxNewPriority:新的任务优先级。在[0,configMAX_PRIORITIES - 1]范围内,否则会引起断言。

    使能方法:使用该功能需要在FreeRTOSConfig.h中配置INCLUDE_vTaskPrioritySet为1。

    9.4.3 vTaskPrioritySet()源码分析

    更改任务优先级的实现,是更改任务控制块里面记录的任务优先级值,但是需要维护好优先级继承机制。

    看到了源码,产生两个疑问:

    • 如果重置的优先级比优先级继承后的优先级还高,这种情况下为什么不更新该任务在用优先级?
    • 重置优先级后,好像没有重置该任务在事件链表中的顺序。所以重置任务优先级不会更改任务在现有事件阻塞链表的顺序。

    重置优先级简要步骤:

    • 参数校验&参数纠正。
    • 获取任务优先级。任务优先级包括基优先级和在用优先级。
    • 任务调度需求检测。
    • 迁移就绪链表。
    • 触发任务调度。

    9.4.3.1 参数校验

    传入的优先级必须小于限制值,否则会触发断言。

    9.4.3.2 临界处理

    重置任务优先级,涉及到就绪链表、事件链表的操作,而系统时钟节拍这些中断会设计到操作这些链表。

    9.4.3.3 获取任务优先级

    通过任务句柄获取任务控制块,通过任务控制块获取任务优先级。

    如果使能了互斥量,及系统支持优先级继承机制时,需要区分基优先级uxBasePriority和在用优先级uxPriority

    9.4.3.4 任务调度需求检查

    在修改任务优先级前,先检查修改后是否需要进行任务调度,以下情况都需要任务调度:

    1. 新的任务优先级比当前在跑任务优先级高,需要标记触发任务调度。
    2. 把当前在跑任务优先级调低,需要标记触发任务调度。

    实现代码如下:

    9.4.3.5 更新任务优先级

    在更新任务优先级前,需要保存该任务在用优先级,等等用于迁移就绪链表。

    如果开启了互斥量功能,检查该任务是否处于优先级继承状态:

    • 如果是,则不更新该任务在用优先级值。

      • 源码是这样一个逻辑,但是本作者在这里保留个疑问:如果重置的优先级比优先级继承后的优先级还高,这种情况下为什么不更新该任务在用优先级?
    • 如果不是,则需要更新该任务在用优先级值。

    9.4.3.6 更新事件链表

    按照作者的想法,任务优先级会影响到该任务在事件链表中的位置,所以也需要对事件链表处理。

    由于事件链表节点值按功能装载不同的值:

    • 一般情况下装载任务优先级,用于在事件链表中排序,如消息队列阻塞。
    • 如果事件节点挂入了事件组,则装载的是事件组数据。

    所以修改该值前先判断当前是否装载任务优先级。

    当前freertos官方提供的修改任务优先级API内事件链表处理代码就这。

    按照作者的想法,如果更新了任务优先级到事件节点值。

    也应该检查下当前任务是否阻塞在有序事件链表中,如消息队列,这些都是按照优先级插入事件链表的,解除阻塞是取应该排序在前的任务的。

    9.4.3.7 迁移就绪链表

    如果被修改任务优先级的任务在就绪链表,需要迁移到新的优先级就绪链表中。

    该任务如果处于就绪态,会存在在用优先级的就绪链表中,而不是基优先级的就绪链表。

    迁移就绪链表时需要注意,如果迁出就绪链表后,该链表没有就绪任务了,需要对系统任务优先级位图值uxTopReadyPriority进行更新处理。

    • 开启优先级检索优化功能后,uxTopReadyPriority该值是一个位图值。
    • 关闭优先级检索优化功能后,uxTopReadyPriority该值就是系统就绪任务中最高优先级的值。

    所以实现代码如下:

    所有功能都实现后,触发任务调度,退出临界后,便可进入调度异常的回调进行任务调度。

    9.4.4 参考例子

    9.5 挂起任务

    9.5.1 函数原型

    9.5.2 函数说明

    参数:xTaskToSuspend:需要挂起的任务的任务句柄。为NULL时,挂起当前任务。

    使能方法:在FreeRTOSConfig.h中配置INCLUDE_vTaskSuspend为1。

    作用:挂起一个任务。任务挂起后,插入到就挂起链表中,该任务不会被调度,也无权占用CPU。

    配对使用API:调用vTaskResume()恢复被挂起的任务到就绪链表。

    9.5.3 vTaskSuspend()源码分析

    9.5.3.1 进出临界

    挂起任务的处理设计到任务状态链表和任务解除阻塞时间这些全局数据,而这些数据在滴答时钟或者其它中断回调中使用的后缀FromISR API中也可能用到。

    所以为了维护这些数据的原子性,需要使用临界级别来实现。

    进出临界使用的函数:

    9.5.3.2 获取任务控制块

    9.5.3.3 任务转为挂起态

    切换任务状态不是设置某个任务状态值,而是把任务按规则放到各种状态链表。

    1. 先解除任务所有状态,即是把任务从对应状态链表中迁出:
    • 移出后,如果返回0,说明当前链表没有挂载其它任务了,需要重新更新下系统就绪任务位图表。当然,虽然不知道该任务是不是挂起前是不是在就绪态,多做这步是没错的。另外,位图表需要开启优先级优化才生效。
    1. 解除任务状态后,也需要解除任务事件,从事件链表中移除当前任务:
    1. 然后就可以把当前任务挂载到挂起任务链表:
    1. 还有任务通知需要处理。如果任务处于等待任务通知状态,如果收到任务通知,也可能从挂起链表中解除阻塞,所以必须解除任务通知状态到没有等待通知状态:

    完成以上四小步才算是把任务从其它状态切入到挂起态(是挂起任务的挂起态)。

    9.5.3.4 刷新系统解除阻塞任务时间

    为了防止挂起的任务是下一个需要解除阻塞的任务而导致系统提前进入检索解除阻塞任务的多余操作,这里可以刷新下解除阻塞任务的时间。

    • 调度器启动了才会任务去跑,才会有任务进入限时阻塞。
    • 维护系统解除阻塞任务时间的值需要在临界区内。

    9.5.3.5 任务调度器处理

    如果挂起的任务是当前任务,那需要更新下pxCurrentTCB值。

    1. 如果调度器已经启动了,挂起当前任务后,需要强制触发任务调度。

    2. 如果调度器还没有启动,挂起了当前任务,就需要更新pxCurrentTCB值即可。等待调度器启动后先跑pxCurrentTCB

    • 如果全部任务都被挂起了,就设置pxCurrentTCB为空即可。下次创建任务或者恢复任务时会重置pxCurrentTCB。至少会在启动调度器时会创建空闲任务,所以在启动调度器前不必在乎pxCurrentTCB值是否为空。

    • 如果不是全部任务都被挂起,那就从就绪表中选出最合适的任务到pxCurrentTCB

      • 调用vTaskSwitchContext(),该任务的分析可以往前面的任务切换章节翻。

    9.6 恢复任务

    9.6.1 函数原型

    9.6.2 函数说明

    xTaskToResume:需要解除挂起的任务句柄。

    INCLUDE_vTaskSuspend必须定义为vTaskSuspend() 1,这个函数才生效。

    该函数用于解除挂起的任务。

    被一个或多个vTaskSuspend()调用挂起的任务将通过对vTaskResume()的单个调用重新可用。

    9.6.3 实现分析

    解除任务的挂起态的实现比较简单,主要思路:

    • 通过任务句柄找到任务控制块。
    • 判断该任务是否处于挂起态,就是判断当前任务的状态节点是否挂载在挂起链表。
    • 把当前任务从挂起链表迁到就绪链表。
    • 如果解除挂起态的任务优先级更高或相等,就触发一次任务调度。

    9.6.4 完整代码实现

    vTaskResume()

    prvTaskIsTaskSuspended()

    9.6.5 参考例子

    附件

    重置任务优先级:vTaskPrioritySet()

    挂起任务:vTaskSuspend()


    __EOF__

  • 本文作者: 李柱明
  • 本文链接: https://www.cnblogs.com/lizhuming/p/16323321.html
  • 关于博主: 嵌入式从业者。RTOS、Linux ...
  • 版权声明: 版权归博主所有
  • 声援博主: 学习笔记分享
  • 相关阅读:
    JVM虚拟机中如何判断对象可以回收
    golang 在 Mac、Linux、Window 下交叉编译
    Ajax之引入
    坤坤音效键盘(Python实现)
    面试官:设计模式之简单工厂模式
    【蓝桥杯】算法模板题(Floyd算法)
    Flink 实时数仓(一)【实时数仓&离线数仓对比】
    GET请求或者POST请求的特点和业务中的应用场景
    Sentinel 热点规则 (ParamFlowRule)
    OpenCV(二十五):边缘检测(一)
  • 原文地址:https://www.cnblogs.com/lizhuming/p/16323321.html