Linux 内核中 POSIX 消息队列的实现(基于 2.6.12)
概述
基于 2.6.12 内核, 说明 POSIX 消息队列的核心数据结构、系统调用路径及关键实现. POSIX 消息队列通过 mqueue 文件系统实现, 主要文件: ipc/mqueue.c、include/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_open→ipc/mqueue.c:sys_mq_open→do_mq_open()创建或打开队列 → 返回消息队列描述符sys_mq_send→ipc/mqueue.c:sys_mq_send→do_mq_send()构造msg_msg并按优先级入队sys_mq_receive→ipc/mqueue.c:sys_mq_receive→do_mq_receive()取出优先级最高的消息并删除sys_mq_close→ipc/mqueue.c:sys_mq_close→ 关闭消息队列描述符sys_mq_unlink→ipc/mqueue.c:sys_mq_unlink→ 删除消息队列
关键流程
创建队列(mq_open)
- 路径解析: 将名字(如
/my_mq)解析为/dev/mqueue/my_mq路径. - 权限检查: 检查进程是否有权限创建/访问消息队列.
- 创建 mqueue inode: 调用
mqueue_get_inode()创建 mqueue inode. - 初始化队列属性: 设置
mq_maxmsg、mq_msgsize等属性. - 关联到 dentry: 将 inode 关联到
/dev/mqueue目录下的 dentry. - 返回消息队列描述符: 打开文件并返回描述符.
简要实现(基于 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)
- 权限检查与队列查找: 通过描述符获取 mqueue inode.
- 检查长度: 单条消息长度 ≤
mq_msgsize, 总字节数不超过qbytes. - 若队列字节数 + 消息长度超限: 阻塞至可写, 或
O_NONBLOCK则返回EAGAIN. - 分配
msg_msg: 分配消息结构, 复制用户数据. - 按优先级插入
msg_list: 优先级高的在前, 同优先级按 FIFO. - 更新
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)
- 权限检查与队列查找: 通过描述符获取 mqueue inode.
- 从
msg_list头部取出消息: 总是取优先级最高的消息(链表头部). - 未找到消息: 阻塞, 或
O_NONBLOCK则返回EAGAIN. - 拷贝消息正文到用户态: 检查缓冲区大小.
- 从队列删除该
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)
- 通过描述符获取文件对象.
- 减少文件引用计数.
- 如果引用计数为 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)
- 路径解析: 将名字解析为
/dev/mqueue/name路径. - 查找文件: 在
/dev/mqueue目录下查找对应的文件. - 删除文件: 调用 VFS 的
unlink操作删除文件. - 清理资源: 释放消息、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 表 | 文件系统 |
| 可见性 | 内核内部 | 文件系统可见 |