2. 消息队列创建与删除:msgQCreate()与msgQDelete()函数详解

消息队列,说白了就是任务之间传递数据的“信箱”。

你想想看,一个任务往里面扔消息,另一个任务从里面取消息。这个过程,在VxWorks里全靠msgQCreate()msgQDelete()这对函数来管理。

我个人习惯,每次写通信模块时,第一件事就是先把队列创建好。为什么?因为后面所有收发逻辑都依赖这个队列句柄。队列没建好,后面全是白搭。

2.1 msgQCreate():创建消息队列

这个函数的原型长这样:

MSG_Q_ID msgQCreate
    (
    int maxMsgs,       /* 队列最多能放多少条消息 */
    int maxMsgLength,  /* 每条消息的最大字节数 */
    int options        /* 队列选项,决定消息排队方式 */
    )

返回值是一个MSG_Q_ID类型的句柄。如果创建失败,返回NULL

参数详解:

参数 含义 我踩过的坑
maxMsgs 队列容量,即最多能缓存的消息条数 设太小容易丢消息,设太大浪费内存
maxMsgLength 每条消息的最大长度(字节) 注意是字节数,不是消息条数
options MSG_Q_FIFO 或 MSG_Q_PRIORITY 选错会导致消息顺序混乱

options 选项说明:

  • MSG_Q_FIFO(0):先进先出。谁先发,谁先被取走。我大部分场景都用这个。
  • MSG_Q_PRIORITY(1):按消息优先级排队。优先级高的消息,即使后发也会被先取走。

嗯,这里要注意:maxMsgsmaxMsgLength共同决定了队列占用的内存大小。VxWorks内部会分配一块连续内存来存放所有消息。计算公式是:总内存 = maxMsgs * (maxMsgLength + 4)。多出来的4字节是内部管理用的。

核心要点:

创建队列时,VxWorks会一次性分配好所有消息缓冲区。所以队列创建后,maxMsgsmaxMsgLength就不能改了。如果你需要动态调整,只能先删掉再重建。

2.2 msgQDelete():删除消息队列

有创建就有删除。函数原型很简单:

STATUS msgQDelete
    (
    MSG_Q_ID msgQId  /* 要删除的队列句柄 */
    )

返回值:OK(0)表示成功,ERROR(-1)表示失败。

这个函数干三件事:

  1. 释放队列占用的所有内存(包括消息缓冲区)
  2. 唤醒所有正在等待该队列的任务(它们会收到错误码)
  3. 将队列句柄置为无效

警告:

千万不要在中断服务程序(ISR)中调用msgQDelete()!我曾经犯过这个错,结果系统直接挂掉。因为删除操作可能引起任务调度,而ISR里不允许调度。

2.3 实战代码示例

下面是我在一个数据采集项目中用到的典型代码。任务A采集传感器数据,通过队列发给任务B处理。

/* 全局队列句柄 */
MSG_Q_ID g_dataQueue;

/* 创建队列 */
STATUS createDataQueue(void)
{
    g_dataQueue = msgQCreate(10, sizeof(SensorData), MSG_Q_FIFO);
    
    if (g_dataQueue == NULL)
    {
        /* 创建失败,打印错误 */
        logMsg("Failed to create data queue!\n", 0,0,0,0,0);
        return ERROR;
    }
    
    logMsg("Data queue created successfully.\n", 0,0,0,0,0);
    return OK;
}

/* 删除队列 */
STATUS deleteDataQueue(void)
{
    STATUS status;
    
    if (g_dataQueue == NULL)
    {
        return OK;  /* 队列不存在,直接返回 */
    }
    
    status = msgQDelete(g_dataQueue);
    
    if (status != OK)
    {
        logMsg("Failed to delete data queue!\n", 0,0,0,0,0);
        return ERROR;
    }
    
    g_dataQueue = NULL;  /* 防止野指针 */
    logMsg("Data queue deleted.\n", 0,0,0,0,0);
    return OK;
}

我的小技巧:

删除队列后,一定要把句柄置为NULL。不然其他任务拿着过期的句柄去操作,轻则返回错误,重则直接宕机。我早期项目里因为这个bug排查了整整两天。

2.4 避坑指南

这些年用下来,我总结了几条血泪教训:

  • 内存碎片问题:频繁创建删除队列,会导致内存碎片。我建议在系统初始化时一次性创建好所有队列,运行期间不要反复创建删除。
  • 队列容量估算maxMsgs设多少合适?我一般按峰值消息速率的2倍来算。比如任务A每秒最多发50条消息,任务B每秒处理30条,那队列容量至少设50,保险点设100。
  • 消息长度对齐maxMsgLength最好设成4字节的整数倍。VxWorks内部做内存对齐,非对齐长度会浪费空间。
  • 句柄有效性检查:每次使用队列前,检查句柄是否为NULL。别问我为什么强调这个——我曾经在系统运行几天后,因为某个模块异常删除了队列,导致其他模块全部崩溃。

2.5 性能考量

创建和删除操作本身很快,一般几十微秒就能完成。但要注意:

  • msgQCreate()会调用malloc()分配内存。如果系统内存碎片严重,分配时间可能变长。
  • msgQDelete()会唤醒所有等待任务。如果等待任务很多(比如几十个),删除操作可能触发大量任务切换,导致短暂的系统卡顿。

所以我的建议是:队列的生命周期尽量和任务保持一致。任务启动时创建,任务退出时删除。别在热路径上频繁创建删除。

一句话总结:

msgQCreate()是通信的起点,msgQDelete()是终点。创建时想清楚容量和选项,删除时记得清空句柄。这两步做好了,后面的收发操作才能稳如老狗。