μC/OS-II 任务管理详解(三):任务创建、任务栈、删除、优先级修改与挂起恢复

学习依据:Jean J. Labrosse《嵌入式实时操作系统 μC/OS-II(第2版)》第4章"任务管理"。本文沿用书中的 API 名称与经典 μC/OS-II 内核机制。代码示例说明原理;与 CPU 有关的栈地址、数据类型和宏配置,应以实际移植版本为准。

目录

  • 一、任务管理究竟在管理什么
  • 二、建立任务:OSTaskCreate()
  • 三、扩展建立任务:OSTaskCreateExt()
  • 四、任务堆栈:任务为什么能够中断后继续运行
  • 五、堆栈检验:OSTaskStkChk()
  • 六、删除任务:OSTaskDel()
  • 七、请求删除任务:OSTaskDelReq()
  • 八、改变任务优先级:OSTaskChangePrio()
  • 九、挂起任务:OSTaskSuspend()
  • 十、恢复任务:OSTaskResume()
  • 十一、查询任务信息:OSTaskQuery()
  • 十二、任务管理与内核调度的整体关系
  • 十三、结合电机控制系统分析任务管理
  • 十四、典型问答
  • 十五、总结与参考资料

一、任务管理究竟在管理什么

1. 先区分任务、任务函数和任务控制块

任务(Task) 是由内核独立调度的基本执行单位;任务函数(Task Function) 是任务要运行的程序代码;任务控制块(Task Control Block,TCB) 是内核记录任务运行信息的数据结构。在 μC/OS-II 中,TCB 的类型是 OS_TCB。

任务并不是单独的一个 C 函数。内核还必须知道这个任务的优先级、当前状态、任务栈位置以及是否正在等待某个事件。
#mermaid-svg-5kPXrzcqyiVrAUDI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5kPXrzcqyiVrAUDI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5kPXrzcqyiVrAUDI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5kPXrzcqyiVrAUDI .error-icon{fill:#552222;}#mermaid-svg-5kPXrzcqyiVrAUDI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5kPXrzcqyiVrAUDI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5kPXrzcqyiVrAUDI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5kPXrzcqyiVrAUDI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5kPXrzcqyiVrAUDI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5kPXrzcqyiVrAUDI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5kPXrzcqyiVrAUDI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5kPXrzcqyiVrAUDI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5kPXrzcqyiVrAUDI .marker.cross{stroke:#333333;}#mermaid-svg-5kPXrzcqyiVrAUDI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5kPXrzcqyiVrAUDI p{margin:0;}#mermaid-svg-5kPXrzcqyiVrAUDI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5kPXrzcqyiVrAUDI .cluster-label text{fill:#333;}#mermaid-svg-5kPXrzcqyiVrAUDI .cluster-label span{color:#333;}#mermaid-svg-5kPXrzcqyiVrAUDI .cluster-label span p{background-color:transparent;}#mermaid-svg-5kPXrzcqyiVrAUDI .label text,#mermaid-svg-5kPXrzcqyiVrAUDI span{fill:#333;color:#333;}#mermaid-svg-5kPXrzcqyiVrAUDI .node rect,#mermaid-svg-5kPXrzcqyiVrAUDI .node circle,#mermaid-svg-5kPXrzcqyiVrAUDI .node ellipse,#mermaid-svg-5kPXrzcqyiVrAUDI .node polygon,#mermaid-svg-5kPXrzcqyiVrAUDI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5kPXrzcqyiVrAUDI .rough-node .label text,#mermaid-svg-5kPXrzcqyiVrAUDI .node .label text,#mermaid-svg-5kPXrzcqyiVrAUDI .image-shape .label,#mermaid-svg-5kPXrzcqyiVrAUDI .icon-shape .label{text-anchor:middle;}#mermaid-svg-5kPXrzcqyiVrAUDI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5kPXrzcqyiVrAUDI .rough-node .label,#mermaid-svg-5kPXrzcqyiVrAUDI .node .label,#mermaid-svg-5kPXrzcqyiVrAUDI .image-shape .label,#mermaid-svg-5kPXrzcqyiVrAUDI .icon-shape .label{text-align:center;}#mermaid-svg-5kPXrzcqyiVrAUDI .node.clickable{cursor:pointer;}#mermaid-svg-5kPXrzcqyiVrAUDI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5kPXrzcqyiVrAUDI .arrowheadPath{fill:#333333;}#mermaid-svg-5kPXrzcqyiVrAUDI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5kPXrzcqyiVrAUDI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5kPXrzcqyiVrAUDI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5kPXrzcqyiVrAUDI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5kPXrzcqyiVrAUDI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5kPXrzcqyiVrAUDI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5kPXrzcqyiVrAUDI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5kPXrzcqyiVrAUDI .cluster text{fill:#333;}#mermaid-svg-5kPXrzcqyiVrAUDI .cluster span{color:#333;}#mermaid-svg-5kPXrzcqyiVrAUDI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5kPXrzcqyiVrAUDI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5kPXrzcqyiVrAUDI rect.text{fill:none;stroke-width:0;}#mermaid-svg-5kPXrzcqyiVrAUDI .icon-shape,#mermaid-svg-5kPXrzcqyiVrAUDI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5kPXrzcqyiVrAUDI .icon-shape p,#mermaid-svg-5kPXrzcqyiVrAUDI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5kPXrzcqyiVrAUDI .icon-shape rect,#mermaid-svg-5kPXrzcqyiVrAUDI .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5kPXrzcqyiVrAUDI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5kPXrzcqyiVrAUDI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5kPXrzcqyiVrAUDI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-5kPXrzcqyiVrAUDI .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-5kPXrzcqyiVrAUDI .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-5kPXrzcqyiVrAUDI .default tspan{fill:#000000!important;} 任务函数:执行具体业务
任务控制块 OS_TCB
独立任务栈:保存执行现场
优先级、状态、延时等管理信息
就绪表:记录可运行的任务
调度器:选择最高优先级就绪任务
任务函数、任务栈与任务控制块的关系

注意:图中的箭头表示管理关联,不代表函数调用方向。TCB 保存栈指针等信息,而不是把整个任务栈复制进 TCB。

2. 一个任务必须提供怎样的入口函数

教材给出的典型形式是:

c 复制代码
void MotorTask(void *pdata)
{
    (void)pdata;

    for (;;) {
        /* 执行用户程序 */
        OSTimeDly(1);
    }
}

void *pdata 是通用指针参数 ,即用一个地址把任务运行所需的参数传入函数;for (;;) 是无限循环,与 while (1) 等价。OSTimeDly(1) 表示任务请求等待一个操作系统时钟节拍(Tick),进入延时等待状态。

教材强调:μC/OS-II 的普通任务函数不应像一般子函数那样直接返回。 如果确实需要结束任务,应通过任务删除接口退出任务管理。无限循环也并不意味着一直占有 CPU;任务执行延时、等待信号量等操作后,可以让出 CPU。

3. 第4章各项操作分别改变了什么

操作 影响对象 本质变化
创建任务 栈、TCB、就绪表 为新任务建立可调度的运行环境
删除任务 TCB、就绪表及等待队列 从内核的任务管理结构中移除任务
修改优先级 TCB、优先级映射和有关位图 改变任务参与调度的顺序
挂起任务 TCB 状态、就绪表 暂时禁止任务获得 CPU
恢复任务 TCB 状态、就绪表 撤销挂起限制,但未必立即运行
查询任务 TCB 信息 读取任务的管理状态,不改变运行逻辑

