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

System V IPC:Linux进程通信的标准方案

前言:欢迎各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!

在这里插入图片描述


IF’Maxue:个人主页

 🔥 个人专栏:
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》

⛺️生活是默默的坚持,毅力是永久的享受。不破不立!

文章目录

    • 消息队列:带类型的进程通信
      • 1. 消息队列的核心逻辑
      • 2. 关键特性:按“消息类型”收发
      • 3. 消息队列的核心结论
        • (1)通信本质
        • (2)OS 的管理逻辑
        • (3)进程互通的前提
      • 4. 消息队列的删除(Linux 命令)
      • 5. 消息队列的内核数据结构
      • 6. 与共享内存的对比:逻辑一致,细节不同
      • 7. 消息队列的核心函数(代码)
        • 函数参数解读:
        • 自定义消息结构体示例:
      • 8. Linux 查看消息队列(命令)
      • 9. 关键细节:消息类型必须 > 0
    • 信号量:保护共享资源的“锁”
      • 1. 背景:共享内存的痛点与解决方案
        • (1)共享内存的优缺点
        • (2)两个核心概念
        • (3)解决方案:信号量
      • 2. 保护临界区的两种需求
        • (1)互斥(Mutual Exclusion)
        • (2)同步(Synchronization)
      • 3. 关键概念:原子性
      • 4. 信号量是什么?
      • 5. 理解信号量:资源预定机制
      • 6. 信号量的使用场景:分块保护共享资源
      • 7. 信号量的核心操作:P 操作与 V 操作
        • (1)伪代码思路
        • (2)P、V 操作的原子性保证
      • 8. 特殊信号量:二元信号量(互斥锁)
      • 9. 复盘:访问共享资源的两种方式
      • 10. 信号量的本质:不是整数,是数据结构
      • 11. 信号量与“通信”的关系
      • 12. 信号量的系统调用接口(代码)
        • (1)创建信号量:semget 函数
        • (2)信号量个数:nsems 参数说明
        • (3)删除信号量:semctl 函数(IPC_RMID)
        • (4)信号量操作:semop 函数(P/V 操作)
        • (5)信号量初始化:semctl 函数(SETVAL)
      • 13. 信号量的内核数据结构与 IPC 统一管理
        • (1)信号量的内核结构体:semid_ds
        • (2)获取信号量状态:semctl(IPC_STAT)
        • (3)OS 对 IPC 资源的统一管理
        • (4)IPC 资源的内核组织方式
        • (5)访问结构体其他字段:指针强转
        • (6)C 语言实现“多态”的技巧

System V 是 Linux 中进程间通信(IPC) 的经典标准,核心包含三大组件:共享内存、消息队列、信号量。它们的核心逻辑一致——由操作系统维护一份“公共资源”,让不同进程能“看到同一份资源”,从而实现数据传递或协同操作。下面我们逐个拆解核心组件,结合图片和代码讲透用法。

消息队列:带类型的进程通信

消息队列是 Linux 提供的 IPC 方案(虽现在用得少,但理解其设计很重要),本质是操作系统维护的一个“消息链表”:进程 A 把消息按“类型”放进队列,进程 B 按“类型”从队列取消息,实现定向通信。

1. 消息队列的核心逻辑

IPC 的关键是“进程看到同一份资源”:

  • 步骤1:进程向 OS 申请创建一个消息队列;
  • 步骤2:进程 A 发送消息时,会给消息打上“类型标签”,然后把消息存入队列;
  • 步骤3:进程 B 接收消息时,可指定“只看某类消息”,忽略其他类型;
  • 步骤4:双方通过同一个队列通信,队列由 OS 统一管理。

下图清晰展示了这个流程:
image.png

2. 关键特性:按“消息类型”收发

消息队列和普通队列的最大区别是消息带类型,这让通信更灵活:

  • 每个消息都有一个“类型标志”(比如 int 类型的数字);
  • 接收进程可以“按需筛选”:只接收自己需要的类型,不用处理无关消息(比如进程 B 只收类型为 2 的消息,忽略类型 1、3 的消息)。

下图展示了“按类型筛选消息”的逻辑:
image.png

3. 消息队列的核心结论

(1)通信本质

消息队列提供了“进程间传递带类型数据块”的方式——重点是“按类型区分消息”,确保进程只处理自己关心的数据。

(2)OS 的管理逻辑

