当前位置: 首页 > news >正文

System消息队列

System V 消息队列

生命周期随内核,和共享内存一样,调用后删除命令后不会立刻销毁,仅做删除标记;所有当前挂载使用队列的进程全部退出后,内核才真正释放队列和里面所有消息

收发逻辑

  • 进程 A 发送调用msgsnd(),封装type=1+ 自定义数据,内核在队列链表尾部新增红色 node 节点。
  • 进程 B 接收调用msgrcv(),指定type=2,链表无 type=2 消息 → B 进程阻塞等待,等到内核从链表中寻找第一条 type=2 的绿色消息取出,唤醒B;队列中 type=1 的红色消息会继续留在链表,不会被读取。也可以B的type设置为1,则就能和A进行通信

结论 1:带类型的数据块传输

消息队列的核心特色:每条消息绑定一个整型type标记

  • 进程 A 发送type=1的红色消息;
  • 进程 B 可以指定只接收type=2的绿色消息,跳过队列里其他类型数据;
  • 对比管道:管道只能按先进先出顺序读,无法分类筛选。

结论2:操作系统统一管理

内核必须维护管理结构:

  1. struct msqid_ds:队列总控结构体(权限、消息总数、字节上限、创建者 PID 等);
  2. struct node:单条消息链表节点,保存 type、数据、下一条消息指针;所有消息、权限、统计信息全部由 OS 内核管理,进程只通过系统调用收发数据。

结论3:多进程共享同一队列的关键 —— key

和共享内存逻辑完全一致:

  1. 双方用相同路径 + 项目 ID 调用ftok()生成同一个key
  2. 通过msgget(key, flag)打开 / 创建队列;内核依靠key匹配,保证不同进程映射到内核里同一个消息队列对象

msgget 创建或打开

#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg);
参数msgflg

创建标志 +权限(和 shmget 规则完全一致)

  • IPC_CREAT:key 不存在则新建队列;存在则直接打开;
  • IPC_CREAT | IPC_EXCL:不存在才创建,已存在直接报错,防止重复创建;
返回值
  • 成功:返回非负整数msqid(消息队列操作句柄,供 msgsnd/msgrcv/msgctl 使用)
  • 失败:返回-1

eg:

// 创建队列,不存在则新建,权限0666,存在就直接打开 int msqid = msgget(key, IPC_CREAT | 0666); // 只打开已有队列,不创建 int msqid = msgget(key,0); // 强制创建 int msqid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);

msgctl —— 消息队列控制函数(删队列、改权限、查信息)

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数解析
  • msqid:msgget 返回的队列 id;
  • cmd:控制命令(3 个最常用):IPC_STAT 【读取队列内核信息】,IPC_SET 【修改队列权限 / 所有者】,IPC_RMID 【删除消息队列(最常用)
  • bufstruct msqid_ds结构体,存放队列内核信息,根据 cmd 决定是否使用。
//IPC_STAT 【读取队列内核信息】 struct msqid_ds info; msgctl(msqid, IPC_STAT, &info); printf("队列权限:%ho\n", info.msg_perm.mode); printf("当前消息条数:%ld\n", info.msg_qnum); //IPC_SET 【修改队列权限 / 所有者】 msgctl(msqid, IPC_STAT, &info); info.msg_perm.mode = 0644; // 修改权限 msgctl(msqid, IPC_SET, &info); // IPC_RMID 【删除消息队列(最常用)】 msgctl(msqid, IPC_RMID, NULL); 调用后不会立刻销毁,仅做删除标记;所有当前挂载使用队列的进程全部退出后,内核才真正释放队列和里面所有消息
内核描述队列

msgsnd 发送 + msgrcv 接收

所有收发都必须自定义消息缓冲区,第一个成员必须是 long mtype(>0),后面自定义数据

struct MsgBuf { long mtype; // 消息类型,正数,核心过滤标识 char mtext[128];// 自定义承载数据 };

msgsnd:发送消息到队列尾部

#include <sys/msg.h> int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数解析

  • msqid:msgget 返回的消息值
  • msgp:自定义MsgBuf结构体指针
  • msgsz仅数据长度,不含 mtype;即sizeof(buf.mtext)
  • msgflg阻塞控制标志:
    • 0:默认阻塞。队列满时,发送进程休眠,直到队列腾出空间
    • IPC_NOWAIT:非阻塞。队列满直接返回 - 1,不等待

eg:

struct MsgBuf buf; buf.mtype = 1; // 设置消息类型 strcpy(buf.mtext, "hello"); // 阻塞发送,数据长度是字符串长度 msgsnd(msqid, &buf, strlen(buf.mtext), 0);

返回值:
成功返回0,失败-1并设置errno

msgrcv:从队列读取匹配类型消息

#include <sys/msg.h> ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数解析:
  • msqid:msgget返回值
  • msgp:接收消息的缓冲区结构体指针
  • msgsz:缓冲区数据区最大可容纳字节(mtext 数组长度)
  • msgtyp消息过滤核心参数,分 3 种规则
  • msgflg:阻塞控制
    • 0:无匹配消息时阻塞休眠
    • IPC_NOWAIT:无匹配消息直接返回 - 1,不阻塞
    • MSG_NOERROR:消息数据超过 msgsz 时截断,不报错;不加则返回 - 1
msgtyp 三种读取规则:
  • msgtyp > 0(精准匹配)遍历链表,取出第一条 mtype == msgtyp的消息;其他类型消息保留在队列。例:msgtyp=2 → 只读 type=2 的消息,type=1 完全忽略。
  • msgtyp = 0(先进先出,不筛选)永远取出链表最头部第一条消息,不管 type 是多少。
  • msgtyp < 0(极少使用)取出队列中mtype ≤ abs(msgtyp)里最小 type 的第一条消息。
返回值

成功返回实际拷贝到mtext缓冲区的字节数,失败返回-1,同步设置errno

eg:

struct MsgBuf buf; // 阻塞读取type=2的消息,缓冲区最大128字节 ssize_t n = msgrcv(msqid, &buf, sizeof(buf.mtext), 2, 0); if(n > 0) { printf("收到数据:%s\n", buf.mtext); }

查看和删除消息队列

ipcs -q查看
ipcrm -q msqid删除

怎么通过同步和互斥来保护共享资源

临界资源

被保护起来的共享资源叫做临界资源,同一时刻只能一个进程使用

临界区 vs 非临界区

进程代码分成两部分:

