学习依据:《嵌入式实时操作系统 μC/OS-II(第2版)》第3章"内核结构"。这一章回答四个问题:操作系统如何记录每个任务?如何快速找到应该运行的任务?CPU 如何从一个任务切换到另一个任务?中断、时钟节拍又如何影响任务调度?
目录
- 一、从"多任务"走进操作系统内核
- 二、任务控制块:操作系统如何记住一个任务
- 三、就绪表:如何用位运算管理任务
- 四、任务调度器:谁有资格占用 CPU
- 五、任务切换:保存现场与恢复现场
- 六、临界区与调度器上锁
- 七、空闲任务与统计任务
- 八、中断处理:为什么中断结束后可能换一个任务
- 九、时钟节拍与任务延时
- 十、内核初始化与启动
- 十一、用 C 程序验证就绪表的工作过程
- 十二、嵌入式控制系统中的应用与易错点
- 十三、总结与参考资料
一、从"多任务"走进操作系统内核
1. 内核到底是什么
上一章介绍了任务(Task)、优先级(Priority)、就绪态(Ready)与抢占(Preemption)。但多任务系统并不是把几个 while(1) 函数放在同一个工程中,就能够自动实现任务切换。
内核(Kernel) 是操作系统中直接负责管理任务运行的核心部分。在 μC/OS-II 中,内核主要承担任务状态管理、任务调度、延时管理以及任务间同步与通信等工作。
其中,调度器(Scheduler) 负责决定下一步应该运行哪个任务;真正让 CPU 切换运行现场,还需要处理器相关的底层代码配合。
#mermaid-svg-vGMJ8NLGw6onk09n{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-vGMJ8NLGw6onk09n .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vGMJ8NLGw6onk09n .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vGMJ8NLGw6onk09n .error-icon{fill:#552222;}#mermaid-svg-vGMJ8NLGw6onk09n .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vGMJ8NLGw6onk09n .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vGMJ8NLGw6onk09n .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vGMJ8NLGw6onk09n .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vGMJ8NLGw6onk09n .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vGMJ8NLGw6onk09n .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vGMJ8NLGw6onk09n .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vGMJ8NLGw6onk09n .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vGMJ8NLGw6onk09n .marker.cross{stroke:#333333;}#mermaid-svg-vGMJ8NLGw6onk09n svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vGMJ8NLGw6onk09n p{margin:0;}#mermaid-svg-vGMJ8NLGw6onk09n .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vGMJ8NLGw6onk09n .cluster-label text{fill:#333;}#mermaid-svg-vGMJ8NLGw6onk09n .cluster-label span{color:#333;}#mermaid-svg-vGMJ8NLGw6onk09n .cluster-label span p{background-color:transparent;}#mermaid-svg-vGMJ8NLGw6onk09n .label text,#mermaid-svg-vGMJ8NLGw6onk09n span{fill:#333;color:#333;}#mermaid-svg-vGMJ8NLGw6onk09n .node rect,#mermaid-svg-vGMJ8NLGw6onk09n .node circle,#mermaid-svg-vGMJ8NLGw6onk09n .node ellipse,#mermaid-svg-vGMJ8NLGw6onk09n .node polygon,#mermaid-svg-vGMJ8NLGw6onk09n .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vGMJ8NLGw6onk09n .rough-node .label text,#mermaid-svg-vGMJ8NLGw6onk09n .node .label text,#mermaid-svg-vGMJ8NLGw6onk09n .image-shape .label,#mermaid-svg-vGMJ8NLGw6onk09n .icon-shape .label{text-anchor:middle;}#mermaid-svg-vGMJ8NLGw6onk09n .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vGMJ8NLGw6onk09n .rough-node .label,#mermaid-svg-vGMJ8NLGw6onk09n .node .label,#mermaid-svg-vGMJ8NLGw6onk09n .image-shape .label,#mermaid-svg-vGMJ8NLGw6onk09n .icon-shape .label{text-align:center;}#mermaid-svg-vGMJ8NLGw6onk09n .node.clickable{cursor:pointer;}#mermaid-svg-vGMJ8NLGw6onk09n .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vGMJ8NLGw6onk09n .arrowheadPath{fill:#333333;}#mermaid-svg-vGMJ8NLGw6onk09n .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vGMJ8NLGw6onk09n .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vGMJ8NLGw6onk09n .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vGMJ8NLGw6onk09n .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vGMJ8NLGw6onk09n .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vGMJ8NLGw6onk09n .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vGMJ8NLGw6onk09n .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vGMJ8NLGw6onk09n .cluster text{fill:#333;}#mermaid-svg-vGMJ8NLGw6onk09n .cluster span{color:#333;}#mermaid-svg-vGMJ8NLGw6onk09n 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-vGMJ8NLGw6onk09n .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vGMJ8NLGw6onk09n rect.text{fill:none;stroke-width:0;}#mermaid-svg-vGMJ8NLGw6onk09n .icon-shape,#mermaid-svg-vGMJ8NLGw6onk09n .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vGMJ8NLGw6onk09n .icon-shape p,#mermaid-svg-vGMJ8NLGw6onk09n .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vGMJ8NLGw6onk09n .icon-shape .label rect,#mermaid-svg-vGMJ8NLGw6onk09n .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vGMJ8NLGw6onk09n .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vGMJ8NLGw6onk09n .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vGMJ8NLGw6onk09n :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-vGMJ8NLGw6onk09n .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-vGMJ8NLGw6onk09n .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-vGMJ8NLGw6onk09n .default tspan{fill:#000000!important;} 应用任务:控制、通信、显示
μC/OS-II 内核服务
任务管理、调度与同步
CPU 移植层:寄存器与堆栈操作
处理器及外设
μC/OS-II 内核与硬件之间的关系
这里的移植层(Port),是指针对某一种 CPU 架构编写的适配代码。不同 CPU 的寄存器数量、栈增长方向、中断返回方式不同,因此上下文切换的底层实现也不同。
2. 教材中内核源码的组织方式
μC/OS-II 将大部分与 CPU 无关的内核功能用 C 语言实现,再由少量与 CPU 有关的文件完成移植。
| 文件 | 主要作用 |
|---|---|
OS_CORE.C |
内核初始化、任务调度、中断进入与退出等核心操作 |
OS_TASK.C |
创建、删除、挂起和恢复任务 |
OS_TIME.C |
任务延时与时间管理 |
OS_SEM.C、OS_MUTEX.C |
信号量与互斥资源管理 |
OS_MBOX.C、OS_Q.C |
消息邮箱与消息队列 |
OS_FLAG.C、OS_MEM.C |
事件标志组与内存块管理 |
OS_CPU.H、OS_CPU_C.C、OS_CPU_A.ASM |
处理器相关的数据类型、临界区、任务堆栈和切换操作 |
OS_CFG.H |
配置内核功能开关及相关参数 |
uCOS_II.H |
内核数据结构、宏和函数声明 |
表中的文件名对应本书所用的经典 μC/OS-II 代码组织形式;不同版本和移植工程中的目录、汇编文件扩展名可能有所区别。
理解这层划分后,就能解释为什么同一个 RTOS 内核可以移植到不同的 MCU:任务管理规则基本相同,但保存与恢复 CPU 现场的方式必须适配处理器。
二、任务控制块:操作系统如何记住一个任务
1. 为什么任务不仅仅是一个函数
考虑两个任务:
c
void TaskA(void *pdata)
{
(void)pdata;
while (1) {
/* 处理任务 A */
}
}
void TaskB(void *pdata)
{
(void)pdata;
while (1) {
/* 处理任务 B */
}
}
假设 TaskA 执行了一半,CPU 转而运行 TaskB。以后再回到 TaskA 时,必须知道它的优先级、之前运行到哪一条指令、寄存器中原来的值,以及它正在等待什么。
因此,μC/OS-II 为每个已创建的任务维护一个任务控制块(Task Control Block,TCB) ,对应的数据结构类型是 OS_TCB。
TCB 可以理解为任务在内核中的"管理档案"。需要注意:TCB 并不保存整个任务栈,它通常保存指向任务栈的指针以及其他管理信息。
#mermaid-svg-H3ktRy2jsiwBrm87{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-H3ktRy2jsiwBrm87 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-H3ktRy2jsiwBrm87 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-H3ktRy2jsiwBrm87 .error-icon{fill:#552222;}#mermaid-svg-H3ktRy2jsiwBrm87 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-H3ktRy2jsiwBrm87 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-H3ktRy2jsiwBrm87 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-H3ktRy2jsiwBrm87 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-H3ktRy2jsiwBrm87 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-H3ktRy2jsiwBrm87 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-H3ktRy2jsiwBrm87 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-H3ktRy2jsiwBrm87 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-H3ktRy2jsiwBrm87 .marker.cross{stroke:#333333;}#mermaid-svg-H3ktRy2jsiwBrm87 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-H3ktRy2jsiwBrm87 p{margin:0;}#mermaid-svg-H3ktRy2jsiwBrm87 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-H3ktRy2jsiwBrm87 .cluster-label text{fill:#333;}#mermaid-svg-H3ktRy2jsiwBrm87 .cluster-label span{color:#333;}#mermaid-svg-H3ktRy2jsiwBrm87 .cluster-label span p{background-color:transparent;}#mermaid-svg-H3ktRy2jsiwBrm87 .label text,#mermaid-svg-H3ktRy2jsiwBrm87 span{fill:#333;color:#333;}#mermaid-svg-H3ktRy2jsiwBrm87 .node rect,#mermaid-svg-H3ktRy2jsiwBrm87 .node circle,#mermaid-svg-H3ktRy2jsiwBrm87 .node ellipse,#mermaid-svg-H3ktRy2jsiwBrm87 .node polygon,#mermaid-svg-H3ktRy2jsiwBrm87 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-H3ktRy2jsiwBrm87 .rough-node .label text,#mermaid-svg-H3ktRy2jsiwBrm87 .node .label text,#mermaid-svg-H3ktRy2jsiwBrm87 .image-shape .label,#mermaid-svg-H3ktRy2jsiwBrm87 .icon-shape .label{text-anchor:middle;}#mermaid-svg-H3ktRy2jsiwBrm87 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-H3ktRy2jsiwBrm87 .rough-node .label,#mermaid-svg-H3ktRy2jsiwBrm87 .node .label,#mermaid-svg-H3ktRy2jsiwBrm87 .image-shape .label,#mermaid-svg-H3ktRy2jsiwBrm87 .icon-shape .label{text-align:center;}#mermaid-svg-H3ktRy2jsiwBrm87 .node.clickable{cursor:pointer;}#mermaid-svg-H3ktRy2jsiwBrm87 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-H3ktRy2jsiwBrm87 .arrowheadPath{fill:#333333;}#mermaid-svg-H3ktRy2jsiwBrm87 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-H3ktRy2jsiwBrm87 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-H3ktRy2jsiwBrm87 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-H3ktRy2jsiwBrm87 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-H3ktRy2jsiwBrm87 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-H3ktRy2jsiwBrm87 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-H3ktRy2jsiwBrm87 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-H3ktRy2jsiwBrm87 .cluster text{fill:#333;}#mermaid-svg-H3ktRy2jsiwBrm87 .cluster span{color:#333;}#mermaid-svg-H3ktRy2jsiwBrm87 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-H3ktRy2jsiwBrm87 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-H3ktRy2jsiwBrm87 rect.text{fill:none;stroke-width:0;}#mermaid-svg-H3ktRy2jsiwBrm87 .icon-shape,#mermaid-svg-H3ktRy2jsiwBrm87 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-H3ktRy2jsiwBrm87 .icon-shape p,#mermaid-svg-H3ktRy2jsiwBrm87 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-H3ktRy2jsiwBrm87 .icon-shape .label rect,#mermaid-svg-H3ktRy2jsiwBrm87 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-H3ktRy2jsiwBrm87 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-H3ktRy2jsiwBrm87 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-H3ktRy2jsiwBrm87 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-H3ktRy2jsiwBrm87 .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-H3ktRy2jsiwBrm87 .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-H3ktRy2jsiwBrm87 .default tspan{fill:#000000!important;} 任务 A 的函数代码
任务 A 的 TCB
优先级、状态、延时等信息
任务 A 当前栈指针
任务 A 的独立栈
任务函数、TCB 与任务栈的关系
2. OS_TCB 中有哪些重要成员
教材3.03节给出了完整的 OS_TCB 结构体。其成员可能受到编译配置宏控制,初学时先掌握以下内容。
| 成员 | 含义 | 用途 |
|---|---|---|
OSTCBStkPtr |
任务栈指针(Stack Pointer) | 保存该任务的栈位置,供任务切换时恢复 |
OSTCBPrio |
任务优先级 | 决定任务被调度的先后次序 |
OSTCBStat |
任务状态字段 | 记录任务是否因事件等条件而等待 |
OSTCBDly |
任务延时计数 | 保存剩余等待的时钟节拍数 |
OSTCBNext、OSTCBPrev |
链表前后指针 | 将任务控制块串接起来,方便遍历与管理 |
OSTCBEventPtr |
事件控制块指针 | 关联任务正在等待的事件对象 |
OSTCBX、OSTCBY |
就绪表位置 | 避免每次重新计算任务优先级所在的组与位 |
OSTCBBitX、OSTCBBitY |
就绪表位掩码 | 便于快速设置或清除相应的就绪位 |
指针(Pointer) 是保存内存地址的变量;链表(Linked List) 是通过指针把多个数据节点连接起来的数据结构;位掩码(Bit Mask) 是专门用于检测、设置或清除某些二进制位的数值。
其中,OSTCBStkPtr 非常关键。任务切换时,CPU 运行现场首先与任务栈关联,内核再通过这个指针找到任务的栈。
3. TCB、任务栈与就绪表不是同一个东西
这三个概念最容易混淆:
- TCB:记录任务管理信息,相当于任务的档案。
- 任务栈(Task Stack):保存函数调用信息、局部变量以及切换时的 CPU 现场。
- 就绪表(Ready Table):记录哪些优先级的任务具备运行条件。
任务被阻塞(Blocked,即暂时不能运行)后,其 TCB 和任务栈通常仍然存在,只是相应优先级不再参与普通就绪任务的竞争。
三、就绪表:如何用位运算管理任务
1. 为什么不直接遍历全部任务
μC/OS-II 的基本规则是:运行当前所有就绪任务中优先级最高的任务。在经典版本中,优先级数字越小,优先级越高,而且一个优先级只能分配给一个任务。
如果每次调度都从所有任务中逐个查找,任务越多,查找所需的工作就可能越多。为此,μC/OS-II 采用位图(Bitmap,也叫位映射) 表示就绪状态:用一个二进制位表示某个优先级的任务是否就绪。
教材中的典型配置使用 0~63 共64个优先级位置,并分成8组,每组8个位置。实际可用优先级范围还受配置和内核保留任务限制。
两个核心变量是:
c
INT8U OSRdyGrp; /* 哪些优先级组中存在就绪任务 */
INT8U OSRdyTbl[8]; /* 每组内部哪些优先级已就绪 */
INT8U 表示8位无符号整数。一个字节(Byte)通常为8位(Bit) ,因此 OSRdyTbl[0] 的8个位可以描述优先级0~7的任务,OSRdyTbl[1] 描述8~15,依此类推。
OSRdyTbl 元素 |
表示的优先级 | 对应 OSRdyGrp 的位 |
|---|---|---|
OSRdyTbl[0] |
0~7 | bit0 |
OSRdyTbl[1] |
8~15 | bit1 |
OSRdyTbl[2] |
16~23 | bit2 |
| ... | ... | ... |
OSRdyTbl[7] |
56~63 | bit7 |
OSRdyGrp 是对8组的再次汇总:某一组中只要存在至少一个就绪任务,其对应的组标志位就为1。
2. 优先级如何转换为组号和位号
设任务优先级为 ppp,组号为 yyy,组内位号为 xxx,则有:
y=p≫3=⌊p8⌋ y=p\gg3=\left\lfloor\frac{p}{8}\right\rfloor y=p≫3=⌊8p⌋
x=p & 0x07=p mod 8 x=p\ \&\ 0x07=p\bmod 8 x=p & 0x07=pmod8
这里,>>3 是右移3位 ,对于非负整数相当于整除8;& 0x07 是按位与,只保留低3位,相当于取除以8的余数。
例如优先级为19:
y=19÷8(取整数商)=2,x=19 mod 8=3 y=19\div8\text{(取整数商)}=2,\qquad x=19\bmod8=3 y=19÷8(取整数商)=2,x=19mod8=3
即优先级19对应 OSRdyTbl[2] 中的 bit3。
3. 如何把一个任务设置为就绪
教材使用 OSMapTbl[](位掩码查找表)加速位操作。其核心逻辑等价于:
c
/* prio 为任务优先级,范围为 0~63 */
uint8_t y = prio >> 3;
uint8_t x = prio & 0x07;
OSRdyTbl[y] |= (uint8_t)(1u << x); /* 设置组内就绪位 */
OSRdyGrp |= (uint8_t)(1u << y); /* 设置对应组标志位 */
|= 是按位或赋值。某一位与1按位或后变成1,其余位保持不变。
任务转为等待态时,应清除对应的就绪位;仅当该组已没有任何就绪任务时,才清除 OSRdyGrp 中的组标志位。否则可能误把同组其他就绪任务一起隐藏。
4. 用三个任务观察位图变化
假设优先级3、10、19的任务都处于就绪态:
| 优先级 | 组号 yyy | 位号 xxx | 对应字节的结果 |
|---|---|---|---|
| 3 | 0 | 3 | OSRdyTbl[0] = 00001000 |
| 10 | 1 | 2 | OSRdyTbl[1] = 00000100 |
| 19 | 2 | 3 | OSRdyTbl[2] = 00001000 |
此时 OSRdyGrp = 00000111,因为第0、1、2组三组都有任务就绪。
#mermaid-svg-H2K0UVzEIEFAr8PN{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-H2K0UVzEIEFAr8PN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-H2K0UVzEIEFAr8PN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-H2K0UVzEIEFAr8PN .error-icon{fill:#552222;}#mermaid-svg-H2K0UVzEIEFAr8PN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-H2K0UVzEIEFAr8PN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-H2K0UVzEIEFAr8PN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-H2K0UVzEIEFAr8PN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-H2K0UVzEIEFAr8PN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-H2K0UVzEIEFAr8PN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-H2K0UVzEIEFAr8PN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-H2K0UVzEIEFAr8PN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-H2K0UVzEIEFAr8PN .marker.cross{stroke:#333333;}#mermaid-svg-H2K0UVzEIEFAr8PN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-H2K0UVzEIEFAr8PN p{margin:0;}#mermaid-svg-H2K0UVzEIEFAr8PN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-H2K0UVzEIEFAr8PN .cluster-label text{fill:#333;}#mermaid-svg-H2K0UVzEIEFAr8PN .cluster-label span{color:#333;}#mermaid-svg-H2K0UVzEIEFAr8PN .cluster-label span p{background-color:transparent;}#mermaid-svg-H2K0UVzEIEFAr8PN .label text,#mermaid-svg-H2K0UVzEIEFAr8PN span{fill:#333;color:#333;}#mermaid-svg-H2K0UVzEIEFAr8PN .node rect,#mermaid-svg-H2K0UVzEIEFAr8PN .node circle,#mermaid-svg-H2K0UVzEIEFAr8PN .node ellipse,#mermaid-svg-H2K0UVzEIEFAr8PN .node polygon,#mermaid-svg-H2K0UVzEIEFAr8PN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-H2K0UVzEIEFAr8PN .rough-node .label text,#mermaid-svg-H2K0UVzEIEFAr8PN .node .label text,#mermaid-svg-H2K0UVzEIEFAr8PN .image-shape .label,#mermaid-svg-H2K0UVzEIEFAr8PN .icon-shape .label{text-anchor:middle;}#mermaid-svg-H2K0UVzEIEFAr8PN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-H2K0UVzEIEFAr8PN .rough-node .label,#mermaid-svg-H2K0UVzEIEFAr8PN .node .label,#mermaid-svg-H2K0UVzEIEFAr8PN .image-shape .label,#mermaid-svg-H2K0UVzEIEFAr8PN .icon-shape .label{text-align:center;}#mermaid-svg-H2K0UVzEIEFAr8PN .node.clickable{cursor:pointer;}#mermaid-svg-H2K0UVzEIEFAr8PN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-H2K0UVzEIEFAr8PN .arrowheadPath{fill:#333333;}#mermaid-svg-H2K0UVzEIEFAr8PN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-H2K0UVzEIEFAr8PN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-H2K0UVzEIEFAr8PN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-H2K0UVzEIEFAr8PN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-H2K0UVzEIEFAr8PN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-H2K0UVzEIEFAr8PN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-H2K0UVzEIEFAr8PN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-H2K0UVzEIEFAr8PN .cluster text{fill:#333;}#mermaid-svg-H2K0UVzEIEFAr8PN .cluster span{color:#333;}#mermaid-svg-H2K0UVzEIEFAr8PN 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-H2K0UVzEIEFAr8PN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-H2K0UVzEIEFAr8PN rect.text{fill:none;stroke-width:0;}#mermaid-svg-H2K0UVzEIEFAr8PN .icon-shape,#mermaid-svg-H2K0UVzEIEFAr8PN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-H2K0UVzEIEFAr8PN .icon-shape p,#mermaid-svg-H2K0UVzEIEFAr8PN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-H2K0UVzEIEFAr8PN .icon-shape .label rect,#mermaid-svg-H2K0UVzEIEFAr8PN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-H2K0UVzEIEFAr8PN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-H2K0UVzEIEFAr8PN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-H2K0UVzEIEFAr8PN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-H2K0UVzEIEFAr8PN .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-H2K0UVzEIEFAr8PN .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-H2K0UVzEIEFAr8PN .default tspan{fill:#000000!important;} OSRdyGrp = 00000111
查找最低的置1位:y = 0
读取 OSRdyTbl0 = 00001000
查找最低的置1位:x = 3
最高就绪优先级 = 8 × 0 + 3 = 3
两级就绪位图查找最高优先级任务
注意:这里寻找的是最低位的1,因为最低位对应数值更小、优先级更高的任务。
5. OSUnMapTbl[] 为什么重要
教材使用 OSUnMapTbl[256](位位置查找表):输入一个8位整数,直接得到其中最低置1位的位置。
c
/* 核心关系:查找最低置1位的位置 */
y = OSUnMapTbl[OSRdyGrp];
x = OSUnMapTbl[OSRdyTbl[y]];
prio = (y << 3) + x;
完整流程只需要对固定大小的表进行两次查找,而不需要逐个检查所有任务。因此,在经典64优先级的位图结构下,寻找最高优先级就绪任务的工作量是固定的。
这里的 O(1)O(1)O(1) 称为常数时间复杂度(Constant Time Complexity),表示操作步骤不会随着已经创建的任务数量增加而线性增长;它不表示任务切换本身没有时间开销。
四、任务调度器:谁有资格占用 CPU
1. OS_Sched() 做了什么
教材第3.05节的程序清单将内核任务调度函数写作 OS_Sched()。这是内核内部使用的调度函数,应用程序通常通过任务等待、事件发布、任务恢复等系统服务间接触发调度,而不是自行反复调用它。
调度(Scheduling)是确定谁应该运行的过程。经典 μC/OS-II 的任务级调度主要由 OS_Sched() 完成,其执行条件可以概括为:
- 当前不在中断嵌套之中;
- 调度器没有被锁住;
- 最高优先级就绪任务与当前运行任务不同。
教材使用以下几个变量:
| 变量 | 含义 |
|---|---|
OSIntNesting |
中断嵌套层数,即当前进入了多少层尚未退出的中断 |
OSLockNesting |
调度器上锁的嵌套层数 |
OSPrioCur |
当前运行任务的优先级 |
OSPrioHighRdy |
就绪任务中最高的优先级 |
OSTCBCur |
指向当前任务 TCB 的指针 |
OSTCBHighRdy |
指向将要运行的任务 TCB 的指针 |
嵌套(Nesting) 指一种操作尚未结束时,又进入同类操作。例如,一个中断执行过程中又被允许抢占它的另一个中断打断,就形成了中断嵌套。
#mermaid-svg-pnraeNraTlfQQfbq{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-pnraeNraTlfQQfbq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pnraeNraTlfQQfbq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pnraeNraTlfQQfbq .error-icon{fill:#552222;}#mermaid-svg-pnraeNraTlfQQfbq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pnraeNraTlfQQfbq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pnraeNraTlfQQfbq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pnraeNraTlfQQfbq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pnraeNraTlfQQfbq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pnraeNraTlfQQfbq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pnraeNraTlfQQfbq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pnraeNraTlfQQfbq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pnraeNraTlfQQfbq .marker.cross{stroke:#333333;}#mermaid-svg-pnraeNraTlfQQfbq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pnraeNraTlfQQfbq p{margin:0;}#mermaid-svg-pnraeNraTlfQQfbq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pnraeNraTlfQQfbq .cluster-label text{fill:#333;}#mermaid-svg-pnraeNraTlfQQfbq .cluster-label span{color:#333;}#mermaid-svg-pnraeNraTlfQQfbq .cluster-label span p{background-color:transparent;}#mermaid-svg-pnraeNraTlfQQfbq .label text,#mermaid-svg-pnraeNraTlfQQfbq span{fill:#333;color:#333;}#mermaid-svg-pnraeNraTlfQQfbq .node rect,#mermaid-svg-pnraeNraTlfQQfbq .node circle,#mermaid-svg-pnraeNraTlfQQfbq .node ellipse,#mermaid-svg-pnraeNraTlfQQfbq .node polygon,#mermaid-svg-pnraeNraTlfQQfbq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pnraeNraTlfQQfbq .rough-node .label text,#mermaid-svg-pnraeNraTlfQQfbq .node .label text,#mermaid-svg-pnraeNraTlfQQfbq .image-shape .label,#mermaid-svg-pnraeNraTlfQQfbq .icon-shape .label{text-anchor:middle;}#mermaid-svg-pnraeNraTlfQQfbq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pnraeNraTlfQQfbq .rough-node .label,#mermaid-svg-pnraeNraTlfQQfbq .node .label,#mermaid-svg-pnraeNraTlfQQfbq .image-shape .label,#mermaid-svg-pnraeNraTlfQQfbq .icon-shape .label{text-align:center;}#mermaid-svg-pnraeNraTlfQQfbq .node.clickable{cursor:pointer;}#mermaid-svg-pnraeNraTlfQQfbq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pnraeNraTlfQQfbq .arrowheadPath{fill:#333333;}#mermaid-svg-pnraeNraTlfQQfbq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pnraeNraTlfQQfbq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pnraeNraTlfQQfbq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pnraeNraTlfQQfbq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pnraeNraTlfQQfbq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pnraeNraTlfQQfbq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pnraeNraTlfQQfbq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pnraeNraTlfQQfbq .cluster text{fill:#333;}#mermaid-svg-pnraeNraTlfQQfbq .cluster span{color:#333;}#mermaid-svg-pnraeNraTlfQQfbq 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-pnraeNraTlfQQfbq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pnraeNraTlfQQfbq rect.text{fill:none;stroke-width:0;}#mermaid-svg-pnraeNraTlfQQfbq .icon-shape,#mermaid-svg-pnraeNraTlfQQfbq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pnraeNraTlfQQfbq .icon-shape p,#mermaid-svg-pnraeNraTlfQQfbq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pnraeNraTlfQQfbq .icon-shape .label rect,#mermaid-svg-pnraeNraTlfQQfbq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pnraeNraTlfQQfbq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pnraeNraTlfQQfbq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pnraeNraTlfQQfbq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-pnraeNraTlfQQfbq .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-pnraeNraTlfQQfbq .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-pnraeNraTlfQQfbq .default tspan{fill:#000000!important;} 是
否
是
否
调用 OS_Sched()
中断嵌套或调度器已上锁?
暂不进行任务级切换
通过就绪表计算 OSPrioHighRdy
与 OSPrioCur 相同?
继续运行当前任务
找到新任务 TCB
触发 OS_TASK_SW()
任务级调度器的主要判断流程
2. 调度器与任务切换函数的区别
OS_Sched() 的主要职责是选出应该运行的任务 ;OS_TASK_SW() 是用于触发任务级上下文切换的接口,具体如何保存和恢复寄存器由对应 CPU 的移植代码实现。
因此,不能把"调度"与"切换"混为一谈:
- 调度 回答"该运行谁";
- 上下文切换(Context Switch) 回答"怎样让 CPU 转去运行它"。
五、任务切换:保存现场与恢复现场
1. 什么是上下文
上下文(Context) 就是任务暂停时所需保存的 CPU 运行现场,通常包括程序计数器 PC(记录接下来执行的指令位置)、栈指针 SP(指向当前栈位置)、通用寄存器与处理器状态寄存器等。
假设低优先级 TaskB 正在运行,而更高优先级 TaskA 进入就绪态。CPU 要从 TaskB 切换到 TaskA,主要经过以下过程:
#mermaid-svg-2B95zfWxK8qdGZ8l{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-2B95zfWxK8qdGZ8l .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2B95zfWxK8qdGZ8l .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2B95zfWxK8qdGZ8l .error-icon{fill:#552222;}#mermaid-svg-2B95zfWxK8qdGZ8l .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2B95zfWxK8qdGZ8l .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2B95zfWxK8qdGZ8l .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2B95zfWxK8qdGZ8l .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2B95zfWxK8qdGZ8l .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2B95zfWxK8qdGZ8l .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2B95zfWxK8qdGZ8l .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2B95zfWxK8qdGZ8l .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2B95zfWxK8qdGZ8l .marker.cross{stroke:#333333;}#mermaid-svg-2B95zfWxK8qdGZ8l svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2B95zfWxK8qdGZ8l p{margin:0;}#mermaid-svg-2B95zfWxK8qdGZ8l .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2B95zfWxK8qdGZ8l .cluster-label text{fill:#333;}#mermaid-svg-2B95zfWxK8qdGZ8l .cluster-label span{color:#333;}#mermaid-svg-2B95zfWxK8qdGZ8l .cluster-label span p{background-color:transparent;}#mermaid-svg-2B95zfWxK8qdGZ8l .label text,#mermaid-svg-2B95zfWxK8qdGZ8l span{fill:#333;color:#333;}#mermaid-svg-2B95zfWxK8qdGZ8l .node rect,#mermaid-svg-2B95zfWxK8qdGZ8l .node circle,#mermaid-svg-2B95zfWxK8qdGZ8l .node ellipse,#mermaid-svg-2B95zfWxK8qdGZ8l .node polygon,#mermaid-svg-2B95zfWxK8qdGZ8l .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2B95zfWxK8qdGZ8l .rough-node .label text,#mermaid-svg-2B95zfWxK8qdGZ8l .node .label text,#mermaid-svg-2B95zfWxK8qdGZ8l .image-shape .label,#mermaid-svg-2B95zfWxK8qdGZ8l .icon-shape .label{text-anchor:middle;}#mermaid-svg-2B95zfWxK8qdGZ8l .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2B95zfWxK8qdGZ8l .rough-node .label,#mermaid-svg-2B95zfWxK8qdGZ8l .node .label,#mermaid-svg-2B95zfWxK8qdGZ8l .image-shape .label,#mermaid-svg-2B95zfWxK8qdGZ8l .icon-shape .label{text-align:center;}#mermaid-svg-2B95zfWxK8qdGZ8l .node.clickable{cursor:pointer;}#mermaid-svg-2B95zfWxK8qdGZ8l .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2B95zfWxK8qdGZ8l .arrowheadPath{fill:#333333;}#mermaid-svg-2B95zfWxK8qdGZ8l .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2B95zfWxK8qdGZ8l .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2B95zfWxK8qdGZ8l .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2B95zfWxK8qdGZ8l .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2B95zfWxK8qdGZ8l .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2B95zfWxK8qdGZ8l .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2B95zfWxK8qdGZ8l .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2B95zfWxK8qdGZ8l .cluster text{fill:#333;}#mermaid-svg-2B95zfWxK8qdGZ8l .cluster span{color:#333;}#mermaid-svg-2B95zfWxK8qdGZ8l 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-2B95zfWxK8qdGZ8l .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2B95zfWxK8qdGZ8l rect.text{fill:none;stroke-width:0;}#mermaid-svg-2B95zfWxK8qdGZ8l .icon-shape,#mermaid-svg-2B95zfWxK8qdGZ8l .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2B95zfWxK8qdGZ8l .icon-shape p,#mermaid-svg-2B95zfWxK8qdGZ8l .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2B95zfWxK8qdGZ8l .icon-shape .label rect,#mermaid-svg-2B95zfWxK8qdGZ8l .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2B95zfWxK8qdGZ8l .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2B95zfWxK8qdGZ8l .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2B95zfWxK8qdGZ8l :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-2B95zfWxK8qdGZ8l .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-2B95zfWxK8qdGZ8l .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-2B95zfWxK8qdGZ8l .default tspan{fill:#000000!important;} TaskB 正在运行
保存 TaskB 的寄存器等现场到其栈
把当前栈指针写入 TaskB 的 TCB
选中 TaskA 的 TCB
从 TaskA 的 TCB 取出其栈指针
从 TaskA 的栈恢复 CPU 现场
CPU 继续执行 TaskA
任务上下文切换的基本过程
真正的底层执行顺序、哪些寄存器由硬件自动压栈(Push,即把数据保存到栈中)、哪些由软件保存,与处理器架构及移植方式有关。流程图表示通用逻辑,不是某一颗芯片的汇编指令顺序。
2. 为什么换回 TaskB 后能接着执行
因为 TaskB 的运行现场保存在其独立任务栈中,OSTCBStkPtr 又保留了恢复这个现场所需的栈位置。以后 TaskB 再次获得 CPU 使用权时,就能从原来暂停的位置继续运行。
这也是为什么不能让两个普通任务无保护地共用同一个任务栈。
3. 任务切换并非没有代价
一次任务切换可能涉及寄存器保存、栈指针更新、寄存器恢复以及处理器流水线等额外开销。流水线(Pipeline) 是 CPU 为提高吞吐量而对多条指令进行重叠处理的一种机制。
因此,高频执行任务并不天然意味着"放到 RTOS 任务中就更实时"。是否采用任务级唤醒,必须根据响应时间、最坏执行时间(WCET,Worst-Case Execution Time)与抖动(Jitter,即执行时刻相对理想时刻的偏差)来判断。
六、临界区与调度器上锁
1. 临界区保护的是什么
临界区(Critical Section) 是访问共享数据时不允许发生危险交错执行的一段代码。内核维护就绪表、任务状态和链表时,经常需要保护关键操作不被不恰当的中断访问打断。
教材3.00节介绍了 OS_ENTER_CRITICAL() 与 OS_EXIT_CRITICAL()。它们通常由处理器相关的宏或函数实现,常见手段是暂时屏蔽可屏蔽中断。
c
/* 示例采用支持 OS_CRITICAL_METHOD == 3 的移植配置 */
OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL();
/* 此处只放需要保护的短小共享数据操作 */
OS_EXIT_CRITICAL();
OS_CPU_SR 是用于保存处理器状态的类型。教材说明可以通过 OS_CRITICAL_METHOD 选择不同的临界区实现方式:
| 配置方式 | 基本实现思路 | 主要注意事项 |
|---|---|---|
OS_CRITICAL_METHOD == 1 |
进入时关中断,退出时直接开中断 | 无法保留进入前的中断开关状态 |
OS_CRITICAL_METHOD == 2 |
将原处理器状态压入栈中,退出时恢复 | 依赖处理器指令与编译器对栈操作的支持 |
OS_CRITICAL_METHOD == 3 |
把原处理器状态保存在 C 局部变量中,退出时恢复 | 使用临界区宏的函数通常需声明 OS_CPU_SR cpu_sr |
处理器状态字(Processor Status Word,PSW) 是记录处理器工作状态的一组标志位,其中可能包含中断使能状态。采用保存并恢复原状态的方式,可以避免在退出临界区时错误地打开原本就关闭的中断。
不同移植版本的临界区宏实现并不完全一样。关中断不是关闭所有类型的异常,也不是一种可以随意长时间使用的互斥机制。
2. 临界区与锁调度器不是一回事
OSSchedLock() 与 OSSchedUnlock() 用于控制任务级调度,并通过 OSLockNesting 记录嵌套次数。
| 机制 | 主要阻止的操作 | 是否允许中断响应 | 典型用途 |
|---|---|---|---|
| 临界区关中断 | 被屏蔽中断对当前代码的打断 | 暂时不允许相应中断 | 极短的内核共享状态修改 |
| 锁调度器 | 任务级上下文切换 | 通常允许硬件中断 | 必须连续执行的短小任务级操作 |
锁调度器期间,中断仍可能发生,只是任务调度可能推迟。特别注意:锁调度器不能替代任务与 ISR 共享数据时所需的保护。
任务持有调度锁太久,会推迟已就绪的高优先级任务运行,因此也会损害实时性。更重要的是:锁调度器期间不能再调用可能导致当前任务等待或挂起的 API ,例如 OSTimeDly()、OSSemPend() 等,否则当前任务需要释放 CPU,却又禁止内核切换到其他任务,容易使系统无法正常运行。
七、空闲任务与统计任务
1. 没有用户任务就绪时怎么办
μC/OS-II 在初始化过程中建立空闲任务(Idle Task),它通常使用最低的任务优先级。当其他任务都因为等待事件或延时而不能运行时,CPU 就运行空闲任务。
空闲任务不断运行一个循环,可以调用 OSTaskIdleHook()(空闲任务钩子函数)。钩子函数(Hook Function) 是内核预留的扩展调用点,应用程序可在满足移植和实时性要求的前提下加入自己的处理逻辑。
空闲任务不是"CPU 没有任务可做所以停机",而是确保调度器始终有一个可运行的任务。在本书的经典配置中,最低优先级通常为63,但应以 OS_LOWEST_PRIO 的实际设置为准。
2. 统计任务如何估计 CPU 占用率
如果启用了相关配置,μC/OS-II 可以建立统计任务(Statistics Task),借助空闲任务计数估计 CPU 使用情况。
基本思想是:先在已知条件下估计空闲循环的参考计数,再比较运行用户任务后空闲循环还剩多少执行机会。
CPU使用率≈(1−当前空闲计数参考空闲计数)×100% \text{CPU使用率}\approx\left(1-\frac{\text{当前空闲计数}}{\text{参考空闲计数}}\right)\times100\% CPU使用率≈(1−参考空闲计数当前空闲计数)×100%
这一指标是基于空闲循环的估计值 ,不是硬件逐指令测量结果,也不直接等同于某个控制算法的执行时间。教材还介绍了与 OSStatInit()、OSTaskStat() 有关的初始化与统计过程。
八、中断处理:为什么中断结束后可能换一个任务
1. ISR 不属于普通任务
中断服务程序(Interrupt Service Routine,ISR) 是 CPU 响应硬件或软件中断后执行的处理程序。它不是通过普通任务调度得到 CPU 的,也不具有普通任务那样的等待与恢复方式。
μC/OS-II 用 OSIntEnter() 记录中断进入,用 OSIntExit() 处理退出时的嵌套计数和必要调度;具体调用位置由移植层和处理器的中断入口、出口约定决定。
2. 中断退出前为什么要重新判断任务
假设低优先级显示任务正在运行,ADC 转换完成产生中断。ISR 在处理数据后发布信号量,唤醒了高优先级数据处理任务。
#mermaid-svg-ep1yc3hh3MCtxGpj{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-ep1yc3hh3MCtxGpj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ep1yc3hh3MCtxGpj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ep1yc3hh3MCtxGpj .error-icon{fill:#552222;}#mermaid-svg-ep1yc3hh3MCtxGpj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ep1yc3hh3MCtxGpj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ep1yc3hh3MCtxGpj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ep1yc3hh3MCtxGpj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ep1yc3hh3MCtxGpj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ep1yc3hh3MCtxGpj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ep1yc3hh3MCtxGpj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ep1yc3hh3MCtxGpj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ep1yc3hh3MCtxGpj .marker.cross{stroke:#333333;}#mermaid-svg-ep1yc3hh3MCtxGpj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ep1yc3hh3MCtxGpj p{margin:0;}#mermaid-svg-ep1yc3hh3MCtxGpj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ep1yc3hh3MCtxGpj .cluster-label text{fill:#333;}#mermaid-svg-ep1yc3hh3MCtxGpj .cluster-label span{color:#333;}#mermaid-svg-ep1yc3hh3MCtxGpj .cluster-label span p{background-color:transparent;}#mermaid-svg-ep1yc3hh3MCtxGpj .label text,#mermaid-svg-ep1yc3hh3MCtxGpj span{fill:#333;color:#333;}#mermaid-svg-ep1yc3hh3MCtxGpj .node rect,#mermaid-svg-ep1yc3hh3MCtxGpj .node circle,#mermaid-svg-ep1yc3hh3MCtxGpj .node ellipse,#mermaid-svg-ep1yc3hh3MCtxGpj .node polygon,#mermaid-svg-ep1yc3hh3MCtxGpj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ep1yc3hh3MCtxGpj .rough-node .label text,#mermaid-svg-ep1yc3hh3MCtxGpj .node .label text,#mermaid-svg-ep1yc3hh3MCtxGpj .image-shape .label,#mermaid-svg-ep1yc3hh3MCtxGpj .icon-shape .label{text-anchor:middle;}#mermaid-svg-ep1yc3hh3MCtxGpj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ep1yc3hh3MCtxGpj .rough-node .label,#mermaid-svg-ep1yc3hh3MCtxGpj .node .label,#mermaid-svg-ep1yc3hh3MCtxGpj .image-shape .label,#mermaid-svg-ep1yc3hh3MCtxGpj .icon-shape .label{text-align:center;}#mermaid-svg-ep1yc3hh3MCtxGpj .node.clickable{cursor:pointer;}#mermaid-svg-ep1yc3hh3MCtxGpj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ep1yc3hh3MCtxGpj .arrowheadPath{fill:#333333;}#mermaid-svg-ep1yc3hh3MCtxGpj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ep1yc3hh3MCtxGpj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ep1yc3hh3MCtxGpj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ep1yc3hh3MCtxGpj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ep1yc3hh3MCtxGpj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ep1yc3hh3MCtxGpj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ep1yc3hh3MCtxGpj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ep1yc3hh3MCtxGpj .cluster text{fill:#333;}#mermaid-svg-ep1yc3hh3MCtxGpj .cluster span{color:#333;}#mermaid-svg-ep1yc3hh3MCtxGpj 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-ep1yc3hh3MCtxGpj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ep1yc3hh3MCtxGpj rect.text{fill:none;stroke-width:0;}#mermaid-svg-ep1yc3hh3MCtxGpj .icon-shape,#mermaid-svg-ep1yc3hh3MCtxGpj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ep1yc3hh3MCtxGpj .icon-shape p,#mermaid-svg-ep1yc3hh3MCtxGpj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ep1yc3hh3MCtxGpj .icon-shape .label rect,#mermaid-svg-ep1yc3hh3MCtxGpj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ep1yc3hh3MCtxGpj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ep1yc3hh3MCtxGpj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ep1yc3hh3MCtxGpj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-ep1yc3hh3MCtxGpj .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-ep1yc3hh3MCtxGpj .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-ep1yc3hh3MCtxGpj .default tspan{fill:#000000!important;} 是
否
低优先级显示任务运行
ADC 触发中断
ISR 读取结果并发布信号量
高优先级任务从等待态转为就绪态
OSIntExit() 检查能否进行中断级调度
有更高优先级就绪任务?
切换到高优先级任务
恢复先前被中断的执行现场
中断唤醒任务后的调度关系
如果中断嵌套尚未完全退出,内核需要等待合适的退出层次才能完成任务切换。教材还区分了 OS_TASK_SW()(任务级切换)与 OSIntCtxSw()(中断级上下文切换入口)。
结论:中断结束后,不一定继续运行中断前的那个任务。 当更高优先级任务进入就绪态且满足调度条件时,CPU 可以直接转而运行该任务。
3. 为什么 ISR 不应随意调用等待函数
OSSemPend() 等 Pend 类函数可能使调用者进入等待态(Pend,表示等待某事件)。ISR 没有普通任务级阻塞和恢复语义,因此不应在 ISR 中调用可能阻塞当前执行过程的函数。
典型设计是让 ISR 快速采样、清除中断标志并发送事件,再由任务完成较复杂的后续工作;但最紧急的硬件保护动作仍应在具有足够低延迟的硬件或中断路径中完成。
九、时钟节拍与任务延时
1. 时钟节拍不是 CPU 主频
时钟节拍(Clock Tick) 是操作系统使用的周期性时间基准,它通常由定时器中断提供。它与 CPU 的系统时钟频率、PWM 载波频率并不是同一个概念。
若每秒发生 ftickf_{tick}ftick 次节拍,则:
Ttick=1ftick T_{tick}=\frac{1}{f_{tick}} Ttick=ftick1
例如每秒100次节拍,对应每个节拍间隔10 ms。
2. OSTimeTick() 做什么
教材3.11节介绍的 OSTimeTick() 主要负责更新与任务延时有关的状态。简化来看:
- 一个新的时钟节拍到来;
- 内核检查相关任务的剩余延时或等待超时;
- 需要倒计时的任务,其等待节拍数递减;
- 到期的任务根据等待原因更新状态,并在条件满足时重新进入就绪态;
- 由合适的调度时机决定是否切换任务。
例如某任务调用:
c
OSTimeDly(5); /* 以操作系统节拍为单位申请延时 */
它表示等待5个节拍计数到期,不意味着从调用瞬间开始精确经过 5Ttick5T_{tick}5Ttick 后立刻重新运行。调用时刻相对于节拍中断的相位、其他高优先级任务占用 CPU 的时间,都会影响重新获得 CPU 的实际时刻。
因此,10 kHz PWM 或精确的 ADC 触发时间不应依赖普通 RTOS Tick 来实现。
十、内核初始化与启动
1. OSInit() 与 OSStart() 有什么区别
教材3.12~3.14节分别涉及内核初始化、启动和版本号获取。
OSInit():初始化内核管理结构,如就绪表、TCB 管理链表、各类空闲控制块链表,并建立内核所需的基础任务。OSTaskCreate():创建用户任务,配置入口函数、任务栈和优先级等。OSStart():选择已经就绪的最高优先级任务,进行第一次任务运行现场恢复,使多任务调度真正开始。OSVersion():获取 μC/OS-II 的内核版本号。
#mermaid-svg-eR4zU6tZTSGOqaR3{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-eR4zU6tZTSGOqaR3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eR4zU6tZTSGOqaR3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eR4zU6tZTSGOqaR3 .error-icon{fill:#552222;}#mermaid-svg-eR4zU6tZTSGOqaR3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eR4zU6tZTSGOqaR3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eR4zU6tZTSGOqaR3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eR4zU6tZTSGOqaR3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eR4zU6tZTSGOqaR3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eR4zU6tZTSGOqaR3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eR4zU6tZTSGOqaR3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eR4zU6tZTSGOqaR3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eR4zU6tZTSGOqaR3 .marker.cross{stroke:#333333;}#mermaid-svg-eR4zU6tZTSGOqaR3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eR4zU6tZTSGOqaR3 p{margin:0;}#mermaid-svg-eR4zU6tZTSGOqaR3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eR4zU6tZTSGOqaR3 .cluster-label text{fill:#333;}#mermaid-svg-eR4zU6tZTSGOqaR3 .cluster-label span{color:#333;}#mermaid-svg-eR4zU6tZTSGOqaR3 .cluster-label span p{background-color:transparent;}#mermaid-svg-eR4zU6tZTSGOqaR3 .label text,#mermaid-svg-eR4zU6tZTSGOqaR3 span{fill:#333;color:#333;}#mermaid-svg-eR4zU6tZTSGOqaR3 .node rect,#mermaid-svg-eR4zU6tZTSGOqaR3 .node circle,#mermaid-svg-eR4zU6tZTSGOqaR3 .node ellipse,#mermaid-svg-eR4zU6tZTSGOqaR3 .node polygon,#mermaid-svg-eR4zU6tZTSGOqaR3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eR4zU6tZTSGOqaR3 .rough-node .label text,#mermaid-svg-eR4zU6tZTSGOqaR3 .node .label text,#mermaid-svg-eR4zU6tZTSGOqaR3 .image-shape .label,#mermaid-svg-eR4zU6tZTSGOqaR3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-eR4zU6tZTSGOqaR3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eR4zU6tZTSGOqaR3 .rough-node .label,#mermaid-svg-eR4zU6tZTSGOqaR3 .node .label,#mermaid-svg-eR4zU6tZTSGOqaR3 .image-shape .label,#mermaid-svg-eR4zU6tZTSGOqaR3 .icon-shape .label{text-align:center;}#mermaid-svg-eR4zU6tZTSGOqaR3 .node.clickable{cursor:pointer;}#mermaid-svg-eR4zU6tZTSGOqaR3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eR4zU6tZTSGOqaR3 .arrowheadPath{fill:#333333;}#mermaid-svg-eR4zU6tZTSGOqaR3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eR4zU6tZTSGOqaR3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eR4zU6tZTSGOqaR3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eR4zU6tZTSGOqaR3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eR4zU6tZTSGOqaR3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eR4zU6tZTSGOqaR3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eR4zU6tZTSGOqaR3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eR4zU6tZTSGOqaR3 .cluster text{fill:#333;}#mermaid-svg-eR4zU6tZTSGOqaR3 .cluster span{color:#333;}#mermaid-svg-eR4zU6tZTSGOqaR3 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-eR4zU6tZTSGOqaR3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eR4zU6tZTSGOqaR3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-eR4zU6tZTSGOqaR3 .icon-shape,#mermaid-svg-eR4zU6tZTSGOqaR3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eR4zU6tZTSGOqaR3 .icon-shape p,#mermaid-svg-eR4zU6tZTSGOqaR3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eR4zU6tZTSGOqaR3 .icon-shape .label rect,#mermaid-svg-eR4zU6tZTSGOqaR3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eR4zU6tZTSGOqaR3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eR4zU6tZTSGOqaR3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eR4zU6tZTSGOqaR3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-eR4zU6tZTSGOqaR3 .default>*{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-eR4zU6tZTSGOqaR3 .default span{fill:#ffffff!important;stroke:#000000!important;color:#000000!important;stroke-width:1px!important;}#mermaid-svg-eR4zU6tZTSGOqaR3 .default tspan{fill:#000000!important;} 完成处理器与必要外设初始化
OSInit() 初始化内核
创建至少一个应用任务
OSStart() 启动调度
最高优先级就绪任务开始运行
μC/OS-II 的典型初始化和启动顺序
下面是结构示意代码。任务栈顶地址如何传入,应以具体移植端口的栈增长方向为准;它不是无需配置即可适用于所有 MCU 的完整工程。
c
#define APP_TASK_PRIO 10u
#define APP_TASK_STK_SIZE 256u
OS_STK AppTaskStk[APP_TASK_STK_SIZE];
void AppTask(void *pdata)
{
(void)pdata;
while (1) {
/* 用户任务处理 */
OSTimeDly(1);
}
}
int main(void)
{
Hardware_Init();
OSInit();
OSTaskCreate(AppTask,
(void *)0,
&AppTaskStk[APP_TASK_STK_SIZE - 1u],
APP_TASK_PRIO);
OSStart();
for (;;) {
/* 正常启动后一般不会执行到这里 */
}
}
其中 Hardware_Init() 代表具体工程的硬件初始化函数,需要由实际应用实现;上述代码用于说明教材中的启动逻辑,而非某个指定板卡的已编译工程。
十一、用 C 程序验证就绪表的工作过程
1. 完整的独立演示程序
下面的程序只模拟经典64优先级结构的就绪位设置、清除与最高优先级查询 ,不包含真实的 RTOS 任务创建和切换。为了便于独立运行,程序在启动时生成一份与教材 OSUnMapTbl 含义相同的查找表;实际内核中,该表通常是预先定义好的常量数组。
c
#include <stdint.h>
#include <stdio.h>
static uint8_t OSRdyGrp = 0u;
static uint8_t OSRdyTbl[8] = {0u};
static uint8_t OSUnMapTbl[256] = {0u};
/* 生成查找表:记录每个非零字节最低置1位的位置 */
static void InitUnMapTable(void)
{
unsigned int value;
unsigned int bit;
for (value = 1u; value < 256u; ++value) {
for (bit = 0u; bit < 8u; ++bit) {
if ((value & (1u << bit)) != 0u) {
OSUnMapTbl[value] = (uint8_t)bit;
break;
}
}
}
}
/* 将指定优先级加入就绪表 */
static void SetReady(uint8_t prio)
{
uint8_t y = (uint8_t)(prio >> 3);
uint8_t x = (uint8_t)(prio & 0x07u);
if (prio >= 64u) {
return;
}
OSRdyTbl[y] |= (uint8_t)(1u << x);
OSRdyGrp |= (uint8_t)(1u << y);
}
/* 将指定优先级移出就绪表 */
static void ClearReady(uint8_t prio)
{
uint8_t y = (uint8_t)(prio >> 3);
uint8_t x = (uint8_t)(prio & 0x07u);
if (prio >= 64u) {
return;
}
OSRdyTbl[y] &= (uint8_t)~(1u << x);
/* 该组中已无就绪任务,才清除组标志 */
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (uint8_t)~(1u << y);
}
}
/* 返回最高优先级就绪任务;-1 表示当前位图为空 */
static int GetHighestReady(void)
{
uint8_t y;
uint8_t x;
if (OSRdyGrp == 0u) {
return -1;
}
y = OSUnMapTbl[OSRdyGrp];
x = OSUnMapTbl[OSRdyTbl[y]];
return (int)((y << 3) + x);
}
int main(void)
{
InitUnMapTable();
SetReady(19u);
SetReady(10u);
SetReady(3u);
printf("最高就绪优先级:%d\n", GetHighestReady());
ClearReady(3u);
printf("移除优先级3后:%d\n", GetHighestReady());
ClearReady(10u);
printf("再移除优先级10后:%d\n", GetHighestReady());
return 0;
}
2. 运行结果与分析
text
最高就绪优先级:3
移除优先级3后:10
再移除优先级10后:19
这说明就绪表的两级位图结构能够按优先级数值从小到大选出就绪任务。示例中的空位图分支仅为独立演示方便;真实 μC/OS-II 通过空闲任务保证正常调度情况下仍有就绪任务可选。
十二、嵌入式控制系统中的应用与易错点
1. 以电机驱动程序为例
假设一个控制器需要完成 PWM 更新、ADC 采样、故障处理、CAN 通信与数据显示。可以把 CPU 的工作粗略划分为:
| 功能 | 主要触发机制 | 设计重点 |
|---|---|---|
| 过流保护与 PWM 禁止 | 硬件保护或高优先级中断 | 关注最坏关断延迟 |
| ADC 采样 | PWM 定时触发 ADC,转换后产生中断 | 保证采样时刻与数据一致性 |
| 高频电流环 | 定时 ISR 或经验证可满足时限的高优先级任务 | 控制周期、抖动与最坏执行时间 |
| CAN 消息处理 | 接收中断配合通信任务 | 收包及时、缓冲区正确管理 |
| 参数记录与显示 | 低优先级任务 | 避免阻塞关键实时路径 |
这个示例说明:RTOS 负责协调多个执行单元,但不应机械地把所有定时控制工作都交给操作系统 Tick。 高频控制环究竟放在 ISR 还是任务中,必须结合实际 CPU、移植层、中断延迟和测量结果决定。
2. 本章最容易混淆的概念
| 常见误解 | 正确理解 |
|---|---|
| TCB 就是任务栈 | TCB 记录管理信息,并保存与任务栈有关的指针 |
| 任务进入就绪态就开始运行 | 就绪只是具备运行条件,仍需参与优先级调度 |
| 优先级数字越大越高 | 经典 μC/OS-II 中数字越小,优先级越高 |
OS_Sched() 负责全部寄存器切换 |
调度器选任务,底层移植代码完成现场切换 |
| 锁调度器等于关闭中断 | 锁调度器通常不阻止硬件中断响应 |
OSTimeDly(5) 一定精确等待5个完整节拍 |
延时受调用相位及重新调度时机影响 |
| ISR 返回必定接着运行原任务 | 中断唤醒更高优先级任务时可能发生中断级切换 |
OSStart() 可以在没有用户任务时随意调用 |
启动前应至少创建一个应用任务,遵循内核初始化流程 |
3. 四道自测题
- 任务优先级为26时,对应
OSRdyTbl[]的第几个元素、其中第几位? OSRdyGrp = 0x04,OSRdyTbl[2] = 0x20时,最高就绪优先级是多少?OS_Sched()已经找到更高优先级的就绪任务,为什么仍可能暂时不发生任务切换?- 在 ADC 中断中发布信号量后,为什么
OSIntExit()可能导致 CPU 运行另一个任务?
参考答案:①第3个元素 OSRdyTbl[3] 的 bit2;②组号2、位号5,优先级21;③可能仍在中断中或调度器被锁住;④被唤醒的任务优先级更高,且满足中断退出调度条件。
十三、总结与参考资料
μC/OS-II 的内核结构可以浓缩为一条工作链:
任务创建后建立 TCB 和独立任务栈 → 任务就绪时更新两级位图 → 调度器选择最高优先级就绪任务 → 移植层保存和恢复 CPU 现场 → 中断及系统节拍持续改变任务的就绪条件。
学习本章应特别掌握 OS_TCB、OSRdyGrp、OSRdyTbl[]、OSUnMapTbl[]、OS_Sched()、OS_TASK_SW()、OSIntExit()、OSTimeTick()、OSInit() 和 OSStart() 的分工。理解这些机制后,第4章"任务管理"中的任务创建、删除、挂起和恢复,就不再是孤立的函数调用,而是对内核任务状态与数据结构的具体操作。
参考资料
- Jean J. Labrosse 著,邵贝贝等译:《嵌入式实时操作系统 μC/OS-II(第2版)》,北京航空航天大学出版社,第3章"内核结构"。
- μC/OS-II 经典版本内核文件:
OS_CORE.C、OS_TASK.C、OS_TIME.C、OS_CPU.H、OS_CPU_C.C以及相关 CPU 移植文件。