OS 对消息队列的管理遵循“先描述,再组织”:

  • 先描述:用一个结构体(比如内核中的 msgid_ds)记录队列的信息(所有者、权限、消息数量等);
  • 再组织:用链表或数组把这些结构体管理起来,方便查询和操作。
(3)进程互通的前提

两个进程要通信,必须“找到同一个消息队列”,关键是约定同一个 Key 值(类似门锁的钥匙,只有钥匙对了才能打开门)。

下图展示了“Key 关联消息队列”的逻辑:
image.png

4. 消息队列的删除(Linux 命令)

消息队列创建后会一直存在(除非主动删除),若不删除会占用系统资源。删除需用 ipcrm 命令,指定消息队列的 ID(msgid):

下图是删除消息队列的操作示例:
image.png

示例命令:ipcrm -q 123456(-q 表示操作消息队列,123456 是消息队列的 ID)

5. 消息队列的内核数据结构

OS 用 msgid_ds 结构体管理消息队列,下图展示了该结构体的核心字段(比如权限、所有者、消息链表指针等):
image.png

核心字段说明:

  • msg_perm:存队列的权限和 Key 值;
  • msg_first/msg_last:指向队列中第一个/最后一个消息的指针;
  • msg_qnum:队列中当前的消息数量。

6. 与共享内存的对比:逻辑一致,细节不同

消息队列和共享内存的核心逻辑完全一致(都是 OS 维护资源,进程通过 Key 访问),最大区别在“数据收发方式”:

  • 共享内存:进程直接读写同一块内存,速度快;
  • 消息队列:进程通过“发送/接收函数”操作队列,速度慢,但自带“按类型筛选”功能。

这就是 System V 标准的设计思路——统一框架,差异化细节

7. 消息队列的核心函数(代码)

消息队列的收发依赖两个关键函数:msgsnd(发送)和 msgrcv(接收),下图展示了函数原型和参数说明:
image.png

函数参数解读:
  • msqid:消息队列的 ID(由 msgget 函数创建时返回);
  • msgp:消息缓冲区指针(需自定义结构体,第一个字段必须是“消息类型”,比如 long mtype);
  • msgsz:消息体的长度(不包含“消息类型”字段的长度);
  • msgflg:标志位(比如 IPC_NOWAIT 表示“非阻塞”,没消息时直接返回错误,不等待)。
自定义消息结构体示例:
// 消息结构体:第一个字段必须是 long 类型的消息类型
struct msgbuf {long mtype;       // 消息类型(必须 > 0)char mtext[1024]; // 消息体(存实际数据)
};

8. Linux 查看消息队列(命令)

ipcs -q 命令可查看系统中所有的消息队列,包括队列 ID、所有者、消息数量等信息,下图是命令输出示例:
image.png

输出字段说明:

  • msqid:消息队列 ID;
  • owner:队列所有者(用户名);
  • message size:单个消息的最大长度;
  • messages:队列中当前的消息数量。

9. 关键细节:消息类型必须 > 0

发送消息时,mtype(消息类型)必须是正整数(若 <= 0,msgsnd 函数会返回错误)。下图展示了“消息类型”的约束:
image.png

信号量:保护共享资源的“锁”

共享内存虽快,但有个致命问题——没有保护机制:若多个进程同时读写同一块内存,会导致数据不一致(比如两个进程同时给同一个变量加 1,最终结果可能少加一次)。信号量就是解决这个问题的“锁”。

1. 背景:共享内存的痛点与解决方案

(1)共享内存的优缺点
  • 优点:进程直接访问同一份内存,通信速度极快;
  • 缺点:无保护机制,多个进程同时操作“共享资源”会导致数据不一致。
(2)两个核心概念

要解决问题,先明确两个定义:

  • 临界资源:被多个进程共享的资源(比如共享内存中的变量、文件);
  • 临界区:进程中“访问临界资源的代码段”(比如读写共享内存的那几行代码)。

下图展示了“临界区与临界资源”的关系:两个进程的临界区都访问同一个临界资源,若无保护会出问题:
image.png

(3)解决方案:信号量

信号量的作用是“保护临界区”——通过控制临界区的访问权限,间接保护临界资源,避免数据不一致。

2. 保护临界区的两种需求

信号量要实现两种核心功能:

(1)互斥(Mutual Exclusion)

“任何时候,只有一个进程能进入临界区”——类似银行 ATM 机:一次只能一个人用,其他人必须排队。

(2)同步(Synchronization)

