前言

在进程间通信(IPC)的体系中,System V IPC 是 Linux 系统中经典且高效的通信方案,包含共享内存、消息队列、信号量三种核心机制,适用于不同场景下的跨进程数据交互与协同。其中,System V 共享内存凭借 “零拷贝” 的特性,成为所有 IPC 方式中速度最快的方案,而消息队列与信号量则分别聚焦于 “带类型数据传输” 和 “进程同步互斥”,三者互补构成了底层系统编程的重要基础。

本文将以 System V 共享内存 为核心,从原理、结构体、核心函数到实操指令逐层拆解:详解共享内存如何通过页表映射实现物理内存共享,深入分析 shmget「创建共享内存」、shmat「挂载地址空间」、shmdt「脱离映射」、shmctl「控制共享内存」四大函数的参数用法与返回值逻辑,补充 ipcs/ipcrm 等调试指令的实战技巧;同时简要介绍消息队列的 “带类型数据传输” 原理与信号量的 “同步互斥” 核心(含 PV 操作、临界资源等关键概念),并通过内核 IPC 数据结构设计帮助读者理解底层管理逻辑。文末附上针对性习题,巩固核心知识点。

无论你是正在学习 Linux 系统编程的学生,还是需要夯实 IPC 底层原理的开发者,本文都将通过 “原理拆解 + 函数实操 + 指令实战 + 习题巩固” 的方式,带你吃透 System V IPC 的核心逻辑,掌握跨进程通信的高效实现方案,为后续开发多进程协同程序、底层服务提供坚实基础。

System V共享内存

System V IPC 机制包含三种常用的通信方式:

分别是 System V共享内存 System V 消息队列 System V 信号量

资源种类的不同决定了通信方式的不同,比如:资源是文件缓冲区的话就用管道,是内存块的话就用共享内存

这里先讲解共享内存

当然哈,除了System的共享内存,还有其他版本的共享内存,比如mmap函数

共享内存没用特殊处理的话,不是追加写,而是覆盖写

共享内存的生命周期是随内核的,如果用户不主动关闭,共享内存会一直存在,除非内核重启

共享内存的特性:

1.共享内存没有同步互斥之类的保护机制–所以多进程写入时容易造成数据混乱

2.共享内存是所有进程间通信中,速度最快的–因为拷贝少

3.共享内存内部的数据需要用户自己维护

原理

就是让不同进程的进程地址空间共享区的页表指向同一块物理内存

步骤:1.先在物理内存上申请空间 2.再挂接到进程地址空间上

(这些是操作系统做的,而不是进程–因为进程具有独立性)

内核为每个 System V 共享内存段都维护了一个 shmid_ds 结构体来记录属性

如果想要释放共享内存的话,先要去关联之后才能释放

操作系统会管理所有的共享内存–采用的是先描述,再组织

共享内存的结构体

共享内存的属性都在这个里面,比如:shm_segsz表示共享内存的真实大小

在这里插入图片描述

这个struct ipc_perm的话是在这里插入图片描述

消息队列那些也有这个部分

共享内存的函数

shmget

作用:用来创建共享内存

在这里插入图片描述

其返回值是:成功的话返回一个共享内存的标识符一般叫做shmid,失败的话返回-1

参数的意义:

size:是创建的共享内存的大小

shmflg:只有两种填法,然后还可以|共享内存访问权限上去,这个共享内存访问权限也可以不显示指定

写法比如: IPC_CREAT|0666

IPC_CREAT 如果申请的共享内存不存在,就创建;存在的话,就获取并返回

IPC_CREAT | IPC_EXCL 如果申请的共享内存不存在,就创建;存在的话,就出错返回–确保申请到的共享内存一定是新的

怎么个出错法:全局变量 errno 会被设置为 EEXIST

key:是个数字,在内核中具有唯一性,内存共享的进程这个数字是相同的(第一个进程的话,需要自己创建一个key)

对于一个已经创建好的共享内存,key在共享内存的结构体对象里面

想要创建出一个key的话,一般用ftok

在这里插入图片描述

返回值:成功的话就是key,失败的话返回-1,并设置全局变量 errno 以指示错误原因

pathname是传路径名称 proj_id的话看心情传(一般只用低8位)

ftok里面是一套算法,对pathname 和proj_id做运算得个结果出来

keyshmid的区别:

key是操作系统内核层面用的,让全系统的进程能找到同一份资源

shmid是用户层面用的,让进程能在内核的帮助下,操作具体的资源

引申:

共享内存的大小操作系统一般都是开的4096的整数倍

eg:要4097的话,就给你开8192

shmat

作用:将共享内存段连接到进程地址空间

在这里插入图片描述

返回值:成功的的话返回共享内存段映射到进程地址空间后的起始地址;失败的话返回-1,并改errno

参数:

shmidshmflg跟上面的意思一样

shmaddr:决定挂到共享内存的什么位置,一般传空指针就行了,让系统去决定

一旦共享内存挂到了自己的地址空间上,就当向内存申请的空间那样用就行了

shmdt

作用:将共享内存段与当前进程脱离

在这里插入图片描述

返回值还有形参都跟上面一样

如果进程退出了的话,会自动跟共享内存段脱离,不需要shmdt

shmctl

作用:用于控制共享内存

在这里插入图片描述

返回值:成功返回0,失败返回-1

cmd:将要采取的动作(有三个值供选择)

IPC_STAT:将 shmid 对应的共享内存段的 shmid_ds 结构,复制到 buf 指向的内存区域。