  1. 临界区访问临界资源的那一段代码

    重点结论:保护资源的本质,不是锁住数据本身,而是锁住访问数据的这段代码。

  2. 非临界区:不碰共享资源的代码,多个进程可以随便并行执行,不需要加锁

互斥

互斥定义:任何时刻,只允许一个执行流访问临界资源,同一临界区同一时间只能有一个进程进去。就比如区ATM取钱每次只能一个人

互斥的底层硬性要求:操作必须具备原子性,加锁 / 申请锁操作必须具备原子性(要么完整执行、要么完全不执行),否则可能加锁操作过程到一半就会被打断,进而竞争失效。

进入区(加锁)→ 临界区(操作共享资源)→ 退出区(解锁)→ 非临界区

同步

在互斥基础上,规定多个执行流访问资源的先后顺序。

同步只管控时序,但无法阻止多执行流同时进入临界区;没有互斥,同步逻辑再完善也会数据错乱

2者结合:

  • 进入区① 先执行互斥加锁(原子操作,保证独占临界区);② 判断同步条件:不满足访问条件就阻塞等待(比如缓冲区满,生产者等待);
  • 临界区:独占读写共享缓冲区;
  • 退出区① 同步唤醒等待的另一个执行流(生产者写完唤醒消费者);② 互斥解锁,释放资源访问权限;
  • 非临界区:无关逻辑自由并发。

即先用互斥先保证同一时间只有一个进程进入临界区,;如果有先后执行要求,再叠加同步,控制访问顺序;

http://www.jsqmd.com/news/1182967/

相关文章:

  • 小程序计算机毕设之农产品展示溯源与在线交易小程序的设计与实现 基于移动端的助农农产品零售交易系统(完整前后端 代码+说明文档+LW,调试定制等)
  • Ruby JSON库高级用法:自定义类型序列化与反序列化终极指南
  • 【YOLOv11多模态融合改进】| CVPR2025 LCA轻量交叉注意力 双向模态交叉引导 + 双支路噪点抑制,兼顾纹理与热目标特征融合
  • 12、 AI + 讯飞听见:跨省双语客服沟通会议
  • Leaflet-plugins完全指南:解锁地图开发的终极插件集合
  • Mixtral-8x7B-Instruct-v0.1-da8w8-torchao-v0.17.0 vs 原版模型:CPU推理速度提升200%的秘密
  • Drab安装完全指南:Phoenix应用集成步骤与常见问题解决
  • 3分钟学会:在Windows 10上运行Android应用的完整指南
  • 数码产品回收销售排行:具备验货服务的可信服务商盘点 - 互联网科技品牌测评
  • 深入理解环境变量:从配置到原理,全景解析
  • 深入 Git:它是如何记录世界的(二)
  • 如何5分钟上手ino:Arduino命令行工具完全入门指南
  • 5步快速上手Hermes Studio:从零开始构建AI对话工作台终极指南
  • 东南大学等打造:让机器人真正“看懂世界、动起来“的统一推理引擎
  • 南亚NT5CC256M16ER-EKT:4Gb DDR3L SDRAM内存颗粒技术规格
  • Unity URP动态天空盒实现:程序化昼夜循环与大气散射实战
  • 深入理解 Rust 所有权
  • Unity FSM框架设计:从零构建可扩展的AI行为状态机
  • Nanite监控与故障恢复:确保Ruby服务稳定运行的10个关键策略
  • 终极nosa命令参考:nestos-assembler 30+实用指令全解析
  • Stage 应用包结构:entry、feature、har、hsp 如何拆
  • Copilot写不出正确代码?不是模型问题,是这4类注释结构在 silently sabotage 你的补全质量
  • AI肉鸡/肉鸭/肉鹅多品类防疫注射机器人 Qt信创完整项目
  • AMD显卡AI绘画终极指南:ComfyUI-Zluda完整安装与优化教程
  • LinearRAG: Linear Graph Retrieval Augmented Generation on Large-scale Corpora——面向大规模语料的线性图检索增强生成
  • 网页数据提取实战 ※ 第1关:从HTML表格到结构化数据
  • 为什么“老方法“在高维数据搜索中反而越来越好用?
  • kytuning-server扩展开发教程:如何添加新的基准测试工具支持
  • 如何快速上手Gemma-4-E2B-it-qat-OptiQ-4bit:5分钟完成安装与首次推理
  • 如何用ChatGPT查询真实文献,并轻松撰写文献综述,七步搞定,全攻略指南