Linux进程间通信之消息队列(POSIX)实现篇

Linux 内核中 POSIX 消息队列的实现(基于 2.6.12)

概述

基于 2.6.12 内核, 说明 POSIX 消息队列的核心数据结构、系统调用路径及关键实现. POSIX 消息队列通过 mqueue 文件系统实现, 主要文件: ipc/mqueue.cinclude/linux/mqueue.h.

核心文件与路径

  • ipc/mqueue.c: POSIX 消息队列系统调用与核心逻辑
  • include/linux/mqueue.h: 结构体与常量定义
  • fs/mqueue.c: mqueue 文件系统实现(如果存在)
  • include/linux/msg.h: 消息结构定义(复用 System V 消息队列的 msg_msg)

核心数据结构

mqueue_inode_info (位于 ipc/mqueue.c)

c 复制代码
// ipc/mqueue.c
struct mqueue_inode_info {
    spinlock_t lock;              // 保护消息队列的锁
    struct inode vfs_inode;       // VFS inode
    struct msg_msg *node_cache;   // 消息节点缓存
    struct list_head msg_list;    // 消息链表(按优先级排序)
    ssize_t qsize;                // 队列当前总字节数
    unsigned int qbytes;          // 队列最大字节数
    unsigned int qsize_max;       // 单条消息最大大小
    unsigned int qnum;            // 当前消息数量
    unsigned int qnum_max;        // 队列最大消息数
    wait_queue_head_t wait_q;     // 等待队列(接收者)
    struct user_struct *user;     // 用户结构(用于资源限制)
    struct mq_attr attr;          // 队列属性
};

msg_msg (单条消息, 复用 System V 消息队列)

c 复制代码
// include/linux/msg.h
struct msg_msg {
    struct list_head m_list;      // 用于链入消息队列的链表节点
    long m_type;                  // 消息类型(在 POSIX 中用作优先级)
    int m_ts;                     // 消息正文的实际长度(字节数)
    struct msg_msgseg *next;      // 当消息大于一页时, 指向下一个分段
    void *security;               // LSM 安全模块钩子
    /* 紧随其后是消息正文数据 */
};

mq_attr (用户可见队列属性)

c 复制代码
// include/linux/mqueue.h
struct mq_attr {
    long mq_flags;    // 标志位(如 O_NONBLOCK)
    long mq_maxmsg;   // 队列中最大消息数
    long mq_msgsize;  // 单条消息最大大小
    long mq_curmsgs;  // 当前队列中的消息数(只读)
};

ext_wait_queue (等待队列)

c 复制代码
// ipc/mqueue.c
struct ext_wait_queue {
    struct task_struct *task;     // 等待的进程
    struct list_head list;        // 链表节点
    struct msg_msg *msg;          // 指向已加载的消息(用于快速路径)
    int state;                    // 状态(STATE_NONE, STATE_PENDING, STATE_READY)
};

系统调用路径

  • sys_mq_openipc/mqueue.c:sys_mq_opendo_mq_open() 创建或打开队列 → 返回消息队列描述符
  • sys_mq_sendipc/mqueue.c:sys_mq_senddo_mq_send() 构造 msg_msg 并按优先级入队
  • sys_mq_receiveipc/mqueue.c:sys_mq_receivedo_mq_receive() 取出优先级最高的消息并删除
  • sys_mq_closeipc/mqueue.c:sys_mq_close → 关闭消息队列描述符
  • sys_mq_unlinkipc/mqueue.c:sys_mq_unlink → 删除消息队列

关键流程

创建队列(mq_open)

  1. 路径解析: 将名字(如 /my_mq)解析为 /dev/mqueue/my_mq 路径.
  2. 权限检查: 检查进程是否有权限创建/访问消息队列.
  3. 创建 mqueue inode: 调用 mqueue_get_inode() 创建 mqueue inode.
  4. 初始化队列属性: 设置 mq_maxmsgmq_msgsize 等属性.
  5. 关联到 dentry: 将 inode 关联到 /dev/mqueue 目录下的 dentry.
  6. 返回消息队列描述符: 打开文件并返回描述符.

简要实现(基于 2.6.12 内核逻辑)

c 复制代码
// 系统调用入口: 创建或打开 POSIX 消息队列
asmlinkage long sys_mq_open(const char __user *name, int oflag, mode_t mode,
                            struct mq_attr __user *u_attr)
{
    struct mqueue_inode_info *info;
    struct inode *inode;
    struct dentry *dentry;
    struct mq_attr attr;
    int retval, fd;

