资讯动态

Linux 进程间通信 (IPC):System V 共享内存从原理到实战

发布时间:2026/9/11 18:55:45 来源:尧图企业网站定制
大家好我是程序员小青蛙今天介绍共享内存。在 Linux 操作系统中进程间通信 (IPC) 是一个至关重要的概念它允许不同的进程之间进行数据交换和同步。在众多 IPC 方式中System V 共享内存以其极致的性能脱颖而出被誉为最快的进程间通信方式。本文将从原理、API、实战案例到优缺点带你全面掌握 System V 共享内存。一、共享内存为什么是最快的 IPC1.1 核心原理共享内存的本质是让多个进程的虚拟地址空间映射到同一块物理内存区域。当进程 A 和进程 B 都将同一块物理内存映射到自己的虚拟地址空间后它们对这块内存的读写操作就直接作用于物理内存完全不需要经过内核的中转。对比传统的管道、消息队列等 IPC 方式管道 / 消息队列需要经历用户态缓冲区 → 内核缓冲区 → 用户态缓冲区两次数据拷贝共享内存直接操作物理内存仅需一次拷贝数据写入物理内存这就是共享内存速度远超其他 IPC 方式的根本原因。1.2 内存映射示意图在 Linux 进程的虚拟地址空间布局中共享内存通常被映射到栈和堆之间的区域大约 0x40000000 附近。1.3 关键特性生命周期随内核这是 System V IPC共享内存、消息队列、信号量最重要的特性之一共享内存的生命周期不随创建它的进程结束而结束它会一直存在于内核中直到手动调用shmctl(IPC_RMID)删除系统重启这意味着如果程序异常退出而没有删除共享内存会造成内核资源泄漏二、核心数据结构与 API 详解2.1 共享内存描述符struct shmid_ds内核为每个共享内存段维护一个shmid_ds结构体记录其所有属性struct shmid_ds { struct ipc_perm shm_perm; /* 操作权限 */ int shm_segsz; /* 共享内存段大小字节 */ __kernel_time_t shm_atime; /* 最后一次挂载时间 */ __kernel_time_t shm_dtime; /* 最后一次解除挂载时间 */ __kernel_time_t shm_ctime; /* 最后一次修改时间 */ __kernel_ipc_pid_t shm_cpid; /* 创建者PID */ __kernel_ipc_pid_t shm_lpid; /* 最后一次操作PID */ unsigned short shm_nattch; /* 当前挂载的进程数 */ unsigned short shm_unused; /* 兼容保留 */ void *shm_unused2; /* DIP使用 */ void *shm_unused3; /* 未使用 */ };其中shm_nattch字段非常重要它记录了当前有多少个进程挂载了这个共享内存段。当我们调用IPC_RMID删除共享内存时内核会先将其标记为删除状态直到shm_nattch变为 0 时才会真正释放物理内存。2.2 四大核心函数1.shmget创建或获取共享内存#include sys/ipc.h #include sys/shm.h int shmget(key_t key, size_t size, int shmflg);功能创建一个新的共享内存段或获取一个已存在的共享内存段参数key共享内存的唯一标识符通常由ftok()生成size共享内存段的大小字节创建时必须指定获取时可设为 0shmflg标志位用法与文件权限类似IPC_CREAT如果不存在则创建存在则获取IPC_EXCL与IPC_CREAT一起使用如果已存在则返回错误保证创建的是新的权限位如0666表示所有用户都有读写权限返回值成功返回共享内存标识符shmid失败返回 - 1生成 key 的常用方法key_t key ftok(., 0x6666); // 使用当前目录和项目ID生成唯一key2.shmat将共享内存挂载到进程地址空间void *shmat(int shmid, const void *shmaddr, int shmflg);功能将shmid标识的共享内存段映射到调用进程的虚拟地址空间参数shmid由shmget返回的共享内存标识符shmaddr指定映射的虚拟地址建议设为 NULL让内核自动选择合适的地址shmflg挂载标志SHM_RDONLY以只读方式挂载0以读写方式挂载返回值成功返回指向共享内存首地址的指针失败返回(void *)-13.shmdt解除共享内存挂载int shmdt(const void *shmaddr);功能将共享内存段从当前进程的虚拟地址空间中分离参数shmaddr是shmat返回的指针返回值成功返回 0失败返回 - 1注意这只是解除挂载并不会删除共享内存段4.shmctl控制共享内存int shmctl(int shmid, int cmd, struct shmid_ds *buf);功能对共享内存段进行各种控制操作参数shmid共享内存标识符cmd要执行的命令IPC_STAT获取共享内存的状态填充到buf中IPC_SET设置共享内存的属性需要足够权限IPC_RMID删除共享内存段标记删除等待所有进程解除挂载后释放buf用于存储或设置共享内存属性的结构体指针返回值成功返回 0失败返回 - 1三、实战案例服务端 - 客户端通信我们将实现一个简单的 C/S 架构程序客户端向共享内存写入 A-Z 的字母服务端从共享内存中读取并打印。3.1 代码结构. ├── comm.h // 公共头文件 ├── comm.c // 公共函数实现 ├── server.c // 服务端代码 ├── client.c // 客户端代码 └── Makefile // 编译脚本3.2 公共头文件comm.h#ifndef COMM_H #define COMM_H #include stdio.h #include sys/types.h #include sys/ipc.h #include sys/shm.h #define PATHNAME . #define PROJ_ID 0x6666 // 创建共享内存 int createShm(int size); // 获取已存在的共享内存 int getShm(int size); // 删除共享内存 int destroyShm(int shmid); #endif3.3 公共函数实现comm.c#include comm.h // 通用的共享内存获取/创建函数 static int commShm(int size, int flags) { key_t key ftok(PATHNAME, PROJ_ID); if (key 0) { perror(ftok); return -1; } int shmid shmget(key, size, flags); if (shmid 0) { perror(shmget); return -1; } return shmid; } int createShm(int size) { // IPC_CREAT|IPC_EXCL确保创建一个全新的共享内存 return commShm(size, IPC_CREAT | IPC_EXCL | 0666); } int getShm(int size) { // 只获取已存在的共享内存 return commShm(size, IPC_CREAT); } int destroyShm(int shmid) { if (shmctl(shmid, IPC_RMID, NULL) 0) { perror(shmctl); return -1; } return 0; }3.4 服务端代码server.c#include comm.h #include unistd.h int main() { // 创建4096字节的共享内存 int shmid createShm(4096); printf(服务端创建共享内存成功shmid: %d\n, shmid); // 挂载共享内存 char *addr shmat(shmid, NULL, 0); if (addr (void *)-1) { perror(shmat); return 1; } // 循环读取共享内存中的数据 int i 0; while (i 26) { printf(Client# %s\n, addr); sleep(1); i; } // 解除挂载 shmdt(addr); printf(服务端解除挂载\n); // 删除共享内存 sleep(2); destroyShm(shmid); printf(服务端删除共享内存\n); return 0; }3.5 客户端代码client.c#include comm.h #include unistd.h #include string.h int main() { // 获取服务端创建的共享内存 int shmid getShm(4096); printf(客户端获取共享内存成功shmid: %d\n, shmid); // 挂载共享内存 char *addr shmat(shmid, NULL, 0); if (addr (void *)-1) { perror(shmat); return 1; } // 向共享内存写入A-Z int i 0; while (i 26) { addr[i] A i; addr[i1] \0; sleep(1); i; } // 解除挂载 shmdt(addr); printf(客户端解除挂载\n); sleep(2); return 0; }3.6 编译与运行编写Makefile.PHONY: all clean all: server client server: server.c comm.c gcc -o $ $^ client: client.c comm.c gcc -o $ $^ clean: rm -f server client编译运行# 编译 make # 先启动服务端 ./server # 再打开另一个终端启动客户端 ./client3.7 运行结果服务端会依次打印Client# A Client# AB Client# ABC ... Client# ABCDEFGHIJKLMNOPQRSTUVWXYZ3.8 常见问题与解决问题第二次运行服务端时提示shmget: File exists原因上一次运行的服务端异常退出没有删除共享内存导致共享内存仍然存在于内核中解决查看系统中的共享内存ipcs -m删除指定的共享内存ipcrm -m shmid四、共享内存的优缺点分析4.1 优点速度最快所有 IPC 方式中性能最高适合大数据量传输灵活性高可以直接通过指针操作内存支持任意数据结构无数据拷贝避免了内核态和用户态之间的多次数据拷贝4.2 缺点没有同步互斥机制这是共享内存最大的缺点。多个进程同时读写共享内存时会出现数据不一致的问题。必须配合信号量、互斥锁等同步机制使用生命周期随内核如果程序忘记删除共享内存会造成内核资源泄漏没有数据保护任何挂载了共享内存的进程都可以随意修改数据安全性较低五、总结与扩展System V 共享内存是 Linux 下性能最优的进程间通信方式特别适合需要频繁、大量传输数据的场景。但它的缺点也很明显 —— 缺乏同步机制需要开发者自己实现进程间的同步与互斥。在实际开发中我们通常会将共享内存 信号量结合使用用共享内存传输数据用信号量保证数据的一致性。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价