本文以《嵌入式实时操作系统 μC/OS-II》第 2 章“实时系统概念”为主线,从前后台系统开始,逐步理解任务、调度、抢占、临界区、信号量、优先级反转、任务通信、中断和时钟节拍。文章适合第一次接触 RTOS 的读者,也可以作为后续学习 μC/OS-II 源码与 API 前的复习提纲。
目录
- 一、实时系统到底“实时”在哪里
- 二、从前后台系统走向多任务系统
- 三、任务、状态、栈与上下文切换
- 四、内核如何完成任务调度
- 五、临界区、共享资源与互斥
- 六、优先级反转与死锁
- 七、任务同步与任务间通信
- 八、中断、时钟节拍与延时误差
- 九、用电机控制系统串起全部概念
- 十、易错点与复习提纲
- 十一、总结
一、实时系统到底“实时”在哪里
1. 实时不等于单纯运行得快
“实时系统”最容易被误解成“速度特别快的系统”。实际上,实时系统真正强调的是:
系统能否在规定的时间限制内,对事件作出确定、可预测的响应。
假设电机驱动器检测到过流后,要求在100 μs100\ \mu s100μs内关断 PWM。某系统每次都能在20∼80 μs20\sim 80\ \mu s20∼80μs内完成保护,即使平均速度不是最快,它仍然具有较好的实时性。反过来,另一个系统平均只需10 μs10\ \mu s10μs,却偶尔需要2 ms2\ ms2ms,那么它可能无法满足保护要求。
设事件发生时刻为tet_ete,任务真正开始处理该事件的时刻为tst_sts,则响应时间可以写成:
tr=ts−te t_r=t_s-t_etr=ts−te
实时系统关心的不只是平均响应时间,还关心最坏情况下的响应时间:
tr,max≤D t_{r,\max}\leq Dtr,max≤D
其中,DDD是任务允许的截止时间(Deadline)。
2. 硬实时与软实时
根据错过截止时间造成的后果,可以把实时系统粗略分为两类。
| 类型 | 主要特点 | 典型应用 |
|---|---|---|
| 硬实时系统 | 一旦错过截止时间,可能产生不可接受的后果 | 保护装置、安全控制、飞行控制 |
| 软实时系统 | 偶尔错过截止时间会降低性能,但通常不会立即造成灾难 | 显示刷新、音视频播放、普通通信 |
实际系统往往同时包含两者。例如,电流保护任务接近硬实时,而液晶显示任务通常属于软实时。
二、从前后台系统走向多任务系统
1. 什么是前后台系统
传统单片机程序常写成一个无限循环:
intmain(void){Hardware_Init();while(1){Key_Scan();Data_Process();CAN_Transmit();Display_Update();}}其中:
while(1)中循环执行的程序称为后台(Background);- 由硬件事件触发的中断服务程序 ISR 称为前台(Foreground)。
CPU 平时运行后台循环。ADC、定时器或串口等外设产生中断后,CPU 暂停后台程序,转入 ISR;ISR 执行结束后,再返回原来的程序。
2. 前后台系统为什么会遇到瓶颈
假设主循环如下:
while(1){OLED_Update();// 最坏执行时间 10 msCAN_Process();// 最坏执行时间 2 msKey_Scan();// 最坏执行时间 1 msMotor_Control();// 希望每 100 us 执行一次}Motor_Control()必须等前面的函数执行完才能运行。只要显示刷新时间发生变化,控制函数的响应时间也会变化。前后台系统的主要问题并不是“绝对不能用”,而是:
- 重要功能的执行时刻容易受其他代码影响;
- 主循环变长后,最坏响应时间难以估计;
- 各功能之间耦合较强,维护一个模块可能改变整个循环周期;
- 复杂系统中的同步、通信和资源保护需要程序员自行管理。
对于功能简单、时序要求不高的产品,前后台系统仍然有效;当任务数量增加、响应时间要求变严格时,引入实时内核通常更容易管理。
3. 多任务系统不等于真正并行
在单核 MCU 中,任意一个瞬间只能执行一个任务。所谓多任务,是 CPU 在多个任务之间快速切换:
T1→T2→T3→T1→⋯ T_1\rightarrow T_2\rightarrow T_3\rightarrow T_1\rightarrow \cdotsT1→T2→T3→T1→⋯
多个任务在一段时间内交替推进,称为并发(Concurrency);多个处理器核心在同一时刻分别执行不同任务,才是严格意义上的并行(Parallelism)。
μC/OS-II 主要解决的是单处理器上的多任务并发管理。
三、任务、状态、栈与上下文切换
1. 任务不只是一个函数
任务通常以一个不会返回的函数表示:
voidMotorTask(void*pdata){(void)pdata;while(1){Motor_Control();OSTimeDly(1);}}从操作系统角度看,一个任务至少包含:
任务=代码+优先级+状态+CPU现场+私有栈+控制信息 \text{任务}=\text{代码}+\text{优先级}+\text{状态}+\text{CPU现场}+\text{私有栈}+\text{控制信息}任务=代码+优先级+状态+CPU现场+私有栈+控制信息
μC/OS-II 使用任务控制块 TCB(Task Control Block)保存任务管理信息。任务栈则保存局部变量、函数返回地址、CPU 寄存器和被切换时的运行现场。
2. 任务的基本状态
初学时先掌握三个核心状态:
- 就绪态 Ready:运行条件已经满足,只差 CPU 使用权;
- 运行态 Running:当前正在占用 CPU;
- 等待态 Waiting:正在等待时间、事件、消息或资源,暂时不能参与调度。
此外,教材还给出了睡眠态(任务尚未建立或已被删除)以及被中断态等状态。
任务状态变化通常由以下事件引起:
- 创建任务:睡眠态进入就绪态;
- 调度器选中任务:就绪态进入运行态;
- 调用延时或等待函数:运行态进入等待态;
- 延时到期、收到信号或消息:等待态进入就绪态;
- 更高优先级任务就绪:当前任务被抢占,运行态回到就绪态;
- 中断到来:运行任务暂时进入被中断状态。
需要特别注意:就绪并不等于正在执行。就绪任务可能有多个,但单核 CPU 同一时刻只有一个运行任务。
3. 为什么每个任务都要有独立栈
任务运行时会产生自己的局部变量、函数调用层次和 CPU 现场。如果所有任务共用一套栈,任务切换后原来的调用现场会被破坏。因此,每个任务都要分配独立的栈空间。
创建任务时常见的写法如下:
#defineTASK_STK_SIZE256uOS_STK MotorTaskStk[TASK_STK_SIZE];OSTaskCreate(MotorTask,NULL,&MotorTaskStk[TASK_STK_SIZE-1u],5u);这里传入的并不是普通数据数组,而是MotorTask的专用任务栈。示例按栈向低地址增长的常见移植方式传入栈顶,实际写法应以具体 CPU 端口为准。
4. 上下文切换到底保存了什么
上下文(Context)是任务被暂停时的 CPU 运行现场,通常包含:
- PC:程序计数器,决定任务从哪条指令继续执行;
- SP:栈指针,指向该任务当前栈位置;
- 通用寄存器;
- 状态寄存器或程序状态字;
- 特定处理器要求保存的其他寄存器。
任务切换可以概括为:
任务切换=保存旧任务现场+选择新任务+恢复新任务现场 \text{任务切换}=\text{保存旧任务现场}+\text{选择新任务}+\text{恢复新任务现场}任务切换=保存旧任务现场+选择新任务+恢复新任务现场
任务切换不是零成本操作。CPU 寄存器越多、移植层保存的内容越多,切换开销通常越大。因此,不能只看“每秒能切换多少次”,还要结合 CPU 架构、编译器、任务栈和实际应用评价系统性能。
四、内核如何完成任务调度
1. 内核与调度器分别做什么
内核(Kernel)负责管理任务、分配 CPU 时间、处理任务之间的同步和通信。调度器(Scheduler)是内核中负责选择下一个运行任务的部分。
μC/OS-II 的核心调度规则可以写成:
CPU运行所有就绪任务中优先级最高的任务 \boxed{\text{CPU运行所有就绪任务中优先级最高的任务}}CPU运行所有就绪任务中优先级最高的任务
在 μC/OS-II 中,优先级数值越小,优先级越高。例如:
| 任务 | 优先级数值 | 当前状态 |
|---|---|---|
| 电流控制任务 | 5 | Ready |
| CAN 通信任务 | 10 | Ready |
| 显示任务 | 20 | Running |
由于5<10<205<10<205<10<20,调度器应让电流控制任务运行。当前显示任务将失去 CPU 使用权并回到就绪态。
2. 不可抢占型内核
在不可抢占型内核中,即使高优先级任务已经就绪,也必须等待当前任务主动释放 CPU,才能开始运行。
它的优点是任务切换位置比较明确,共享数据保护相对容易;缺点是高优先级任务的响应时间受当前任务影响,最坏响应时间可能较长。
3. 可抢占型内核
在可抢占型内核中,只要更高优先级任务进入就绪态,当前低优先级任务就会被挂起,高优先级任务立即获得 CPU。
μC/OS-II 属于基于优先级的可抢占式实时内核。它的主要价值是让高优先级任务的响应时间更确定,但同时也要求程序正确保护共享资源。
4. 抢占通常发生在什么时刻
常见触发点包括:
- ISR 发布信号量、消息或事件,使高优先级任务就绪;
- 任务释放互斥资源,使等待该资源的高优先级任务就绪;
- 延时到期,高优先级任务重新进入就绪态;
- 创建或恢复了一个更高优先级任务。
中断结束后不一定返回被中断的任务。如果 ISR 唤醒了更高优先级任务,OSIntExit()可能触发一次调度,CPU 会直接运行该高优先级任务。
5. 可重入函数为什么重要
可抢占系统中,一个函数可能在尚未执行完成时,再次被另一个任务或 ISR 调用。若多次调用互不破坏彼此的数据,该函数称为可重入函数(Reentrant Function)。
不可重入函数常见原因包括:
- 修改未保护的全局变量或静态变量;
- 返回指向同一静态缓冲区的指针;
- 使用不能并发访问的外设;
- 调用了其他不可重入函数。
下面的函数依赖共享静态变量,因此不是天然可重入的:
intGetNextValue(void){staticintvalue=0;value++;returnvalue;}改进方向包括使用局部变量、由调用者提供缓冲区,或者使用互斥机制保护共享状态。
6. 时间片轮转与 μC/OS-II 的关系
时间片轮转是指同优先级任务轮流占用 CPU,每个任务运行一个时间片后切换到下一个任务。它常用于通用操作系统,以改善同优先级任务之间的公平性。
经典 μC/OS-II 采用固定优先级抢占调度,并要求每个任务具有唯一优先级。因此,理解时间片有助于比较不同操作系统,但不能把通用操作系统的“同优先级轮转”直接套用到经典 μC/OS-II 上。
五、临界区、共享资源与互斥
1. 什么是资源与共享资源
任务运行时使用的变量、缓冲区、串口、SPI、ADC、文件和存储区都可以称为资源。如果一个资源可能被多个任务或 ISR 使用,它就是共享资源。
共享资源并不一定立刻出错,真正的问题是:多个执行单元能否在没有保护的情况下交错访问它。
2. 一句count++为什么也不安全
count++;这条 C 语句通常不是一条不可分割的 CPU 指令,而可能被分解为:
- 从内存读取
count; - 在寄存器中执行加一;
- 将结果写回内存。
如果任务和 ISR 在中间发生交错(均读取到5),两次加一可能只产生一次有效结果。
最终结果依赖执行先后顺序的现象称为竞态条件(Race Condition)。
3. 什么是临界区
临界区是访问共享资源、且不能被相关执行单元同时进入的一段代码。最直接的保护方式是短时间关闭中断:
OS_CPU_SR cpu_sr;OS_ENTER_CRITICAL();shared_count++;OS_EXIT_CRITICAL();临界区必须尽量短。长时间关闭中断会增加中断延迟,破坏系统实时性。不能把耗时计算、串口轮询发送或等待外设完成放在关闭中断的区域内。
4. 常见保护手段的区别
| 方法 | 适用场景 | 主要注意事项 |
|---|---|---|
| 关闭中断 | 任务与 ISR 共享极短数据 | 临界区必须非常短 |
| 锁调度器 | 防止任务切换,但允许中断响应 | ISR 仍可能访问共享数据 |
| 信号量 | 事件计数、同步,也可用于简单互斥 | 不一定具有所有权语义 |
| 互斥信号量 | 多任务共享资源 | 关注优先级反转处理机制 |
| 消息传递 | 尽量避免直接共享数据 | 需要设计消息生命周期和缓冲区 |
不要看到共享变量就一律关闭中断。应先判断竞争双方是谁:
- 任务与 ISR 竞争:普通任务级互斥锁通常保护不了 ISR;
- 任务与任务竞争:互斥信号量通常比长时间关闭中断更合适;
- 单写者、多读者且数据天然原子:可能只需保证可见性与一致性;
- 多字节或复合结构更新:要保证读者不会看到“更新一半”的状态。
六、优先级反转与死锁
1. 优先级反转是怎样发生的
假设有三个任务:
- 高优先级任务 H;
- 中优先级任务 M;
- 低优先级任务 L。
任务 L 先获得共享资源 R。随后任务 H 就绪并抢占 L,但 H 也需要 R,只能进入等待态。此时 L 本应继续运行并尽快释放 R,却又被不需要 R 的任务 M 抢占。结果是高优先级任务 H 间接等待中优先级任务 M 完成。
这就是优先级反转:高优先级任务的实际进度反而受低优先级任务控制。
教材中的完整时序关系如下。
2. 如何缓解优先级反转
常见方案包括:
- 优先级继承:低优先级任务占有高优先级任务所需资源时,临时继承较高优先级;
- 优先级天花板:任务获得互斥资源后,优先级临时提升到预先设定的上限;
- 缩短持锁时间;
- 避免在持锁期间等待其他事件;
- 用消息传递减少资源共享。
不同 μC/OS-II 版本及其互斥量实现采用的优先级处理方式可能不同,使用时应以对应版本手册和OSMutex...实现为准,不能把二值信号量和互斥量完全等同。
3. 死锁是怎样发生的
假设:
- 任务 T1 已占有资源 R1,同时等待 R2;
- 任务 T2 已占有资源 R2,同时等待 R1。
两个任务都不会主动释放已经占有的资源,于是系统永久等待。这就是死锁。
工程中常用的预防方法有:
- 所有任务按统一顺序申请多个资源;
- 一次取得全部资源后再开始操作;
- 按申请的相反顺序释放资源;
- 为等待操作设置合理超时,并处理超时后的清理;
- 尽量避免嵌套持有多个互斥资源。
超时只能帮助系统发现和恢复部分死锁,并不能替代正确的资源顺序设计。
七、任务同步与任务间通信
同步解决“什么时候可以继续执行”,通信解决“要传递什么数据”。在 RTOS 中,两者经常一起出现,但概念不能混淆。
1. 信号量:同步、计数与简单互斥
信号量可以理解为内核维护的计数值。任务调用Pend获取信号量,没有可用信号量时进入等待态;任务或 ISR 调用Post释放信号量,并可能唤醒等待任务。
OS_EVENT*AdcSem;voidSystem_Init(void){AdcSem=OSSemCreate(0u);}voidAdcTask(void*pdata){INT8U err;(void)pdata;while(1){OSSemPend(AdcSem,0u,&err);if(err==OS_ERR_NONE){Process_ADC_Data();}}}如果初值为 0,任务会一直等待,直到 ISR 或其他任务发布一次信号量。这种用法主要表达“事件已经发生”。如果把信号量用于互斥,还要认真考虑所有权和优先级反转问题。
2. 事件标志组:等待多个条件的组合
一个任务可能需要等待多个事件。例如:
- “ADC 完成”或“通信收到数据”,任意一个发生即可继续;
- “参数加载完成”并且“母线电压正常”,两个条件都满足才能启动。
事件标志组把多个事件映射为不同位,并支持 OR、AND 等组合等待。
信号量更适合表示事件计数或单个资源;事件标志组更适合表示多个布尔条件的组合。
3. 消息邮箱:传递一个消息指针
消息邮箱可以保存一个消息指针。发送任务把指针放入邮箱,接收任务从邮箱取出指针。若邮箱为空,接收任务可以等待;若邮箱已有消息,再次投递通常需要处理“邮箱已满”错误。
常见服务包括:
OSMboxCreate():建立邮箱;OSMboxPost():投递消息;OSMboxPend():等待消息;OSMboxAccept():非阻塞地检查邮箱。
邮箱传递的通常是指针,而不是自动复制整块数据。因此必须保证消息所指向的对象在接收者使用完之前仍然有效。
4. 消息队列:让多条消息排队
消息队列可以看成邮箱的扩展。邮箱只能保存一个消息指针,队列可以保存多个消息指针。
典型服务包括:
OSQCreate():建立消息队列;OSQPost():从队尾投递消息;OSQPostFront():从队首投递消息;OSQPend():等待并取出消息;OSQAccept():非阻塞地检查消息。
5. 常用同步与通信机制对比
| 机制 | 是否携带数据 | 能否累计多个事件/消息 | 典型用途 |
|---|---|---|---|
| 二值信号量 | 否 | 通常只表示 0/1 状态 | ISR 唤醒任务、简单同步 |
| 计数信号量 | 否 | 可以计数 | 资源数量、事件累计 |
| 互斥信号量 | 否 | 不用于事件累计 | 保护共享资源 |
| 事件标志组 | 携带多个布尔位 | 可以组合多个条件 | 等待 OR/AND 条件 |
| 消息邮箱 | 一个消息指针 | 只能保留一个待取消息 | 传递最新命令或对象指针 |
| 消息队列 | 多个消息指针 | 可以排队 | 数据包、命令流、采样结果 |
选择机制时先问三个问题:
- 需要传递的是“事件”还是“数据”?
- 事件或数据能否丢失、覆盖或合并?
- 发送者和接收者是否需要阻塞等待?
八、中断、时钟节拍与延时误差
1. 中断与任务的关系
ISR 可以打断普通任务,但 ISR 本身不是普通任务。ISR 应尽量只完成必要的硬件处理,例如:
- 读取外设状态并清除中断标志;
- 取走 ADC、串口或定时器数据;
- 发布信号量、事件标志或消息;
- 让复杂处理转移到高优先级任务中完成。
典型结构如下:
voidADC_ISR(void){OSIntEnter();ADC_ClearInterruptFlag();SampleBuffer=ADC_ReadResult();OSSemPost(AdcSem);OSIntExit();}该代码只用于说明结构,寄存器保护、中断声明、向量表和清标志顺序应根据具体 MCU 移植层调整。
ISR 中通常不能调用会使当前执行单元阻塞的Pend类函数,因为 ISR 没有普通任务那样可以挂起和恢复的调度语义。
2. 为什么中断退出后不一定回原任务
假设低优先级显示任务正在运行,ADC 中断到来后发布信号量,使高优先级控制任务进入就绪态。OSIntExit()检查到更高优先级任务已经就绪,就会触发调度:
低优先级任务→ADC ISR→高优先级控制任务 \text{低优先级任务}\rightarrow\text{ADC ISR}\rightarrow\text{高优先级控制任务}低优先级任务→ADC ISR→高优先级控制任务
高优先级任务完成或进入等待态后,低优先级任务才有机会继续运行。
3. 什么是时钟节拍
时钟节拍(Clock Tick)是周期性到来的操作系统时基。若系统每秒产生OS_TICKS_PER_SEC次节拍,则节拍周期为:
Ttick=1OS_TICKS_PER_SEC T_{tick}=\frac{1}{\text{OS\_TICKS\_PER\_SEC}}Ttick=OS_TICKS_PER_SEC1
例如:
OS_TICKS_PER_SEC=100 \text{OS\_TICKS\_PER\_SEC}=100OS_TICKS_PER_SEC=100
则:
Ttick=10 ms T_{tick}=10\ msTtick=10ms
节拍 ISR 中通常调用OSTimeTick()更新任务延时,并在退出中断时根据需要重新调度。
4.OSTimeDly(1)不一定精确延时一个完整节拍
任务调用延时函数的时刻可能位于两个节拍中断之间。若刚过一个 Tick 就调用OSTimeDly(1),实际等待接近一个节拍;若在下一个 Tick 即将到来时调用,实际等待可能接近 0。
在没有其他抢占影响时,可以近似理解为:
0<Tdelay≤Ttick 0<T_{delay}\leq T_{tick}0<Tdelay≤Ttick
若任务必须至少等待一个完整节拍,通常需要请求两个节拍;若系统中还有高优先级任务和中断,任务重新获得 CPU 的时刻还可能进一步推迟。
因此,操作系统节拍适合任务级定时和超时管理,不应直接代替高精度 PWM、ADC 触发或控制环定时器。
九、用电机控制系统串起全部概念
假设一个控制器包含以下功能:
| 功能 | 触发方式 | 建议执行位置 | 优先级思路 |
|---|---|---|---|
| ADC 采样完成 | ADC 中断 | ISR 中取数并唤醒控制任务 | ISR 快速完成 |
| 电流控制与 PWM 更新 | 信号量唤醒 | 高优先级任务 | 最高任务优先级 |
| 故障处理 | 比较器或保护中断 | ISR 快速关断,任务记录故障 | 高 |
| CAN 通信 | 接收中断加通信任务 | 中断收包,任务解析 | 中 |
| 参数存储 | 消息或事件触发 | 后台任务 | 低 |
| OLED 刷新 | 周期延时 | 低优先级任务 | 最低 |
一种典型执行过程如下:
- ADC 转换完成并进入 ISR;
- ISR 保存采样结果,发布
AdcSem; - 高优先级控制任务由等待态进入就绪态;
OSIntExit()发现控制任务优先级更高,发生任务切换;- 控制任务计算电流环并更新 PWM;
- 控制任务再次等待
AdcSem; - CAN 或显示等低优先级任务继续运行。
控制任务的结构可以写成:
voidCurrentControlTask(void*pdata){INT8U err;(void)pdata;while(1){OSSemPend(AdcSem,0u,&err);if(err==OS_ERR_NONE){Current_Sample_Process();Current_Loop_Calculate();PWM_Update();}}}这里最重要的设计思想不是“把所有代码都放进任务”,而是:
- ISR 负责快速响应硬件;
- 高优先级任务负责确定性的实时计算;
- 低优先级任务处理显示、记录和非关键通信;
- 任务之间通过信号量、事件或消息连接;
- 共享外设和共享数据必须有明确的保护策略。
对于10 kHz10\ kHz10kHz、20 kHz20\ kHz20kHz等高速电流环,是否放入 RTOS 任务还要结合 CPU 性能、任务切换开销和抖动指标判断。很多系统仍会在高频 ISR 中完成最核心、执行时间固定的控制算法,而把通信、状态机和慢速环放到任务中。RTOS 不是为了机械地替换全部中断,而是帮助系统分层管理实时工作。
十、易错点与复习提纲
1. 高频易错点
误区一:实时系统就是执行速度快。
实时系统强调最坏响应时间可预测,并且能够满足截止时间。
误区二:任务处于 Ready 就是在运行。
Ready 只表示具备运行条件;单核 CPU 同一时刻只能有一个 Running 任务。
误区三:中断结束一定返回原任务。
ISR 如果唤醒了更高优先级任务,中断退出时可能直接切换到该任务。
误区四:一行 C 语句一定是原子操作。
count++、结构体赋值和多字节变量访问都可能由多条机器指令组成。
误区五:信号量与互斥量完全相同。
信号量可用于事件计数和同步;互斥量强调资源所有权,并可能包含处理优先级反转的机制。
误区六:延时一个 Tick 就是精确延时一个 Tick。
节拍量化会造成相位误差,任务重新运行还会受到更高优先级任务影响。
误区七:任务优先级越大越高。
在 μC/OS-II 中,优先级数值越小,优先级越高。
2. 一张表记住整章逻辑
| 核心问题 | 对应概念 | 一句话理解 |
|---|---|---|
| CPU 给谁使用 | 调度、优先级、抢占 | 最高优先级就绪任务运行 |
| 任务如何暂停再继续 | 上下文、任务栈、TCB | 保存旧现场,恢复新现场 |
| 多个任务如何安全访问数据 | 临界区、互斥量 | 同一时刻只允许一个执行单元访问 |
| 任务如何等待事件 | 信号量、事件标志 | 条件不满足时进入等待态 |
| 任务如何传递数据 | 邮箱、消息队列 | 通过内核传递消息指针 |
| 中断怎样连接任务 | ISR、Post、OSIntExit | ISR 快速处理并唤醒任务 |
| 延时怎样计算 | Tick、OSTimeDly | 延时精度受节拍量化影响 |
| 高优先级为何可能被拖延 | 优先级反转 | 低优先级任务占有共享资源 |
3. 自测问题
- 为什么实时系统不能只比较平均运行速度?
- Ready、Running、Waiting 三种状态有什么区别?
- 为什么每个任务必须有自己的栈?
- 可抢占型内核如何处理刚刚就绪的高优先级任务?
count++为什么可能产生竞态条件?- 信号量用于同步和用于互斥时,含义有什么区别?
- 邮箱和消息队列的核心差别是什么?
- 为什么
OSTimeDly(1)的实际延时可能远小于一个完整 Tick? - 优先级反转中,中优先级任务为什么会进一步延长高优先级任务的等待时间?
- ISR 为什么不应该执行耗时操作或调用阻塞式等待函数?
如果能够不用背定义、而是结合任务状态和执行时间线回答这些问题,说明第 2 章的基本框架已经建立。
十一、总结
μC/OS-II 的知识点很多,但第 2 章最终围绕两个核心问题展开:
谁在什么时候获得CPU? \boxed{\text{谁在什么时候获得CPU?}}谁在什么时候获得CPU?
以及:
多个任务怎样安全地等待、共享和传递信息? \boxed{\text{多个任务怎样安全地等待、共享和传递信息?}}多个任务怎样安全地等待、共享和传递信息?
理解这两个问题后,后续各章会自然对应起来:
- 任务管理解决任务的创建、删除、挂起和恢复;
- 时间管理解决延时与超时;
- 事件控制块为等待机制提供统一基础;
- 信号量和互斥量解决同步与资源保护;
- 事件标志组解决多个条件的组合等待;
- 邮箱和消息队列解决任务间数据传递;
- 内存管理解决固定内存块的分配与回收;
- 移植章节说明任务切换、中断和栈如何落到具体 CPU 上。
学习 RTOS 时,不必一开始死记全部 API。先把“任务状态如何变化、什么时候发生调度、为什么需要保护共享资源”想清楚,再去看函数原型和内核源码,会容易很多。
参考资料
- Jean J. Labrosse,《嵌入式实时操作系统 μC/OS-II(第 2 版)》第 2 章:实时系统概念。
- μC/OS-II 源码中的任务管理、时间管理、信号量、事件标志组、邮箱、消息队列及移植接口。