    // 解析用户空间属性
    if (u_attr) {
        if (copy_from_user(&attr, u_attr, sizeof(attr)))
            return -EFAULT;
    } else {
        // 使用默认属性
        attr.mq_maxmsg = DFLT_QUEUESMAX;
        attr.mq_msgsize = DFLT_MSGSIZEMAX;
        attr.mq_flags = 0;
    }

    // 路径解析和文件查找/创建
    // ... (路径解析逻辑, 类似 shm_open)

    // 如果指定了 O_CREAT, 创建新队列
    if (oflag & O_CREAT) {
        // 创建 mqueue inode
        inode = mqueue_get_inode(sb, S_IFREG | mode, &attr);
        if (!inode) {
            retval = -ENOSPC;
            goto out;
        }

        info = MQUEUE_I(inode);
        // 初始化消息队列
        INIT_LIST_HEAD(&info->msg_list);
        spin_lock_init(&info->lock);
        init_waitqueue_head(&info->wait_q);
        info->qsize = 0;
        info->qnum = 0;

        // 关联 dentry 和 inode
        d_instantiate(dentry, inode);
    } else {
        // 打开已存在的队列
        // ...
    }

    // 打开文件, 获取文件描述符
    // ... (类似 shm_open)

    return fd;
}

发送消息(mq_send)

  1. 权限检查与队列查找: 通过描述符获取 mqueue inode.
  2. 检查长度: 单条消息长度 ≤ mq_msgsize, 总字节数不超过 qbytes.
  3. 若队列字节数 + 消息长度超限: 阻塞至可写, 或 O_NONBLOCK 则返回 EAGAIN.
  4. 分配 msg_msg: 分配消息结构, 复制用户数据.
  5. 按优先级插入 msg_list: 优先级高的在前, 同优先级按 FIFO.
  6. 更新 qsize/qnum, 唤醒等待接收者.

简要实现(ipc/mqueue.c:sys_mq_send)

c 复制代码
// 系统调用入口: 向消息队列发送消息
asmlinkage long sys_mq_send(mqd_t mqdes, const char __user *u_msg_ptr,
                            size_t msg_len, unsigned int msg_prio)
{
    struct file *file;
    struct mqueue_inode_info *info;
    struct msg_msg *msg;
    int retval;

    // 通过描述符获取文件对象
    file = fget(mqdes);
    if (!file)
        return -EBADF;

    // 检查是否为消息队列文件
    if (file->f_op != &mqueue_file_operations) {
        retval = -EBADF;
        goto out;
    }

    info = MQUEUE_I(file->f_dentry->d_inode);

    // 参数校验
    if (msg_len > info->qsize_max) {
        retval = -EMSGSIZE;
        goto out;
    }

    // 分配消息结构
    msg = load_msg(u_msg_ptr, msg_len);
    if (IS_ERR(msg)) {
        retval = PTR_ERR(msg);
        goto out;
    }

    // 设置优先级(存储在 m_type 中)
    msg->m_type = msg_prio;

    // 获取队列锁
    spin_lock(&info->lock);

    // 检查队列容量
    while (info->qsize + msg_len > info->qbytes ||
           info->qnum >= info->qnum_max) {
        if (file->f_flags & O_NONBLOCK) {
            retval = -EAGAIN;
            spin_unlock(&info->lock);
            free_msg(msg);
            goto out;
        }

        // 阻塞等待
        // ... (等待队列逻辑)
    }

    // 按优先级插入消息链表
    // 优先级高的在前, 同优先级按 FIFO
    insert_msg(info, msg);

    // 更新队列统计
    info->qsize += msg_len;
    info->qnum++;

    // 唤醒等待的接收者
    wake_up(&info->wait_q);

    spin_unlock(&info->lock);
    retval = 0;

out:
    fput(file);
    return retval;
}

// 按优先级插入消息
static void insert_msg(struct mqueue_inode_info *info, struct msg_msg *msg)
{
    struct list_head *p;

    // 遍历消息链表, 找到合适的位置
    list_for_each(p, &info->msg_list) {
        struct msg_msg *m = list_entry(p, struct msg_msg, m_list);
        // 如果新消息优先级更高, 插入到前面
        if (msg->m_type > m->m_type) {
            list_add_tail(&msg->m_list, p);
            return;
        }
    }

    // 如果优先级最低或队列为空, 插入到末尾
    list_add_tail(&msg->m_list, &info->msg_list);
}