IPC_SET:将 buf 指向的 shmid_ds 结构中所有者 ID,所属组 ID,权限位,设置到目标共享内存段。

IPC_RMID:删除shmid对应的共享内存段(那啥时也是标记的待删除哈)

buf:指向 struct shmid_ds 结构体的指针(没用到这个时,可以传空指针,比如IPC_RMID时)

一些指令

ipcs -m:可以看到共享内存段的信息:比如每个keyshmid,owner,nattch啥的

(nattch就是那个计数器)

ipcrm -m shmid的值:这个shmid被标记为待删除,已经在使用了的还能用(如果没真被删除了的话),但是还没用的就不让了;nattch减为0的话,这个共享内存段就会被删除

消息队列(了解即可)

它的接口,函数,结构体,指令都跟上面的那种共享内存相似

这个也算进程间通信的一种

比如:指令:ipcs -q 查系统中存在的消息队列的信息

            `ipcrm -q 消息队列的msgid`   删除指定的消息队列

消息队列的原理: 操作系统搞了一个message queue 进程间通信可以通过给这个队列里面增加或者提取数据块来实现通信

所以这个队列需要1.可以被不同进程看到 2.允许不同进程向内核发送带类型的数据块(带类型是为了可以让进程知道这个数据是关于什么的)

IPC在内核中的数据结构设计(了解即可)

共享内存,消息队列和信号量都属于IPC,从他们的结构体里面可以看出–有点像多态那种的基类和子类的关系

操作系统为了管理这些IPC,就搞了一个struct ipc_perm *array[],这的下标是是这些的id,比如:msgid shmid

这个下标的话在创建时是线性递增的;就算释放了以前的下标,也只会在下标到头时才复用

这个数组不属于任何进程,是操作系统的

信号量(了解即可)

信号量也叫做信号灯,本质其实就是一把计数器(但是跟int cnt来计数不相等),用来描述临界资源中资源数量的多少

如果没用信号量的话,那么多个进程就有可能会争抢同一份临界资源,导致数据混乱

–信号量有效的保证1进入共享资源的执行流的数量

注意:信号和信号量没有任何的关联!

信号量属于是控制型资源, 数据传输和存储那种叫做存储型资源

信号量的接口那些的话,也跟上面的那种共享内存相似

申请计数器成功,就表示这个执行流具有访问资源的权限了

–申请计数器成功,是对资源的预定机制,不需要立马访问

把值只能为0,1两态的那种计数器叫做二元信号量–本质就是一个锁,实现了互斥功能

这种的话,那个的临界资源就被当作一个整体去搞了,而不是分成很多块的那种(所以很多块的那种计数器>1)

申请信号量,本质是对计数器-- --P操作

释放资源,释放信号量,本质是对计数器++ --V操作

PV操作是原子的 --要么不做,要做就做完了(是两态的)–没有正在做的概念

注意:多个信号量和信号量是几是不同的概念!

一些概念:

任何时刻,只允许一个执行流访问共享资源 这个叫做互斥

多个执行流共享的资源,但是任何时刻只允许一个执行流访问(就是执行访问代码)的资源 叫做临界资源(这个一般用的是内存空间)

访问临界资源的代码 叫做临界区 ,比如从里面read

信号量也算进程间通信中的一种

原因: 通信不仅仅单单是通信数据,互相协同也算是通信

作业部分

以下描述正确的有(B)
A.进程之间可以直接通过地址访问进行相互通信
B.进程之间不可以直接通过地址访问进行相互通信//进程具有独立性
C.所有的进程间通信都是通过内核中的缓冲区实现的//还有比如共享内存
D.以上都是错误的
以下选项属于进程间通信的是(ABD)[多选]
A.管道
B.套接字
C.内存
D.消息队列
管道的容量仅受磁盘容量大小限制(错误)
引申:
1.如果数组相当字符串用的话
可以eg: char buffer[1000];
buffer[0] = 0;//0在这其实就是\0,这样可以提醒阅读人自己把这个当字符串用了
注意,字符串的sizeof跟strlen是不一样的!
管道可以实现双向通信()

多个进程在通过管道通信时,删除管道文件则无法继续通信()
//命名管道文件只是标识,之前已经打开了管道的进程依然持有有效的文件描述符,所以不会受到影响
进程间通讯的方式中哪种的访问速度最快?(C)
A.管道
B.消息队列
C.共享内存
D.套接字
以下描述正确的有(A)
A.共享内存实现通信的原理是因为所有进程操作映射同一块物理内存
B.共享内存的操作是进程安全的
//是非进程安全的,比如:多个进程同时对共享内存读写,可能会造成数据混乱
C.共享内存被删除后,则其它进程直接无法实现通信
//这里的删除指的是执行删除操作,用比如:shmctl
//必须要链接数为0才会被真实删除;不然只是先拒绝后续映射,等链接数为0再删除
D.所有进程与共享内存断开映射后,则共享内存自动被释放
 以下描述正确的有(C)
A.使用ipcrm -m命令删除指定共享内存后,则会直接释放共享内存
//并非直接释放,应该说成ipcrm -m命令可以删除共享内存
B.使用ipcs -m命令删除指定共享内存后,则会直接释放共享内存
C.使用ipcrm -a选项可以删除所有进程间通信资源
D.使用ipcrm命令不指定选项可以删除所有进程间通信资源
以下关于ipc命令描述正确的有:(B)
A.ipcs -m用于查看消息队列的信息
B.ipcs -q可以查看消息队列的信息
C.ipcrm -s可以查看共享内存的信息
D.ipcrm -q可以查看共享内存的信息
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