我们把进程间通信,叫做进程IPC Inter Process Communication
1.匿名&&命名管道 基于文件 基于已有的文件内核代码,进行当前改良本质是在复用代码
2.System V进程间通信(同一台主机)
这里写目录标题
- 共享内存
- 原理
- 理解
- 是什么
- 接口
- shmget:创建或获取共享内存
- 为什么 key 需要由用户提供?
- 如何生成 key?
- ftok:生成 System V IPC key
- 共享内存的查看与删除
- 查看共享内存
- 删除共享内存
- key 与 shmid 的区别
- 如何把共享内存挂接到虚拟地址空间
共享内存
原理
共享内存的核心思想是:让不同的进程看到同一份物理内存资源。
具体来说,操作系统将同一块物理内存区域,分别映射到多个进程各自的虚拟地址空间中。每个进程都会获得一个指向该共享内存区域的起始虚拟地址。通过这种方式,多个进程可以直接读写同一块物理内存,从而实现高效的数据共享。
图:共享内存原理示意图
技术细节:
在同一台主机上,物理内存是唯一的。每个进程都有自己独立的task_struct和虚拟地址空间。虽然不同进程的虚拟地址空间是隔离的,但通过操作系统的页表机制,可以将它们各自的虚拟地址映射到同一块物理内存页上。这样,进程 A 和进程 B 访问各自虚拟地址空间中的某个地址时,实际上读写的是同一块物理内存。
理解
可以从以下几个层面理解共享内存:
- 映射到共享区:共享内存被映射到进程虚拟地址空间的“共享区”,而非私有数据区(如栈、堆)。
- 类比动态库:其原理类似于动态链接库(.so/.dll),都是将一段内存映射到多个进程的地址空间。但共享内存更“轻量”,它只提供一块原始的内存区域,不包含代码逻辑。内存更"轻量",它只提供一块原始的内存区域,不包含代码逻辑。
- 操作系统管理:系统中可能存在多块共享内存,因此需要被操作系统统一管理。遵循"先描述,再组织"的原则,内核会为每块共享内存维护一个管理结构体(如
struct shmid_ds)。共享内存 = 管理结构体 + 实际的内存块。 - 使用步骤:使用共享内存通常遵循以下五个步骤:
- 创建:申请一块指定大小的共享内存。
- 关联(挂接):将共享内存映射到本进程的虚拟地址空间。
- 使用:通过获得的指针进行读写操作。
- 去关联(分离):解除本进程与共享内存的映射关系。
- 释放(删除):当所有进程都分离后,可销毁该共享内存。
是什么
共享内存(Shared Memory)是一种进程间通信(IPC)机制。它允许多个进程通过将同一块物理内存映射到各自虚拟地址空间的方式,直接访问同一片内存区域。这是最快的一种 IPC 方式,因为数据不需要在进程间复制,而是直接在内存中共享。
接口
共享内存的创建和管理必须由操作系统完成,因为进程通过new或malloc分配的空间位于其私有堆上,具有进程独立性,无法直接与其他进程共享。因此,操作系统提供了一系列系统调用来实现共享内存。
shmget:创建或获取共享内存
shmget是创建或获取共享内存段的关键系统调用。
函数原型:
intshmget(key_tkey,size_tsize,intshmflg);参数说明:
- key (
key_t):共享内存的键值(key),用于在内核中唯一标识一个共享内存段。多个进程通过相同的 key 来访问同一块共享内存。 - size (
size_t):要创建的共享内存大小(以字节为单位)。必须是系统页大小(通常为 4096 字节)的整数倍。 - shmflg (
int):创建标志和权限的组合,通常使用宏定义。
返回值:
- 成功:返回一个非负整数,即共享内存标识符(shmid),用于后续操作。
- 失败:返回
-1。
常用标志(shmflg):
IPC_CREAT:如果 key 对应的共享内存不存在,则创建它;如果已存在,则获取其标识符。IPC_EXCL:必须与IPC_CREAT一起使用(IPC_CREAT | IPC_EXCL)。表示“独占创建”——仅当 key 对应的共享内存不存在时才创建;如果已存在,则调用失败。这常用于确保创建的是全新的共享内存段。- 权限位:通常需要与上述标志按位或(
|)一个八进制权限值,例如0666,表示所有用户可读写。如果不指定,创建的共享内存可能没有访问权限。
图:shmget 函数示意图
为什么 key 需要由用户提供?
操作系统无法自动为需要通信的进程分配合适的 key。因为:
- 进程独立性:操作系统创建的 key 是进程 A 的私有数据,无法直接传递给进程 B。
- 通信意图未知:操作系统无法预知哪些进程需要通信。
因此,key 必须由用户(程序员)约定并传递给双方进程。双方进程使用相同的 key 调用shmget,操作系统内核会根据这个 key 找到或创建对应的共享内存结构体,从而让两个进程“看到”同一块物理内存。
图:用户约定 key 的必要性
如何生成 key?
key_t本质是一个整数。你可以手动指定一个固定值(如0x1234),但更推荐使用ftok函数动态生成,以避免冲突。
ftok:生成 System V IPC key
ftok根据文件路径和一个项目标识符生成一个唯一的 key。
函数原型:
key_tftok(constchar*pathname,intproj_id);参数说明:
- pathname (
const char*):一个已存在文件的路径(任何文件均可)。函数会读取该文件的 inode 号。 - proj_id (
int):项目标识符,一个非零的整数(通常用字符的 ASCII 码,如'a')。
返回值:
- 成功:返回生成的
key_t值。 - 失败:返回
(key_t) -1。
函数内部会将文件的 inode 号和 proj_id 进行运算,生成一个唯一的 key。只要两个进程使用相同的pathname和proj_id,就能得到相同的 key。
图:ftok 函数生成 key 的原理
共享内存的查看与删除
查看共享内存
使用ipcs -m命令可以查看系统中当前存在的所有 System V 共享内存段。
删除共享内存
共享内存的生命周期随内核,除非显式删除或系统重启,否则会一直存在。
1. 命令行删除:
ipcrm-m<shmid>其中<shmid>是shmget返回的共享内存标识符。
2. 代码删除:使用shmctlshmctl是一个多功能控制函数,可用于删除共享内存或获取/设置其属性。
函数原型:
intshmctl(intshmid,intcmd,structshmid_ds*buf);参数说明:
- shmid (
int):要操作的共享内存标识符。 - cmd (
int):控制命令。 - buf (
struct shmid_ds *):指向shmid_ds结构体的指针,用于获取或设置属性。删除时可设为NULL。
常用命令(cmd):
IPC_RMID:立即删除共享内存段。即使仍有进程与之关联,该段也会被标记为“待删除”,在所有进程都分离(detach)后,内核会真正回收资源。调用时,buf参数可设为NULL。
图:shmctl 函数示意图
key 与 shmid 的区别
- key:在内核中唯一标识共享内存段。由用户约定,用于
shmget的创建/获取。 - shmid:在用户空间使用的共享内存句柄。由
shmget返回,用于后续的shmat、shmdt、shmctl等操作。
类比:key 类似于文件的 inode 号(唯一标识),shmid 类似于文件描述符 fd(用户使用的句柄)。删除时使用的是 shmid,而不是 key。
如何把共享内存挂接到虚拟地址空间
共享内存创建后,需要将其“挂接”(Attach)到进程的虚拟地址空间,进程才能访问它。这通过shmat系统调用实现。
函数原型:
void*shmat(intshmid,constvoid*shmaddr,intshmflg);参数说明:
- shmid:要连接的共享内存标识符(即
shmget的返回值)。 - shmaddr:指定挂接的起始虚拟地址。通常设为
NULL,由系统自动选择合适的地址。 - shmflg:挂接标志,例如设置只读(
SHM_RDONLY)等。一般传0表示可读写。
返回值:
- 成功:返回挂接后共享内存段在进程虚拟地址空间中的起始地址(
void*类型)。 - 失败:返回
(void*) -1。
这个返回值类似于malloc返回的堆内存地址,进程后续通过该指针访问共享内存。
当进程不再需要访问共享内存时,应调用shmdt进行“去挂接”(Detach)。
函数原型:
intshmdt(constvoid*shmaddr);参数说明:
- shmaddr:要断开连接的共享内存起始地址(即
shmat的返回值)。
返回值:
- 成功:返回
0。 - 失败:返回
-1。
注意:shmdt只是断开进程与共享内存的映射关系,并不会删除共享内存本身。删除共享内存需要使用shmctl命令(IPC_RMID)。