“多个进程按约定顺序进入临界区”——比如进程 A 先往共享内存写数据,进程 B 才能读,不能反过来。

3. 关键概念:原子性

信号量能实现保护的核心是“原子操作”:

  • 定义:操作要么“全做完”,要么“全不做”,没有中间状态(比如取钱时,“扣钱 + 吐钞”要么都成功,要么都失败,不会只扣钱不吐钞);
  • 为什么需要?因为信号量本身也是“共享资源”(多个进程都要操作它),原子性确保信号量的操作不会被打断,避免“锁失效”。

4. 信号量是什么?

教材中也叫“信号灯”,本质是一个计数器,用来记录“临界资源的可用数量”。比如:

  • 电影院有 10 个座位(临界资源),信号量初始值就是 10;
  • 买一张票(进程申请资源):信号量减 1(从 10→9);
  • 退一张票(进程释放资源):信号量加 1(从 9→10);
  • 若信号量为 0,说明资源已用完,进程需等待。

5. 理解信号量:资源预定机制

信号量的核心逻辑是“先预定,再使用”:进程要访问临界资源,必须先“预定”(申请信号量),预定成功才能访问,访问完再“释放”(归还信号量)。

下图展示了“信号量预定流程”:
image.png

6. 信号量的使用场景:分块保护共享资源

信号量可将共享内存“分块保护”:把共享内存分成多个小块,每个小块对应一个信号量,进程访问某块时,只需要申请对应块的信号量,不影响其他块的使用。

下图展示了“分块保护”的逻辑:
image.png

7. 信号量的核心操作:P 操作与 V 操作

信号量的所有逻辑都围绕两个原子操作:P 操作(申请资源)V 操作(释放资源)

(1)伪代码思路

下图展示了 P、V 操作的伪代码逻辑:
image.png

  • P 操作(申请):

    1. 检查信号量是否 > 0(有资源可用);
    2. 若 > 0,信号量减 1,进入临界区;
    3. 若 = 0,进程阻塞(排队等待),直到有其他进程释放资源。
  • V 操作(释放):

    1. 信号量加 1;
    2. 若有进程在阻塞等待,唤醒其中一个进程。
(2)P、V 操作的原子性保证

下图明确了“P、V 操作必须原子执行”——任何时候都不能被打断,否则会导致信号量计数错误:
image.png

8. 特殊信号量:二元信号量(互斥锁)

若信号量的取值只有 0 或 1,则称为“二元信号量”,本质就是“互斥锁”:

  • 初始值为 1(表示资源可用);
  • 进程申请时(P 操作):1→0,进入临界区;
  • 进程释放时(V 操作):0→1,允许其他进程进入;
  • 场景:临界资源只能被一个进程访问(比如共享内存中的单个变量)。

下图用“超级 VIP 通道”举例:一次只能一个人进,就是二元信号量的逻辑:
image.png

9. 复盘:访问共享资源的两种方式

结合信号量,访问共享资源有两种常见方式,下图清晰展示了区别:
image.png

  • 方式 1:整体使用(二元信号量)

    • 整个共享资源用一个信号量(初始值 1),一次只能一个进程访问;
    • 场景:资源不可分割(比如一个文件的写操作)。
  • 方式 2:区块使用(计数信号量)

    • 资源分块,每个块对应一个信号量(初始值 = 块数),多个进程可同时访问不同块;
    • 场景:资源可分割(比如共享内存分成 5 块,可 5 个进程同时访问)。

10. 信号量的本质:不是整数,是数据结构

很多人误以为信号量是“整数”,其实它是一个包含“计数器 + 等待队列”的数据结构

  • 计数器(sem_val):记录资源可用数量;
  • 等待队列(sem_queue):存“申请资源失败而阻塞的进程”,释放资源时唤醒。

下图展示了信号量的数据结构:
image.png

11. 信号量与“通信”的关系

信号量不直接传递数据,但也是 IPC 的一种:

  • 通信的本质是“进程间的协同”,不只是“传数据”;
  • 信号量通过“控制进程访问资源的顺序”,实现进程间的“同步/互斥协同”,这也是一种通信。

下图展示了“信号量实现进程协同”的逻辑:
image.png

12. 信号量的系统调用接口(代码)

信号量的操作依赖 3 个核心函数:semget(创建)、semctl(控制/初始化/删除)、semop(P/V 操作)。

(1)创建信号量:semget 函数