就绪表(Ready Table) 是记录各优先级是否已经满足运行条件的位图结构;调度(Scheduling) 是决定接下来让哪个就绪任务使用 CPU 的过程。

经典 μC/OS-II 是固定优先级抢占式内核 :每个任务具有不同的优先级编号,且编号越小,优先级越高 。并非所有优先级都能交给用户任务:空闲任务、可选的统计任务以及特定版本预留的优先级需要避开,可用范围以 OS_CFG.H 等配置为准。

二、建立任务:OSTaskCreate()

1. 函数原型与四个参数

教材4.00节给出的基本接口为:

c 复制代码
INT8U OSTaskCreate(
    void (*task)(void *pd),
    void *pdata,
    OS_STK *ptos,
    INT8U prio
);
参数 术语解释 具体作用
task 函数指针(保存函数入口地址的指针) 指向新任务执行的 C 函数
pdata 参数指针 将应用数据传给任务函数
ptos Top of Stack,初始栈顶指针 告诉内核从哪里初始化新任务的栈
prio Priority,任务优先级 决定新任务参与调度的先后顺序

返回值类型 INT8U 是 μC/OS-II 定义的无符号8位整数类型,用来返回成功或失败状态。例如 OS_NO_ERR 表示操作成功,OS_PRIO_EXIST 表示要使用的优先级已经被占用。

2. 任务创建的五个关键步骤

查看教材的 OSTaskCreate() 程序清单,主要过程可以归纳为:

  1. 检查优先级是否有效:如果启用了参数检查,拒绝非法优先级。
  2. 检查优先级是否被占用 :通过 OSTCBPrioTbl[prio] 判断该编号能否分配。
  3. 保留优先级位置:先写入特定的保留标记,避免其他创建操作同时占用。
  4. 初始化任务栈和 TCB :调用 OSTaskStkInit() 构造任务初始 CPU 现场,再用 OS_TCBInit() 初始化任务控制块。
  5. 必要时执行调度:创建成功后任务进入就绪态;如果内核已启动,则可能立即切换到新任务。

#mermaid-svg-GMHoWpvYz7iAru0m{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GMHoWpvYz7iAru0m .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GMHoWpvYz7iAru0m .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GMHoWpvYz7iAru0m .error-icon{fill:#552222;}#mermaid-svg-GMHoWpvYz7iAru0m .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GMHoWpvYz7iAru0m .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GMHoWpvYz7iAru0m .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GMHoWpvYz7iAru0m .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GMHoWpvYz7iAru0m .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GMHoWpvYz7iAru0m .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GMHoWpvYz7iAru0m .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GMHoWpvYz7iAru0m .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GMHoWpvYz7iAru0m .marker.cross{stroke:#333333;}#mermaid-svg-GMHoWpvYz7iAru0m svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GMHoWpvYz7iAru0m p{margin:0;}#mermaid-svg-GMHoWpvYz7iAru0m .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GMHoWpvYz7iAru0m .cluster-label text{fill:#333;}#mermaid-svg-GMHoWpvYz7iAru0m .cluster-label span{color:#333;}#mermaid-svg-GMHoWpvYz7iAru0m .cluster-label span p{background-color:transparent;}#mermaid-svg-GMHoWpvYz7iAru0m .label text,#mermaid-svg-GMHoWpvYz7iAru0m span{fill:#333;color:#333;}#mermaid-svg-GMHoWpvYz7iAru0m .node rect,#mermaid-svg-GMHoWpvYz7iAru0m .node circle,#mermaid-svg-GMHoWpvYz7iAru0m .node ellipse,#mermaid-svg-GMHoWpvYz7iAru0m .node polygon,#mermaid-svg-GMHoWpvYz7iAru0m .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GMHoWpvYz7iAru0m .rough-node .label text,#mermaid-svg-GMHoWpvYz7iAru0m .node .label text,#mermaid-svg-GMHoWpvYz7iAru0m .image-shape .label,#mermaid-svg-GMHoWpvYz7iAru0m .icon-shape .label{text-anchor:middle;}#mermaid-svg-GMHoWpvYz7iAru0m .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GMHoWpvYz7iAru0m .rough-node .label,#mermaid-svg-GMHoWpvYz7iAru0m .node .label,#mermaid-svg-GMHoWpvYz7iAru0m .image-shape .label,#mermaid-svg-GMHoWpvYz7iAru0m .icon-shape .label{text-align:center;}#mermaid-svg-GMHoWpvYz7iAru0m .node.clickable{cursor:pointer;}#mermaid-svg-GMHoWpvYz7iAru0m .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GMHoWpvYz7iAru0m .arrowheadPath{fill:#333333;}#mermaid-svg-GMHoWpvYz7iAru0m .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GMHoWpvYz7iAru0m .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GMHoWpvYz7iAru0m .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GMHoWpvYz7iAru0m .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GMHoWpvYz7iAru0m .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GMHoWpvYz7iAru0m .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GMHoWpvYz7iAru0m .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GMHoWpvYz7iAru0m .cluster text{fill:#333;}#mermaid-svg-GMHoWpvYz7iAru0m .cluster span{color:#333;}#mermaid-svg-GMHoWpvYz7iAru0m div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GMHoWpvYz7iAru0m .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GMHoWpvYz7iAru0m rect.text{fill:none;stroke-width:0;}#mermaid-svg-GMHoWpvYz7iAru0m .icon-shape,#mermaid-svg-GMHoWpvYz7iAru0m .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GMHoWpvYz7iAru0m .icon-shape p,#mermaid-svg-GMHoWpvYz7iAru0m .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GMHoWpvYz7iAru0m .icon-shape rect,#mermaid-svg-GMHoWpvYz7iAru0m .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GMHoWpvYz7iAru0m .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GMHoWpvYz7iAru0m .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GMHoWpvYz7iAru0m :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-GMHoWpvYz7iAru0m .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-GMHoWpvYz7iAru0m .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-GMHoWpvYz7iAru0m .default tspan{fill:#000000!important;} 否
是
否
是
否
是
调用 OSTaskCreate
优先级有效且未占用?
返回错误码
预留优先级位置
OSTaskStkInit:初始化任务栈
OS_TCBInit 成功?
释放预留位置并返回错误
新任务进入就绪态
内核已经启动?
返回;等待 OSStart
执行调度判断
创建任务时内核执行的主要步骤

OSTCBPrioTbl[] 是优先级到 TCB 的映射表 ,可以通过优先级快速找到对应任务控制块。OSTaskStkInit() 属于处理器移植相关函数,因为不同 CPU 保存的寄存器和栈结构不同。

3. 为什么任务栈初始化后,新任务就能开始运行

任务首次运行时,CPU 需要获得任务入口地址和适当的初始寄存器值。OSTaskStkInit() 通过在任务栈上预先布置一个符合 CPU 切换规则的初始现场(Initial Context),使新任务第一次被调度时可以像"恢复一个旧任务"一样启动。

这里的上下文(Context) 是指 CPU 继续执行某个任务所必需的状态,例如程序计数器、状态寄存器及通用寄存器等。它不是 C 语言函数中的某个普通局部变量。

4. 创建成功以后,任务会立刻运行吗

不一定。创建成功只保证任务已经纳入内核管理并满足就绪条件。

  • 在 OSStart() 调用前创建任务:内核尚未开始多任务调度,任务需要等待操作系统启动。
  • 在内核运行过程中创建任务:内核会检查调度条件;如果新任务的优先级高于当前运行任务,并且允许调度,新任务可能立即抢占 CPU。
  • 如果新任务的优先级较低:它通常留在就绪态,等待更高优先级任务阻塞或让出 CPU。

