3. 消息队列发送与接收:msgQSend与msgQReceive函数详解
好,咱们直接进入正题。消息队列的核心操作,说白了就两个:发和收。在VxWorks里,对应的就是msgQSend和msgQReceive。这两个函数我用了十几年,每次带新人时都会说:搞懂它们,你就掌握了任务间通信的一半。
3.1 msgQSend:把消息扔进队列
先看函数原型,我习惯把它记成「往队列里塞东西」:
STATUS msgQSend
(
MSG_Q_ID msgQId, /* 队列ID,创建时拿到的 */
char * buffer, /* 要发送的消息内容 */
UINT nBytes, /* 消息长度,单位是字节 */
int timeout, /* 超时时间,单位是tick */
int priority /* 发送优先级,MSG_PRI_NORMAL或MSG_PRI_URGENT */
)
参数不多,但每个都有讲究。我一个个说。
msgQId:这个不用多解释,就是msgQCreate返回的那个句柄。丢了它,你就找不到队列了。
buffer和nBytes:注意,这里传的是消息内容的指针,不是消息本身。VxWorks会把buffer指向的数据拷贝到队列内部。所以发送完后,你原来的buffer可以随便用,不影响队列里的消息。
⚠️ 我曾经踩过的坑:消息长度不能超过创建队列时设定的maxMsgLength。否则函数直接返回ERROR,errno会被设为S_msgQLib_MSG_TOO_BIG。我早期有个项目,结构体定义改了但队列没重建,结果消息发不出去,查了半天才发现是长度超了。
timeout:超时机制,这个值得好好说说。
3.2 超时机制:等还是不等?
超时参数决定了当队列满时,发送任务的行为。有三种情况:
| timeout值 | 行为 | 适用场景 |
|---|---|---|
| NO_WAIT (0) | 队列满则立即返回ERROR | 实时性要求极高,不能阻塞 |
| WAIT_FOREVER (-1) | 一直等到队列有空位 | 消息必须发出,可以无限等 |
| 正数(tick数) | 等待指定时间,超时返回ERROR | 大部分实际场景 |
我个人习惯,能不阻塞就不阻塞。你想想看,一个实时系统里,任务阻塞在发送上,万一接收端处理慢了,整个系统都可能被拖垮。所以我一般用NO_WAIT,或者给一个很短的超时。
举个例子,我在做数据采集系统时,传感器数据每秒来1000次。如果队列满了,说明接收端处理不过来。这时候与其死等,不如丢掉旧数据:
if (msgQSend(dataQueue, (char *)&sensorData, sizeof(sensorData),
NO_WAIT, MSG_PRI_NORMAL) == ERROR)
{
/* 队列满了,丢掉这包数据,记录一下 */
logMsg("Data dropped! Queue full.\n", 0,0,0,0,0);
}
嗯,这里要注意:NO_WAIT时如果队列满,函数会立即返回ERROR,但不会阻塞当前任务。这对高实时性场景非常友好。
3.3 优先级发送:紧急消息插队
最后一个参数priority,取值只有两个:
- MSG_PRI_NORMAL (0):正常优先级,消息排到队尾
- MSG_PRI_URGENT (1):紧急优先级,消息插到队首
说白了,就是让紧急消息「加塞」。我做过一个飞控系统,控制指令必须优先于遥测数据。控制指令用MSG_PRI_URGENT发送,遥测数据用MSG_PRI_NORMAL。这样即使队列里积压了100条遥测数据,控制指令一来,接收端立刻就能拿到。
💡 我的建议:优先级发送别滥用。如果所有消息都用URGENT,那就没有紧急可言了。我一般只在「丢了会出大事」的消息上用紧急优先级,比如急停指令、故障告警。
3.4 msgQReceive:从队列里取消息
接收函数,就是把消息从队列里拿出来:
int msgQReceive
(
MSG_Q_ID msgQId, /* 队列ID */
char * buffer, /* 存放消息的缓冲区 */
UINT maxNBytes, /* 缓冲区大小 */
int timeout /* 超时时间 */
)
返回值是实际接收到的消息长度(字节数)。如果超时或出错,返回ERROR。
这里有个关键点:maxNBytes必须大于等于队列中消息的实际长度。否则消息会被截断,而且函数会返回ERROR,errno设为S_msgQLib_BUFFER_TOO_SMALL。我见过有人图省事,缓冲区只开10个字节,结果消息长度是20,每次都收不全。
接收的超时机制和发送类似:
- NO_WAIT:队列空就立即返回,不阻塞
- WAIT_FOREVER:死等,直到有消息
- 正数:等一会儿,没有就拉倒
我一般这么用接收:
char msgBuf[256];
int msgLen;
msgLen = msgQReceive(cmdQueue, msgBuf, sizeof(msgBuf), WAIT_FOREVER);
if (msgLen == ERROR)
{
/* 出错了,处理错误 */
logMsg("msgQReceive failed, errno = 0x%x\n", errnoGet(),0,0,0,0);
}
else
{
/* 正常处理消息 */
processCommand(msgBuf, msgLen);
}
为什么这里用WAIT_FOREVER?因为这是一个命令处理任务,没有命令来的时候它本来就该等着。等到了就处理,处理完继续等。这种模式在嵌入式系统里很常见。
3.5 避坑指南:我踩过的几个雷
最后,分享几个实战中容易出问题的地方:
- 缓冲区大小不匹配:发送和接收的缓冲区大小要一致。我曾经在A任务里发一个结构体,B任务里用不同大小的缓冲区收,结果数据错位,查了两天才发现。
- 超时时间单位:VxWorks的超时单位是系统tick,不是毫秒!默认一个tick是16.67ms(60Hz)。如果你写
timeout=5,实际等了83ms。要精确控制时间,用sysClkRateGet()换算。 - 任务优先级反转:如果高优先级任务在等低优先级任务发消息,而低优先级任务被中等优先级任务抢占了,那就出事了。这种情况可以用优先级继承或使用消息队列的紧急发送来缓解。
- 队列溢出:创建队列时
maxMsgs设得太小,高负载下消息会丢。我一般根据最坏情况估算,再留20%余量。
好了,msgQSend和msgQReceive就这些。说白了,一个发一个收,加上超时和优先级控制,就能应对大部分场景。下一节我们聊聊管道,那个东西用起来更灵活,但坑也更多。