4. 任务间同步与通信:信号量、消息队列、管道与套接字、事件机制
各位同学,咱们今天聊点实在的。任务间同步与通信,说白了就是让多个任务能好好“说话”,别打架。我在VxWorks上摸爬滚打这么多年,见过太多因为同步没做好导致的“血案”——系统死锁、数据错乱、任务饿死……嗯,今天咱们就把这些坑一个个填上。
4.1 信号量:最基础的同步工具
信号量,我习惯叫它“交通信号灯”。它控制着任务对共享资源的访问权。VxWorks里主要有三种:二进制、计数、互斥。
4.1.1 二进制信号量
二进制信号量只有两个状态:0(不可用)和1(可用)。它最适合做“事件通知”。
典型场景:一个任务等待某个硬件中断发生,另一个任务处理中断数据。
// 创建二进制信号量
SEM_ID semBinary = semBCreate(SEM_Q_FIFO, SEM_EMPTY);
// 任务A:等待中断
taskWait() {
semTake(semBinary, WAIT_FOREVER); // 阻塞等待
// 处理数据...
}
// ISR:通知任务
isrHandler() {
semGive(semBinary); // 释放信号量
}
4.1.2 计数信号量
计数信号量维护一个计数器,允许多个任务同时访问有限数量的资源。比如一个连接池有5个连接,最多允许5个任务同时使用。
// 创建计数信号量,初始值5,最大值5
SEM_ID semCount = semCCreate(SEM_Q_FIFO, 5);
// 任务获取连接
taskUseConnection() {
semTake(semCount, WAIT_FOREVER);
// 使用连接...
semGive(semCount); // 归还
}
你想想看,如果没有计数信号量,5个任务同时抢一个连接,那不乱套了?
4.1.3 互斥信号量
互斥信号量是二进制信号量的“升级版”。它解决了优先级反转问题——这是实时系统里的大坑。
互斥信号量支持优先级继承:当低优先级任务持有锁时,它会临时“继承”等待该锁的最高优先级任务的优先级,从而不被中间优先级的任务抢占。
// 创建互斥信号量(带优先级继承)
SEM_ID semMutex = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE);
| 类型 | 适用场景 | 注意事项 |
|---|---|---|
| 二进制信号量 | 事件通知、简单互斥 | 不支持优先级继承 |
| 计数信号量 | 资源池管理 | 注意最大计数值 |
| 互斥信号量 | 保护共享资源 | 支持优先级继承,但开销稍大 |
4.2 消息队列:任务间的“快递员”
消息队列(Message Queue)是VxWorks里最常用的通信方式。它允许任务之间发送和接收消息,消息可以是任意长度的数据。
我个人习惯用消息队列做“生产者-消费者”模式。比如一个任务采集传感器数据,另一个任务处理数据并写入日志。
// 创建消息队列,最多10条消息,每条最大100字节
MSG_Q_ID msgQ = msgQCreate(10, 100, MSG_Q_FIFO);
// 生产者任务
taskProducer() {
char buffer[100];
while(1) {
// 采集数据...
msgQSend(msgQ, buffer, 100, WAIT_FOREVER, MSG_PRI_NORMAL);
}
}
// 消费者任务
taskConsumer() {
char buffer[100];
while(1) {
msgQReceive(msgQ, buffer, 100, WAIT_FOREVER);
// 处理数据...
}
}
4.3 管道与套接字:跨任务/跨系统通信
4.3.1 管道(Pipe)
管道本质上是一个特殊的消息队列,但它使用文件描述符接口。这意味着你可以用read()/write()来操作它,就像操作文件一样。
为什么要有管道?说白了,就是为了兼容POSIX接口。如果你想把Linux上的代码移植到VxWorks,管道能省不少事。
// 创建管道
int fds[2];
pipe(fds);
// 任务A写入
write(fds[1], "hello", 5);
// 任务B读取
char buf[10];
read(fds[0], buf, 5);
4.3.2 套接字(Socket)
套接字用于网络通信。VxWorks支持标准的BSD套接字接口。我在一个分布式控制系统中用过——多个VxWorks节点通过UDP广播同步状态。
// UDP接收端
int sock = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sock, (struct sockaddr*)&addr, sizeof(addr));
char buf[1024];
recvfrom(sock, buf, 1024, 0, NULL, NULL);
4.4 事件(Event)机制:轻量级同步
事件机制是VxWorks的特色之一。它允许任务等待一组事件中的任意一个或多个发生。每个任务有32个事件位(0-31)。
事件机制比信号量更灵活。比如一个任务需要等待“数据就绪”或“超时”或“错误”中的任意一个发生,用事件就非常方便。
// 任务等待事件
UINT32 events;
eventReceive(0x07, EVENTS_WAIT_ANY, WAIT_FOREVER, &events);
if (events & 0x01) {
// 数据就绪
} else if (events & 0x02) {
// 超时
} else if (events & 0x04) {
// 错误
}
为什么会用事件而不是信号量?你想想看,如果用信号量实现“等待多个条件”,你得创建多个信号量,然后一个个去取,代码会变得很啰嗦。事件机制一个调用就搞定了。
好了,这一章的内容就这些。信号量、消息队列、管道、套接字、事件——每个工具都有自己的用武之地。选对工具,你的系统才能跑得稳、跑得快。下一章咱们聊聊内存管理,那又是另一番天地了。