LOADING
1221 字
6 分钟
ChibiOS/RT 同步机制

ChibiOS/RT 提供了丰富的同步原语,涵盖从简单的信号量到复杂的事件驱动机制。本文详细讲解每种同步机制的使用方法和适用场景。

信号量(Semaphore)#

信号量是 ChibiOS/RT 中最基础的同步机制,用于线程间的同步和资源计数。

初始化#

semaphore_t sem;
chSemInit(&sem, 5); /* 初始计数值为 5 */

基本操作#

/* 等待信号量(阻塞) */
chSemWait(&sem);
/* 发送信号量 */
chSemSignal(&sem);
/* 尝试获取信号量(非阻塞) */
msg_t result = chSemTryWait(&sem);

超时等待#

/* 带超时的等待,超时返回 MSG_TIMEOUT */
msg_t result = chSemWaitTimeout(&sem, MS2ST(100));
if (result == MSG_TIMEOUT) {
/* 超时处理 */
}

二值信号量#

初始值为 1 的信号量即为二值信号量,用于互斥访问共享资源:

semaphore_t bin_sem;
chSemInit(&bin_sem, 1); /* 二值信号量 */

计数信号量#

初始值大于 1 的信号量,可用于资源池管理:

#define MAX_RESOURCES 4
semaphore_t pool_sem;
chSemInit(&pool_sem, MAX_RESOURCES);
void acquire_resource(void) {
chSemWait(&pool_sem); /* 申请资源 */
/* 使用资源 */
}
void release_resource(void) {
chSemSignal(&pool_sem); /* 释放资源 */
}

互斥锁(Mutex)#

互斥锁用于保护临界区,ChibiOS/RT 实现了带优先级继承的递归互斥锁。

基本用法#

mutex_t mtx;
chMtxObjectInit(&mtx);
void critical_section(void) {
chMtxLock(&mtx);
/* 临界区代码 */
chMtxUnlock(&mtx);
}

递归互斥#

同一线程可以多次获取同一把锁:

void nested_lock(void) {
chMtxLock(&mtx);
/* 第一次获取 */
chMtxLock(&mtx);
/* 递归获取成功 */
chMtxUnlock(&mtx);
chMtxUnlock(&mtx); /* 两次 unlock 对应两次 lock */
}

优先级继承#

当高优先级线程被低优先级线程持有的互斥锁阻塞时,内核会临时提升低优先级线程的优先级,避免优先级反转问题。

/* 内核自动处理优先级继承,无需额外配置 */
static void high_prio_thread(void *arg) {
(void)arg;
chMtxLock(&mtx); /* 如果锁被低优先级线程持有,会触发优先级继承 */
/* 临界区 */
chMtxUnlock(&mtx);
}
static void low_prio_thread(void *arg) {
(void)arg;
chMtxLock(&mtx); /* 持有锁 */
/* 长时间操作 */
chMtxUnlock(&mtx);
}

条件变量(Condition Variable)#

条件变量用于线程间基于条件的同步等待。

初始化#

condvar_t cv;
chCondObjectInit(&cv);

等待和通知#

mutex_t cv_mtx;
condvar_t cv;
void producer_thread(void *arg) {
(void)arg;
chMtxLock(&cv_mtx);
/* 准备数据 */
chCondSignal(&cv); /* 唤醒一个等待线程 */
chMtxUnlock(&cv_mtx);
}
void consumer_thread(void *arg) {
(void)arg;
chMtxLock(&cv_mtx);
chCondWait(&cv); /* 等待条件满足 */
/* 处理数据 */
chMtxUnlock(&cv_mtx);
}

广播唤醒#

/* chCondBroadcast 唤醒所有等待线程 */
void notify_all(void) {
chMtxLock(&cv_mtx);
chCondBroadcast(&cv);
chMtxUnlock(&cv_mtx);
}

事件(Event)#

事件机制允许线程等待一组事件标志的组合。

初始化#

event_source_t es;
chEvtObjectInit(&es);

注册监听#

event_listener_t el;
void listener_thread(void *arg) {
(void)arg;
/* 注册监听,关注 EVT1 和 EVT2 */
chEvtRegister(&es, &el, EVT1 | EVT2);
while (1) {
eventmask_t events = chEvtWaitAny(ALL_EVENTS);
if (events & EVT1) {
/* 处理事件 1 */
}
if (events & EVT2) {
/* 处理事件 2 */
}
}
}

发送事件#

/* 向事件源发送信号 */
void send_event(void) {
chEvtSignal(&es, EVT1);
}

广播事件#

/* 广播事件给所有监听者 */
void broadcast_event(void) {
chEvtBroadcast(&es, EVT3);
}

邮箱(Mailbox)#

邮箱是有界的消息队列,用于线程间传递固定大小的消息。

初始化#

#define MAILBOX_SIZE 8
msg_t mb_buffer[MAILBOX_SIZE];
mailbox_t mb;
chMBObjectInit(&mb, mb_buffer, MAILBOX_SIZE);

发送和接收#

/* 发送消息 */
void producer(void) {
chMBPost(&mb, 0x1234, MS2ST(50)); /* 带超时 */
}
/* 接收消息 */
void consumer(void) {
msg_t msg;
if (chMBFetch(&mb, &msg, TIME_INFINITE) == MSG_OK) {
/* 处理消息 msg */
}
}

非阻塞操作#

/* 非阻塞发送 */
msg_t result = chMBPostTimeout(&mb, data, TIME_IMMEDIATE);
/* 非阻塞接收 */
msg_t result = chMBFetchTimeout(&mb, &msg, TIME_IMMEDIATE);

管道(Pipe)#

管道提供面向字节流的通信机制,与邮箱不同,管道传输的是字节序列。

初始化#

#define PIPE_SIZE 128
uint8_t pipe_buffer[PIPE_SIZE];
pipe_t pipe;
chPipeObjectInit(&pipe, pipe_buffer, PIPE_SIZE);

写入和读取#

/* 写入字节流 */
void writer(void) {
const uint8_t data[] = {0x01, 0x02, 0x03, 0x04};
chPipeWrite(&pipe, data, sizeof(data));
}
/* 读取字节流 */
void reader(void) {
uint8_t buf[32];
size_t n = chPipeRead(&pipe, buf, sizeof(buf));
/* 处理 n 个字节 */
}

查询可用数据#

size_t available = chPipeGetFull(&pipe);
if (available >= 10) {
/* 有足够数据可读 */
}

同步机制选择指南#

机制适用场景特点
信号量资源计数、线程同步轻量、灵活、无优先级继承
互斥锁保护临界区递归、优先级继承
条件变量基于条件的等待需配合互斥锁使用
事件多条件等待、异步通知事件标志位、广播支持
邮箱固定大小消息传递有界队列、超时控制
管道字节流通信面向字节、可查询长度

选择建议#

  • 简单资源同步 → 信号量
  • 临界区保护 → 互斥锁
  • 生产者-消费者 → 邮箱或管道
  • 多事件等待 → 事件机制
  • 复杂条件同步 → 条件变量 + 互斥锁

总结#

ChibiOS/RT 的同步机制覆盖了嵌入式系统中常见的各种同步需求。信号量和互斥锁是使用最广泛的基础原语,事件机制适合复杂的异步场景,邮箱和管道则解决了线程间数据传递的问题。合理选择和组合这些同步原语,是构建可靠实时系统的关键。

ChibiOS/RT 同步机制
/posts/chibios-rt-synchronization/
作者
JJZBQA
发布于
2024-12-18
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时