这是理论考试中常见的区分:任务创建、任务就绪和任务运行是三个不同概念。

三、扩展建立任务:OSTaskCreateExt()

1. 为什么还需要扩展创建函数

OSTaskCreate() 只有四个参数,功能较简洁,但不能充分描述任务栈容量、栈检查及任务扩展信息。教材4.01节因此介绍 OSTaskCreateExt()。

c 复制代码
INT8U OSTaskCreateExt(
    void (*task)(void *pd),
    void *pdata,
    OS_STK *ptos,
    INT8U prio,
    INT16U id,
    OS_STK *pbos,
    INT32U stk_size,
    void *pext,
    INT16U opt
);

与基本创建函数相比,它在前四个参数的基础上又增加了五项:

扩展参数 含义 为什么需要
id 用户任务标识号(Identifier) 为任务提供附加识别信息;书中版本未充分使用其扩展能力
pbos Bottom of Stack,栈底指针 便于确定栈边界并进行栈检查
stk_size 栈容量,单位为 OS_STK 元素个数 供内核计算可用栈空间
pext 用户扩展数据指针 关联任务额外的应用信息
opt Options,创建选项位 控制栈清零、栈检查等功能

位选项(Bit Options) 是用一个整数的不同二进制位分别控制不同功能。多个选项可以使用按位或 | 组合,例如:

c 复制代码
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR

OS_TASK_OPT_STK_CHK 表示允许该任务接受栈检查;OS_TASK_OPT_STK_CLR 表示创建任务时清零栈区域。进行书中介绍的栈空间估算时,通常需要同时设置这两个选项。教材还给出 OS_TASK_OPT_SAVE_FP,用于要求支持浮点运算现场保存的移植层处理浮点寄存器;是否生效取决于具体处理器与移植实现。

2. 一段对应教材的扩展创建示例

下面的示例假设移植层定义 OS_STK_GROWTH == 1,即任务栈从高地址向低地址增长。

c 复制代码
#define MOTOR_TASK_PRIO      5u
#define MOTOR_TASK_STK_SIZE  256u

static OS_STK MotorTaskStk[MOTOR_TASK_STK_SIZE];

void MotorTask(void *pdata)
{
    (void)pdata;

    for (;;) {
        /* 任务实际业务代码 */
        OSTimeDly(1);
    }
}

void CreateMotorTask(void)
{
    INT8U err;

    err = OSTaskCreateExt(
        MotorTask,                          /* 任务入口函数 */
        (void *)0,                         /* 任务入口参数 */
        &MotorTaskStk[MOTOR_TASK_STK_SIZE - 1u], /* 初始栈顶 */
        MOTOR_TASK_PRIO,                   /* 任务优先级 */
        MOTOR_TASK_PRIO,                   /* 用户任务 ID */
        &MotorTaskStk[0],                  /* 栈底地址 */
        MOTOR_TASK_STK_SIZE,               /* 栈元素个数 */
        (void *)0,                         /* 用户扩展数据 */
        OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR
    );

    if (err != OS_NO_ERR) {
        /* 处理任务创建失败,例如记录错误码 */
    }
}

以上是接口用法示例,不表示已在某一块 MCU 上编译运行。在具体工程中,头文件、内核配置、栈增长方向和创建时机都应与移植包一致。

3. 两种创建函数怎么选

基本创建函数适合只需提供入口函数、参数、栈顶和优先级的场景。扩展创建函数额外提供任务栈容量、栈边界、任务扩展数据和检查选项,便于分析任务的内存使用情况。

需要特别注意:两个创建函数并不会自动替任务分配任意大小的栈空间;用户必须在创建前提供合法的任务栈存储区域。

四、任务堆栈:任务为什么能够中断后继续运行

1. 什么是堆栈

栈(Stack) 是一块遵循"后进先出"(Last In, First Out,LIFO)访问规律的内存区域。函数调用时,返回地址、部分寄存器和局部数据可能被保存到栈中;函数退出时,再按照约定恢复相关信息。

任务被切走以后,为了将来能回到原来的执行位置,必须保留它的执行现场。因此在 μC/OS-II 中,每个任务都必须有自己的任务栈。

三个容易混淆的术语是:

  • SP(Stack Pointer,栈指针):CPU 当前使用的栈位置;发生任务切换时需要妥善保存与恢复。
  • 栈顶(Top of Stack):某个任务初始化或当前压栈时所使用的一端,具体地址由 CPU 栈增长规则决定。
  • 栈底(Bottom of Stack):任务栈相对的另一端,通常用于确定边界和检查容量。

"栈顶"并不恒等于"内存的最高地址"。必须根据 OS_STK_GROWTH 的定义理解:取值为 1 表示书中所述的向低地址增长 ,取值为 0 表示向高地址增长。

2. 为什么向下增长时传入数组的最后一个元素

假设:

c 复制代码
OS_STK TaskStk[256];

任务栈向低地址增长时,初始栈顶通常取:

c 复制代码
&TaskStk[255]

栈底对应:

c 复制代码
&TaskStk[0]
内存地址方向 数组位置 在向下增长的栈中的作用
高地址 TaskStk[255] 初始栈顶,任务现场从这一端开始布置
中间地址 TaskStk[1]~TaskStk[254] 可供运行期间使用的栈空间
低地址 TaskStk[0] 栈的另一端,作为栈边界参考

若处理器栈向高地址增长,则传参方式不同,不能照搬上述数组下标。教材通过这一差别说明 μC/OS-II 的可移植性(Portability):内核管理思想基本一致,但处理器相关细节由移植层实现。

3. 为什么实际工程不宜随便在运行中分配任务栈

教材4.02节列举了使用 malloc() 分配任务栈可能带来的内存碎片(Memory Fragmentation) 问题:多次申请和释放内存后,虽然空闲字节总数足够,但空闲区域被分割成多个小块,未必能找到一段连续空间满足新任务栈的要求。

对于资源受限、任务数量相对固定的控制器,通常更容易管理的方案是预先准备静态任务栈:

c 复制代码
static OS_STK ControlTaskStk[256];
static OS_STK ComTaskStk[192];

静态分配不是绝对规则,但它有利于在系统启动前明确 RAM 占用和任务栈边界。

五、堆栈检验:OSTaskStkChk()

1. 栈大小为什么不能完全靠猜

栈空间太小,任务可能发生栈溢出(Stack Overflow),即函数调用或保存现场所需空间超过分配边界,可能破坏邻近变量甚至造成系统异常。栈空间太大,又会浪费嵌入式系统有限的 RAM。

教材4.03节介绍 OSTaskStkChk(),用于估算已用和未使用的任务栈空间。

c 复制代码
INT8U OSTaskStkChk(INT8U prio, OS_STK_DATA *pdata);

OS_STK_DATA 是保存栈统计信息的数据结构,其中重要成员有:

成员 说明
OSFree 估算的空闲栈空间,单位为字节
OSUsed 估算的已使用栈空间,单位为字节

2. 检验到底如何工作