接收消息(mq_receive)

  1. 权限检查与队列查找: 通过描述符获取 mqueue inode.
  2. msg_list 头部取出消息: 总是取优先级最高的消息(链表头部).
  3. 未找到消息: 阻塞, 或 O_NONBLOCK 则返回 EAGAIN.
  4. 拷贝消息正文到用户态: 检查缓冲区大小.
  5. 从队列删除该 msg_msg, 更新 qsize/qnum, 唤醒等待发送者.

简要实现(ipc/mqueue.c:sys_mq_receive)

c 复制代码
// 系统调用入口: 从消息队列接收消息
asmlinkage long sys_mq_receive(mqd_t mqdes, char __user *u_msg_ptr,
                               size_t msg_len, unsigned int __user *u_msg_prio)
{
    struct file *file;
    struct mqueue_inode_info *info;
    struct msg_msg *msg;
    ssize_t retval;

    // 通过描述符获取文件对象
    file = fget(mqdes);
    if (!file)
        return -EBADF;

    // 检查是否为消息队列文件
    if (file->f_op != &mqueue_file_operations) {
        retval = -EBADF;
        goto out;
    }

    info = MQUEUE_I(file->f_dentry->d_inode);

    // 参数校验
    if (msg_len < info->qsize_max) {
        retval = -EMSGSIZE;
        goto out;
    }

    // 获取队列锁
    spin_lock(&info->lock);

    // 循环查找消息, 直到找到或出错
    for (;;) {
        // 从链表头部取出消息(优先级最高)
        if (!list_empty(&info->msg_list)) {
            msg = list_entry(info->msg_list.next, struct msg_msg, m_list);
            break;  // 找到消息
        }

        // 如果指定了非阻塞标志, 立即返回错误
        if (file->f_flags & O_NONBLOCK) {
            retval = -EAGAIN;
            spin_unlock(&info->lock);
            goto out;
        }

        // 无消息, 需要阻塞等待
        // ... (等待队列逻辑)
    }

    // 从队列删除消息
    list_del(&msg->m_list);

    // 更新队列统计
    info->qsize -= msg->m_ts;
    info->qnum--;

    spin_unlock(&info->lock);

    // 拷贝消息优先级到用户空间
    if (u_msg_prio) {
        if (put_user(msg->m_type, u_msg_prio)) {
            retval = -EFAULT;
            free_msg(msg);
            goto out;
        }
    }

    // 拷贝消息正文到用户空间
    retval = store_msg(u_msg_ptr, msg, msg_len);

    // 释放消息结构
    free_msg(msg);

    // 唤醒等待的发送者
    wake_up(&info->wait_q);

out:
    fput(file);
    return retval;
}

关闭队列(mq_close)

  1. 通过描述符获取文件对象.
  2. 减少文件引用计数.
  3. 如果引用计数为 0, 关闭文件.

简要实现

c 复制代码
// 系统调用入口: 关闭消息队列
asmlinkage long sys_mq_close(mqd_t mqdes)
{
    struct file *file;

    // 通过描述符获取文件对象
    file = fget(mqdes);
    if (!file)
        return -EBADF;

    // 检查是否为消息队列文件
    if (file->f_op != &mqueue_file_operations) {
        fput(file);
        return -EBADF;
    }

    // 关闭文件(减少引用计数)
    fput(file);

    return 0;
}

删除队列(mq_unlink)

  1. 路径解析: 将名字解析为 /dev/mqueue/name 路径.
  2. 查找文件: 在 /dev/mqueue 目录下查找对应的文件.
  3. 删除文件: 调用 VFS 的 unlink 操作删除文件.
  4. 清理资源: 释放消息、dentry 和 inode.

简要实现

