思澈面试笔记-RTOS
思澈面试笔记-RTOS
为巽RTOS 高频问点
这份笔记用于准备 RT-Thread、FreeRTOS、Zephyr 等实时操作系统相关面试。重点是任务调度、同步通信、中断配合、资源保护和常见问题排查。
1. RTOS 是什么?
RTOS 是 Real-Time Operating System,实时操作系统。它为嵌入式系统提供任务调度、任务间通信、同步机制、定时器和内存管理等能力。
RTOS 不等于“速度一定更快”,它的重点是:
- 让多个任务结构更清晰。
- 提供可控的实时响应。
- 管理任务优先级。
- 提供任务间通信和同步机制。
面试答法:
RTOS 主要解决复杂嵌入式程序的结构和实时性问题。裸机程序通常依靠主循环和中断,业务复杂后会变得难维护;RTOS 可以把通信、采集、控制、显示等功能拆成不同任务,通过队列、信号量、事件等机制协作。
2. RTOS 和裸机程序的区别
裸机程序
特点:
- 一个
while(1)主循环。 - 外设事件通常靠中断处理。
- 程序结构简单。
- 适合小项目、逻辑简单的设备。
缺点:
- 多功能并发时结构容易混乱。
- 延时函数可能阻塞其他逻辑。
- 任务优先级不好管理。
RTOS 程序
特点:
- 多任务并发运行。
- 每个任务有独立栈。
- 调度器根据优先级切换任务。
- 提供信号量、互斥锁、队列、事件等机制。
缺点:
- 有调度开销。
- 需要管理任务栈和优先级。
- 并发问题更复杂。
面试答法:
裸机适合简单控制逻辑,RTOS 适合多任务场景。例如一个设备同时要采集传感器、处理串口协议、刷新屏幕、控制电机,用 RTOS 拆任务会更清晰。但 RTOS 也会带来优先级、栈大小和共享资源保护的问题。
3. 任务和线程
任务是什么?
任务是 RTOS 调度的基本单位。每个任务通常包含:
- 任务函数。
- 任务栈。
- 优先级。
- 任务控制块。
- 任务状态。
示例:
1 | void sensor_task(void *arg) |
任务状态
常见状态:
- Running:正在运行。
- Ready:就绪,等待 CPU。
- Blocked:阻塞,等待延时、信号量、队列等。
- Suspended:挂起。
- Deleted:删除。
面试答法:
任务不是一直运行的,它会在运行、就绪、阻塞等状态之间切换。比如调用延时函数后任务进入阻塞状态,等时间到后重新进入就绪状态。
4. 调度器
高频问点:RTOS 如何决定运行哪个任务?
调度器根据任务状态和优先级选择任务。
常见规则:
- 高优先级就绪任务先运行。
- 同优先级任务可能时间片轮转。
- 阻塞任务不参与调度。
面试答法:
大多数 RTOS 使用基于优先级的抢占式调度。只要高优先级任务进入就绪态,就可以抢占低优先级任务。这样能保证关键任务及时响应。
抢占式调度和协作式调度
抢占式:
- 高优先级任务可以打断低优先级任务。
- 实时性更好。
- 需要注意共享资源保护。
协作式:
- 任务主动让出 CPU。
- 实现简单。
- 某个任务不让出 CPU 会影响系统。
时间片轮转
同优先级任务之间,RTOS 可以按 tick 时间片轮流运行,避免某个同优先级任务长期占用 CPU。
5. 任务优先级
高频问点:任务优先级怎么设置?
原则:
- 实时性要求高的任务优先级高。
- 耗时但不紧急的任务优先级低。
- 不要所有任务都设成高优先级。
- 高优先级任务不能长时间占用 CPU。
- 低优先级任务也要有机会运行。
示例:
| 任务 | 优先级建议 |
|---|---|
| 电机保护、紧急停止 | 高 |
| 通信接收解析 | 较高 |
| 传感器周期采集 | 中 |
| 屏幕刷新 | 中低 |
| 日志输出 | 低 |
面试答法:
优先级要根据实时性设置。高优先级任务应该短小,处理关键事件后尽快阻塞或让出 CPU。如果高优先级任务一直运行,低优先级任务会被饿死。
高频问点:什么是任务饥饿?
低优先级任务长期得不到 CPU,就是任务饥饿。
原因:
- 高优先级任务没有阻塞或延时。
- 高优先级任务死循环。
- 优先级设计不合理。
解决:
- 高优先级任务等待事件而不是空转。
- 增加适当延时。
- 调整优先级。
- 检查 CPU 使用率。
6. 任务栈
高频问点:每个任务为什么需要独立栈?
每个任务都有自己的函数调用链、局部变量、返回地址和上下文,因此需要独立栈。
任务栈太小会导致:
- 程序异常。
- HardFault。
- 数据被覆盖。
- 随机崩溃。
任务栈过大:
- 浪费 RAM。
如何估算任务栈?
关注:
- 函数调用深度。
- 局部变量大小。
- 是否使用 printf。
- 是否有大数组。
- 是否调用复杂库函数。
调试方式:
- 使用栈水位检测。
- 增大栈观察问题是否消失。
- 查看 map 文件和 RTOS 统计信息。
面试答法:
RTOS 中每个任务都有自己的栈,栈大小需要根据任务函数调用深度和局部变量估算。嵌入式 RAM 有限,不能盲目给很大,也不能太小。实际项目中会用栈水位检测来确认余量。
7. 延时与 Tick
delay_ms 和 RTOS 延时有什么区别?
裸机忙等延时:
- CPU 一直空转。
- 会阻塞其他逻辑。
RTOS 延时:
- 当前任务进入阻塞。
- CPU 可以调度其他任务。
示例:
1 | vTaskDelay(pdMS_TO_TICKS(100)); |
面试答法:
在 RTOS 任务中不应该使用长时间忙等延时,而应该使用 RTOS 提供的 delay,让当前任务阻塞,把 CPU 让给其他任务。
Tick 是什么?
Tick 是 RTOS 的系统节拍,通常由 SysTick 或定时器周期中断产生。
用途:
- 时间片调度。
- 任务延时。
- 软件定时器。
- 超时判断。
8. 信号量
信号量是什么?
信号量用于任务同步或资源计数。
常见类型:
- 二值信号量。
- 计数信号量。
二值信号量
只有 0 和 1 两种状态,常用于事件通知。
场景:
- UART 接收完成后通知任务。
- GPIO 中断通知按键任务。
- DMA 完成中断通知处理任务。
面试答法:
二值信号量常用于中断和任务之间的同步。中断里释放信号量,任务阻塞等待信号量,收到后再执行耗时处理。
计数信号量
可以表示多个资源数量。
场景:
- 缓冲区个数。
- 连接池。
- 多个相同资源。
9. 互斥锁
互斥锁是什么?
互斥锁用于保护共享资源,保证同一时间只有一个任务访问。
共享资源例子:
- I2C 总线。
- SPI 总线。
- 全局数据结构。
- 文件系统。
- 日志输出。
示例:
1 | mutex_lock(i2c_mutex); |
面试答法:
如果多个任务会访问同一个 I2C 或 SPI 总线,需要用互斥锁保护,避免两个任务的传输交叉导致总线数据混乱。
高频问点:信号量和互斥锁有什么区别?
信号量:
- 更偏事件通知或资源计数。
- 可以由一个任务释放,另一个任务获取。
- 二值信号量可用于中断通知任务。
互斥锁:
- 用于保护共享资源。
- 谁加锁谁解锁。
- 通常支持优先级继承。
面试答法:
信号量解决“有没有事件或资源”,互斥锁解决“同一时间只能一个任务访问共享资源”。保护共享资源时优先用 mutex,而不是普通二值信号量。
10. 队列
队列是什么?
队列用于任务之间传递数据。
场景:
- UART 接收任务把数据发给协议解析任务。
- 传感器任务把采样结果发给显示任务。
- 中断把事件发送给事件处理任务。
示例:
1 | typedef struct { |
高频问点:队列和共享全局变量哪个好?
队列更适合任务间传递数据:
- 数据边界清晰。
- 可以阻塞等待。
- 减少共享变量竞争。
- 更容易维护。
全局变量适合简单状态,但需要考虑同步。
面试答法:
如果是任务间传递一条条消息,我更倾向用队列,而不是多个任务直接读写全局变量。队列可以自带同步机制,也能让数据流向更清楚。
11. 事件标志
事件标志用于等待一个或多个事件。
场景:
- 等待 Wi-Fi 连接成功。
- 等待传感器数据就绪。
- 等待多个初始化条件完成。
示例思路:
- bit0 表示网络连接。
- bit1 表示时间同步完成。
- bit2 表示配置加载完成。
任务可以等待全部事件或任意事件。
12. 软件定时器
软件定时器是什么?
软件定时器由 RTOS tick 驱动,到期后执行回调或通知任务。
适合:
- 周期状态检查。
- 超时处理。
- LED 心跳。
- 延迟执行。
注意:
- 软件定时器回调中不要做耗时操作。
- 精度受 tick 影响。
13. 中断和 RTOS
高频问点:中断里能调用 RTOS API 吗?
可以,但必须使用 RTOS 专门提供的 FromISR 版本或中断安全接口。
例如 FreeRTOS 中:
xSemaphoreGiveFromISRxQueueSendFromISRportYIELD_FROM_ISR
不能在中断中调用可能阻塞的 API。
面试答法:
中断上下文不能阻塞,所以不能随便调用普通任务 API。需要使用 RTOS 提供的 ISR 安全版本,把事件通知给任务处理。
高频问点:中断和任务如何配合?
推荐模式:
- 中断中读取状态、清标志。
- 中断中释放信号量或发送消息。
- 高优先级任务被唤醒。
- 任务中处理耗时逻辑。
这样中断短,任务逻辑清晰。
14. 优先级反转
什么是优先级反转?
低优先级任务持有互斥资源,高优先级任务等待该资源;此时中优先级任务一直运行,导致高优先级任务无法及时运行。
示例:
- 低优先级任务拿到 I2C mutex。
- 高优先级任务也要用 I2C,被阻塞。
- 中优先级任务运行,占用 CPU。
- 低优先级任务无法运行释放 mutex。
- 高优先级任务一直等。
如何解决?
- 使用支持优先级继承的 mutex。
- 缩短持锁时间。
- 不要持锁做耗时操作。
- 合理设计任务优先级。
面试答法:
优先级反转可以通过优先级继承缓解。持有 mutex 的低优先级任务会临时继承等待它的高优先级任务优先级,尽快运行并释放资源。
15. 死锁
什么是死锁?
多个任务互相等待对方持有的资源,导致都无法继续运行。
例子:
- 任务 A 持有 mutex1,等待 mutex2。
- 任务 B 持有 mutex2,等待 mutex1。
避免方法:
- 固定加锁顺序。
- 避免嵌套锁。
- 设置锁超时。
- 缩短临界区。
- 明确资源所有权。
16. 临界区
临界区是访问共享资源时不希望被打断的代码区域。
常见方式:
- 关中断。
- 加 mutex。
- 使用原子操作。
选择:
- 中断和主循环共享变量:短时间关中断。
- 多任务共享资源:mutex。
- 简单计数:原子操作。
注意:
临界区要尽量短,不能在关中断状态下做耗时操作,否则会影响实时性。
17. 内存管理
RTOS 中内存从哪里来?
RTOS 通常需要为以下对象分配内存:
- 任务控制块。
- 任务栈。
- 队列。
- 信号量。
- 定时器。
- 事件组。
分配方式:
- 静态分配。
- 动态分配。
静态分配和动态分配怎么选?
静态分配:
- 更确定。
- 没有运行时分配失败。
- 适合安全性要求高的系统。
动态分配:
- 灵活。
- 需要处理失败。
- 有碎片风险。
面试答法:
资源固定的嵌入式系统更适合静态分配,实时性和可靠性更好。动态分配适合资源变化较大的场景,但要考虑失败处理和碎片问题。
18. RTOS 调试
常见问题:
- 某个任务不运行。
- 系统卡死。
- 栈溢出。
- 队列满。
- 信号量丢失。
- 死锁。
- CPU 占用过高。
排查思路:
- 查看任务状态。
- 查看任务优先级。
- 查看栈水位。
- 查看队列长度。
- 查看是否有任务一直不阻塞。
- 检查 mutex 是否未释放。
- 检查中断优先级和 FromISR API。
面试答法:
RTOS 调试我会先看任务状态和优先级,判断任务是没有创建、被阻塞、栈溢出还是被高优先级任务饿死。再看队列、信号量、mutex 等同步对象的状态。
19. 任务划分示例
一个传感器设备可以这样划分:
| 任务 | 作用 | 通信方式 |
|---|---|---|
| sensor_task | 周期读取 I2C/ADC 数据 | 队列发送数据 |
| uart_task | 处理串口命令 | 队列接收命令 |
| control_task | 根据数据控制输出 | 队列/事件 |
| led_task | 状态指示 | 事件标志 |
| log_task | 统一输出日志 | 消息队列 |
面试表达:
我会把周期采集、通信处理、控制逻辑和日志输出拆成不同任务。任务之间尽量用队列或事件通信,减少共享全局变量。访问 I2C、SPI 这类共享总线时用 mutex 保护。
20. 高频问答速背
RTOS 一定比裸机好吗?
不是。简单项目裸机更轻量。复杂多任务项目使用 RTOS 更清晰,但会增加调度、栈和同步复杂度。
任务里能不能一直 while(1) 不延时?
一般不行。如果高优先级任务一直运行不阻塞,会导致低优先级任务得不到 CPU。任务应等待事件、队列、信号量或适当 delay。
中断里为什么不能直接处理复杂逻辑?
中断执行太久会影响系统实时性,阻塞其他中断。应该只做短处理,把耗时逻辑交给任务。
什么时候用队列?
任务之间传递数据时用队列,例如串口接收数据传给解析任务。
什么时候用信号量?
通知事件或表示资源可用时用信号量,例如 DMA 完成中断通知处理任务。
什么时候用互斥锁?
保护共享资源时用互斥锁,例如多个任务访问同一条 I2C 总线。
什么是优先级反转?
低优先级任务持有资源,高优先级任务等待资源,中优先级任务占用 CPU,导致高优先级任务被间接阻塞。
RTOS 中最容易出的问题是什么?
任务栈不足、优先级设计不合理、共享资源没保护、死锁、中断里调用错误 API、队列满或信号量使用错误。



