fork() 后的消息行为

Messages behavior after fork()

我正在学习通过消息进行进程间通信。

在下面的代码片段中,我应用 fork() 并在父进程和子进程之间发送消息。

我希望控制台输出“1 - 2 - 3 - 4”。但是,我得到了“1 - 2”,在此之后,程序似乎永远停留在打印“3”之前的 msgrcv 行。谁能告诉我代码有什么问题?

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

#define BUF_SIZE 16
#define MSG_KEY1 75
#define MSG_KEY2 76

struct msgform
{
    long     mtype;
    char     mbuf[BUF_SIZE];
    long     mind;
} msg;

struct msgping
{
    long     mtype;
    long     ping ;
} msgPing;

int main() {

    if(fork() == 0) {
    // child process
    int msgid1;
    int msgid2;
    msgid1 = msgget(MSG_KEY1, 0666 | IPC_CREAT);
    msgid2 = msgget(MSG_KEY2, 0666 | IPC_CREAT);
    msg.mtype = 1;
    msgPing.mtype = 1;

    printf("1 - started, sending msgPing\n");

    msgsnd(msgid1, &msgPing, sizeof(msgPing), 0);

    msgrcv(msgid2, &msg, sizeof(msg), 1, 0);

    printf("3 - msg received, sending msgPing\n");

    msgsnd(msgid1, &msgPing, sizeof(msgPing), 0);

    msgctl(msgid1, IPC_RMID, 0);
    msgctl(msgid2, IPC_RMID, 0);

    return 0;
    }

    //parent process
    sleep(1);
    int msgid1;
    int msgid2;
    msgid1 = msgget(MSG_KEY1, 0666 | IPC_CREAT);
    msgid2 = msgget(MSG_KEY2, 0666 | IPC_CREAT);

    msgrcv(msgid1, &msgPing, sizeof(msgPing), 1, 0);

    printf("2 - msgPing received, sending msg\n");

    msgsnd(msgid2, &msg, sizeof(msg), 0);

    msgrcv(msgid1, &msgPing, sizeof(msgPing), 1, 0);

    printf("4 - msgPing received, finished\n");

    return 0;
}

这里的消息类型初始化有问题:

msg.mtype = 1;
msgPing.mtype = 1;

这些值仅在 child 中初始化。当 parent 尝试在 msg 中发送消息时, mtype 尚未设置,因此 child 无休止地等待 mtype 的消息 1,永远不会到达。

要么在 fork() 之前设置这两个值,要么在 parent 和 child 中设置它们,这样就可以了:

$ ./msg
1 - started, sending msgPing
3 - msg received, sending msgPing
2 - msgPing received, sending msg
4 - msgPing received, finished

大约一秒钟。

您只在 child 中设置了 mtype,但在 parent 中设置了 而不是

此外,child 中的 msgctl 次调用 与 parent 中的最终 msgrcv 竞争。也就是说,child 可能会在 之前完成 msgctl 调用 parent 有机会完成其最终的 msgrcv

而且,我认为 child 的 return 0; 应该是 exit(0); 以防止落入 parent 代码。


在下面的重构代码中,我使用 cpp 条件来表示 old/broken 与 new/fixed 代码:

#if 0
// old code
#else
// new code
#endif

#if 1
// new code
#endif

重构代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#if 1
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#endif

#define BUF_SIZE 16
#define MSG_KEY1 75
#define MSG_KEY2 76

struct msgform {
    long mtype;
    char mbuf[BUF_SIZE];
    long mind;
} msg;

struct msgping {
    long mtype;
    long ping;
} msgPing;

#define ONERR(_fd) \
    do { \
        if (_fd >= 0) \
            break; \
        printf("msgget error " #_fd " at line %d cldpid=%d -- %s\n", \
            __LINE__,cldpid,strerror(errno)); \
        exit(1); \
    } while (0)