c 复制代码
// 系统调用入口: 删除消息队列
asmlinkage long sys_mq_unlink(const char __user *name)
{
    char *tmp;
    int err;
    struct nameidata nd;
    struct dentry *dentry;
    struct mqueue_inode_info *info;

    // 分配临时缓冲区
    tmp = getname(name);
    if (IS_ERR(tmp))
        return PTR_ERR(tmp);

    // 构建完整路径
    err = path_lookup("/dev/mqueue", LOOKUP_FOLLOW, &nd);
    if (err)
        goto out;

    // 查找文件
    dentry = lookup_hash(&nd);
    if (IS_ERR(dentry)) {
        err = PTR_ERR(dentry);
        goto out_path;
    }

    info = MQUEUE_I(dentry->d_inode);

    // 获取队列锁
    spin_lock(&info->lock);

    // 释放所有消息
    while (!list_empty(&info->msg_list)) {
        struct msg_msg *msg = list_entry(info->msg_list.next, struct msg_msg, m_list);
        list_del(&msg->m_list);
        free_msg(msg);
    }

    spin_unlock(&info->lock);

    // 删除文件
    err = vfs_unlink(nd.dentry->d_inode, dentry);
    dput(dentry);

out_path:
    path_release(&nd);
out:
    putname(tmp);
    return err;
}

等待与唤醒

  • 发送端阻塞 : 超限时将当前任务挂到 wait_q, 被接收唤醒.
  • 接收端阻塞 : 无消息时挂到 wait_q, 被发送唤醒.
  • 保护 : 使用 spin_lock 保护队列的并发访问.

关键限制(2.6.12 默认)

  • mq_maxmsg: 队列最大消息数(/proc/sys/fs/mqueue/queues_max, 默认 256)
  • mq_msgsize: 单条消息最大大小(/proc/sys/fs/mqueue/msg_max, 默认 8192)
  • queues_max: 系统消息队列最大数量(/proc/sys/fs/mqueue/queues_max)
  • msg_max: 单条消息最大大小(/proc/sys/fs/mqueue/msg_max)

文件与路径

  • ipc/mqueue.c: 系统调用实现、队列管理、发送/接收核心逻辑
  • include/linux/mqueue.h: 结构体与常量定义
  • include/linux/msg.h: 消息结构定义(复用 System V 消息队列)

小结

  • POSIX 消息队列在 2.6.12 中通过 mqueue 文件系统实现, 在 /dev/mqueue 目录下可见
  • 消息按优先级排序, 高优先级消息先被接收
  • 队列容量受 mq_maxmsg、单条消息受 mq_msgsize 限制
  • 阻塞语义通过等待队列实现, mq_unlink 会释放所有消息
  • 权限与名字管理基于文件系统, 消息队列描述符本质上是文件描述符

与 System V 消息队列的区别

特性 System V POSIX
实现方式 IPC 框架 mqueue 文件系统
标识方式 键值(key) + IPC ID 表 名字(name) + 文件系统
数据结构 msg_queue mqueue_inode_info
消息类型 mtype (long) 优先级(unsigned int, 存储在 m_type)
接收规则 可按类型选择接收 总是接收最高优先级
系统调用 msgget/msgsnd/msgrcv/msgctl mq_open/mq_send/mq_receive/mq_close/mq_unlink
管理方式 IPC ID 表 文件系统
可见性 内核内部 文件系统可见
相关推荐
loosed3 小时前
ubuntu navicat17连接本机msyql8 /run/mysqld/mysqld.sock问题
linux·mysql·ubuntu·adb
小猪佩奇TONY3 小时前
Linux 内核学习(13) --- linux 内核并发与竞态
linux·服务器·学习
倔强的石头1063 小时前
Linux 进程深度解析(四):环境变量 —— 进程的“环境 DNA”
linux·运维·服务器
牛奶咖啡133 小时前
在Linux中搭建本地yum/dnf仓库
linux·搭建yum/dnf本地仓库·添加rpm文件到yum仓库·添加rpm文件到dnf仓库·生成仓库索引·测试本地搭建的yum仓库·搭建http服务并开启目录浏览
大聪明-PLUS3 小时前
优雅的操作系统开发:用现代 C++ 编写操作系统内核(不使用宏)。第一部分——HAL 为王。
linux·嵌入式·arm·smarc
sunflower_level24 小时前
【ssh key】登陆云服务器,github的安全密码
服务器·ssh·github
qq_455760854 小时前
Docker - 镜像
linux·运维·docker
m0_534875054 小时前
Ditto局域网同步功能实现宿主机和VMware虚拟机之间的复制粘贴共享
linux·运维·服务器
zbguolei4 小时前
Windows平台下SRS实时视频服务器的搭建
服务器·windows·音视频