第三章 RTOS系统架构详解:内核、任务、调度器、临界区、同步与通信机制概览
好,咱们进入正题。RTOS 这东西,说白了就是一个能帮你「管事儿」的小系统。你想想看,裸机编程就像你一个人开个小作坊,所有活都得自己盯着。但 RTOS 不一样,它给你请了个「管家」——也就是内核。
3.1 内核:RTOS 的心脏
内核是什么?我习惯把它理解成「调度中心」。它负责分配 CPU 时间,管理内存,协调各个任务之间的通信。没有内核,RTOS 就是一盘散沙。
我在项目中遇到过一种情况:一个同事把内核的 tick 频率设得太高,结果系统一半的 CPU 时间都在处理 tick 中断。嗯,这里要注意,内核的 tick 频率不是越高越好,一般 100Hz 到 1000Hz 就够用了。
3.2 任务:系统的基本工作单元
任务,就是你要让 CPU 干的一件件具体的事。比如读传感器、刷屏幕、处理网络数据包。每个任务都有自己的栈空间和优先级。
任务有几种状态:运行态、就绪态、阻塞态、挂起态。说白了就是:正在干活、排队等着干活、等某个条件满足才能干活、被强制暂停干活。
我刚开始用 RTOS 时,总喜欢把任务写得特别大,一个任务里塞几百行代码。后来发现,任务切换时栈空间消耗巨大,还容易出 bug。现在我习惯把大任务拆成几个小任务,每个任务只做一件事,做好就睡。
// 一个典型任务的写法
void sensor_task(void *param) {
while(1) {
read_sensor(); // 读传感器
process_data(); // 处理数据
vTaskDelay(100); // 休眠100个tick
}
}
3.3 调度器:谁先跑,谁后跑
调度器是内核里最核心的模块。它决定下一个该哪个任务运行。常见的调度策略有两种:
- 抢占式调度:高优先级的任务随时可以打断低优先级的任务。实时性高,但要注意优先级反转问题。
- 时间片轮转调度:同优先级的任务轮流跑,每人分一个时间片。公平,但实时性差一些。
我个人习惯在实时性要求高的场景用抢占式,比如电机控制、通信协议处理。而在人机交互这类场景,时间片轮转更合适,用户体验更流畅。
你想想看,如果按键扫描任务和电机控制任务用同一个优先级,电机控制可能因为按键扫描而延迟响应,这就不对了。
3.4 临界区:别让任务互相捣乱
临界区,说白了就是一段「不允许被打断」的代码。比如你在更新一个全局变量,刚读到一半,任务被切换了,另一个任务也来读写这个变量——数据就乱套了。
保护临界区的方法主要有两种:
- 关中断:最简单粗暴,但关中断时间不能太长,否则会影响系统实时性。
- 使用互斥量:更优雅的方式,但要注意死锁问题。
我的原则:临界区代码越短越好。我一般控制在 10 条指令以内。如果超过这个数,我会重新设计数据结构,或者用其他同步机制代替。
我曾经在一个项目中,为了图省事,把整个函数都包在临界区里。结果中断响应延迟从 5us 飙升到 200us,整个系统实时性崩了。嗯,从那以后我再也不敢这么干了。
3.5 同步与通信机制:任务间的「对话」
任务之间总要交流吧?比如传感器任务采集到数据,要告诉显示任务去刷新屏幕。这就用到了同步与通信机制。
| 机制 | 用途 | 我的评价 |
|---|---|---|
| 信号量 | 任务同步、资源管理 | 轻量级,适合「通知」场景 |
| 互斥量 | 保护共享资源 | 注意优先级继承,防止反转 |
| 消息队列 | 任务间传递数据 | 我最常用的方式,灵活可靠 |
| 事件标志组 | 等待多个条件 | 适合复杂同步场景 |
| 邮箱 | 传递指针或小数据 | 效率高,但要注意指针有效性 |
我个人最常用的是消息队列。为什么?因为它自带缓冲,发送方和接收方可以「解耦」。发送方只管发,接收方有空再取,互不阻塞。
// 消息队列的使用示例
QueueHandle_t data_queue;
void sender_task(void *param) {
int data = 100;
while(1) {
xQueueSend(data_queue, &data, 0);
vTaskDelay(1000);
}
}
void receiver_task(void *param) {
int received;
while(1) {
if(xQueueReceive(data_queue, &received, portMAX_DELAY)) {
// 处理接收到的数据
process(received);
}
}
}
信号量呢?我习惯用它来做「事件通知」。比如按键按下时释放一个信号量,按键处理任务等待这个信号量。这样按键处理任务平时可以休眠,省电又省 CPU。
互斥量有个坑——优先级反转。高优先级任务等低优先级任务释放互斥量,而低优先级任务又被中等优先级任务抢占,导致高优先级任务迟迟拿不到资源。解决办法是使用支持优先级继承的互斥量,或者干脆重新设计任务优先级。
嗯,说到这,我想起一个项目。当时用了一个第三方库,里面用了全局变量做状态同步,没加任何保护。结果两个任务同时读写,状态值一会儿对一会儿错。排查了三天,最后发现是同步问题。从那以后,我定了个规矩:所有跨任务的数据访问,必须用同步机制,哪怕只是一个标志位。
3.6 小结
RTOS 的内核、任务、调度器、临界区、同步与通信机制,这几个概念是相互关联的。内核负责调度任务,任务之间通过同步机制通信,临界区保护共享数据不被破坏。
你想想看,没有这些机制,多任务编程就是一场灾难。有了它们,你才能写出稳定、可靠、可维护的嵌入式系统。
下一章,我会拿一个具体的 RTOS 来实战,带大家一步步搭建一个多任务系统。到时候,这些概念都会用上。