常见嵌入式操作系统中的线程(任务)间通信机制
目录
- 1. 概述:线程间通信解决什么问题
- 2. 机制全景与选型维度
- 3. 数据传递类机制
- 3.1 消息队列(Message Queue)
- 3.2 邮箱(Mailbox)
- 3.3 管道与字节流(Pipe / Stream Buffer)
- 3.4 变长缓冲队列与环形缓冲(Ring Buffer)
- 4. 事件通知类机制
- 4.1 信号量(通知用法)
- 4.2 事件标志组(Event Flags)
- 4.3 任务通知与内嵌任务队列
- 4.4 信号与异步回调(Signal)
- 5. 资源共享类机制
- 6. Linux 扩展机制
- 7. 跨系统接口对比总表
- 8. ISR(中断上下文)与任务通信的特殊性
- 9. 选型建议与典型场景
- 10. 常见误区与注意事项
1. 概述:线程间通信解决什么问题
在多线程(任务)系统中,每个线程拥有独立的栈和调度上下文,但共享同一地址空间(无 MMU 的 RTOS)或各自独立的地址空间(Linux 进程)。线程之间要协作完成一项功能,必然需要交换信息,这就是线程间通信(Inter-Thread/Task Communication,常归入 IPC)。
需要先厘清三个经常被混为一谈的概念:
| 通信(Communication) | 传递「数据」或「事件内容」 | 消息队列、邮箱、管道 |
| 同步(Synchronization) | 协调「执行的先后顺序」 | 信号量、事件标志组、任务通知 |
| 互斥(Mutual Exclusion) | 保护「共享资源不被同时访问」 | 互斥锁、临界区、自旋锁 |
三者在工程实践中是交织使用的:消息队列本身既是通信也是同步(队列为空时接收方自然阻塞等待);共享内存必须配合互斥锁才安全;事件标志组只做同步不带数据,但往往配合共享缓冲使用。本文聚焦「通信」与「同步」中承担信息传递职责的机制;互斥机制属于「并发与竞争保护」范畴,另有专文讨论,本文仅在资源共享类机制中涉及其配合关系。
一个几乎所有 RTOS 内核对象共享的底层结构值得先记住:
内核对象 = 数据区(缓冲/位掩码/计数值) + 等待列表(等待该对象的阻塞任务队列,通常按优先级排序)
无论是队列、信号量还是事件组,当条件不满足时,任务被挂到该对象的等待列表上让出 CPU;当条件满足(消息入队、位置位、信号量释放)时,内核从等待列表中唤醒最高优先级任务,必要时触发任务调度。理解这一点,后面所有机制的行为(阻塞、超时、唤醒顺序、优先级继承与否)都能推导出来。
2. 机制全景与选型维度

按通信目的,常见机制可分为四大类:
(1)数据传递类 —— 通信双方要交换具体的数据内容:
- 消息队列(Message Queue):多对多、按值拷贝、FIFO 排队缓冲,是最通用的机制;
- 邮箱(Mailbox):一对一为主,常传指针或定长小消息,缓冲浅(语义因内核差异大);
- 管道 / 字节流(Pipe / Stream Buffer):无消息边界的字节流,适合串口数据、日志流;
- 环形缓冲 / 变长缓冲队列:字节级或变长消息的轻量缓冲。
(2)事件通知类 —— 只传递「发生了什么」,不带或只带极少量数据:
- 信号量(通知用法):可计数的事件通知(give/take);
- 事件标志组(Event Flags):32 个 bit 表达多种事件,支持 AND/OR 复合等待;
- 任务通知(Task Notification)/ 内嵌任务队列:直接挂在任务控制块上的定向通知,最快最省 RAM;
- 信号 / 回调(Signal):异步打断式通知(POSIX 信号、软中断信号)。
(3)资源共享类 —— 多线程共同读写同一块数据:
- 共享内存 / 全局变量:零拷贝、效率最高,但必须配合互斥锁、临界区等保护手段;
- 内存池 / 内存块 + 队列传递指针:「数据放池里、指针走队列」的经典组合。
(4)Linux 扩展类 —— 嵌入式 Linux 下进程与线程通用的 IPC:
- POSIX 消息队列、Unix Domain Socket、netlink、eventfd/signalfd、pipe/FIFO、shm/mmap、DBus/Binder 等。
选型时常用的评估维度:
| 数据量与形式 | 只通知一个事件,还是要传几十上百字节?定长还是变长? |
| 通信拓扑 | 一对一、多对一、多对多?是否需要广播? |
| 缓冲深度 | 生产快于消费时,允许积压多少?满了怎么办(阻塞/丢弃/覆盖)? |
| 时效性 | 能否容忍排队延迟?是否要紧急消息插队? |
| 拷贝开销 | 按值拷贝(安全、解耦)还是传指针(零拷贝、需管理生命周期)? |
| 上下文 | 发送方/接收方是否在 ISR 中?ISR 中只能调用 FromISR / no-wait 接口 |
| 资源占用 | 内核对象本身消耗多少 RAM?创建多少个合适? |
3. 数据传递类机制
3.1 消息队列(Message Queue)
机制原理
消息队列是最通用的线程间通信机制:发送方把一条消息按值拷贝进内核维护的队列,接收方按 FIFO 顺序取出。RTOS 中的队列通常是固定槽位数 × 固定消息尺寸的环形缓冲(便于确定性内存管理),Linux/POSIX 消息队列同样限定最大消息数和单消息最大长度。

