一什么是共享内存共享内存Shared Memory是进程间通信IPC最快的方式核心思想是让多个进程共享同一块物理内存区域直接读写内存来交换数据不需要经过内核中转。图示1.1核心原理1.内存映射操作系统开辟一段物理内存然后将它分别映射到不同进程的虚拟地址空间里。1进程 A 和进程 B 虽然有各自独立的虚拟地址空间但它们指向同一块物理内存。2进程 A 往这块内存写数据进程 B 立刻就能读到反之亦然。2.零拷贝优势(1)对比管道 / 消息队列数据需要从用户态 → 内核态 → 用户态两次拷贝 系统调用开销。 (2)共享内存直接在用户态读写物理内存没有内核拷贝速度极快。二接口函数1.创建 / 获取共享内存段 shmgetint shmget(key_t key,size_t size,int shmfig);参数key唯一标识符键值通常由 ftok() 生成或者使用固定值。size请求的内存大小字节。如果是创建必须指定如果是获取可设为 0。shmflg权限标志。有两个(1)IPC_CREAT1.如果指定 key 对应的共享内存段不存在 → 内核会创建新的共享内存段。 2如果该共享内存段已存在 → 函数会尝试获取这个已存在的段。简化不存在内存则创建如果存在内存则获取。2IPC_EXCL1.无法单独使用必须与 IPC_CREAT 组合使用单独传入 IPC_EXCL 没有意义也不符合接口规范。2.组合使用逻辑IPC_CREAT | IPC_EXCL若指定 key 对应的共享内存段不存在则创建新的共享内存段。若指定 key 对应的共享内存段已存在则函数直接出错返回-1不会复用已存在的内存段。如果创建成功肯定是新的内存段。0666权限设置读写。返回值成功返回 共享内存标识符 ID失败返回 -1。2.共享内存控制函数 shmctlint shmctl(int shmid, int cmd, struct shmid_ds *buf);参数 1int shmid含义共享内存段的唯一标识符ID由 shmget() 函数创建 / 获取共享内存时返回。作用告诉系统「你要操作哪一块共享内存」。参数 2int cmd核心参数指定要对共享内存执行的「控制命令」决定函数的行为最常用的有 3 个(1)IPC_STAT「查询状态」读取共享内存的属性大小、权限、关联进程数等存入 buf 指向的结构体。(2)IPC_SET「修改属性」用 buf 中的数据更新共享内存的属性仅权限 / 所有者等可修改。(3)IPC_RMID「标记删除」标记共享内存为「待删除」当最后一个关联进程脱离shmdt后内存会被真正释放。此时 buf 传 NULL 即可。参数 3struct shmid_ds *buf类型指向共享内存属性结构体的指针。作用执行 IPC_STAT 时传出参数 → 系统把共享内存的属性写入这个结构体供你读取执行 IPC_SET 时传入参数 → 你把要修改的属性写入这个结构体传给系统执行 IPC_RMID 时无需交互 → 直接传 NULL。struct shmid_ds { struct ipc_perm shm_perm; // 权限信息所有者UID、组GID、访问权限 size_t shm_segsz; // 共享内存大小字节 pid_t shm_cpid; // 创建者进程PID shmatt_t shm_nattch;// 当前关联到该内存的进程数关键 time_t shm_ctime; // 最后一次修改属性的时间 };3.挂载函数shmatvoid *shmat(int shmid,const void *shmaddr,int shmflg);作用是将已创建的共享内存段由 shmid 标识挂载Attach到当前进程的虚拟地址空间中。挂载成功后进程就可以像访问本地内存一样访问这块共享内存了。参数 1int shmid含义共享内存段的唯一标识 ID。参数 2const void *shmaddr挂载地址指定用于手动指定共享内存挂载到进程地址空间的哪个位置。日常开发中通常传 NULL。(1)NULL(最常用):由操作系统自动选择挂载地址。(2)非 NULL:操作系统尝试按指定地址挂载。参数 3int shmflg挂载模式与权限用于控制挂载的权限和行为通常传 0 或 特定宏。(1)0 (默认)默认是读写权限。(2)SHM_RDONLY只读模式。挂载后进程只能读共享内存写操作会报错。(3)SHM_REMAP强制重映射。如果目标地址已挂载了其他共享内存强制替换。4.共享内存的卸载函数shmdt核心作用是将当前进程与已挂载shmat的共享内存段解除关联卸载切断进程虚拟地址空间到共享内存物理地址的映射关系。int shmdt(const void* shmaddr);核心参数const void *shmaddr含义需要卸载的共享内存地址指针必须是 shmat 函数的返回值挂载时得到的地址作用告诉系统 “要卸载哪一块已挂载的共享内存”成功返回 0失败返回 -1。三思考1.共享内存有几次数据拷贝共享内存的典型流程键盘输入 → 共享内存 → 屏幕输出拷贝次数2 次这是共享内存高效的核心原因详细过程1.用户输入 → 共享内存由用户进程把数据从用户缓冲区拷贝到共享内存。2.共享内存 → 屏幕输出显示进程直接从共享内存读取数据 → 输出到屏幕。关键点没有内核中间缓冲区数据不经过系统内核拷贝。最大特点零内核拷贝只有 2 次用户态拷贝。2.管道有几次数据拷贝管道的典型流程键盘输入 → 管道 → 屏幕输出拷贝次数4 次固定详细过程1.用户输入 → 管道写缓冲区用户进程 → 内核缓冲区第 1 次2.内核缓冲区 → 管道读缓冲区内核内部拷贝第 2 次3.管道读缓冲区 → 显示进程用户空间内核 → 显示进程第 3 次4.显示进程用户空间 → 屏幕显示进程写标准输出第 4 次3.为什么比共享内存慢因为管道经过内核缓冲区每一次数据传递都需要两次内核态拷贝读、写。四共享内存的缺点共享内存本身只负责 “让多个进程看到同一块内存”不提供任何内置的同步与互斥机制也没有对数据的保护逻辑这会带来几个关键问题1.没有同步与互斥 → 数据竞争多个进程同时写共享内存时会出现写覆盖、数据错乱。比如进程 A 和进程 B 同时修改同一个变量最终结果可能是任意一个的中间状态而不是预期的 “先 A 后 B” 或 “先 B 后 A”。没有互斥锁无法保证 “同一时间只有一个进程在写”。2. 没有数据保护 → 并发访问不安全共享内存没有 “原子操作” 的保证哪怕是简单的 count在多进程下也可能被打断导致结果错误。没有内置的读写锁、信号量等机制需要开发者手动实现同步。3. 没有通知机制 → 生产者 - 消费者问题管道 / 消息队列会阻塞等待数据而共享内存不会主动通知“数据已就绪” 或 “空间已可用”。进程需要自己轮询或借助其他 IPC如信号、信号量、互斥锁来同步读写时机。4.1怎么解决共享内存必须搭配其他同步机制一起使用常见方案信号量控制 “有多少数据可读 / 可写”实现生产者 - 消费者模型。互斥锁保证同一时间只有一个进程在写共享内存。文件锁简单的进程间互斥方案。
Linux间通信方式——共享内存
一什么是共享内存共享内存Shared Memory是进程间通信IPC最快的方式核心思想是让多个进程共享同一块物理内存区域直接读写内存来交换数据不需要经过内核中转。图示1.1核心原理1.内存映射操作系统开辟一段物理内存然后将它分别映射到不同进程的虚拟地址空间里。1进程 A 和进程 B 虽然有各自独立的虚拟地址空间但它们指向同一块物理内存。2进程 A 往这块内存写数据进程 B 立刻就能读到反之亦然。2.零拷贝优势(1)对比管道 / 消息队列数据需要从用户态 → 内核态 → 用户态两次拷贝 系统调用开销。 (2)共享内存直接在用户态读写物理内存没有内核拷贝速度极快。二接口函数1.创建 / 获取共享内存段 shmgetint shmget(key_t key,size_t size,int shmfig);参数key唯一标识符键值通常由 ftok() 生成或者使用固定值。size请求的内存大小字节。如果是创建必须指定如果是获取可设为 0。shmflg权限标志。有两个(1)IPC_CREAT1.如果指定 key 对应的共享内存段不存在 → 内核会创建新的共享内存段。 2如果该共享内存段已存在 → 函数会尝试获取这个已存在的段。简化不存在内存则创建如果存在内存则获取。2IPC_EXCL1.无法单独使用必须与 IPC_CREAT 组合使用单独传入 IPC_EXCL 没有意义也不符合接口规范。2.组合使用逻辑IPC_CREAT | IPC_EXCL若指定 key 对应的共享内存段不存在则创建新的共享内存段。若指定 key 对应的共享内存段已存在则函数直接出错返回-1不会复用已存在的内存段。如果创建成功肯定是新的内存段。0666权限设置读写。返回值成功返回 共享内存标识符 ID失败返回 -1。2.共享内存控制函数 shmctlint shmctl(int shmid, int cmd, struct shmid_ds *buf);参数 1int shmid含义共享内存段的唯一标识符ID由 shmget() 函数创建 / 获取共享内存时返回。作用告诉系统「你要操作哪一块共享内存」。参数 2int cmd核心参数指定要对共享内存执行的「控制命令」决定函数的行为最常用的有 3 个(1)IPC_STAT「查询状态」读取共享内存的属性大小、权限、关联进程数等存入 buf 指向的结构体。(2)IPC_SET「修改属性」用 buf 中的数据更新共享内存的属性仅权限 / 所有者等可修改。(3)IPC_RMID「标记删除」标记共享内存为「待删除」当最后一个关联进程脱离shmdt后内存会被真正释放。此时 buf 传 NULL 即可。参数 3struct shmid_ds *buf类型指向共享内存属性结构体的指针。作用执行 IPC_STAT 时传出参数 → 系统把共享内存的属性写入这个结构体供你读取执行 IPC_SET 时传入参数 → 你把要修改的属性写入这个结构体传给系统执行 IPC_RMID 时无需交互 → 直接传 NULL。struct shmid_ds { struct ipc_perm shm_perm; // 权限信息所有者UID、组GID、访问权限 size_t shm_segsz; // 共享内存大小字节 pid_t shm_cpid; // 创建者进程PID shmatt_t shm_nattch;// 当前关联到该内存的进程数关键 time_t shm_ctime; // 最后一次修改属性的时间 };3.挂载函数shmatvoid *shmat(int shmid,const void *shmaddr,int shmflg);作用是将已创建的共享内存段由 shmid 标识挂载Attach到当前进程的虚拟地址空间中。挂载成功后进程就可以像访问本地内存一样访问这块共享内存了。参数 1int shmid含义共享内存段的唯一标识 ID。参数 2const void *shmaddr挂载地址指定用于手动指定共享内存挂载到进程地址空间的哪个位置。日常开发中通常传 NULL。(1)NULL(最常用):由操作系统自动选择挂载地址。(2)非 NULL:操作系统尝试按指定地址挂载。参数 3int shmflg挂载模式与权限用于控制挂载的权限和行为通常传 0 或 特定宏。(1)0 (默认)默认是读写权限。(2)SHM_RDONLY只读模式。挂载后进程只能读共享内存写操作会报错。(3)SHM_REMAP强制重映射。如果目标地址已挂载了其他共享内存强制替换。4.共享内存的卸载函数shmdt核心作用是将当前进程与已挂载shmat的共享内存段解除关联卸载切断进程虚拟地址空间到共享内存物理地址的映射关系。int shmdt(const void* shmaddr);核心参数const void *shmaddr含义需要卸载的共享内存地址指针必须是 shmat 函数的返回值挂载时得到的地址作用告诉系统 “要卸载哪一块已挂载的共享内存”成功返回 0失败返回 -1。三思考1.共享内存有几次数据拷贝共享内存的典型流程键盘输入 → 共享内存 → 屏幕输出拷贝次数2 次这是共享内存高效的核心原因详细过程1.用户输入 → 共享内存由用户进程把数据从用户缓冲区拷贝到共享内存。2.共享内存 → 屏幕输出显示进程直接从共享内存读取数据 → 输出到屏幕。关键点没有内核中间缓冲区数据不经过系统内核拷贝。最大特点零内核拷贝只有 2 次用户态拷贝。2.管道有几次数据拷贝管道的典型流程键盘输入 → 管道 → 屏幕输出拷贝次数4 次固定详细过程1.用户输入 → 管道写缓冲区用户进程 → 内核缓冲区第 1 次2.内核缓冲区 → 管道读缓冲区内核内部拷贝第 2 次3.管道读缓冲区 → 显示进程用户空间内核 → 显示进程第 3 次4.显示进程用户空间 → 屏幕显示进程写标准输出第 4 次3.为什么比共享内存慢因为管道经过内核缓冲区每一次数据传递都需要两次内核态拷贝读、写。四共享内存的缺点共享内存本身只负责 “让多个进程看到同一块内存”不提供任何内置的同步与互斥机制也没有对数据的保护逻辑这会带来几个关键问题1.没有同步与互斥 → 数据竞争多个进程同时写共享内存时会出现写覆盖、数据错乱。比如进程 A 和进程 B 同时修改同一个变量最终结果可能是任意一个的中间状态而不是预期的 “先 A 后 B” 或 “先 B 后 A”。没有互斥锁无法保证 “同一时间只有一个进程在写”。2. 没有数据保护 → 并发访问不安全共享内存没有 “原子操作” 的保证哪怕是简单的 count在多进程下也可能被打断导致结果错误。没有内置的读写锁、信号量等机制需要开发者手动实现同步。3. 没有通知机制 → 生产者 - 消费者问题管道 / 消息队列会阻塞等待数据而共享内存不会主动通知“数据已就绪” 或 “空间已可用”。进程需要自己轮询或借助其他 IPC如信号、信号量、互斥锁来同步读写时机。4.1怎么解决共享内存必须搭配其他同步机制一起使用常见方案信号量控制 “有多少数据可读 / 可写”实现生产者 - 消费者模型。互斥锁保证同一时间只有一个进程在写共享内存。文件锁简单的进程间互斥方案。