共享内存删除的安全“陷阱”

共享内存就是使多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享内存段“连接到”他们自己的地址空间里去。所有进程都能访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会即时被有访问同一段共享内存的其他进程看到。共享内存的使用大大降低了在大规模数据处理过程中内存的消耗,但是共享内存的使用中有很多的陷阱,一不注意就很容易导致程序崩溃。

比如,共享内存的删除中就存在陷阱。

1       共享内存脱离进程

当进程结束使用共享内存区时,要通过函数 shmdt 断开与共享内存区的连接,其原型如下

       #include <sys/types.h>

       #include <sys/shm.h>

 

       int shmdt(const void *shmaddr);

其中:

Shmaddr是共享内存的地址,是shmat的返回值。

另外,涉及到一个数据结构,

/* Obsolete, used only forbackwards compatibility and libc5 compiles */

struct shmid_ds {

    struct ipc_perm     shm_perm;   /* operation perms */

    int         shm_segsz;  /* size of segment (bytes) */

    __kernel_time_t     shm_atime;  /* last attach time */

    __kernel_time_t     shm_dtime;  /* last detach time */

    __kernel_time_t     shm_ctime;  /* last change time */

    __kernel_ipc_pid_t  shm_cpid;   /* pid of creator */

    __kernel_ipc_pid_t  shm_lpid;   /* pid of last operator */

    unsigned short       shm_nattch;   /* no. of current attaches */

    unsigned short      shm_unused; /* compatibility */

    void            *shm_unused2;   /* ditto - used by DIPC */

    void            *shm_unused3;   /* unused */

};

进程脱离共享内存区后,数据结构 shmid_ds 中的 shm_nattch 就会减 1 。但是共享段内存依然存在,只有 shm_attch 为 0 后,即没有任何进程再使用该共享内存区,共享内存区才在内核中被删除。一般来说,当一个进程终止时,它所附加的共享内存区都会自动脱离。

 

2       删除共享内存

当进程删除共享内存区时,要通过函数 shmdctl来删除已存在的共享内存,其原型如下

       #include <sys/ipc.h>

       #include <sys/shm.h>

 

       int shmctl(int shmid, int cmd, structshmid_ds *buf);

其中:

Shmid:是由shmget所返回的标记符。

Cmd:是要执行的动作。他可以有三个值,

IPC_STAT,设置shmid_ds结构中的数据反射与共享内存相关联的值。

IPC_SET,如果进程有相应的权限,将与共享内存相关联的值设置为shmid_ds数据结构中所提供的值。

IPC_RMID,删除共享内存段。

Buf:是一个指向包含共享内存模式与权限的结构的指针,删除的时候可以默认为0

如果共享内存已经与所有访问它的进程断开了连接,则调用IPC_RMID子命令后,系统将立即删除共享内存的标识符,并删除该共享内存区,以及所有相关的数据结构;

如果仍有别的进程与该共享内存保持连接,则调用IPC_RMID子命令后,该共享内存并不会被立即从系统中删除,而是被设置为IPC_PRIVATE状态,并被标记为"已被删除"(使用ipcs命令可以看到dest字段);直到已有连接全部断开,该共享内存才会最终从系统中消失。

另外:一旦通过shmctl对共享内存进行了删除操作,则该共享内存将不能再接受任何新的连接,即使它依然存在于系统中!所以,可以确知,在对共享内存删除之后不可能再有新的连接,则执行删除操作是安全的;否则,在删除操作之后如仍有新的连接发生,则这些连接都将可能失败!

