任务间为什么要通信
多任务系统里,任务之间必须交换数据和同步。直接用全局变量会有竞态:一个任务在读、另一个在写,读到半更新的数据就崩了。FreeRTOS 提供了一套原语:消息队列传数据,信号量做同步,互斥量保护共享资源。
能传值就别传指针。队列传值自带拷贝,安全;传指针得自己管生命周期。
这篇把 FreeRTOS 四种原语讲透。
消息队列 Queue
队列是任务间传数据的主力:
// 创建:长度 10,每项 4 字节(一个 int)
QueueHandle_t q = xQueueCreate(10, sizeof(int));
// 发送(中断里用 FromISR 版本)
int v = 42;
xQueueSend(q, &v, portMAX_DELAY);
// 接收
int r;
xQueueReceive(q, &r, pdMS_TO_TICKS(100));
队列要点
- 拷贝语义发送时把数据拷进队列,不共享指针。
- FIFO先进先出。
- 满/空阻塞发送满了等,接收空了等。
二值信号量
二值信号量只有 0/1 两个状态,用来做"中断通知任务":
// ISR 里 give,任务里 take
SemaphoreHandle_t s = xSemaphoreCreateBinary();
// 中断:GPIO 按下
void EXTI_IRQHandler() {
BaseType_t hpw = pdFALSE;
xSemaphoreGiveFromISR(s, &hpw);
portYIELD_FROM_ISR(hpw);
}
// 任务:等信号量
void vTask(void *p) {
while (1) {
xSemaphoreTake(s, portMAX_DELAY);
// 处理按键
}
}
计数信号量
计数信号量维护一个计数,典型用法是资源池:
// 比如 3 个缓冲区,任务拿一个用一个
SemaphoreHandle_t pool = xSemaphoreCreateCounting(3, 3);
xSemaphoreTake(pool, portMAX_DELAY); // 申请一个
// ...用缓冲区...
xSemaphoreGive(pool); // 归还
互斥量与优先级继承
互斥量保护共享资源,自带优先级继承:
SemaphoreHandle_t m = xSemaphoreCreateMutex();
xSemaphoreTake(m, portMAX_DELAY);
// 临界区:访问串口/全局变量
xSemaphoreGive(m);
互斥量和二值信号量长得像,但互斥量有主(owner),会做优先级继承;二值信号量没有主,纯同步用。
递归互斥量
同一任务要重复 take 同一个锁,用递归互斥量:
SemaphoreHandle_t rm = xSemaphoreCreateRecursiveMutex();
xSemaphoreTakeRecursive(rm, portMAX_DELAY);
xSemaphoreTakeRecursive(rm, portMAX_DELAY); // 同任务再 take 不会死锁
// ...
xSemaphoreGiveRecursive(rm);
xSemaphoreGiveRecursive(rm);
常见踩坑
// 1. 中断里只能用 FromISR 版本
// 2. 队列传大结构体浪费内存,考虑传指针
// 3. 互斥量不能在中断里用
// 4. take 超时别写 portMAX_DELAY 在高优先级任务里,系统卡死
// 5. 删除队列/信号量前,确保没有任务在等
FreeRTOS 的同步原语选对了,多任务就顺;选错了,偶发卡死调到怀疑人生。
把队列和信号量吃透,RTOS 编程才算入门。