教材采用的思路是:创建任务前把栈初始化为0;任务运行时,一部分栈空间会被现场、调用数据等写入;检查函数再从特定栈边界扫描连续的0,推算尚未使用的容量。
#mermaid-svg-mV8WuDkusHnIOtlw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mV8WuDkusHnIOtlw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mV8WuDkusHnIOtlw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mV8WuDkusHnIOtlw .error-icon{fill:#552222;}#mermaid-svg-mV8WuDkusHnIOtlw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mV8WuDkusHnIOtlw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mV8WuDkusHnIOtlw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mV8WuDkusHnIOtlw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mV8WuDkusHnIOtlw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mV8WuDkusHnIOtlw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mV8WuDkusHnIOtlw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mV8WuDkusHnIOtlw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mV8WuDkusHnIOtlw .marker.cross{stroke:#333333;}#mermaid-svg-mV8WuDkusHnIOtlw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mV8WuDkusHnIOtlw p{margin:0;}#mermaid-svg-mV8WuDkusHnIOtlw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mV8WuDkusHnIOtlw .cluster-label text{fill:#333;}#mermaid-svg-mV8WuDkusHnIOtlw .cluster-label span{color:#333;}#mermaid-svg-mV8WuDkusHnIOtlw .cluster-label span p{background-color:transparent;}#mermaid-svg-mV8WuDkusHnIOtlw .label text,#mermaid-svg-mV8WuDkusHnIOtlw span{fill:#333;color:#333;}#mermaid-svg-mV8WuDkusHnIOtlw .node rect,#mermaid-svg-mV8WuDkusHnIOtlw .node circle,#mermaid-svg-mV8WuDkusHnIOtlw .node ellipse,#mermaid-svg-mV8WuDkusHnIOtlw .node polygon,#mermaid-svg-mV8WuDkusHnIOtlw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mV8WuDkusHnIOtlw .rough-node .label text,#mermaid-svg-mV8WuDkusHnIOtlw .node .label text,#mermaid-svg-mV8WuDkusHnIOtlw .image-shape .label,#mermaid-svg-mV8WuDkusHnIOtlw .icon-shape .label{text-anchor:middle;}#mermaid-svg-mV8WuDkusHnIOtlw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mV8WuDkusHnIOtlw .rough-node .label,#mermaid-svg-mV8WuDkusHnIOtlw .node .label,#mermaid-svg-mV8WuDkusHnIOtlw .image-shape .label,#mermaid-svg-mV8WuDkusHnIOtlw .icon-shape .label{text-align:center;}#mermaid-svg-mV8WuDkusHnIOtlw .node.clickable{cursor:pointer;}#mermaid-svg-mV8WuDkusHnIOtlw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mV8WuDkusHnIOtlw .arrowheadPath{fill:#333333;}#mermaid-svg-mV8WuDkusHnIOtlw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mV8WuDkusHnIOtlw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mV8WuDkusHnIOtlw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mV8WuDkusHnIOtlw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mV8WuDkusHnIOtlw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mV8WuDkusHnIOtlw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mV8WuDkusHnIOtlw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mV8WuDkusHnIOtlw .cluster text{fill:#333;}#mermaid-svg-mV8WuDkusHnIOtlw .cluster span{color:#333;}#mermaid-svg-mV8WuDkusHnIOtlw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mV8WuDkusHnIOtlw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mV8WuDkusHnIOtlw rect.text{fill:none;stroke-width:0;}#mermaid-svg-mV8WuDkusHnIOtlw .icon-shape,#mermaid-svg-mV8WuDkusHnIOtlw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mV8WuDkusHnIOtlw .icon-shape p,#mermaid-svg-mV8WuDkusHnIOtlw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mV8WuDkusHnIOtlw .icon-shape rect,#mermaid-svg-mV8WuDkusHnIOtlw .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mV8WuDkusHnIOtlw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mV8WuDkusHnIOtlw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mV8WuDkusHnIOtlw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-mV8WuDkusHnIOtlw .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-mV8WuDkusHnIOtlw .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-mV8WuDkusHnIOtlw .default tspan{fill:#000000!important;} 任务创建:清零栈空间
任务运行:保存现场、调用函数
堆栈中留下使用痕迹
OSTaskStkChk 扫描连续零值
计算 OSFree 与 OSUsed
任务栈使用情况的估算过程

要使用书中的这一机制,需要在 OS_CFG.H 中开启扩展任务创建等必要功能,并在建立任务时设置 OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR;OS_TASK_OPT_STK_CHK 和 OS_TASK_OPT_STK_CLR 是创建选项位,不是 OS_CFG.H 中的功能开关。

3. 为什么栈检查不能证明任务绝对安全

因为它依据栈内存中的写入痕迹来估算使用量,并不是实时监控每一条压栈指令。函数调用层次、局部数组、中断嵌套(即一个中断处理过程被其他更高优先级中断打断)和某些异常路径,都可能影响实际栈需求;中断使用任务栈还是独立中断栈,还取决于具体移植方式。

因此,检查结果适合辅助估算栈余量,不能据此断言所有运行路径都不会发生溢出。理论上还应结合最深函数调用路径、最坏情况中断嵌套以及移植层的上下文保存开销分析。

六、删除任务:OSTaskDel()

1. 删除任务究竟删掉什么

教材4.04节给出了:

c 复制代码
INT8U OSTaskDel(INT8U prio);

prio 是待删除任务的优先级;如果传入 OS_PRIO_SELF,表示要求当前任务删除自己。OS_PRIO_SELF 是内核规定的特殊参数值,而不是用户任务实际被分配的优先级。

删除任务并不是把 C 语言函数从 Flash 中抹掉,也不是自动释放所有用户申请的内存。 它的主要意义是把该任务从内核的调度管理体系中撤销。

从源码流程上看,删除操作涉及:清除就绪表中的对应位、将任务从有关事件等待结构中移除、处理任务 TCB 的链表连接、释放 TCB 供后续任务创建复用,并重新判断调度。

2. 为什么可以删除自己,却不能随意由 ISR 删除任务

ISR(Interrupt Service Routine,中断服务程序) 不是一个普通任务。任务删除会修改内核用于调度的数据结构,而且当前任务删除自身后应切换到其他就绪任务。书中的 OSTaskDel() 会检查中断嵌套状态,不允许在 ISR 中直接删除普通任务。

如果某个中断表示"当前功能不再需要",更合适的思路通常是让 ISR 通知某个任务,再由任务上下文执行必要的清理和删除。

3. 为什么删除其他任务存在资源风险

假设通信任务获得某个资源后被直接删除:

text 复制代码
通信任务已占用共享缓冲区
          ↓
其他任务直接删除通信任务
          ↓
通信任务未执行释放资源代码
          ↓
其他任务可能一直等待该资源

互斥资源(Mutual-Exclusion Resource) 是同一时刻不允许多个任务同时使用的资源。强制删除资源持有者,可能使资源长期无法正常交接。

因此,在任务仍持有互斥量、外设或动态内存时,直接删除通常具有风险。内核回收 TCB,不代表自动释放任务使用的所有应用资源。

七、请求删除任务:OSTaskDelReq()

1. 与直接删除的根本区别

教材4.05节为资源安全退出提供了另一种协作方法:

c 复制代码
INT8U OSTaskDelReq(INT8U prio);

该接口的核心思想是:其他任务可以提出删除请求,但由目标任务在合适的时刻清理资源,再自行删除。

调用方式有两类:

  • 管理任务调用 OSTaskDelReq(目标优先级):向目标任务登记删除请求。
  • 目标任务调用 OSTaskDelReq(OS_PRIO_SELF):检查是否已经有人请求删除自己。

当目标任务检查到 OS_TASK_DEL_REQ,表示内核中已有待处理的删除请求。

2. 协作删除的完整时序