int
main()
{

#if 0
    if (fork() == 0) {
#else
    pid_t cldpid = fork();
    if (cldpid == 0) {
#endif
        // child process
        int msgid1;
        int msgid2;

        msgid1 = msgget(MSG_KEY1, 0666 | IPC_CREAT);
        ONERR(msgid1);
        msgid2 = msgget(MSG_KEY2, 0666 | IPC_CREAT);
        ONERR(msgid2);
        msg.mtype = 1;
        msgPing.mtype = 1;

        printf("1 - started, sending msgPing\n");

        msgsnd(msgid1, &msgPing, sizeof(msgPing), 0);

        msgrcv(msgid2, &msg, sizeof(msg), 1, 0);
        printf("3 - msg received, sending msgPing\n");

        msgsnd(msgid1, &msgPing, sizeof(msgPing), 0);

#if 0
        msgctl(msgid1, IPC_RMID, 0);
        msgctl(msgid2, IPC_RMID, 0);
#endif

#if 0
        return 0;
#else
        exit(0);
#endif
    }

    // parent process
    sleep(1);
    int msgid1;
    int msgid2;

    msgid1 = msgget(MSG_KEY1, 0666 | IPC_CREAT);
    ONERR(msgid1);
    msgid2 = msgget(MSG_KEY2, 0666 | IPC_CREAT);
    ONERR(msgid2);

// NOTE/FIX: the parent needs to set this as well as the child
#if 1
    msg.mtype = 1;
    msgPing.mtype = 1;
#endif

    msgrcv(msgid1, &msgPing, sizeof(msgPing), 1, 0);

    printf("2 - msgPing received, sending msg\n");

    msgsnd(msgid2, &msg, sizeof(msg), 0);

    msgrcv(msgid1, &msgPing, sizeof(msgPing), 1, 0);

    printf("4 - msgPing received, finished\n");

#if 1
    waitpid(cldpid,NULL,0);

    msgctl(msgid1, IPC_RMID, 0);
    msgctl(msgid2, IPC_RMID, 0);
#endif

    return 0;
}

当我为实时、关键任务、生产代码完成 msgsnd/msgrcv 后,我在两个方向上都使用了 single msgid,使用不同 mtype 值(例如 mtype = 1 用于 parent 和 mtype = 2 用于 ping)。

这是一个进一步清理和简化的版本:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#if 1
#include <sys/wait.h>
#endif

#define BUF_SIZE 16
#define MSG_KEY1 75
#define MSG_KEY2 76

struct msgform {
    long mtype;
    char mbuf[BUF_SIZE];
    long mind;
} msg;

struct msgping {
    long mtype;
    long ping;
} msgPing;

enum {
    MSGTYPE = 1,
    MSGPING = 2,
};

int
main(void)
{

    int msgid = msgget(MSG_KEY1, 0666 | IPC_CREAT);

    pid_t cldpid = fork();

    msg.mtype = MSGTYPE;
    msgPing.mtype = MSGPING;

    if (cldpid == 0) {
        sleep(1);

        printf("1 - started, sending msgPing\n");
        msgsnd(msgid, &msgPing, sizeof(msgPing), 0);

        msgrcv(msgid, &msg, sizeof(msg), MSGTYPE, 0);

        printf("3 - msg received, sending msgPing\n");
        msgsnd(msgid, &msgPing, sizeof(msgPing), 0);

        exit(0);
    }

    // parent process
    sleep(1);

    msgrcv(msgid, &msgPing, sizeof(msgPing), MSGPING, 0);
    printf("2 - msgPing received, sending msg\n");

    msgsnd(msgid, &msg, sizeof(msg), 0);

    msgrcv(msgid, &msgPing, sizeof(msgPing), MSGPING, 0);
    printf("4 - msgPing received, finished\n");

    waitpid(cldpid,NULL,0);
    msgctl(msgid, IPC_RMID, 0);

    return 0;
}