使用 System V message queue 实现进程间通信

被问到“Linux上用什么命令删除一个MQ”. 为了验证这个问题需要先整出来一个MQ,然后删除它。

相关系统调用

// ftok(): convert a pathname and a project identifier to a System V IPC key.
#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);
// msgget(): either returns the message queue identifier for a newly created message queue or returns the identifiers for a queue which exists with the same key value.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);
// msgsnd(): Data is placed on to a message queue by calling msgsnd().
// msgrcv(): messages are retrieved from a queue.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
                      int msgflg);
// msgctl(): It performs various operations on a queue. Generally it is use to destroy message queue.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

Writer - 往MQ里写入消息

代码是从网上抄的[1],挺好用。

// C Program for Message Queue (Writer Process)
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
 
// structure for message queue
struct mesg_buffer {
    long mesg_type;
    char mesg_text[100];
} message;
 
int main()
{
    key_t key;
    int msgid;
 
    // ftok to generate unique key
    // 每个程序都可以用相同的 pathname 和 proj_id 来获取相同的 key.
    key = ftok("progfile", 65);
 
    // msgget creates a message queue
    // and returns identifier
    msgid = msgget(key, 0666 | IPC_CREAT);
    message.mesg_type = 1;
 
    // 获取用户输入
    printf("Write Data : ");
    gets(message.mesg_text);
 
    // msgsnd to send message
    msgsnd(msgid, &message, sizeof(message), 0);
 
    // display the message
    printf("Data send is : %s \n", message.mesg_text);
 
    return 0;
}

Reader - 从MQ中读取消息

// C Program for Message Queue (Reader Process)
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
 
// structure for message queue
struct mesg_buffer {
    long mesg_type;
    char mesg_text[100];
} message;
 
int main()
{
    key_t key;
    int msgid;
 
    // ftok to generate unique key
    // 使用跟 writer 相同的 pathname 和 proj_id 来获取 key.
    key = ftok("progfile", 65);
 
    // msgget creates a message queue
    // and returns identifier
    msgid = msgget(key, 0666 | IPC_CREAT);
 
    // msgrcv to receive message
    msgrcv(msgid, &message, sizeof(message), 1, 0);
 
    // display the message
    printf("Data Received is : %s \n",
                    message.mesg_text);
 
    // to destroy the message queue
    msgctl(msgid, IPC_RMID, NULL);
 
    return 0;
}

测试验证

运行 writer 往 mq 写信息,reader 可以接收到这个信息。

# echo "test message" | ./writer
Write Data : Data send is : test message
# ./reader
Data Received is : test message

从 writer 的 strace 可以看到,它调用了 msgget() 和 msgsnd() 两个 system call.

stat("progfile", 0x7ffe6e4f1560)        = -1 ENOENT (No such file or directory)
msgget(0xffffffff, IPC_CREAT|0666)      = 32768
msgsnd(32768, {1, "haha\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...}, 112, 0) = 0

从 reader 的 strace 可以看到,它调用了 msgget() 和 msgrcv() 来获取数据,调用 msgctl() 来删除这个 queue.

stat("progfile", 0x7fff6b97ea70)        = -1 ENOENT (No such file or directory)
msgget(0xffffffff, IPC_CREAT|0666)      = 98304
msgrcv(98304, {1, "haha\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...}, 112, 1, 0) = 112
msgctl(98304, IPC_RMID, NULL)           = 0

删除一个MQ

好了,终于可以尝试手动删除MQ了。从上面可以知道,如果没有任何一个程序调用 msgctl() 来删除一个MQ,而所有用这个MQ的程序都退出了,这个MQ的记录就一直存在于内存里了。

# ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0xffffffff 0          root       666        112          1          

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

用 ipcrm -q 命令可以删除这个 queue.

# ipcrm -q 0

从 strace 中也能看到,它其实也是调用 msgctl().

msgctl(163840, IPC_RMID, NULL)          = 0

MQ属于什么内存类型?

看到我写这文章,同事补了个问题,这些 Message Queue 算是什么类型的内存? 匿名页还是共享页?

我觉得是共享页,先挖坑,后面再举证。

参考

[1] https://www.geeksforgeeks.org/ipc-using-message-queues/
[2] https://stackoverflow.com/questions/3056307/how-do-i-use-mqueue-in-a-c-program-on-a-linux-based-system
[3] Chapter 15, Advanced Programming in the UNIX Environment 3rd Edition