进程间通信 之 共享内存
目录前言一、核心概念与本质1.1什么是共享内存1.2 为什么是最快的IPC1.3 共享内存的特点二、共享内存函数介绍1.shmget2.shmat3.shmdt4.shmctl三、示例双进程通信1.写进程writer.c2.读进程reader.c四、命令行管理工具1.查看共享内存ipcs -m2.删除共享内存ipcrm -m五、同步问题与解决方案5.1 共享内存的最大缺陷5.2解决方案信号量配合前言共享内存是进程间通信IPC中效率最高的一种方式。它允许多个进程直接读写同一块物理内存区域数据无需在内核和用户空间之间复制从而实现了极快的数据交换。一、核心概念与本质1.1什么是共享内存共享内存是操作系统在物理内存中开辟的一块区域多个进程可以将这块内存映射到自己的虚拟地址空间中。一旦映射完成进程就可以像访问自己的私有内存一样直接读写这块共享区域无需系统调用介入。如果某个进程向共享内存写入了数据所做的改动将立刻被可以访问同一段共享内存的任何其他 进程看到。1.2 为什么是最快的IPC与管道、消息队列等方式相比共享内存的核心优势在于零拷贝:通信方式数据流向拷贝次数管道/消息队列进程A → 内核 → 进程B2次数据拷贝共享内存进程A → 共享内存 → 进程B0次数据拷贝管道的工作流程写进程调用write()将数据从用户空间拷贝到内核缓冲区读进程调用read()将数据从内核缓冲区拷贝到用户空间共享内存的工作流程进程A直接往映射的虚拟地址写入数据直接操作物理内存进程B直接从同一地址读取数据直接读取物理内存全程无需内核介入省去了两次拷贝开销1.3 共享内存的特点特性说明高效性无需数据拷贝是速度最快的IPC形式全局性所有附加到该内存的进程均可访问无同步机制内核不提供任何同步保护需要用户自行解决生命周期随内核除非显式删除或系统重启用户维护创建、使用、释放都需要用户手动管理二、共享内存函数介绍1.shmgetint shmget(key_t key, size_t size, int shmflg);shmget()用于创建或者获取共享内存shmget()成功返回共享内存的 ID 失败返回-1key 不同的进程使用相同的 key 值可以获取到同一个共享内存,(这里的值和信号量的值一样也没有关系,因为类型不一样;)size 创建共享内存时指定要申请的共享内存空间大小shmflg: IPC_CREAT IPC_EXCL2.shmatvoid* shmat(int shmid, const void *shmaddr, int shmflg);shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上shmat()成功返回返回共享内存的首地址失败返回 NULL(像malloc一样给共享空间的起始地址),看帮助手册失败返回的是-1;shmaddr一般给 NULL由系统自动选择映射的虚拟地址空间shmflg 一般给 0(给0就代表的可读可写) 可以给 SHM_RDONLY 为只读模式其他的为读写3.shmdtint shmdt(const void *shmaddr);shmdt()断开当前进程的 shmaddr 指向的共享内存映射shmdt()成功返回 0 失败返回-1//为什么不是删除共享内存而是断开共享内存呢?因为你不使用了,别的进程可能还在使用这块共享内存;//删除共享内存用shmctl,就是对共享内存做控制,那么可以设置,也可以删除,如下的函数;4.shmctlint shmctl(int shmid, int cmd, struct shmid_ds *buf);shmctl()控制共享内存shmctl()成功返回 0失败返回-1cmd IPC_RMID//删除的时候如果还有人在使用共享内存,这个函数就会延迟删除,等到最后一个进程断开链接它才会删除共享内存.//第三个参数buf是一个结构指针它指向共享内存模式和访问权限的结构。三、示例双进程通信1.写进程writer.c#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/ipc.h #include sys/shm.h #define SHM_SIZE 1024 // 共享内存大小 int main() { key_t key; int shmid; char *data; // 1. 生成key key ftok(/tmp, 66); if (key -1) { perror(ftok); exit(1); } // 2. 创建共享内存 shmid shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); if (shmid -1) { perror(shmget); exit(1); } printf(共享内存创建成功shmid %d\n, shmid); // 3. 挂接共享内存 data (char *)shmat(shmid, NULL, 0); if (data (char *)-1) { perror(shmat); exit(1); } printf(共享内存挂接成功地址 %p\n, data); // 4. 写入数据 const char *message Hello from writer process!; strcpy(data, message); printf(数据已写入: %s\n, message); // 5. 等待用户输入后退出让读进程有机会读取 printf(按回车键退出并清理...\n); getchar(); // 6. 解除挂接 shmdt(data); // 7. 删除共享内存 shmctl(shmid, IPC_RMID, NULL); printf(共享内存已删除\n); return 0; }2.读进程reader.c#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/ipc.h #include sys/shm.h #define SHM_SIZE 1024 int main() { key_t key; int shmid; char *data; // 1. 生成相同的key key ftok(/tmp, 66); if (key -1) { perror(ftok); exit(1); } // 2. 获取已存在的共享内存注意不使用IPC_EXCL shmid shmget(key, SHM_SIZE, IPC_CREAT | 0666); if (shmid -1) { perror(shmget); exit(1); } printf(共享内存获取成功shmid %d\n, shmid); // 3. 挂接共享内存 data (char *)shmat(shmid, NULL, 0); if (data (char *)-1) { perror(shmat); exit(1); } printf(共享内存挂接成功地址 %p\n, data); // 4. 读取数据 printf(读取到数据: %s\n, data); // 5. 解除挂接不删除让写进程负责清理 shmdt(data); return 0; }四、命令行管理工具ipcrm -m 12345 # 删除shmid为12345的共享内存1.查看共享内存ipcs -m$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x2e0a0156 12345 user 666 1024 1字段说明key共享内存的键值shmid共享内存标识符owner创建者perms权限bytes大小nattch当前挂接的进程数2.删除共享内存ipcrm -mipcrm -m 12345 # 删除shmid为12345的共享内存【注】共享内存的生命周期随内核即使创建它的进程已经退出共享内存仍然存在除非显式删除或系统重启五、同步问题与解决方案5.1 共享内存的最大缺陷共享内存本身不提供任何同步机制。如果多个进程同时读写会出现数据竞争问题5.2解决方案信号量配合通常使用信号量来保护共享内存的访问。// 伪代码生产者-消费者模式 #include semaphore.h sem_t *sem_empty; // 空位计数 sem_t *sem_full; // 数据计数 sem_t *sem_mutex; // 互斥锁 // 生产者 void producer() { sem_wait(sem_empty); // 等待有空位 sem_wait(sem_mutex); // 加锁 // 写入共享内存 sem_post(sem_mutex); // 解锁 sem_post(sem_full); // 增加数据计数 } // 消费者 void consumer() { sem_wait(sem_full); // 等待有数据 sem_wait(sem_mutex); // 加锁 // 读取共享内存 sem_post(sem_mutex); // 解锁 sem_post(sem_empty); // 增加空位计数 }