#mermaid-svg-eGk2WlysVzOj2DsC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eGk2WlysVzOj2DsC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eGk2WlysVzOj2DsC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eGk2WlysVzOj2DsC .error-icon{fill:#552222;}#mermaid-svg-eGk2WlysVzOj2DsC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eGk2WlysVzOj2DsC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eGk2WlysVzOj2DsC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eGk2WlysVzOj2DsC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eGk2WlysVzOj2DsC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eGk2WlysVzOj2DsC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eGk2WlysVzOj2DsC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eGk2WlysVzOj2DsC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eGk2WlysVzOj2DsC .marker.cross{stroke:#333333;}#mermaid-svg-eGk2WlysVzOj2DsC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eGk2WlysVzOj2DsC p{margin:0;}#mermaid-svg-eGk2WlysVzOj2DsC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eGk2WlysVzOj2DsC .cluster-label text{fill:#333;}#mermaid-svg-eGk2WlysVzOj2DsC .cluster-label span{color:#333;}#mermaid-svg-eGk2WlysVzOj2DsC .cluster-label span p{background-color:transparent;}#mermaid-svg-eGk2WlysVzOj2DsC .label text,#mermaid-svg-eGk2WlysVzOj2DsC span{fill:#333;color:#333;}#mermaid-svg-eGk2WlysVzOj2DsC .node rect,#mermaid-svg-eGk2WlysVzOj2DsC .node circle,#mermaid-svg-eGk2WlysVzOj2DsC .node ellipse,#mermaid-svg-eGk2WlysVzOj2DsC .node polygon,#mermaid-svg-eGk2WlysVzOj2DsC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eGk2WlysVzOj2DsC .rough-node .label text,#mermaid-svg-eGk2WlysVzOj2DsC .node .label text,#mermaid-svg-eGk2WlysVzOj2DsC .image-shape .label,#mermaid-svg-eGk2WlysVzOj2DsC .icon-shape .label{text-anchor:middle;}#mermaid-svg-eGk2WlysVzOj2DsC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eGk2WlysVzOj2DsC .rough-node .label,#mermaid-svg-eGk2WlysVzOj2DsC .node .label,#mermaid-svg-eGk2WlysVzOj2DsC .image-shape .label,#mermaid-svg-eGk2WlysVzOj2DsC .icon-shape .label{text-align:center;}#mermaid-svg-eGk2WlysVzOj2DsC .node.clickable{cursor:pointer;}#mermaid-svg-eGk2WlysVzOj2DsC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eGk2WlysVzOj2DsC .arrowheadPath{fill:#333333;}#mermaid-svg-eGk2WlysVzOj2DsC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eGk2WlysVzOj2DsC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eGk2WlysVzOj2DsC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eGk2WlysVzOj2DsC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eGk2WlysVzOj2DsC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eGk2WlysVzOj2DsC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eGk2WlysVzOj2DsC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eGk2WlysVzOj2DsC .cluster text{fill:#333;}#mermaid-svg-eGk2WlysVzOj2DsC .cluster span{color:#333;}#mermaid-svg-eGk2WlysVzOj2DsC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eGk2WlysVzOj2DsC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eGk2WlysVzOj2DsC rect.text{fill:none;stroke-width:0;}#mermaid-svg-eGk2WlysVzOj2DsC .icon-shape,#mermaid-svg-eGk2WlysVzOj2DsC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eGk2WlysVzOj2DsC .icon-shape p,#mermaid-svg-eGk2WlysVzOj2DsC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eGk2WlysVzOj2DsC .icon-shape rect,#mermaid-svg-eGk2WlysVzOj2DsC .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eGk2WlysVzOj2DsC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eGk2WlysVzOj2DsC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eGk2WlysVzOj2DsC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-eGk2WlysVzOj2DsC .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-eGk2WlysVzOj2DsC .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-eGk2WlysVzOj2DsC .default tspan{fill:#000000!important;} 否
是
管理任务:调用 OSTaskDelReq
内核:记录目标任务的删除请求
目标任务:检查本任务删除请求
检测到 OS_TASK_DEL_REQ?
继续正常业务,稍后再次检查
目标任务:释放占用的应用资源
目标任务:调用 OSTaskDel 删除自身
内核:移除该任务并重新调度
协作式任务删除过程

该过程体现了任务的协作式退出:删除请求由管理任务提出,而资源清理与最终自删除由目标任务完成。

3. 为什么请求删除适合需要善后的任务

例如:通信任务可能需要完成当前数据帧发送、归还共享缓冲区、释放互斥资源后才能退出;存储任务可能需要等待一次写操作完成。

但请求删除机制有一个重要前提:目标任务必须有机会运行并检查请求。 如果目标任务永远阻塞或不主动检查删除请求,仅仅发出请求并不能保证它立即退出。

八、改变任务优先级:OSTaskChangePrio()

1. 修改优先级的目的

教材4.06节介绍运行过程中动态改变任务优先级:

c 复制代码
INT8U OSTaskChangePrio(INT8U oldprio, INT8U newprio);

oldprio 表示原优先级,newprio 表示新优先级。oldprio 可以通过 OS_PRIO_SELF 指代当前任务。新优先级必须处于允许使用的范围内,并且不能已被其他任务占用。

2. 只修改 TCB 中的一个数字就够了吗

不够。 第3章已经介绍,μC/OS-II 用多个数据结构管理优先级。修改优先级时,内核需要协调更新:

  • OSTCBPrio:TCB 保存的优先级编号;
  • OSTCBPrioTbl[]:优先级到 TCB 的映射关系;
  • OSRdyTbl[]、OSRdyGrp:任务处于就绪态时的就绪位;
  • 任务正等待某个事件时,该事件对应的任务等待位图。

事件等待位图(Event Wait Bitmap) 与普通就绪表不同:它记录哪些任务正在等待某个信号量、邮箱或其他事件。如果等待任务的优先级改变,内核也应同步改变它在事件等待结构中的位置。
#mermaid-svg-XMJD8XEiP7ekdHWb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XMJD8XEiP7ekdHWb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XMJD8XEiP7ekdHWb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XMJD8XEiP7ekdHWb .error-icon{fill:#552222;}#mermaid-svg-XMJD8XEiP7ekdHWb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XMJD8XEiP7ekdHWb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XMJD8XEiP7ekdHWb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XMJD8XEiP7ekdHWb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XMJD8XEiP7ekdHWb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XMJD8XEiP7ekdHWb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XMJD8XEiP7ekdHWb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XMJD8XEiP7ekdHWb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XMJD8XEiP7ekdHWb .marker.cross{stroke:#333333;}#mermaid-svg-XMJD8XEiP7ekdHWb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XMJD8XEiP7ekdHWb p{margin:0;}#mermaid-svg-XMJD8XEiP7ekdHWb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XMJD8XEiP7ekdHWb .cluster-label text{fill:#333;}#mermaid-svg-XMJD8XEiP7ekdHWb .cluster-label span{color:#333;}#mermaid-svg-XMJD8XEiP7ekdHWb .cluster-label span p{background-color:transparent;}#mermaid-svg-XMJD8XEiP7ekdHWb .label text,#mermaid-svg-XMJD8XEiP7ekdHWb span{fill:#333;color:#333;}#mermaid-svg-XMJD8XEiP7ekdHWb .node rect,#mermaid-svg-XMJD8XEiP7ekdHWb .node circle,#mermaid-svg-XMJD8XEiP7ekdHWb .node ellipse,#mermaid-svg-XMJD8XEiP7ekdHWb .node polygon,#mermaid-svg-XMJD8XEiP7ekdHWb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XMJD8XEiP7ekdHWb .rough-node .label text,#mermaid-svg-XMJD8XEiP7ekdHWb .node .label text,#mermaid-svg-XMJD8XEiP7ekdHWb .image-shape .label,#mermaid-svg-XMJD8XEiP7ekdHWb .icon-shape .label{text-anchor:middle;}#mermaid-svg-XMJD8XEiP7ekdHWb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XMJD8XEiP7ekdHWb .rough-node .label,#mermaid-svg-XMJD8XEiP7ekdHWb .node .label,#mermaid-svg-XMJD8XEiP7ekdHWb .image-shape .label,#mermaid-svg-XMJD8XEiP7ekdHWb .icon-shape .label{text-align:center;}#mermaid-svg-XMJD8XEiP7ekdHWb .node.clickable{cursor:pointer;}#mermaid-svg-XMJD8XEiP7ekdHWb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XMJD8XEiP7ekdHWb .arrowheadPath{fill:#333333;}#mermaid-svg-XMJD8XEiP7ekdHWb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XMJD8XEiP7ekdHWb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XMJD8XEiP7ekdHWb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XMJD8XEiP7ekdHWb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XMJD8XEiP7ekdHWb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XMJD8XEiP7ekdHWb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XMJD8XEiP7ekdHWb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XMJD8XEiP7ekdHWb .cluster text{fill:#333;}#mermaid-svg-XMJD8XEiP7ekdHWb .cluster span{color:#333;}#mermaid-svg-XMJD8XEiP7ekdHWb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XMJD8XEiP7ekdHWb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XMJD8XEiP7ekdHWb rect.text{fill:none;stroke-width:0;}#mermaid-svg-XMJD8XEiP7ekdHWb .icon-shape,#mermaid-svg-XMJD8XEiP7ekdHWb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XMJD8XEiP7ekdHWb .icon-shape p,#mermaid-svg-XMJD8XEiP7ekdHWb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XMJD8XEiP7ekdHWb .icon-shape rect,#mermaid-svg-XMJD8XEiP7ekdHWb .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XMJD8XEiP7ekdHWb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XMJD8XEiP7ekdHWb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XMJD8XEiP7ekdHWb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-XMJD8XEiP7ekdHWb .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-XMJD8XEiP7ekdHWb .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-XMJD8XEiP7ekdHWb .default tspan{fill:#000000!important;} 就绪
等待事件
其他状态
修改任务优先级
检查新优先级是否可用
更新 TCB 的优先级与位掩码
更新优先级到 TCB 的映射
任务原来处于什么状态?
更新就绪表中的位置
更新事件等待位图
保留相应状态信息
重新进行调度判断
修改优先级涉及的内核信息

