2. 消息队列创建与删除:msgQCreate与msgQDelete

好,咱们直接进入正题。消息队列的创建和删除,是使用消息队列的第一步,也是基础中的基础。我见过不少新手,上来就发消息、收消息,结果队列都没创建成功,程序跑起来就崩了。所以,咱们先把地基打牢。

2.1 msgQCreate 函数详解

这个函数,说白了就是向VxWorks内核申请一个消息队列的资源。它的原型长这样:

MSG_Q_ID msgQCreate
    (
    int maxMsgs,      /* 队列最多能存多少条消息 */
    int maxMsgLength, /* 每条消息的最大字节数 */
    int options       /* 队列的选项,比如 FIFO 还是优先级 */
    )

嗯,三个参数,一个返回值。咱们一个一个说。

2.1.1 参数一:maxMsgs

这个参数指定了队列最多能同时容纳多少条消息。注意,是「最多」。我个人的习惯是,根据系统中可能出现的最大消息积压量,再留出 20%-30% 的余量。

举个例子:

  • 你的发送任务每 10ms 发一条消息
  • 接收任务每 50ms 才能处理一条
  • 那么 50ms 内会积压 5 条消息
  • 我一般会设成 8 或 10,留点余量
注意: 设得太小,消息发不进去,发送任务会被阻塞或返回错误。设得太大,浪费内存。每个消息队列占用的内存大约是 maxMsgs * (maxMsgLength + 开销) ,这个开销大概 20 字节左右。

2.1.2 参数二:maxMsgLength

这个参数指定了每条消息的最大字节数。注意,是「最大」。你实际发送的消息可以小于这个值,但不能大于。

我在项目中遇到过一个问题:有个同事把 maxMsgLength 设成了 256,但实际发送的消息只有 10 个字节。结果每个消息都占用了 256 字节的缓冲区,内存浪费很严重。你想想看,如果 maxMsgs 是 100,那光消息缓冲区就占了 25KB,实际上只用到了 1KB。

所以我的建议是:

  • 如果消息长度固定,就设成那个固定值
  • 如果消息长度变化,按最长的来,但别太离谱
  • 如果消息内容很少,可以考虑用指针传递,而不是拷贝数据

2.1.3 参数三:options

这个参数决定了消息队列的行为方式。常用的选项有两个:

选项 含义 说明
MSG_Q_FIFO 先进先出 消息按到达顺序排队,先到的先被取走
MSG_Q_PRIORITY 优先级队列 消息按优先级排队,优先级高的先被取走

我个人习惯,90% 的场景用 MSG_Q_FIFO。为什么?因为简单、直观、不容易出错。优先级队列虽然听起来很高级,但用不好容易导致优先级反转或者低优先级消息永远得不到处理。

小技巧: 如果你不确定用哪个,就用 MSG_Q_FIFO。等真的需要优先级处理时,再改也不迟。

2.2 返回值处理

msgQCreate 返回一个 MSG_Q_ID 类型的值。这个值要么是一个有效的队列 ID,要么是 NULL。

嗯,这里要注意:

  • 返回 NULL:创建失败。原因可能是内存不足,或者参数不合理
  • 返回非 NULL:创建成功。但这个 ID 一定要保存好,后面所有操作都靠它

我曾经见过一个案例:有人创建了队列,但没检查返回值,直接拿着 NULL 去发消息。结果系统直接 panic 了。所以,一定要检查返回值:

MSG_Q_ID myQueue = msgQCreate(10, 256, MSG_Q_FIFO);
if (myQueue == NULL) {
    /* 创建失败,处理错误 */
    printf("消息队列创建失败!\n");
    return ERROR;
}

2.3 msgQDelete 函数

有创建就有删除。msgQDelete 就是用来释放消息队列资源的。原型很简单:

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

这个函数会:

  • 释放队列占用的所有内存
  • 丢弃队列中所有未处理的消息
  • 如果有任务正在等待接收消息,这些任务会返回 ERROR
警告: 删除队列时,一定要确保没有任务正在使用它。否则,那些任务会突然收到错误,可能导致不可预知的行为。我一般会在删除前,先通知所有相关任务停止操作。

返回值处理也很简单:

  • OK:删除成功
  • ERROR:删除失败,可能是队列 ID 无效

2.4 一个完整的例子

说了这么多,咱们来看个完整的例子。这个例子展示了从创建到删除的完整流程:

#include <vxWorks.h>
#include <msgQLib.h>

MSG_Q_ID g_myQueue;  /* 全局变量,方便其他任务使用 */

STATUS initMessageQueue(void)
{
    /* 创建队列:最多存 20 条,每条最大 100 字节,FIFO 模式 */
    g_myQueue = msgQCreate(20, 100, MSG_Q_FIFO);
    
    if (g_myQueue == NULL) {
        printf("队列创建失败,内存不足?\n");
        return ERROR;
    }
    
    printf("队列创建成功,ID: 0x%x\n", (int)g_myQueue);
    return OK;
}

STATUS cleanupMessageQueue(void)
{
    STATUS status;
    
    if (g_myQueue == NULL) {
        return OK;  /* 已经删过了 */
    }
    
    /* 删除前,通知所有任务停止使用队列 */
    /* 这里省略通知逻辑,实际项目中需要 */
    
    status = msgQDelete(g_myQueue);
    if (status == OK) {
        g_myQueue = NULL;  /* 置空,防止野指针 */
        printf("队列删除成功\n");
    } else {
        printf("队列删除失败\n");
    }
    
    return status;
}
核心要点:
  • 创建时检查返回值,别拿 NULL 当 ID 用
  • 删除后把 ID 置 NULL,防止误用
  • 参数配置要合理,别浪费内存也别不够用
  • 删除前确保没有任务在等待

好了,消息队列的创建和删除就这些内容。说白了就是「申请资源」和「释放资源」两个动作,但细节都在参数配置和错误处理里。下一节咱们聊聊怎么往队列里发消息和收消息,那才是真正干活的地方。