<think>我们正在讨论共享内存和信号量在进程间通信(IPC)中的使用方法和注意事项。根据提供的引用,我们可以总结出以下关键点:1.**共享内存**:-共享内存允许多个进程访问同一块物理内存区域,是进程间通信最快的方式,因为数据不需要在内核和用户空间之间复制[^4]。-使用步骤:创建共享内存段->将共享内存映射到进程的地址空间->使用完毕后解除映射->删除共享内存段。-注意事项:-需要同步机制(如信号量)来避免多个进程同时写入导致的数据不一致[^3]。-共享内存段中的数据通常以字符串形式存储,并以结束符标识一条信息的结尾[^3]。2.**信号量**:-信号量用于协调多个进程对共享资源的访问,保证同步和互斥[^1]。-分为有名信号量和无名信号量:-无名信号量通常用于具有血缘关系的进程(如父子进程)[^2]。-有名信号量可用于无血缘关系的进程[^2]。-使用步骤:创建/打开信号量->初始化信号量值(通常表示可用资源数)->使用信号量(P/V操作)进行同步->关闭并删除信号量。3.**结合使用**:-共享内存提供高效的数据共享,但需要信号量来保护共享内存区域,防止竞争条件[^1][^3]。-示例代码结构(参考引用[^1]):-创建共享内存和信号量。-进程写入共享内存前执行P操作(申请资源)。-写入完成后执行V操作(释放资源)。-读取进程同理。4.**注意事项**:-共享内存本身没有同步机制,必须配合信号量等同步工具使用[^3][^4]。-使用完毕后务必解除映射并删除共享内存,避免资源泄漏[^5]。-匿名映射(`MAP_ANONYMOUS`)可用于父子进程间的共享内存[^5]。下面是一个简单的示例,展示如何使用共享内存和信号量实现两个进程(一个写进程,一个读进程)之间的通信:###示例代码(C语言)```c#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<sys/ipc.h>#include<sys/shm.h>#include<sys/sem.h>//信号量操作结构体(用于P/V操作)unionsemun{intval;structsemid_ds*buf;unsignedshort*array;};//P操作(等待信号量)voidsem_wait(intsemid){structsembufop={0,-1,SEM_UNDO};semop(semid,&op,1);}//V操作(释放信号量)voidsem_signal(intsemid){structsembufop={0,1,SEM_UNDO};semop(semid,&op,1);}intmain(){key_tkey=ftok(".",'s');//生成键值//创建共享内存(大小为1024字节)intshmid=shmget(key,1024,IPC_CREAT|0666);if(shmid==-1){perror("shmget");exit(1);}//创建信号量(初始值为1,表示互斥锁)intsemid=semget(key,1,IPC_CREAT|0666);if(semid==-1){perror("semget");shmctl(shmid,IPC_RMID,NULL);//删除共享内存exit(1);}//初始化信号量值为1unionsemunarg;arg.val=1;if(semctl(semid,0,SETVAL,arg)==-1){perror("semctl");shmctl(shmid,IPC_RMID,NULL);semctl(semid,0,IPC_RMID,0);exit(1);}//创建子进程pid_tpid=fork();if(pid==-1){perror("fork");exit(1);}if(pid==0){//子进程(读进程)char*shmaddr=shmat(shmid,NULL,0);if(shmaddr==(char*)-1){perror("shmatinchild");exit(1);}sem_wait(semid);//等待信号量printf("Childread:%s\n",shmaddr);sem_signal(semid);//释放信号量shmdt(shmaddr);//解除映射exit(0);}else{//父进程(写进程)char*shmaddr=shmat(shmid,NULL,0);if(shmaddr==(char*)-1){perror("shmatinparent");exit(1);}sem_wait(semid);//等待信号量strcpy(shmaddr,"Hellofromparent!");sem_signal(semid);//释放信号量wait(NULL);//等待子进程结束shmdt(shmaddr);//解除映射shmctl(shmid,IPC_RMID,NULL);//删除共享内存semctl(semid,0,IPC_RMID,0);//删除信号量}return0;}```###注意事项总结1.**同步必要**:共享内存本身不提供同步,必须使用信号量(或其他同步机制)来保护共享内存访问[^1][^3]。2.**资源释放**:使用完毕后,父进程应负责删除共享内存和信号量,避免资源泄漏[^1]。3.**错误处理**:每一步系统调用都应检查错误,确保程序健壮性。4.**映射与解除映射**:每个进程在使用共享内存前需调用`shmat`映射,使用后调用`shmdt`解除映射[^5]。5.**匿名映射**:对于父子进程,可以使用匿名映射(`mmap`)代替`shmget`,但本示例使用`shmget`[^5]。§§相关问题§§1.共享内存和消息队列在进程间通信中的主要区别是什么?2.如何选择使用有名信号量和无名信号量?3.在多进程共享内存时,如何避免死锁?</think>### 共享内存和信号量的进程间通信指南 #### 一、核心使用方法 1. **共享内存基础操作** - **创建/获取**:使用 `shmget()` 系统调用创建或获取共享内存段 - **映射**:通过 `shmat()` 将共享内存映射到进程地址空间 - **访问**:直接操作映射后的内存指针(如 `char* shared_data`) - **分离**:使用 `shmdt()` 解除映射关系 - **删除**:通过 `shmctl(IPC_RMID)` 销毁共享内存段[^4][^5] 2. **信号量同步机制** - **初始化**:`sem_init()`(无名信号量)或 `sem_open()`(有名信号量) - **P操作(等待)**:`sem_wait()` 申请资源 - **V操作(释放)**:`sem_post()` 释放资源 - **清理**:`sem_destroy()` 或 `sem_unlink()`[^1][^2] 3. **组合使用流程** ```c // 伪代码示例 shmid = shmget(key, size, IPC_CREAT|0666); // 创建共享内存 shared_ptr = shmat(shmid, NULL, 0); // 映射到进程空间 sem_id = semget(key, 1, IPC_CREAT|0666); // 创建信号量 semctl(sem_id, 0, SETVAL, 1); // 初始化值为1(互斥锁) sem_wait(sem_id); // 进入临界区前等待 sprintf(shared_ptr, "Process %d writing", getpid()); // 写共享内存 sem_post(sem_id); // 退出临界区后释放 ``` #### 二、关键注意事项 1. **同步必选** - 共享内存本身**无同步机制**,必须配合信号量/互斥锁使用 - 未同步会导致**数据竞争**:如进程A写入时进程B同时读取[^1][^3] 2. **内存管理规范** - **解除映射**:进程退出前必须调用 `shmdt()`,否则造成内存泄漏 - **及时销毁**:最后使用共享内存的进程应调用 `shmctl(IPC_RMID)` - **权限控制**:创建时设置合理权限(如 `0666`)[^5] 3. **信号量使用要点** - **初始化值**:互斥锁初始值设为1,资源池根据实际容量设置 - **原子性保证**:P/V操作必须是原子操作 - **跨进程类型**: - 无名信号量:需配合共享内存(`mmap`)用于血缘进程[^2] - 有名信号量:通过名称标识,用于无血缘进程[^2] 4. **数据格式约定** - 在共享内存中定义明确的**数据结束标志**(如 `\0`) - 建议使用**结构化数据**:`struct { int len; char data[100]; }` - 避免存储**指针**(不同进程的虚拟地址空间不同)[^3] 5. **错误处理** - 每次系统调用后检查返回值 - 处理 `EAGAIN`(资源暂时不可用)、`EINTR`(信号中断)等错误 - 使用 `ftok()` 生成key时确保文件路径存在[^1] #### 三、典型应用场景 1. **高性能数据交换** - 适用于视频处理、科学计算等**大数据量传输**场景 - 优势:**零拷贝**机制,数据不经过内核[^4] 2. **实时控制系统** - 工业控制中多进程共享传感器数据 - 配合优先级继承协议解决优先级反转问题 3. **进程池资源共享** - 工作进程通过共享内存访问公共配置 - 信号量控制任务队列的并发访问 > **关键优势对比**: > | 通信方式 | 延迟 | 数据量支持 | 同步需求 | > |---|---|---|---| > | 共享内存+信号量 | 极低(μs级) | GB级 | 必需 | > | 消息队列 | 高(ms级) | MB级 | 内置 | > | 管道 | 中 | KB级 | 自动阻塞 | [^4] #### 四、常见陷阱解决方案 1. **僵尸共享内存** - **现象**:进程异常退出未清理共享内存 - **方案**:使用 `ipcrm -m SHMID` 手动清理,或编程实现超时销毁 2. **信号量死锁** - **预防**: ```c // 设置超时等待 struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 2; // 2秒超时 sem_timedwait(sem_ptr, &ts); ``` 3. **内存一致性** - 使用 `volatile` 关键字声明共享变量 - x86架构下需要 `mfence` 指令保证写可见性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YoungerChina

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值