这也是一道典型的原理题:为什么修改任务优先级可能引起任务切换? 因为优先级改变后,最高优先级就绪任务可能不再是当前任务,内核必须重新决定 CPU 使用权。

3. 数值变大到底是升高还是降低优先级

在 μC/OS-II 中,数字越小优先级越高。因此:

修改前 修改后 实际含义
10 4 优先级升高
4 10 优先级降低
10 10 优先级没有变化

考试中必须先判断编号规则,不能用日常语言中的"数字更大意味着级别更高"理解。

九、挂起任务:OSTaskSuspend()

1. 挂起究竟是什么意思

教材4.07节给出:

c 复制代码
INT8U OSTaskSuspend(INT8U prio);

挂起(Suspend) 是暂时禁止某个任务进入可调度的就绪集合。内核会在 TCB 的任务状态字段中设置 OS_STAT_SUSPEND 标志,并在必要时从就绪表清除该任务对应的位。

挂起并不删除任务。任务的代码、TCB 和任务栈仍然存在,它之前已经建立的运行现场也可以保留。

2. 挂起与延时为什么不是同一个概念

延时(Delay) 是任务主动请求在一段时间内不参与运行,时间由内核节拍管理;挂起(Suspend) 则是额外设置的禁止调度条件,通常需要显式调用恢复函数解除。

例如,一个任务先调用 OSTimeDly(100),随后又被其他任务挂起:即使100个时钟节拍已到,任务也不能仅凭延时结束就立即继续运行,因为它仍然具有挂起标志。

教材明确指出:挂起状态可以与其他等待条件同时存在。将"挂起"等同于"延时停止计数"是错误理解。

3. 为什么任务自己挂起后会发生调度

当正在运行的任务调用 OSTaskSuspend(OS_PRIO_SELF),它设置自己的挂起标志并失去就绪资格。由于它不再能够使用 CPU,调度器需要选择其他符合条件的任务运行。

如果是一个任务挂起另一个尚未运行的任务,那么正在运行的任务未必需要立即切换。

十、恢复任务:OSTaskResume()

1. 恢复的本质是清除挂起标志

教材4.08节给出:

c 复制代码
INT8U OSTaskResume(INT8U prio);

**恢复(Resume)**并不是强制让目标任务立即运行,而是撤销原先由 OSTaskSuspend() 设置的 OS_STAT_SUSPEND 标志。

教材中还有两项判断非常关键:

  1. 目标任务是否仍在等待信号量、邮箱等事件;
  2. 延时计数 OSTCBDly 是否已经归零。

只有这些阻塞条件也全部解除后,任务才能重新进入普通就绪表,再按照优先级参与调度。

2. 挂起、延时和事件等待可能同时存在

挂起标志 其他事件等待 延时计数 调用 OSTaskResume() 后
有 无 0 清除挂起标志,成为就绪任务
有 有 0 清除挂起标志,但继续等待事件
有 无 大于0 清除挂起标志,但继续等待延时结束
有 有 大于0 清除挂起标志,仍需满足其他等待条件
无 任意 任意 本来没有挂起,调用恢复通常返回相应错误状态

表中"大于0"表示内核维护的任务延时或等待超时计数仍未结束,不是 CPU 正在执行的运行时间。

3. 一个需要推导的任务状态例子

假设任务 A 的优先级为5,任务 B 的优先级为10。A 先进入等待状态,B 随后挂起 A,再恢复 A。

时刻 发生的操作 任务 A 的结果
t0t_0t0 A 等待一个信号量 A 阻塞,不在就绪表中
t1t_1t1 B 调用 OSTaskSuspend(5) A 既等待信号量,又带有挂起标志
t2t_2t2 B 调用 OSTaskResume(5) 只撤销挂起,A 仍等待信号量
t3t_3t3 信号量被发布 A 具备就绪条件,可能抢占 B

结论:恢复挂起不等于解除所有等待,更不等于直接切换到该任务。

4. 任务状态和 CPU 运行资格可以分层理解

