一、引言
操作系统 原理是嵌入式面试的必考内容。但与纯软件面试不同,嵌入式面试对 OS 的考察有鲜明的场景侧重:
不常问虚拟内存(MCU 没有 MMU)
不常问文件系统(裸机/RTOS 不用文件系统)
高频考察:进程 vs 线程、任务状态切换、同步互斥机制、死锁、调度算法
本文从嵌入式开发 者的视角出发,将 OS 原理与 RTOS(FreeRTOS)的实际实现对照讲解,帮助理解概念的同时看到它们在代码中是如何落地。
二、进程 vs 线程——嵌入式中的"任务"
2.1 核心区别
2.2 嵌入式场景的特殊性
在 MCU 上运行的 RTOS 中,所有任务(线程)运行在同一个地址空间,没有 MMU 保护:
// RTOS 中:任务 A 可以随意访问任务 B 的变量!
// 因为没有 MMU 做地址隔离
int g_sharedVar = 0;
void vTaskA(void *pv)
{
g_sharedVar = 100; // 可以直接改
while(1);
}
void vTaskB(void *pv)
{
if (g_sharedVar == 100) { // 可以直接读
// ...
}
while(1);
}
工程启示: RTOS 中一个任务的野指针可能破坏另一个任务甚至内核的数据。这是嵌入式的"双刃剑"——通信方便,但错误也容易被放大。
2.3 进程五状态模型
RTOS 的任务状态是进程五状态模型的简化版:
Linux 进程五状态: FreeRTOS 任务状态:
┌──────────┐
创建 ─→ 就绪 ─→ 运行 │ Ready │←──────────┐
(new) (ready) (running) └────┬─────┘ │
↑ │ │ │
│ ▼ ▼ │
│ ┌────────┐ ┌──────────┐ │
│ │ Blocked│ │ Running │ │
│ └────┬───┘ └─────┬────┘ │
│ │ │ │
└───────┘ │ │
▼ │
┌──────────┐ ┌──────────┐ │
│ │ │ Blocked │──────────┘(超时/事件到来)
│Terminated│ └──────────┘
└──────────┘
┌──────────┐
│Suspended │──→ Ready (vTaskResume)
└──────────┘
三、同步与互斥机制
3.1 竞争条件与临界区
/* 竞争条件示例:两个任务同时访问共享资源 */
int g_counter = 0;
// 任务 A(每 10ms 执行一次)
void vTaskA(void *pv) {
while(1) {
g_counter++; // ← RMW 操作:读→加→写
vTaskDelay(10);
}
}
// 任务 B(每 12ms 执行一次)
void vTaskB(void *pv) {
while(1) {
g_counter = 0; // ← 写操作
vTaskDelay(12);
}
}
/* g_counter++ 的汇编(非原子):
LDR R0, [g_counter] ; 读
ADD R0, R0, #1 ; 加
STR R0, [g_counter] ; 写
→ 如果在 LDR 和 STR 之间被任务 B 的 g_counter=0 打断
→ STR 覆盖了任务 B 的修改 → 数据错误!
*/
3.2 临界区保护:三种粒度的方案
方案一:关中断(最粗暴,影响实时性)
// FreeRTOS 中的临界区宏
taskENTER_CRITICAL(); // 内部:通过 BASEPRI 屏蔽 configMAX_SYSCALL_INTERRUPT_PRIORITY 及以上数值优先级的中断(或通过 PRIMASK 全局关中断,取决于具体端口实现)
g_counter++;
taskEXIT_CRITICAL();
// ⚠️ 临界区中不能调用阻塞 API!
// ⚠️ 临界区执行时间越短越好(通常 < 1μs)
方案二:互斥量(Mutex,带优先级继承)
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
void vTaskA(void *pv) {
while(1) {
xSemaphoreTake(xMutex, portMAX_DELAY); // 加锁
vProcessSharedResource();
xSemaphoreGive(xMutex); // 解锁
}
}
方案三:信号量(Semaphore,用于事件通知)
SemaphoreHandle_t xSem = xSemaphoreCreateBinary();
// ISR 中通知
void EXTI0_IRQHandler(void) {
BaseType_t xWoken = pdFALSE;
xSemaphoreGiveFromISR(xSem, &xWoken);
portYIELD_FROM_ISR(xWoken);
}
// 任务中等待
void vTaskListener(void *pv) {
while(1) {
if (xSemaphoreTake(xSem, pdMS_TO_TICKS(1000)) == pdTRUE) {
vProcessEvent();
}
}
}
3.3 三种同步机制的选型
面试高频题: 互斥量和二值信号量的区别?(答:互斥量有优先级继承机制,解决优先级反转问题;二值信号量没有所有权概念,任何任务/ISR 都可以 Give。)
四、死锁
4.1 死锁的四个必要条件
死锁的四个必要条件(缺一不可):
┌─────────────────────────┐
┌─────────────┤ ① 互斥(Mutual Exclusion)├─────────────┐
│ │ 资源一次只能一个任务使用 │ │
│ └─────────────────────────┘ │
│ │
▼ ▼
┌─────────────┐ ┌──────────────┐
│ ② 持有并等待(Hold & Wait) │ │ ③ 不可抢占(No │
│ 持有资源的同时请求新资源 │ │ Preemption) │
│ │ │ 资源不能被强制拿走 │
└─────────────┘ └───────┬──────────┘
│ │
└──────────────────────┬──────────────────────────────┘
▼
┌─────────────────┐
│④ 循环等待 │
│(Circular Wait) │
│T1 等 T2 的资源 │
│T2 等 T1 的资源 │
└─────────────────┘
嵌入式中最常见的死锁场景:
// 死锁示例:两个互斥量交叉请求
SemaphoreHandle_t xMutexA = xSemaphoreCreateMutex();
SemaphoreHandle_t xMutexB = xSemaphoreCreateMutex();
// 任务 A
void vTaskA(void *pv) {
xSemaphoreTake(xMutexA, portMAX_DELAY); // 持有 A
vTaskDelay(1); // 切换 → 让任务 B 跑
xSemaphoreTake(xMutexB, portMAX_DELAY); // 请求 B → 死锁! B 被任务 B 持有
xSemaphoreGive(xMutexB);
xSemaphoreGive(xMutexA);
}
// 任务 B
void vTaskB(void *pv) {
xSemaphoreTake(xMutexB, portMAX_DELAY); // 持有 B
xSemaphoreTake(xMutexA, portMAX_DELAY); // 请求 A → 死锁! A 被任务 A 持有
xSemaphoreGive(xMutexA);
xSemaphoreGive(xMutexB);
}
4.2 死锁预防策略(嵌入式实战)
// 策略 1:固定锁顺序(最有效!)
// 始终先拿 A 再拿 B,所有任务遵守同一顺序
void vTaskA(void *pv) {
xSemaphoreTake(xMutexA, portMAX_DELAY); // 先 A
xSemaphoreTake(xMutexB, portMAX_DELAY); // 后 B
// ... 使用资源 ...
xSemaphoreGive(xMutexB);
xSemaphoreGive(xMutexA);
}
void vTaskB(void *pv) {
xSemaphoreTake(xMutexA, portMAX_DELAY); // 也先 A
xSemaphoreTake(xMutexB, portMAX_DELAY); // 再 B(不会死锁)
// ... 使用资源 ...
xSemaphoreGive(xMutexB);
xSemaphoreGive(xMutexA);
}
// 策略 2:尝试获取,获取不到就释放已持有的(FreeRTOS 可通过 xSemaphoreTake(xMutex, 0) 实现等效的 TryLock: 不阻塞,立即返回)
// 策略 3:用队列代替多个互斥量(流水线设计,避免交叉持有)
五、调度算法
5.1 常见调度算法对比
5.2 嵌入式中最常用的调度策略
策略 1:固定优先级抢占式调度(FreeRTOS 默认)
// 优先级越高数值越大(FreeRTOS 的习惯)
// 如果 configUSE_PREEMPTION = 1 → 抢占式
// 任务 A(优先级 3)—— 就绪就运行
xTaskCreate(vTaskHigh, "High", 128, NULL, 3, NULL);
// 任务 B(优先级 2)—— A 就绪时 B 被暂停
xTaskCreate(vTaskMid, "Mid", 128, NULL, 2, NULL);
// 任务 C(优先级 1)—— 只有在 A 和 B 都阻塞时才运行
xTaskCreate(vTaskLow, "Low", 128, NULL, 1, NULL);
策略 2:时间片轮转(同优先级)
// FreeRTOSConfig.h 中启用
#define configUSE_TIME_SLICING 1
// 任务 A 和 B 优先级相同(都是 2)
// 每个任务运行一个 Tick(1ms)后自动切换到另一个
xTaskCreate(vTaskA, "TaskA", 128, NULL, 2, NULL);
xTaskCreate(vTaskB, "TaskB", 128, NULL, 2, NULL);
/* 时间片切换示意:
TaskA ┌──┐ ┌──┐ ┌──┐ ┌──
│ │ │ │ │ │ │
TaskB └──┘ └──┘ └──┘ └──
1ms 2ms 3ms 4ms
*/
策略 3:速率单调调度(RMS)——周期任务优先级分配
RMS 理论:周期越短的任务,优先级应该越高。
┌──────────┬──────────┬────────┐
│ 任务 │ 周期 │ 优先级 │
├──────────┼──────────┼────────┤
│ 传感器采集│ 10ms │ 最高 5 │
│ PID 控制 │ 20ms │ 4 │
│ 显示刷新 │ 100ms │ 3 │
│ 通信上报 │ 500ms │ 2 │
│ 看门狗喂狗│ 1000ms │ 1 │
└──────────┴──────────┴────────┘
5.3 优先级反转问题
没有优先级继承时:
任务 H(高优先级) ──────────┐ 等锁
任务 M(中优先级) └── 抢占 L ──►
任务 L(低优先级) ── 持锁 ──► ── 释放锁 ──►
H 等 L 释放锁,但 L 被 M 抢占 → 高优任务被中优任务间接阻塞!
这叫"优先级反转"——H 的实际执行优先级被反转到了 M 之下。
有优先级继承(互斥量)时:
任务 H(高优先级) ──────────────→ 取锁成功
任务 M(中优先级) 被 L 压制
任务 L(低优先级) ── 持锁 ──→【继承 H 的优先级】── 释放锁 ──→
L 持有互斥量时临时升到 H 的优先级 → 不被 M 抢占 → 迅速释放锁
→ 这就是互斥量核心价值:防优先级反转
六、嵌入式 RTOS vs 通用 OS 对比
七、总结
┌─────────────────────────────────────────────────────────────────┐
│ 嵌入式 OS 原理速查表 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 进程与线程:进程有独立地址空间(Linux),RTOS 任务共享地址空间 │
│ 同步互斥:互斥量(防反转)> 二值信号量(事件)> 队列(数据) │
│ 死锁:固定锁顺序是嵌入式中最有效的防死锁策略 │
│ 调度:优先级抢占 + 时间片轮转(同优先级)是 RTOS 标配 │
│ 优先级反转:用互斥量(优先级继承)解决 │
│ │
│ 嵌入式面试高频题: │
│ ① 互斥量和二值信号量的区别 → 优先级继承 │
│ ② 死锁的四个必要条件 → 预防策略 │
│ ③ 抢占式调度 vs 时间片轮转 → FreeRTOS 配置 │
│ ④ 优先级反转 → 用互斥量解决 │
│ │
└─────────────────────────────────────────────────────────────────┘
这些 OS 原理在 FreeRTOS 中都有具体的代码实现。理解了原理再看 FreeRTOS 源码,你看到的就不是"这一堆 if -else 是什么",而是"哦,这里是在做优先级继承,那里是在做任务状态迁移"。
————————————————
版权声明:本文为CSDN博主「2401_85415155」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2401_85415155/article/details/162343757
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?注册
×
|