核心行为特征:
- 解耦:发送方与接收方互不知道对方存在,可任意增减生产者/消费者;
- 缓冲:生产消费速率不匹配时由队列吸收抖动;
- 双向阻塞:队列空 → 接收方可阻塞(带超时);队列满 → 发送方可阻塞(带超时)或返回失败;
- 按值拷贝:消息入队后发送方的缓冲可立即复用,无生命周期管理负担(代价是 memcpy 开销);
- ISR 可用:中断里可以用 FromISR / no-wait 变体发送消息,是「ISR → 任务递延处理」的标准做法。
各系统接口与实现解析
| Linux(POSIX mq) | mqd_t | mq_open() | mq_send() / mq_timedsend() | mq_receive() / mq_timedreceive() | 按值,变长(≤ mq_msgsize) |
| Linux(SysV) | msgid | msgget() | msgsnd() | msgrcv() | 按值,变长,支持按类型选收 |
| FreeRTOS | QueueHandle_t | xQueueCreate() | xQueueSend() / xQueueSendToFront() / xQueueOverwrite();ISR 用 xQueueSendFromISR() | xQueueReceive() / xQueuePeek() | 按值,定长 |
| μC/OS-II | OS_EVENT(Q) | OSQCreate() | OSQPost() / OSQPostFront() / OSQPostOpt() | OSQPend() / OSQAccept() | 传指针(void*),不拷贝 |
| μC/OS-III | OS_Q | OSQCreate() | OSQPost() | OSQPend() | 传指针 + 携带消息大小 |
| RT-Thread | rt_mq_t | rt_mq_create() | rt_mq_send() / rt_mq_send_wait() / rt_mq_urgent()(插队头) | rt_mq_recv() | 按值,定长 |
| Zephyr | struct k_msgq | K_MSGQ_DEFINE() / k_msgq_alloc_init() | k_msgq_put() | k_msgq_get() / k_msgq_peek() | 按值,定长;另有 k_fifo(传指针) |
| ThreadX | TX_QUEUE | tx_queue_create() | tx_queue_send() / tx_queue_front_send() | tx_queue_receive() | 按值,1~16 个 32 位字定长 |
| NuttX | POSIX mq | mq_open() | mq_send() | mq_receive() | 按值,定长上限 |
| LiteOS | 队列 ID | LOS_QueueCreate() | LOS_QueueWrite() / LOS_QueueWriteCopy() | LOS_QueueRead() / LOS_QueueReadCopy() | 指针模式或拷贝模式可选 |
| AliOS Things | kqueue_t | krhino_queue_create()(aos 层 aos_queue_new()) | krhino_queue_send() / krhino_queue_back_send() | krhino_queue_recv() | 按值,定长(环形缓冲) |
实现层面的几个重要差异点:
最小示例(RT-Thread 风格,其他系统结构类似):
static rt_mq_t mq;
struct msg { rt_uint32_t type; rt_uint32_t value; };
void producer(void *p) {
struct msg m = { .type = 1, .value = 100 };
rt_mq_send(mq, &m, sizeof(m)); /* 按值拷贝入队 */
}
void consumer(void *p) {
struct msg m;
if (rt_mq_recv(mq, &m, sizeof(m), RT_WAITING_FOREVER) == RT_EOK)
handle(&m);
}
3.2 邮箱(Mailbox)
机制原理
「邮箱」在嵌入式领域的语义因内核差异极大,使用前先查手册定义。大体分为两种形态:
- 单格邮箱:只能存一封邮件(通常一个指针),新邮件覆盖或失败——μC/OS-II 的 Mbox;
- 排队邮箱:可存多封定长邮件,本质是「消息尺寸很小的队列」——RT-Thread 的 mb(邮件固定 4 字节,通常存指针)、Zephyr 的 k_mbox(支持发送方指定接收线程的握手式投递)。