#mermaid-svg-PwaApBYwghIWiOMJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PwaApBYwghIWiOMJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PwaApBYwghIWiOMJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PwaApBYwghIWiOMJ .error-icon{fill:#552222;}#mermaid-svg-PwaApBYwghIWiOMJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PwaApBYwghIWiOMJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PwaApBYwghIWiOMJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PwaApBYwghIWiOMJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PwaApBYwghIWiOMJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PwaApBYwghIWiOMJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PwaApBYwghIWiOMJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PwaApBYwghIWiOMJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PwaApBYwghIWiOMJ .marker.cross{stroke:#333333;}#mermaid-svg-PwaApBYwghIWiOMJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PwaApBYwghIWiOMJ p{margin:0;}#mermaid-svg-PwaApBYwghIWiOMJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PwaApBYwghIWiOMJ .cluster-label text{fill:#333;}#mermaid-svg-PwaApBYwghIWiOMJ .cluster-label span{color:#333;}#mermaid-svg-PwaApBYwghIWiOMJ .cluster-label span p{background-color:transparent;}#mermaid-svg-PwaApBYwghIWiOMJ .label text,#mermaid-svg-PwaApBYwghIWiOMJ span{fill:#333;color:#333;}#mermaid-svg-PwaApBYwghIWiOMJ .node rect,#mermaid-svg-PwaApBYwghIWiOMJ .node circle,#mermaid-svg-PwaApBYwghIWiOMJ .node ellipse,#mermaid-svg-PwaApBYwghIWiOMJ .node polygon,#mermaid-svg-PwaApBYwghIWiOMJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PwaApBYwghIWiOMJ .rough-node .label text,#mermaid-svg-PwaApBYwghIWiOMJ .node .label text,#mermaid-svg-PwaApBYwghIWiOMJ .image-shape .label,#mermaid-svg-PwaApBYwghIWiOMJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-PwaApBYwghIWiOMJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PwaApBYwghIWiOMJ .rough-node .label,#mermaid-svg-PwaApBYwghIWiOMJ .node .label,#mermaid-svg-PwaApBYwghIWiOMJ .image-shape .label,#mermaid-svg-PwaApBYwghIWiOMJ .icon-shape .label{text-align:center;}#mermaid-svg-PwaApBYwghIWiOMJ .node.clickable{cursor:pointer;}#mermaid-svg-PwaApBYwghIWiOMJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PwaApBYwghIWiOMJ .arrowheadPath{fill:#333333;}#mermaid-svg-PwaApBYwghIWiOMJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PwaApBYwghIWiOMJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PwaApBYwghIWiOMJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PwaApBYwghIWiOMJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PwaApBYwghIWiOMJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PwaApBYwghIWiOMJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PwaApBYwghIWiOMJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PwaApBYwghIWiOMJ .cluster text{fill:#333;}#mermaid-svg-PwaApBYwghIWiOMJ .cluster span{color:#333;}#mermaid-svg-PwaApBYwghIWiOMJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PwaApBYwghIWiOMJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PwaApBYwghIWiOMJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-PwaApBYwghIWiOMJ .icon-shape,#mermaid-svg-PwaApBYwghIWiOMJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PwaApBYwghIWiOMJ .icon-shape p,#mermaid-svg-PwaApBYwghIWiOMJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PwaApBYwghIWiOMJ .icon-shape rect,#mermaid-svg-PwaApBYwghIWiOMJ .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PwaApBYwghIWiOMJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PwaApBYwghIWiOMJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PwaApBYwghIWiOMJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-PwaApBYwghIWiOMJ .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-PwaApBYwghIWiOMJ .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-PwaApBYwghIWiOMJ .default tspan{fill:#000000!important;} 否
是
否
是
否
是
任务已建立并具有 TCB
挂起标志已清除?
不能进入就绪表
延时已到且事件条件满足?
仍处于等待状态
进入就绪表
是否为最高优先级就绪任务?
保持就绪,等待 CPU
具备被调度运行的资格
任务恢复后能否运行的判断条件

图中最后一项仍需满足内核允许调度等条件。例如调度器锁定或中断尚未完成时,即使某个任务优先级最高,也不会在任意指令位置不受约束地立即切换。

十一、查询任务信息:OSTaskQuery()

1. 查询函数能够得到什么

教材4.09节提供:

c 复制代码
INT8U OSTaskQuery(INT8U prio, OS_TCB *pdata);

调用者提供一个 OS_TCB 对象用于接收查询结果;内核会将指定任务的 TCB 信息复制到该对象中。传入 OS_PRIO_SELF 可用于查询当前任务。

重点区别:它返回的是任务控制块的副本(Copy),而不是把原始 TCB 的所有权交给应用程序。

2. 为什么查询信息不能随意修改后当作内核状态

查询结果是独立复制的数据,修改这份副本中的 OSTCBPrio、OSTCBDly 等成员,不会同步修改内核正在维护的原始任务控制块。

TCB 中还可能含有指向内核其他结构的指针。即使复制后能看到这些地址,也不意味着应用程序可以随意沿指针修改内部数据结构,否则可能破坏调度器正常工作。

因此,OSTaskQuery() 更适合状态查看、调试和诊断,不是修改任务内部状态的通用接口。

十二、任务管理与内核调度的整体关系

1. 各个 API 最终是否会触发调度

函数 是否可能改变就绪资格 是否可能改变 CPU 使用者
OSTaskCreate() 是,新任务可进入就绪表 是;若内核已运行且新任务优先级更高
OSTaskCreateExt() 是 是;判断方式与基本创建相同
OSTaskStkChk() 否 不因检查结果主动调度
OSTaskDel() 是,任务被移除 是,特别是删除当前任务时
OSTaskDelReq() 仅登记请求时不直接改变 请求本身通常不直接触发切换
OSTaskChangePrio() 可以改变位图中的优先级位置 是
OSTaskSuspend() 是,取消可运行资格 是,尤其是当前任务挂起自己时
OSTaskResume() 解除挂起;是否就绪取决于其他状态 是;仅在目标真正就绪且优先级足够高时
OSTaskQuery() 否 不因查询本身主动调度

表中"可能"非常重要。内核调用了调度函数,不意味着一定发生上下文切换。 如果调度前后最高优先级就绪任务没有变化,就不需要切换到另一任务。

2. 为什么多个 API 都离不开临界区

临界区(Critical Section) 是对共享数据结构进行不可被有关执行单元交错破坏的访问区域。任务管理操作经常要同时修改 TCB、优先级映射和就绪位图,因此需要保证这些关联结构保持一致。

例如,不能让调度器读到了"新任务在就绪表中",却还找不到该任务对应的 TCB。临界区的设计目标正是避免这种更新到一半就被打断的情况。

但临界区并非越大越好。内核要在数据一致性(Consistency) 和中断响应时间(Interrupt Latency) 之间取得平衡。教材删除任务的源码中,就采用暂时撤销中断保护、执行必要操作后再恢复保护等步骤,避免一次长时间关闭中断。

3. 三个层次的区别

学习时建议分别判断以下三个问题:

  1. 任务是否存在? 看任务是否已经建立、是否存在 TCB。
  2. 任务是否就绪? 看挂起、延时、事件等待等条件是否允许它参与调度。
  3. 任务是否正在运行? 看调度器最终是否把 CPU 分配给它。

任务存在不代表就绪;任务就绪不代表正在运行;当前正在运行的任务也可能因为调用任务管理 API 而立即失去运行资格。

十三、结合电机控制系统分析任务管理

假设一套 MCU 电机控制软件中,存在参数管理、CAN 通信与显示三个软件任务,同时还使用 ADC 中断和 PWM 外设完成快速控制。这里仅讨论 RTOS 如何组织后台任务,并不假定高速电流环必须在 RTOS 任务中完成。

模块 推荐理解方式 对应本章知识
参数管理任务 系统启动时创建,等待参数更新事件 创建、事件等待
CAN 通信任务 可能在不同工作模式下调整任务优先级 修改优先级、调度
显示任务 特定模式下暂停刷新,恢复后继续执行 挂起与恢复
自检任务 自检完成后清理资源并退出 请求删除与自删除
监测任务 查询任务状态、评估任务栈余量 OSTaskQuery()、OSTaskStkChk()

例如系统进入保护模式后,如果暂时不需要刷新显示,可以挂起显示任务。故障解除后调用恢复函数,显示任务才重新取得参与调度的资格。但它仍须服从优先级规则,不会因为"恢复"就一定抢占当前任务。

另一个容易忽视的问题是,高速实时控制的周期要求可能远高于普通 RTOS 时钟节拍。对于 10 kHz、20 kHz 级别的电流控制环,通常还需考虑硬件定时触发、中断延迟、任务切换开销与执行抖动,不能简单地以 OSTimeDly() 代替精密硬件定时。

十四、典型问答

1. 什么是任务管理?它的主要内容有哪些?

