内核篇第十三讲:system-V消息队列

大家好,接下来的一段时间我将开始学习野火的Linux 系统课程并将学习到的干货逐步更新到我的CSDN博客中。没时间刷课的同学可以把我的博客喂给AI 突击一下。

目录

[一、System-V IPC 特点](#一、System-V IPC 特点)

二、消息队列完整使用流程

[三、API 函数详解](#三、API 函数详解)

[1. ftok () ------ 生成 key](#1. ftok () —— 生成 key)

[2. msgget () ------ 创建 / 获取消息队列,得到 msqid](#2. msgget () —— 创建 / 获取消息队列,得到 msqid)

[3. msgsnd () ------ 发送消息到队列](#3. msgsnd () —— 发送消息到队列)

[4. msgrcv () ------ 从队列读取消息](#4. msgrcv () —— 从队列读取消息)

[5. msgctl () ------ 控制消息队列](#5. msgctl () —— 控制消息队列)

四、实战演练

五、核心特点总结

六、命令行调试命令

一、System-V IPC 特点

System-V IPC:包含消息队列、共享内存、信号量,属于经典进程间通信方式

  1. 独立于进程:创建之后,就算创建进程退出,IPC 对象仍然在内核中存在,不会自动销毁,需要手动删除。
  2. 没有文件名、没有文件描述符,不能用 read/write 操作。
  3. IPC 对象拥有两套标识:
  • key:用户层键值,用于不同进程之间打开同一个 IPC 对象
  • ID:内核返回的对象 id,本进程操作时使用

二、消息队列完整使用流程

  1. 用ftok()生成唯一key
  2. msgget():创建 / 获取消息队列,拿到队列 ID
  3. msgsnd():向队列发送消息(带消息类型 mtype)
  4. msgrcv():按指定消息类型读取消息
  5. msgctl():获取属性 / 设置属性 / 删除消息队列

三、API 函数详解

1. ftok () ------ 生成 key

功能 :根据文件路径 + 项目 id,生成唯一的key_t键值,供 msgget 使用

复制代码
key_t ftok(const char *path, int proj_id);
  • 参数
    • path:存在的合法文件路径(一般用当前目录)
    • proj_id:项目编号,仅低 8bit 有效,填一个整数即可
  • 返回值
    • 成功:返回合法 key
    • 失败:-1

注意:同一个 path+proj_id,生成的 key 相同,多个进程就可以打开同一个消息队列

2. msgget () ------ 创建 / 获取消息队列,得到 msqid

功能:使用 key,创建消息队列或者获取已存在队列的 ID

复制代码
int msgget(key_t key, int msgflg);
  • 参数
    • key:ftok 产生的键值
    • msgflg:标志位,IPC_CREAT | 权限
      • IPC_CREAT:队列不存在则创建;存在则直接打开
      • mode:访问权限,如0664
  • 返回值
    • 成功:消息队列 ID(msqid)
    • 失败:-1

3. msgsnd () ------ 发送消息到队列

功能:往消息队列写入一条消息

复制代码
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • 参数
    • msqid:消息队列 ID

    • msgp:消息缓冲区,固定结构体模板

      复制代码
      struct msgbuf{
          long mtype;      // 消息类型标识,必须>0
          char mtext[1];   // 消息正文(柔性数组,存放数据)
      };
    • msgsz:消息正文 mtext 的字节长度,不包含 mtype

    • msgflg:

      • 0:阻塞发送;队列满时,进程阻塞等待
      • IPC_NOWAIT:非阻塞;队列满直接返回失败,不等待
  • 返回值
    • 成功:0
    • 失败:-1

4. msgrcv () ------ 从队列读取消息

功能:从消息队列读取指定类型的消息

复制代码
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  • 参数
    • msqid:消息队列 ID
    • msgp:接收消息缓冲区,和发送端 msgbuf 结构一致
    • msgsz:缓冲区能存放正文的最大字节数
    • msgtyp:要接收的消息类型
      • >0:读取 mtype 等于 msgtyp 的消息
    • msgflg
      • 0:阻塞读取,没有对应消息就阻塞等待
      • IPC_NOWAIT:非阻塞,无消息直接返回 - 1
      • MSG_NOERROR:消息正文过长时,截断超出部分,不报错
  • 返回值
    • 成功:读到的消息正文字节数
    • 失败:-1

5. msgctl () ------ 控制消息队列

功能 :获取、修改消息队列属性 ,删除消息队列

复制代码
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • 参数
    • msqid:消息队列 ID
    • cmd 控制命令:
      • IPC_STAT:获取队列属性,存入 buf
      • IPC_SET:修改队列属性(buf 中设置)
      • IPC_RMID:删除消息队列,buf 传 NULL 即可
    • buf:msqid_ds 结构体,存放属性信息;IPC_RMID 时填 NULL
  • 返回值
    • 成功:0
    • 失败:-1

四、实战演练

发送端代码:

cpp 复制代码
/* msgsnd.c:System V 消息队列发送端 */

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BUFFER_SIZE 512

struct message {
    long msg_type;                  /* 消息类型必须大于 0 */
    char msg_text[BUFFER_SIZE];     /* 消息正文 */
};

int main(void)
{
    int qid;
    key_t key;
    struct message msg;

    /*
     * 根据一个真实存在的路径和项目编号生成 key。
     * /tmp 必须存在;项目编号实际只使用低 8 位。
     */
    key = ftok("/tmp", 11);
    if (key == (key_t)-1) {
        perror("ftok");
        return EXIT_FAILURE;
    }

    /*
     * 获取或创建消息队列:
     * IPC_CREAT:不存在时创建
     * 0666:请求读写权限,最终权限还会受 umask 影响
     */
    qid = msgget(key, IPC_CREAT | 0666);
    if (qid == -1) {
        perror("msgget");
        return EXIT_FAILURE;
    }

    printf("Open queue %d\n", qid);

    for (;;) {
        printf("Enter some message to the queue: ");
        fflush(stdout);

        if (fgets(msg.msg_text, sizeof msg.msg_text, stdin) == NULL) {
            if (feof(stdin)) {
                puts("\nEnd of input");
                break;
            }

            perror("fgets");
            return EXIT_FAILURE;
        }

        /*
         * 消息类型必须是正整数。
         * 使用发送进程 PID,接收端可按 PID 选择消息。
         */
        msg.msg_type = (long)getpid();

        /*
         * msgsz 只计算消息正文长度,
         * 不能把前面的 long msg_type 计算进去。
         */
        if (msgsnd(qid,
                   &msg,
                   strlen(msg.msg_text),
                   0) == -1) {
            perror("msgsnd");
            return EXIT_FAILURE;
        }

        puts("Message posted");

        /*
         * quit 消息已经发送到消息队列,
         * 然后发送端结束。
         *
         * fgets() 会保留换行符,因此输入 quit 后,
         * 缓冲区一般是 "quit\n",比较前 4 个字符即可。
         */
        if (strncmp(msg.msg_text, "quit", 4) == 0) {
            break;
        }
    }

    return EXIT_SUCCESS;
}

编译:

复制代码
gcc -Wall -Wextra -Wpedantic -std=c11 msgsnd.c -o msgsnd

运行:

复制代码
./msgsnd

接收端代码:

cpp 复制代码
/* msgrcv.c:System V 消息队列接收端 */

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE 512

struct message {
    long msg_type;                  /* 消息类型,必须大于 0 */
    char msg_text[BUFFER_SIZE];     /* 消息正文 */
};

int main(void)
{
    int qid;
    key_t key;
    struct message msg;

    /*
     * 必须和发送端使用相同的路径及项目编号,
     * 否则生成的 key 不同,双方无法打开同一个消息队列。
     */
    key = ftok("/tmp", 11);
    if (key == (key_t)-1) {
        perror("ftok");
        return EXIT_FAILURE;
    }

    /*
     * 获取或创建消息队列。
     * 发送端和接收端使用相同的 key,因此会得到同一个 qid。
     */
    qid = msgget(key, IPC_CREAT | 0666);
    if (qid == -1) {
        perror("msgget");
        return EXIT_FAILURE;
    }

    printf("Open queue %d\n", qid);

    for (;;) {
        ssize_t received;

        /*
         * 先清空消息正文。
         * 最多接收 BUFFER_SIZE - 1 字节,
         * 为字符串末尾的 '\0' 保留空间。
         */
        memset(msg.msg_text, 0, sizeof msg.msg_text);

        /*
         * msgtyp == 0:
         * 接收消息队列中的第一条消息,不限制消息类型。
         *
         * flags == 0:
         * 如果队列中没有消息,接收进程阻塞等待。
         */
        received = msgrcv(
            qid,
            &msg,
            sizeof msg.msg_text - 1,
            0,
            0
        );

        if (received == -1) {
            perror("msgrcv");
            return EXIT_FAILURE;
        }

        /*
         * msgrcv() 返回消息正文的实际字节数,
         * 显式补上字符串结束符。
         */
        msg.msg_text[received] = '\0';

        printf(
            "The message from process %ld: %s",
            msg.msg_type,
            msg.msg_text
        );

        /*
         * 发送端使用 fgets() 读取,
         * 所以输入 quit 后消息一般是 "quit\n"。
         * 比较前四个字符即可。
         */
        if (strncmp(msg.msg_text, "quit", 4) == 0) {
            break;
        }
    }

    /*
     * 将消息队列从内核中删除。
     * IPC_RMID 会使该队列不能再用于发送或接收。
     */
    if (msgctl(qid, IPC_RMID, NULL) == -1) {
        perror("msgctl");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

编译:

复制代码
gcc -Wall -Wextra -Wpedantic -std=c11 msgrcv.c -o msgrcv

先运行接收端:

复制代码
./msgrcv

再打开另一个终端运行发送端:

复制代码
./msgsnd

执行关系图:

cpp 复制代码
发送端            消息队列                    接收端

ftok("/tmp", 11) ─────┐       ┌──── ftok("/tmp", 11)
                      ↓       ↓
                      相同的 key
                      ↓
               msgget() 得到相同 qid
                      ↓
msgsnd(qid, ...) ──→ [ 消息 ] ──→ msgrcv(qid, ...)
                      ↓
输入 quit 并发送 ───────────────→ 接收 quit
                                      ↓
                               msgctl(IPC_RMID)
                                      ↓
                                  删除队列

五、核心特点总结

  1. 消息自带mtype类型标签,可以按类型选择性接收消息,不用按先进先出顺序读取,这是消息队列最大特点。
  2. 内核持久化:进程退出,消息队列和里面消息仍然保留,必须 msgctl (IPC_RMID) 手动删除,否则一直占用内核资源。
  3. 阻塞 / 非阻塞模式由 msgsnd、msgrcv 的 msgflg 控制。

六、命令行调试命令

复制代码
ipcs -q      # 查看系统所有消息队列
ipcrm -q msqid  # 删除指定消息队列
相关推荐
今天的砖头有点烫手啊1 小时前
GC 算法与垃圾收集器演进:从 Serial 到 ZGC
java·jvm
XUEYUAN52121 小时前
路由跳数与地理位置校验:为什么 IP 归属地和实际出口位置不一致
网络·网络协议·tcp/ip
Eloudy1 小时前
NVSwitch 和 UALink 的数据(缓存)一致性
网络·缓存·gpu
牧瀬クリスだ1 小时前
Mybatis-Plus:让CRUD快如闪电
java·spring boot·spring·tomcat·mybatis·yml
盐焗鹌鹑蛋1 小时前
【Linux】线程概念和控制
java·linux·服务器
Multipath7121 小时前
Ku保底,Ka提速— 双频卫星聚合,打造应急通信“不断线”保底链路
网络·5g·安全·智能路由器·实时音视频
CarIise1 小时前
MyBatis缓存与分页插件:从一级二级缓存到底层原理与PageHelper实战
java·开发语言·spring
宸津-代码粉碎机8 小时前
OpenAI 连夜迎战 Grok Bot 和 Muse:AI 智能体从 “会聊天” 到 “能办事”,现在入场还来得及吗
java·大数据·人工智能·分布式·python
噢,我明白了9 小时前
java中唯一键和幂等键的应用
java·后端