邮箱的典型用法是**「轻量通知 + 指向共享数据块」**:数据本体放在内存池或全局缓冲中,邮箱里只传指针,兼顾零拷贝与线程安全。
各系统接口与实现解析
| μC/OS-II | OS_EVENT(Mbox) | OSMboxCreate() / OSMboxPost() / OSMboxPostOpt()(可广播) / OSMboxPend() / OSMboxAccept() | 单格,存 void*;PostOpt 可向所有等待者广播 |
| μC/OS-III | —— | 已取消独立 Mbox 对象,推荐用深度为 1 的 OS_Q 或内嵌任务消息队列 | —— |
| RT-Thread | rt_mailbox_t | rt_mb_create() / rt_mb_send() / rt_mb_send_wait() / rt_mb_recv() / rt_mb_urgent() | 多封排队,每封固定 4 字节(指针大小) |
| Zephyr | struct k_mbox | k_mbox_init() / k_mbox_put() / k_mbox_async_put() / k_mbox_get() / k_mbox_data_get() | 支持指定目标线程的投递-接收握手(发送方可阻塞到对方取走) |
| FreeRTOS | —— | 无独立邮箱;用长度 1 队列 + xQueueOverwrite() 模拟「最新值邮箱」 | —— |
| ThreadX | —— | 无独立邮箱;用深度 1 的 TX_QUEUE | —— |
| Linux / NuttX / LiteOS / AliOS | —— | 无独立邮箱概念,用消息队列实现 | —— |
实现要点:
- μC/OS-II Mbox 的 OSMboxPostOpt(…, OS_POST_OPT_BROADCAST) 是少见的广播能力:一条消息同时唤醒所有等待该邮箱的任务;
- Zephyr k_mbox 是同步式投递:k_mbox_put() 默认阻塞到接收方取走消息(rendezvous 语义),适合「必须确认对方收到」的场景;k_mbox_async_put() 则交给系统工作队列异步投递;
- RT-Thread 邮箱满了,rt_mb_send() 立即返回失败,而 rt_mb_send_wait() 允许发送方限时阻塞——这是它与 μC/OS 单格邮箱「满了只能失败」的关键区别。
3.3 管道与字节流(Pipe / Stream Buffer)
机制原理
消息队列传递的是「有边界的一条条消息」,而管道传递的是无边界的字节流——像文件一样 read/write,读者不感知写入的分包。它天然适合串口收发、日志输出、Shell 数据转发等流式场景。Linux 的管道是进程间 IPC 的元老;RTOS 中则以「流缓冲」形式出现。
各系统接口与实现解析
| Linux | 匿名管道 pipe() / pipe2();命名管道 mkfifo() | read() / write() / poll() | 半双工字节流,内核缓冲默认 64 KiB;FIFO 可在无亲缘进程间使用;全双工需求用 socketpair() |
| FreeRTOS | Stream Buffer / Message Buffer | xStreamBufferCreate() / xStreamBufferSend() / xStreamBufferReceive();Message Buffer 版本在流上加长度头 | 单写者、单读者(多写需外加互斥);极轻量、可 ISR 使用;触发级别可配 |
| Zephyr | struct k_pipe | k_pipe_init() / k_pipe_alloc_init() / k_pipe_put() / k_pipe_get() | 多线程安全的字节流管道,支持超时与「至少读够 N 字节」 |
| RT-Thread | 环形缓冲 + 设备框架 | rt_ringbuffer_init() / rt_ringbuffer_put() / rt_ringbuffer_get();DFS 下另有 pipe 设备 | ringbuffer 是无锁前提下的单生产单消费缓冲;串口设备内部即基于它 |
| NuttX | POSIX pipe / FIFO | pipe() / mkfifo() / read() / write() | 完整 POSIX 语义 |
| Linux 通用说明 | —— | —— | 线程间也能用管道(同进程内更常用 socketpair 或自建队列),但开销比纯内存队列大 |
| μC/OS-II/III、ThreadX、LiteOS | —— | 无内核级管道,用「环形缓冲 + 信号量」自行组合 | 见 3.4 |
行为要点:
- 阻塞语义:Linux 管道写满则写者阻塞(或 O_NONBLOCK 返回 EAGAIN),读空则读者阻塞;
- 消息边界:字节流没有边界,要传「包」需自定协议(长度头/分隔符)——FreeRTOS 的 Message Buffer 正是为此把长度头打进流里;
- 触发级别:FreeRTOS Stream Buffer 可设置「缓冲达到 N 字节才唤醒读者」,避免逐字节唤醒的抖动。
3.4 变长缓冲队列与环形缓冲(Ring Buffer)
机制原理
消息队列要求定长消息,但实际业务常要传变长数据(一条 AT 指令、一帧协议)。两条常见路线:
各系统接口与实现解析
| AliOS Things | kbuf_queue_t | krhino_buf_queue_create() / krhino_buf_queue_send() / krhino_buf_queue_recv() | 变长消息按值存入环形区,内核管理分包 |
| Zephyr | struct k_fifo / k_lifo / k_ringbuf | k_fifo_put() / k_fifo_get();ring_buf_put() / ring_buf_get() | k_fifo 传的是链表节点指针(零拷贝、要求节点头部预留内核字段);ring_buf 是裸字节环形区(默认无同步,需自配信号量;另有 ring_buf_item_* 带简单同步) |
| RT-Thread | rt_ringbuffer | rt_ringbuffer_put() / rt_ringbuffer_get() | 单生产单消费无锁;多线程需自配互斥/信号量 |
| FreeRTOS | Stream Buffer(见 3.3) | —— | 本质即带同步的环形缓冲 |
| LiteOS | 软件定时器/队列之外无专用对象 | —— | 用队列指针模式 + 内存池实现变长数据传递 |
| μC/OS-II/III、ThreadX | 无专用对象 | —— | 组合:内存池(OSMemGet / tx_block_allocate)申请块 → 队列传指针 → 用完归还 |
「内存池 + 队列传指针」组合是 RTOS 里传递大块/变长数据的经典范式,务必掌握:
发送方:MemPool.alloc() → 填充数据 → Queue.send(指针)
接收方:Queue.recv(指针) → 使用数据 → MemPool.free()
相比按值拷贝省一次 memcpy,但把缓冲区生命周期管理交给了应用:谁释放、泄漏了怎么办、ISR 里能否 alloc,都要设计清楚。
4. 事件通知类机制
4.1 信号量(通知用法)
机制原理
信号量有两个用法:计数资源(竞争保护范畴)和事件通知(本文范畴)。作为通知机制时:
- 二值信号量(Binary Semaphore)= 「事件发生了」的一次性门铃;
- 计数信号量 = 「事件发生了 N 次」的可累积计数(这是它相对事件标志组的核心优势——能计数)。
发送方 give/post,接收方 take/pend(带超时)。它不带任何数据,是成本最低的通信原语,也是 ISR → 任务唤醒的最常用手段。
各系统接口与实现解析
| FreeRTOS | xSemaphoreCreateBinary() / xSemaphoreCreateCounting() | xSemaphoreGive() | xSemaphoreTake() | xSemaphoreGiveFromISR() |
| μC/OS-II | OSSemCreate() | OSSemPost()(OS_POST_OPT_BROADCAST 可广播) | OSSemPend() / OSSemAccept() | 直接调 Post(μC/OS 无独立 FromISR 命名,ISR 中调非阻塞版) |
| μC/OS-III | OSSemCreate() | OSSemPost()(支持广播 opt) | OSSemPend() | 同上 |
| RT-Thread | rt_sem_create() | rt_sem_release() / rt_sem_release()(连续) | rt_sem_take() / rt_sem_trytake() | ISR 中直接 rt_sem_release()(该 API 本身 ISR 安全) |
| Zephyr | K_SEM_DEFINE() / k_sem_init() | k_sem_give() | k_sem_take() | k_sem_give() ISR 安全 |
| ThreadX | tx_semaphore_create() | tx_semaphore_put() | tx_semaphore_get() | tx_semaphore_put() ISR 安全(tx_semaphore_ceiling_put 有上限版) |
| NuttX | sem_init() | sem_post() | sem_wait() / sem_timedwait() | sem_post() 是异步信号安全的 |
| LiteOS | LOS_SemCreate() / LOS_BinarySemCreate() | LOS_SemPost() | LOS_SemPend() | ISR 中可 LOS_SemPost() |
| AliOS Things | krhino_sem_create() | krhino_sem_give() / krhino_sem_give_all()(广播) | krhino_sem_take() | give 系列 ISR 安全 |
| Linux(线程间) | sem_init(pshared=0) | sem_post() | sem_wait() / sem_timedwait() | 用户态无 ISR 概念;信号处理函数中 sem_post() 安全 |
要点:二值信号量会丢事件——连续 give 两次只等效一次(计数值封顶 1);要计数就用计数信号量。这决定了「每来一次中断都要被处理一次」的场景必须用计数型或队列,不能用二值型。
4.2 事件标志组(Event Flags)
机制原理
事件标志组用**一组 bit(通常 32 位)**表达多种事件:任意任务/ISR 把对应位置 1,等待方声明「我关心哪几位、要全部满足(AND)还是任一满足(OR)」,条件不满足则阻塞。它是「多路事件汇聚到一个任务」的标准方案(如通信任务同时等待:收到数据 | 发送完成 | 出错 | 退出请求)。

