You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

System V消息队列周期性莫名重置,请求技术解析

问题分析与解决

你的代码出现消息队列内容周期性重置、消息丢失的核心原因是函数未声明导致的未定义行为,同时进程输出交错也会干扰你对实际执行流程的判断,以下是具体分析和修复方案:

1. 未声明函数引发的未定义行为

在main函数中调用了child_main,但该函数既没有在main前声明原型,也没有在全局作用域前置声明。在C语言中,未声明的函数会被编译器默认假设为返回int类型,且参数遵循默认类型提升规则,但你的child_main实际返回void类型,这种不匹配会破坏子进程的栈帧结构,导致后续代码执行出现不可预测的错误——比如消息读取逻辑异常、变量被意外覆盖,最终表现为消息丢失或队列状态异常。

2. 输出交错导致的流程误解

父进程和子进程的printf输出共享同一个标准输出流,由于进程调度的随机性,两者的输出会交错显示,可能让你误以为消息被“莫名重置”。比如父进程打印队列数量时,子进程可能正在读取消息,此时队列数量的变化是正常的,但输出顺序会干扰你对实际流程的判断。

修复方案

步骤1:添加函数原型声明

在main函数前添加child_main的原型声明,确保编译器正确识别函数的返回类型和参数:

void child_main(int msgQueue);

步骤2:区分进程输出(可选)

为父进程和子进程的输出添加标识,便于清晰区分两者的执行日志:

  • 父进程打印修改为:
    printf("[Parent] Messages in queue = %ld\n", queueInfo.msg_qnum);
    
  • 子进程打印修改为:
    printf("[Child] read: %d\n", msg.mtext.value);
    

步骤3:添加队列清理逻辑

程序退出时(比如捕获SIGINT信号),删除创建的消息队列,避免残留资源影响后续运行:

#include <signal.h>

int globalMsgQueue;

void cleanup(int sig) {
    msgctl(globalMsgQueue, IPC_RMID, NULL);
    exit(EXIT_SUCCESS);
}

// 在main函数中初始化并注册信号处理
int main() {
    // ... 其他代码
    globalMsgQueue = msgget(queueKey, IPC_CREAT | 0660);
    signal(SIGINT, cleanup);
    // ... 后续代码
}

完整修正后的代码

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

typedef struct 
{
    int value;
} messageText;

typedef struct 
{
    long mtype;
    messageText mtext;
} queueMessage;

// 前置声明child_main函数
void child_main(int msgQueue);

int globalMsgQueue;

void cleanup(int sig) {
    msgctl(globalMsgQueue, IPC_RMID, NULL);
    printf("\nCleaned up message queue.\n");
    exit(EXIT_SUCCESS);
}

int main()
{ 
    key_t queueKey = ftok(__FILE__, 'M');
    globalMsgQueue = msgget(queueKey, IPC_CREAT | 0660);
    if (globalMsgQueue == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }

    // 注册信号处理,用于退出时清理队列
    signal(SIGINT, cleanup);
    
    pid_t pid = fork();
    if (0 == pid)
    {        
        child_main(globalMsgQueue);
        abort();
    }

    queueMessage msg = 
    {
        .mtype = 1,
        .mtext = { 0 }
    };

    for (int i = 0; ; ++i)
    {
        msg.mtext.value = i;

        int rc = msgsnd(globalMsgQueue, &msg, sizeof(msg.mtext), 0);
        if (rc == -1)
            perror("msgsnd");

        usleep(250000);  // 0.25 sec

        struct msqid_ds queueInfo;
        if (msgctl(globalMsgQueue, IPC_STAT, &queueInfo) == -1) {
            perror("msgctl");
            continue;
        }
        printf("[Parent] Messages in queue = %ld\n", queueInfo.msg_qnum);
    }

    return 0;
}

void child_main(int msgQueue)
{
    queueMessage msg = { 0 };
    int msgTyp = 0;  // 接收队列中第一个消息,忽略类型
    int msgRcvFlg = IPC_NOWAIT | MSG_NOERROR;

    while (1)
    {
        while (1)
        {
            int rc = msgrcv(msgQueue, &msg, sizeof(msg.mtext), msgTyp, msgRcvFlg);
            if (-1 == rc)
            {
                if (errno == ENOMSG)
                {
                    goto sleep;
                }
                else
                {
                    perror("msgrcv");
                    exit(EXIT_FAILURE);
                }
            }

            printf("[Child] read: %d\n", msg.mtext.value);
        }

    sleep:
        usleep(5000000);  // 5 sec.
    }
}

额外说明

  • 从ipcs -l的输出来看,你的系统消息队列限制远大于测试所需,因此不存在队列满导致的消息丢失问题。
  • 修正后的代码运行时,子进程每次唤醒后会读取所有累积的消息,队列数量会在子进程读取后归零,这是符合预期的行为。
  • msgctl的IPC_STAT调用仅用于读取队列状态,不会修改队列内容。

内容的提问来源于stack exchange,提问作者tr1ang1e

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 08:28:09