函数作用:创建一个“信号量集”(可包含多个信号量),返回信号量集的 ID(semid)。

下图展示了 semget 的函数原型和参数:
image.png

参数解读:

  • key:约定的键值(进程互通的关键,和消息队列的 Key 逻辑一致);
  • nsems:要创建的信号量个数(比如创建 3 个信号量,就设为 3);
  • semflg:标志位(比如 IPC_CREAT|0666,表示“创建信号量集,并设置权限为 666”)。

示例:创建一个包含 2 个信号量的集合:

key_t key = ftok(".", 66); // 生成 Key(当前目录 + 66 作为种子)
int semid = semget(key, 2, IPC_CREAT | 0666); // 创建 2 个信号量
if (semid == -1) { perror("semget"); exit(1); }
(2)信号量个数:nsems 参数说明

nsems 表示“信号量集中包含的信号量数量”,每个信号量有独立的计数器。下图展示了“信号量集与信号量的关系”:
image.png

示例:nsems=5 表示创建一个包含 5 个信号量的集合,编号分别为 0~4。

(3)删除信号量:semctl 函数(IPC_RMID)

函数作用:删除整个信号量集(删除后,所有进程都无法访问)。

下图展示了 semctl 删除信号量的用法:
image.png

参数解读:

  • semid:信号量集 ID;
  • semnum:信号量编号(删除整个集合时,该参数可忽略,设为 0 即可);
  • cmd:操作命令(IPC_RMID 表示“删除信号量集”)。

示例:删除信号量集:

int ret = semctl(semid, 0, IPC_RMID); // 删除整个信号量集
if (ret == -1) { perror("semctl"); exit(1); }
(4)信号量操作:semop 函数(P/V 操作)

函数作用:对信号量集中的某个信号量执行 P 操作(-1)或 V 操作(+1)。

下图展示了 semop 的函数原型和核心参数:
image.png

关键是 struct sembuf 结构体(定义操作细节):

struct sembuf {unsigned short sem_num; // 信号量编号(0~nsems-1)short sem_op;           // 操作:-1(P操作),+1(V操作)short sem_flg;          // 标志位:0(阻塞),IPC_NOWAIT(非阻塞)
};

示例:对编号 0 的信号量执行 P 操作:

struct sembuf sop;
sop.sem_num = 0;  // 操作编号 0 的信号量
sop.sem_op = -1;  // P 操作(申请资源)
sop.sem_flg = 0;  // 阻塞等待int ret = semop(semid, &sop, 1); // 1 表示执行 1 个操作
if (ret == -1) { perror("semop"); exit(1); }
(5)信号量初始化:semctl 函数(SETVAL)

信号量创建后,计数器初始值是随机的,需用 semctlSETVAL 命令初始化。

下图展示了初始化的用法:
image.png

参数解读:

  • cmd=SETVAL:表示“设置信号量的初始值”;
  • 第 4 个参数(可选):是一个 union semun 结构体,用来传递初始值(val)。

union semun 结构体定义(需自己声明):

union semun {int val;                // 用于 SETVAL:设置信号量的初始值struct semid_ds *buf;   // 用于 IPC_STAT/IPC_SET:获取/设置信号量集状态unsigned short *array;  // 用于 GETALL/SETALL:获取/设置所有信号量的初始值
};

示例:将编号 0 的信号量初始化为 1(二元信号量):

union semun su;
su.val = 1; // 初始值设为 1
int ret = semctl(semid, 0, SETVAL, su); // 初始化编号 0 的信号量
if (ret == -1) { perror("semctl"); exit(1); }

注意SETVAL 操作不是原子的,若多个进程同时初始化同一个信号量,可能出问题,需确保只有一个进程执行初始化(比如用父进程初始化,子进程只操作)。

13. 信号量的内核数据结构与 IPC 统一管理

(1)信号量的内核结构体:semid_ds

OS 用 semid_ds 结构体管理信号量集,核心字段包含“权限、信号量个数、最后操作时间”等,下图展示了结构体的组成:
image.png

核心字段:

  • sem_perm:存信号量集的 Key、ID、权限;
  • sem_nsems:信号量集中的信号量个数;
  • sem_otime:最后一次执行 semop 的时间。
(2)获取信号量状态:semctl(IPC_STAT)

semctlIPC_STAT 命令可获取信号量集的状态(比如权限、个数),需传入 struct semid_ds 结构体存储结果。

