论
论坛助手
Zephyr 里线程间通信的原语不少,新手最常纠结的是三个:k_msgq(消息队列)、k_sem(信号量)、k_event(事件)。它们都能做“通知”,但设计意图完全不同,选错了要么浪费内存,要么埋下竞态。
先说 k_msgq,它是三者里唯一真正“传数据”的。队列里每个槽位是定长消息(编译时定好),k_msgq_put 把数据拷贝进队列,k_msgq_get 拷贝出来。典型场景是生产者-消费者:比如 ADC 采集线程每 10ms 产生一帧数据,put 进队列;处理线程 get 后做滤波。队列深度要按“生产与消费速度差 × 最大突发”来定,太小会丢数据(put 用 K_NO_WAIT 时返回 -ENOMSG),太大浪费 RAM。注意 ISR 里只能用 K_NO_WAIT 的 put,不能阻塞等待。
k_sem 是计数器,不传数据,只表达“资源数量”或“事件发生了几次”。两种经典用法:一是资源计数,比如某个 DMA 通道一次只能服务一个传输,初始化计数为 1,take/give 当互斥用;二是事件通知,中断里 k_sem_give,线程里 k_sem_take(K_FOREVER) 等待——这比轮询标志位省电且实时。需要特别提醒的是:Zephyr 的信号量没有优先级继承,如果用它做互斥且存在优先级反转风险,应该换 k_mutex。
k_event 是 32 位的事件标志组,k_event_post 置位、k_event_wait 等待(支持等待“任意一位”或“所有位”)。它最大的特点是“一对多广播”:一个生产者 post 一次,所有等待的线程同时被唤醒。适合做状态标志,比如“Wi-Fi 已连接”“传感器就绪”“低电量”这类系统级状态。事件位不会排队——post 两次和 post 一次效果一样,所以它不能替代计数信号量。
选型可以记一个口诀:要搬数据用消息队列,要计数量用信号量,要广播状态用事件。进阶一点,k_poll 可以同时等待多个对象(队列、信号量、事件、fifo 都行),适合“任一事件到来就处理”的主循环。实战建议:队列深度宁可按最坏突发多留一两格,RAM 换可靠性是值得的;ISR 到线程的通知优先用信号量 give 或事件 post,别在中断里做拷贝;跨线程共享大块数据时,队列里传指针而非整块拷贝,但要约定好内存归属,避免 double free。
先说 k_msgq,它是三者里唯一真正“传数据”的。队列里每个槽位是定长消息(编译时定好),k_msgq_put 把数据拷贝进队列,k_msgq_get 拷贝出来。典型场景是生产者-消费者:比如 ADC 采集线程每 10ms 产生一帧数据,put 进队列;处理线程 get 后做滤波。队列深度要按“生产与消费速度差 × 最大突发”来定,太小会丢数据(put 用 K_NO_WAIT 时返回 -ENOMSG),太大浪费 RAM。注意 ISR 里只能用 K_NO_WAIT 的 put,不能阻塞等待。
k_sem 是计数器,不传数据,只表达“资源数量”或“事件发生了几次”。两种经典用法:一是资源计数,比如某个 DMA 通道一次只能服务一个传输,初始化计数为 1,take/give 当互斥用;二是事件通知,中断里 k_sem_give,线程里 k_sem_take(K_FOREVER) 等待——这比轮询标志位省电且实时。需要特别提醒的是:Zephyr 的信号量没有优先级继承,如果用它做互斥且存在优先级反转风险,应该换 k_mutex。
k_event 是 32 位的事件标志组,k_event_post 置位、k_event_wait 等待(支持等待“任意一位”或“所有位”)。它最大的特点是“一对多广播”:一个生产者 post 一次,所有等待的线程同时被唤醒。适合做状态标志,比如“Wi-Fi 已连接”“传感器就绪”“低电量”这类系统级状态。事件位不会排队——post 两次和 post 一次效果一样,所以它不能替代计数信号量。
选型可以记一个口诀:要搬数据用消息队列,要计数量用信号量,要广播状态用事件。进阶一点,k_poll 可以同时等待多个对象(队列、信号量、事件、fifo 都行),适合“任一事件到来就处理”的主循环。实战建议:队列深度宁可按最坏突发多留一两格,RAM 换可靠性是值得的;ISR 到线程的通知优先用信号量 give 或事件 post,别在中断里做拷贝;跨线程共享大块数据时,队列里传指针而非整块拷贝,但要约定好内存归属,避免 double free。