核心特性:
- 复合等待:AND/OR 两种逻辑,一个任务可同时等待多种事件的任意组合;
- 多等待者:同一标志组可被多个任务以不同位组合同时等待;
- 消费清零:可选「唤醒后自动清除已满足的位」(如 FreeRTOS xClearOnExit、RT-Thread RT_EVENT_FLAG_CLEAR),否则需手动清位,处理不当会反复唤醒;
- 不排队:同一位连续置位两次 = 一次。事件标志不能计数,要计数用信号量/队列。
各系统接口与实现解析
| FreeRTOS | EventGroupHandle_t | xEventGroupSetBits() / …FromISR() / xEventGroupClearBits() | xEventGroupWaitBits(bits, clearOnExit, waitForAll, timeout);另有 xEventGroupSync() 做任务间会合(rendezvous) | tick 数即位数(24 或 32 位) |
| μC/OS-II | OS_FLAG_GRP | OSFlagPost() | OSFlagPend()(WAIT_SET/CLR + ALL/ANY + consume opt)/ OSFlagAccept() | 位宽 8/16/32 可配 |
| μC/OS-III | OS_FLAG_GRP | OSFlagPost() | OSFlagPend() / OSFlagPendGetFlagsRdy() | 同 II,接口统一为 err 风格 |
| RT-Thread | rt_event_t | rt_event_send() | rt_event_recv(set, option=AND/OR+RT_EVENT_FLAG_CLEAR, timeout, &recved) | 32 位固定 |
| Zephyr | struct k_event | k_event_post()(保留其他位)/ k_event_set()(覆盖) | k_event_wait(events, reset, timeout) / k_event_wait_all() | 32 位;post 是累积式置位 |
| ThreadX | TX_EVENT_FLAGS_GROUP | tx_event_flags_set(TX_OR / TX_AND / *_CLEAR) | tx_event_flags_get(TX_OR / TX_AND / *_CLEAR, timeout) | set 本身就能选 AND/OR/清位语义,非常灵活 |
| NuttX | —— | 无原生事件组对象 | 用多个信号量或 sigwaitinfo 位掩码近似 | POSIX 没有对应原语 |
| LiteOS | EVENT_CB_S | LOS_EventWrite() / LOS_EventSet() | LOS_EventRead(mask, mode=AND/OR + CLEAR, timeout) | 25 个可用事件位 |
| AliOS Things | kevent_t | krhino_event_set() | krhino_event_get(flags, opt=AND/OR+CLEAR, timeout, &actual) | 32 位 |
| Linux | —— | 无内核事件组;用户态可用 eventfd 位掩码、条件变量+标志位模拟 | —— | —— |
实现注意(FreeRTOS 特例):xEventGroupSetBitsFromISR() 不是直接在 ISR 里置位,而是投递给守护任务(timer service task)代为置位——因此 ISR 置位到任务被唤醒之间存在一次队列传递延迟,对时延敏感的场合优先用任务通知。
4.3 任务通知与内嵌任务队列
机制原理
队列、事件组都是独立内核对象(要额外 RAM、要创建句柄)。而「任务通知」直接把通信载体嵌进任务控制块(TCB):每个任务自带一个 32 位通知值和状态,发送方指定目标任务直接改写其通知值并唤醒它。
优势:零额外对象、定向投递、速度最快(FreeRTOS 官方数据:比二值信号量快约 45%、省 RAM);劣势:只能一对一(通知属于目标任务),且通知值只有一个(32 位可兼作事件位图或计数)。
各系统对应机制
| FreeRTOS | Task Notification | xTaskNotifyGive() / vTaskNotifyGiveFromISR()(计数用法);xTaskNotify() / xTaskNotifyFromISR()(设值/设位用法);接收:ulTaskNotifyTake() / xTaskNotifyWait() | 可模拟二值/计数信号量、事件组、轻量邮箱 |
| μC/OS-III | 内嵌任务信号量 + 内嵌任务消息队列 | OSTaskSemPost() / OSTaskSemPend();OSTaskQPost() / OSTaskQPend() | 每个 TCB 内嵌一个信号量和一个消息队列,是 III 相对 II 的重要增强 |
| μC/OS-II | —— | 无,只能用独立信号量/队列 | —— |
| Zephyr | k_poll_signal | k_poll_signal_init() / k_poll_signal_raise() / 线程用 k_poll() 等待一组 signal/event/sem/fifo | k_poll 类似 Linux poll:一次等待多个对象的任意就绪,是 Zephyr 的多路复用方案 |
| RT-Thread | —— | 无内嵌任务通知;用信号量/事件组代替 | —— |
| ThreadX | —— | 无;用 tx_semaphore_* | —— |
| NuttX | POSIX 信号(见 4.4) | sigqueue() 可携带一个 sigval 定向发给线程 | 接近「带一个值的任务通知」 |
| LiteOS / AliOS | —— | 无独立任务通知;用事件/信号量 | —— |
工程经验:ISR → 特定任务 的唤醒链路上,FreeRTOS 任务通知、μC/OS-III 内嵌任务信号量是第一选择;只有在需要多对一汇聚或一对多广播时才退回到信号量/事件组。
4.4 信号与异步回调(Signal)
机制原理
前面所有机制都是同步等待——接收方主动调用等待接口。信号(Signal)则是异步打断:内核(或另一线程)向目标线程投递一个信号,目标线程执行到合适时机时被强制转入信号处理函数。它是 POSIX 的传统机制,在嵌入式 RTOS 中只有部分系统支持。
各系统支持情况
| Linux | 完整 POSIX 信号 | sigaction() 注册处理函数;kill() / tkill() / pthread_kill() 定向发送;sigwait() 同步等待;signalfd() 把信号变成可 poll 的文件描述符(推荐的多线程用法) |
| NuttX | 完整 POSIX 信号(含 sigqueue() 带值实时信号) | sigaction() / sigqueue() / sigwaitinfo() |
| RT-Thread | 提供 POSIX 风格软中断信号组件 | rt_signal_install() / rt_signal_kill()(发信号) / rt_signal_wait()(同步等待,类似 sigwait) |
| Zephyr | 无 POSIX signal;k_poll_signal 提供「对象级信号」(见 4.3) | —— |
| FreeRTOS / μC/OS / ThreadX / LiteOS / AliOS | 无信号概念 | 以回调注册(如定时器回调、驱动回调)实现异步通知 |
要点:信号处理函数运行在被打断线程的上下文里,可调用函数受严格限制(异步信号安全函数集);多线程程序更推荐 sigwait / signalfd 把异步信号转成同步事件处理。
5. 资源共享类机制
机制原理
在无 MMU 的 RTOS(FreeRTOS、μC/OS、RT-Thread 非 smart 版、Zephyr 默认、ThreadX、LiteOS-M、AliOS、NuttX flat build)中,所有任务共享同一物理地址空间——任何全局变量、静态缓冲区天然就是「共享内存」,通信可以简单到「写一个全局结构体」。但正因为谁都能碰,必须配合互斥机制防止撕裂访问:
- 互斥锁(FreeRTOS xSemaphoreCreateMutex、RT-Thread rt_mutex_*、Zephyr k_mutex_*、ThreadX tx_mutex_*、μC/OS OSMutex*、LiteOS LOS_Mux*、AliOS krhino_mutex_*)——支持优先级继承;
- 临界区(关中断 taskENTER_CRITICAL / rt_enter_critical / k_sched_lock)——只适合极短操作;
- 读写顺序约定 + 原子变量(无锁场景)。
Linux / RT-Smart / NuttX kernel build 等有 MMU 的环境:线程间仍共享进程地址空间(全局变量直接用);进程间才需要显式共享内存:
| POSIX 共享内存 | shm_open() + mmap() + ftruncate() | 现代推荐,配合 sem_open 命名信号量做同步 |
| System V 共享内存 | shmget() / shmat() / shmdt() | 传统接口,key 管理较绕 |
| 文件映射 | mmap(MAP_SHARED) | 共享内容可持久化到文件 |
内存池是共享资源类通信的基础设施,常与队列搭配(见 3.4):RT-Thread rt_mp_create/rt_mp_alloc、ThreadX tx_block_pool_create/tx_block_allocate、Zephyr k_mem_slab_alloc / k_heap_alloc、μC/OS-III OSMemGet/OSMemPut、LiteOS LOS_Membox*、AliOS krhino_mblk_pool_*、FreeRTOS 则以静态分配 + heap_1~5 策略提供。
6. Linux 扩展机制
嵌入式 Linux(网关、IPC 摄像头、座舱域控等场景)下,线程间通信往往与进程间通信共用一套设施,值得单独梳理:
| POSIX 消息队列 | mq_open/mq_send/mq_receive | ✔ | 内核维护、按值、带优先级(mq_receive 先收最高优先级消息),可跨进程 |
| SysV 消息队列 | msgget/msgsnd/msgrcv | ✔ | 支持按 mtype 选择性接收(单队列多路复用) |
| Unix Domain Socket | socket(AF_UNIX, SOCK_DGRAM/STREAM) / socketpair() | ✔ | 全双工、可传文件描述符(SCM_RIGHTS)、可 poll/epoll 多路复用,工程上最常用 |
| netlink | socket(AF_NETLINK, …) | 用户态↔内核 | 与内核子系统通信的标准通道(uevent、路由、自定义) |
| 管道 / FIFO | pipe() / mkfifo() | ✔ | 半双工字节流(见 3.3) |
| eventfd | eventfd() + read()/write() | ✔ | 64 位计数事件,可并入 epoll,是「可 poll 的信号量」,线程池唤醒标配 |
| signalfd | signalfd() | ✔ | 把信号转成 fd,统一进事件循环 |
| POSIX 共享内存 | shm_open() + mmap() | 进程间 | 线程间直接用全局内存即可 |
| 条件变量 | pthread_cond_wait/signal/broadcast | ✔(线程间主力) | 与 pthread_mutex 配对,用户态自建消息队列的标准同步件 |
| D-Bus / Binder | 库级 IPC | ✔ | 服务化架构(Android Binder、嵌入式 D-Bus),带接口描述与权限模型 |
工程经验:Linux 用户态多线程程序中,「自建队列 + pthread_mutex + pthread_cond」 和 「eventfd + 线程安全环形队列」 是线程间通信的两大主流范式;需要跨进程或多路复用时则转向 Unix Socket + epoll。
7. 跨系统接口对比总表
下表汇总十个系统对各通信机制的原生支持情况(● 原生支持 / ◐ 需组合或变体实现 / — 不支持):
| 消息队列(按值) | ● mq/msg | ● Queue | ◐ 传指针 | ◐ 传指针 | ● rt_mq | ● k_msgq | ● tx_queue | ● mq | ● 队列(双模式) | ● queue |
| 紧急消息插队 | ◐ 优先级 | ● ToFront | ● PostFront | — | ● urgent | — | ● front_send | ◐ 优先级 | — | ◐ back_send |
| 邮箱 | ◐ 队列模拟 | ◐ 长度1队列+覆盖 | ● Mbox 单格 | ◐ 用 Q 代替 | ● rt_mb 多封 | ● k_mbox 握手 | ◐ 深度1队列 | ◐ | ◐ | ◐ |
| 事件标志组 | ◐ eventfd/cond | ● EventGroup | ● FlagGrp | ● FlagGrp | ● rt_event | ● k_event | ● event_flags | ◐ | ● Event | ● event |
| 任务通知/内嵌队列 | ◐ sigqueue | ● TaskNotify | — | ● TaskSem/TaskQ | ◐ | ◐ k_poll_signal | ◐ | ◐ sigqueue | ◐ | ◐ |
| 计数/二值信号量 | ● sem | ● | ● | ● | ● | ● k_sem | ● | ● | ● | ● |
| 信号(异步) | ● signal | — | — | — | ● rt_signal | ◐ k_poll_signal | — | ● signal | — | — |
| 管道/字节流 | ● pipe | ● StreamBuffer | ◐ 自建 | ◐ 自建 | ◐ ringbuffer | ● k_pipe | ◐ | ● pipe | ◐ | ◐ |
| 变长缓冲队列 | ◐ | ● MessageBuffer | ◐ | ◐ | ◐ | ◐ ring_buf | ◐ | ◐ | ◐ | ● buf_queue |
| 共享内存 | ● shm/mmap | ● 全局地址空间 | ● | ● | ●(smart 另有进程隔离) | ● | ● | ●(flat)/ shm(kernel build) | ● | ● |
| Unix Socket / netlink | ● | — | — | — | ◐ lwip socket | ◐ 网络栈 | — | ● socket | ◐ | ◐ |
8. ISR(中断上下文)与任务通信的特殊性
ISR 与任务间的通信是最常用也最易踩坑的场景,规则可归纳为四条:
9. 选型建议与典型场景
| ISR 通知单一任务(如 DMA 完成) | 任务通知 / 内嵌任务信号量(FreeRTOS TaskNotify、μC/OS-III TaskSem);无此机制用二值信号量 | 定向、最快、零额外对象 |
| ISR 递延数据给任务(串口、ADC 采样块) | 消息队列(按值)或「内存池+队列传指针」 | 带缓冲、可累积、天然解耦 |
| 每帧数据都要处理、不许丢帧 | 计数信号量 + 环形缓冲,或足够深的消息队列 | 二值信号量/事件标志会丢次数 |
| 只关心最新值(传感器采样刷新) | 长度 1 队列 + xQueueOverwrite / 全局变量 + 读写保护 | 「丢旧保新」语义 |
| 一个任务等待多路事件(通信任务主循环) | 事件标志组(AND/OR 等待);Zephyr 用 k_poll;Linux 用 poll/epoll + eventfd | 单点阻塞、多源唤醒 |
| 任务间传大块/变长数据 | 内存池 + 队列传指针(零拷贝);AliOS buf_queue(变长按值);Linux 用 socket 或 shm | 避免大 memcpy |
| 多对多事件分发(总线式) | 消息队列 + 订阅分发任务;Linux 用 D-Bus / Unix Socket 组 | 队列本身不广播,需分发者模式 |
| 严格一对一交付确认(命令-应答) | Zephyr k_mbox_put(握手式);一般系统用「请求队列 + 应答队列」双队列 | 需要 rendezvous 语义 |
| 跨核 / AMP 核间通信 | 共享内存 + mailbox 硬件中断(OpenAMP/rpmsg 框架) | 超出单内核对象范畴,属平台层 |
几条通用原则:
10. 常见误区与注意事项
附录:各系统内核对象命名速查
| FreeRTOS | xQueue* | —(长度1队列) | xEventGroup* | xSemaphore* | xSemaphoreCreateMutex | pvPortMalloc / 静态分配 |
| μC/OS-II | OSQ* | OSMbox* | OSFlag* | OSSem* | OSMutex* | OSMem* |
| μC/OS-III | OSQ* / OSTaskQ* | — | OSFlag* | OSSem* / OSTaskSem* | OSMutex* | OSMem* |
| RT-Thread | rt_mq_* | rt_mb_* | rt_event_* | rt_sem_* | rt_mutex_* | rt_mp_* |
| Zephyr | k_msgq_* / k_fifo_* | k_mbox_* | k_event_* | k_sem_* | k_mutex_* | k_mem_slab_* / k_heap_* |
| ThreadX | tx_queue_* | —(深度1队列) | tx_event_flags_* | tx_semaphore_* | tx_mutex_* | tx_block_pool_* / tx_byte_pool_* |
| NuttX | mq_* | — | — | sem_* | pthread_mutex_* | malloc / 静态 |
| LiteOS | LOS_Queue* | — | LOS_Event* | LOS_Sem* | LOS_Mux* | LOS_Membox* / LOS_Mem* |
| AliOS Things | krhino_queue_* | — | krhino_event_* | krhino_sem_* | krhino_mutex_* | krhino_mblk_pool_* / krhino_mm_* |
| Linux | mq_* / msg* | — | —(eventfd 近似) | sem_* | pthread_mutex_* | malloc / shm_open |
网硕互联帮助中心![【LangGraph实战】《LangGraph实战》_80.[第4章 交互体验] 人机环路四大模式:审批、编辑、反馈、对话-网硕互联帮助中心](https://www.wsisp.com/helps/wp-content/uploads/2026/07/20260727013250-6a66b542d0d46-220x150.png)



评论前必须登录!
注册