[经验分享] 嵌入式操作系统核心原理——进程线程、同步互斥、死锁与调度(结合 RTOS 场景)

[复制链接]
22|1
八层楼 发表于 2026-8-4 18:36 | 显示全部楼层 |阅读模式
一、引言
操作系统 原理是嵌入式面试的必考内容。但与纯软件面试不同,嵌入式面试对 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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
您需要登录后才可以回帖 登录 | 注册

本版积分规则

232

主题

4743

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部
0