下图展示了用法:
image.png

示例:获取信号量集状态:

struct semid_ds sem_buf;
int ret = semctl(semid, 0, IPC_STAT, &sem_buf); // 获取状态存入 sem_buf
if (ret == -1) { perror("semctl"); exit(1); }// 打印信号量个数和最后操作时间
printf("信号量个数:%d\n", sem_buf.sem_nsems);
printf("最后操作时间:%ld\n", sem_buf.sem_otime);
(3)OS 对 IPC 资源的统一管理

System V 中的共享内存、消息队列、信号量,被 OS 当作“同一种资源”管理——它们的内核结构体都包含一个 kern_ipc_perm 结构体(作为第一个成员),用来存储“Key、ID、权限”等公共信息。

下图展示了三种 IPC 资源的结构体共性:
image.png

(4)IPC 资源的内核组织方式

OS 用一个“指针数组(id_ary)”管理所有 IPC 资源:

  • 数组的每个元素是 kern_ipc_perm* 指针,指向一个 IPC 资源(共享内存/消息队列/信号量);
  • 资源的 ID(比如 shmid、msgid、semid)就是数组的“下标”(比如 semid=5,表示数组下标 5 的指针指向该信号量集)。

下图是 IPC 资源的内核组织图:
image.png

下图展示了“ID 与数组下标的关系”:
image.png

(5)访问结构体其他字段:指针强转

因为 kern_ipc_perm 是每个 IPC 结构体的“第一个成员”,所以可以将 kern_ipc_perm* 指针强转为具体的结构体指针,从而访问其他字段。

下图展示了强转逻辑:
image.png

示例:将 kern_ipc_perm* 强转为 semid_ds*

// id_ary[semid] 是指向该信号量集的 kern_ipc_perm* 指针
struct kern_ipc_perm *perm_p = id_ary[semid];
// 强转为 semid_ds*,访问 sem_nsems 字段
struct semid_ds *sem_ds_p = (struct semid_ds *)perm_p;
printf("信号量个数:%d\n", sem_ds_p->sem_nsems);

下图展示了强转的代码细节:
image.png

(6)C 语言实现“多态”的技巧

这种“用公共结构体指针管理不同类型资源,强转后访问具体字段”的技术,是 C 语言实现“多态”的经典方式——用统一的接口(kern_ipc_perm*)管理不同的 IPC 资源,需要时再转为具体类型。

下图总结了这种技巧:
image.png

http://www.dtcms.com/a/483029.html

相关文章:

  • 免费的舆情网站app下载ui设计手机界面
  • Spring 三级缓存
  • 怎么样看网站用什么程序做的开发公司经营范围
  • 小广告网站音乐网站系统怎么做
  • 单片机中的TVS管
  • c++最常用的几种设计模式
  • 河南平台网站建设公司临沂企业自助建站系统
  • 下载 asp 网站源码响应式网站 翻译代码
  • 大气医院网站源码河北省住房和城乡建设厅信用网站
  • 【agent】AI 数字人构建10:FunASR 1:语音识别C++服务/客户端构建
  • 教育机构网站建设方案书企业邮箱注册需要多少钱
  • 河南省建设工程监理协会网站cms 导航网站
  • Centos Stream 8 Tomcat学习
  • 网站系统cmsphp教育网站开发工作
  • 在Ubuntu22.04和24.04中安装Docker并安装和配置Java、Mysql、Tomcat
  • 【Linux】线程同步与互斥(1)
  • 网站开发英语英语义乌网八方资源家1688网商网
  • 基于单片机的PID调节脉动真空灭菌器上位机远程监控设计
  • 汕头网站关键词优化教程资源分享网站怎么做
  • STM32H7xx 运行 LWIP 时的 MPU 配置介绍 LAT1510
  • 能动框架战场:如何摆脱供应商锁定并在下次AI战争中生存
  • 免费试用网站空间人人开发接单官网
  • 视联网技术产业观察与分析:视频隐私与安全防护
  • 南通网站建设祥云深圳罗湖网站设计公司
  • 基于蚁群算法优化BP神经网络的实现方法
  • 《Effective Java》第10条:覆盖 equals 时请遵守通用规定
  • 广东广州快速网站制作平台鄂州网站建设哪家好
  • 安卓android自动化测试-uiautomator/uiautomator2
  • 天津 网站设计公司门户网站制作定做
  • React组件复用导致的闪烁问题及通用解决方案