答: 任务管理是操作系统对任务生命周期及其调度属性进行维护的过程。μC/OS-II 的任务管理包括任务的创建、删除、请求删除、优先级修改、挂起、恢复,以及任务栈检查和任务信息查询。其核心是维护任务控制块、任务栈、优先级映射及任务就绪信息,使调度器能够正确选择任务。

2. OSTaskCreate() 与 OSTaskCreateExt() 的区别是什么?

答: 两者都可创建任务并初始化任务运行环境。OSTaskCreate() 使用任务入口、参数、初始栈顶与优先级四个参数;OSTaskCreateExt() 另外提供任务标识号、栈底指针、栈容量、扩展数据指针和创建选项,可支持任务栈的检查、清零等扩展功能。

3. 为什么 μC/OS-II 的每个任务都需要独立任务栈?

答: 任务可能在任意允许切换的位置暂停,再在以后的某个时刻继续执行。每个任务必须保存自己的函数调用环境和 CPU 运行现场;若多个任务共用同一套任务栈,任务切换后原先保存的数据和返回位置可能被其他任务覆盖。因此每个任务需要独立任务栈,并由 TCB 保存相应栈指针。

4. OSTaskStkInit() 和 OSTaskStkChk() 有什么区别?

答: OSTaskStkInit() 用于任务创建时构造符合处理器要求的初始任务现场,使任务首次运行能够正确恢复寄存器与入口地址;OSTaskStkChk() 用于检查已经建立的任务栈使用情况,估算剩余空间。前者参与创建与移植,后者属于运行诊断。

5. 为什么任务创建成功,不一定立刻运行?

答: 创建成功只表示任务的 TCB、任务栈等初始化完成并具有就绪资格。调度器仍须按照优先级选择运行任务;如果内核尚未启动,或者系统中已有更高优先级就绪任务,新任务就不会立即运行。

6. 删除任务与删除任务函数有什么不同?

答: OSTaskDel() 从内核任务管理结构中撤销任务,使任务不能再被调度;它不会擦除存放在程序存储器中的任务函数代码,也不保证自动释放该任务申请的全部应用资源。

7. 为什么不建议直接强制删除一个正在占用资源的任务?

答: 如果任务被强制删除,它可能来不及释放互斥资源、缓冲区或动态内存,导致其他任务长时间等待、内存泄漏或状态不一致。对于需要善后的任务,可以先用 OSTaskDelReq() 发送请求,让被删除任务完成清理后自行调用 OSTaskDel(OS_PRIO_SELF)。

8. OSTaskDelReq() 是否会直接删除任务?

答: 不会。由其他任务调用时,它主要登记删除请求;目标任务通过 OSTaskDelReq(OS_PRIO_SELF) 查询请求状态,必要时完成资源清理并主动删除自己。仅登记请求并不等于内核已完成删除。

9. 为什么不能只改变 OSTCBPrio 就认为完成了优先级修改?

答: 内核还使用优先级到 TCB 的映射表、就绪位图和事件等待位图维护任务。仅修改 TCB 的优先级字段会使这些结构不一致,可能引起错误调度。因此 OSTaskChangePrio() 需要协调更新相关信息,再重新进行调度判断。

10. 任务挂起与任务延时有什么不同?

答: 任务延时由时钟节拍和延时计数管理,延时结束后任务可在其他条件满足时重新就绪。任务挂起则是在状态字段中增加额外的禁止调度标志,通常需要 OSTaskResume() 解除。延时到期不会自动撤销挂起标志。

11. 为什么执行 OSTaskResume() 后任务仍可能不能运行?

答: 恢复函数只清除挂起标志。如果任务还在等待信号量等事件,或者延时计数尚未结束,它仍然不能进入就绪表。即使所有等待条件已经解除,任务也必须按照优先级与其他就绪任务竞争 CPU。

12. 挂起任务会使任务栈和 TCB 消失吗?

答: 不会。挂起属于暂时改变任务的调度资格;任务控制块与任务栈一般继续保留,便于以后恢复执行。只有任务被删除时,它才从内核任务管理结构中撤销。

13. OSTaskQuery() 返回的是原任务控制块吗?

答: 不是。教材实现通过内存复制,将指定任务的 TCB 信息复制到用户提供的缓冲区,因此返回的是一份副本。修改副本不等于更改内核中原有任务的状态。

14. 调用任务管理 API 后,一定发生任务切换吗?

答: 不一定。任务创建、删除、修改优先级、挂起或恢复等操作可能改变最高优先级就绪任务,从而引起调度。但如果调度结果仍为当前任务,或者系统处于暂不允许切换的状态,就不会因此立即切换到另一任务。

15. 为什么任务管理操作经常需要临界区保护?

答: 因为这些操作可能同时改变任务控制块、优先级映射、就绪位图和事件等待结构。如果更新过程被不合适的中断或调度打断,多个数据结构可能出现不一致。临界区可在关键更新期间保证内核共享数据结构的完整性,但应尽量缩短,以减小中断响应延迟。

16. 综合分析:任务 A 优先级5,任务 B 优先级10。B 正在运行,调用 OSTaskResume(5) 后会怎样?

答: 先判断任务 A 是否已经被挂起。如果没有挂起,恢复操作不能按预期生效。如果 A 确实被挂起,恢复时清除挂起标志;若 A 没有剩余延时,也不再等待任何事件,它将进入就绪态。由于优先级5高于10,在系统允许抢占时 A 可能立即取代 B 运行;如果 A 仍等待其他条件,则 B 可以继续运行。

十五、总结与参考资料

第4章的知识可以归纳为一条连续的逻辑:

创建任务 建立栈和 TCB,使任务具备参与调度的基础;修改优先级 改变它的调度先后;挂起与恢复 改变它是否允许参与调度;请求删除与删除 负责安全结束任务;栈检查与状态查询为运行诊断提供依据。

复习时应重点掌握三个判断条件:任务是否仍然存在、任务是否满足就绪条件、任务是否是当前最高优先级的就绪任务。所有任务管理 API 都可以沿着这三个问题展开推理。

参考资料

  1. Jean J. Labrosse:《嵌入式实时操作系统 μC/OS-II(第2版)》,第4章"任务管理",第4.00~4.09节。
  2. 同书第3章"内核结构":任务控制块、就绪表、调度器和任务切换的相关内容。
相关推荐
硅基手札2 小时前
操作系统设计思想系列 11 · ThreadX 深度 — 预抢占阈值·认证型设计
操作系统·开源项目·嵌入式开发·嵌入式操作系统·设计思想·threadx
云计算练习生3 小时前
什么是 Bash?Linux 默认 Shell 的核心功能
linux·操作系统·bash·shell·命令解释器
硅基手札1 天前
操作系统设计思想系列 10 · VxWorks 深度 — 硬实时旗舰·完全抢占内核
操作系统·实时操作系统·嵌入式开发·vxworks·设计思想
m0_547486661 天前
《Ubuntu Linux操作系统与实验教程》全套PPT课件2026
linux·ubuntu·操作系统
kyle~1 天前
C++ 原子操作、内存模型与无锁并发编程
linux·开发语言·c++·操作系统
硅基手札1 天前
Android 系统全景专栏 00 · 总目录
android·操作系统·嵌入式开发
m0_547486661 天前
《Linux操作系统应用项目教程》全套PPT课件2026
linux·运维·操作系统
Lucis__2 天前
Redis持久化机制解析
redis·缓存·操作系统·文件·非关系型数据库·1024程序员节
m0_547486662 天前
《操作系统教程》全套PPT课件2026
操作系统·操作系统教程