DAYS 34 Linux 进程间通信 —— 共享内存、有名信号量详解
前言
进程是操作系统资源分配的最小单位,每个进程拥有独立的虚拟地址空间,进程与进程之间默认不能互相访问对方内存。如果不同进程需要交换数据,就必须依靠进程间通信 (IPC)。
前面我们了解了管道、信号、消息队列,本篇重点讲解共享内存与有名信号量 (System V 信号量),共享内存是所有 IPC 机制中效率最高的进程间通信方式,而信号量主要用来做同步互斥,解决多进程访问共享资源产生的资源竞争问题。
阅读范围:文档共享内存及以下内容
一、共享内存
1. 什么是共享内存
共享内存是进程间通信效率最高的一种 IPC 方式。 内核在物理内存开辟一块内存空间,多个进程将这块物理内存映射到自己的虚拟地址空间中。多个进程直接读写这块映射后的地址,数据不需要在内核态和用户态之间来回拷贝,因此速度极快。
⚠️注意:共享内存本身不提供同步互斥机制。多个进程同时读写共享内存会产生资源竞争,需要配合信号量 / 互斥锁完成同步控制。
2. 共享内存完整操作步骤
- 使用
ftok生成 IPC 的 key 键值(IPC 对象标识符) shmget:创建 / 获取共享内存,得到共享内存 ID (shmid)shmat:将共享内存映射到当前进程虚拟地址空间,得到可用的内存首地址- 直接对映射后的内存地址读写,完成进程间数据交互
shmdt:进程用完之后,解除映射(不是删除共享内存,只是当前进程脱离这块内存)shmctl:销毁共享内存对象,释放内核资源
3. 核心 API 函数详解
① ftok — 生成 IPC 键值 key_t
key_t ftok(const char *pathname, int proj_id);
- 参数
pathname:必须是一个存在的文件 / 目录路径proj_id:项目 ID,仅低 8 位有效 (0~255)
- 返回值:成功返回 key_t 键值;失败返回
-1
同一个 pathname + 同一个 proj_id,会生成同一个 key 值,不同进程使用相同 key 就可以拿到同一个共享内存对象。
② shmget — 创建或者获取共享内存
int shmget(key_t key, size_t size, int shmflg);
- 参数
key:ftok 生成的 IPC 键size:共享内存大小,建议为 4K 的整数倍(内存页大小)shmflg:标志位IPC_CREAT:如果共享内存不存在则创建;存在则直接获取IPC_EXCL:配合 IPC_CREAT,如果对象已经存在,则报错返回- 还可以或上权限位,如
0664
- 返回:成功返回共享内存 ID
shmid;失败返回-1
③ shmat — 把共享内存映射到进程地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 参数
shmid:shmget 返回的共享内存 idshmaddr:指定映射到进程的哪个虚拟地址,传 NULL 由操作系统自动选地址(推荐)shmflg:映射属性,0 为可读可写;SHM_RDONLY 代表只读映射
- 返回值:成功返回映射后的内存首地址;失败返回
(void*)-1
拿到返回的指针之后,就像操作普通内存一样读写,进程就可以和别的进程交换数据。
④ shmdt — 解除映射
int shmdt(const void *shmaddr);
- 参数:shmat 返回的映射地址
- 返回:成功返回 0;失败返回 - 1
⚠️重点:
shmdt仅仅是让当前进程脱离共享内存,不会销毁内核中的共享内存对象,其他进程依旧可以使用这块共享内存。
⑤ shmctl — 共享内存控制(获取状态、设置权限、删除)
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 参数
shmid:共享内存 IDcmd控制命令:IPC_STAT:获取共享内存属性,存入 bufIPC_SET:修改共享内存权限IPC_RMID:销毁共享内存对象,当所有进程都解除映射之后,内核真正释放内存
buf:属性结构体指针,IPC_RMID 时传 NULL 即可
- 返回值:成功返回 0;失败返回 - 1
4. 命令行操作共享内存 (System V IPC)
# 查看所有IPC对象(消息队列、共享内存、信号量)
ipcs
# 只查看共享内存
ipcs -m
# 根据shmid删除共享内存
ipcrm -m shmid
# 根据key键删除共享内存
ipcrm -M key值
5. 简单示例框架
//写进程示例
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#define SHM_SIZE 1024
int main()
{
key_t key = ftok(".", 100);
int shmid = shmget(key,SHM_SIZE,IPC_CREAT|0664);
char *p = shmat(shmid,NULL,0);
strcpy(p,"hello 共享内存");
shmdt(p);
return 0;
}
读进程使用相同 key,shmget、shmat 拿到同一块内存,直接读取地址中的内容即可。 实际项目中必须搭配信号量做同步,防止一个进程读的时候另一个进程正在写。
二、System V 有名信号量(信号灯)
注意区分:posix 无名信号量 (线程用) 和 System V 信号灯集合,这里讲的是 IPC 有名信号量,用于进程间同步互斥。 信号量本质是内核维护的资源计数器,用于进程 / 线程之间做同步与互斥,解决共享资源竞争问题。
1. 核心概念
信号量是一个资源计数:
- 申请资源(P 操作,semop 减计数):资源 > 0,占用资源,计数 - 1;资源 = 0,进程阻塞等待资源
- 释放资源(V 操作,semop 加计数):资源计数 + 1,唤醒等待该资源的阻塞进程
System V 信号量是以信号量数组(集合) 的形式存在,一个信号量集合里面可以包含多个信号量。
2. 操作步骤
- ftok 生成 key
semget创建 / 获取信号量集合,指定集合内信号量的个数semctl:初始化信号量的计数值;也可以用来删除信号量集合semop:执行 P (申请资源)、V (释放资源) 操作- 使用完毕调用
semctl执行IPC_RMID销毁信号量集合
3. API 详解
① semget 创建信号量集合
int semget(key_t key, int nsems, int semflg);
- 参数
key:ftok 生成 IPC 键nsems:信号量集合里面有多少个信号量semflg:IPC_CREAT | IPC_EXCL | 0664
- 返回:成功返回信号量集合 ID (semid),失败返回 - 1
② semctl 信号量控制:初始化、删除
int semctl(int semid, int semnum, int cmd, ...);
- 参数
semid:信号量集合 idsemnum:要操作集合中第几个信号量的下标,从 0 开始cmd:SETVAL:设置单个信号量的初始计数值,第四个参数传联合体union semunIPC_RMID:删除整个信号量集合,此时 semnum 参数被忽略
注意:System V 信号量创建之后不会自动初始化,必须手动 SETVAL 设置初值,这是极易踩坑点!
③ semop:执行 P/V 操作(核心)
int semop(int semid, struct sembuf *sops, size_t nsops);
struct sembuf结构体:
unsigned short sem_num; /* 集合中信号量下标 */
short sem_op; /* P/V操作值:负数P申请;正数V释放 */
short sem_flg; /* 标志,一般填0;IPC_NOWAIT非阻塞 */
- sem_op < 0:P 操作,申请资源,计数减去绝对值,资源不足则阻塞
- sem_op > 0:V 操作,释放资源,计数加上这个正数
nsops:本次执行多少个 sembuf 操作
返回值:成功 0;失败 - 1
4. 命令行操作信号量
ipcs -s #查看System V信号量集合
ipcrm -s semid #按id删除信号量集合
ipcrm -S key #按key删除信号量集合
三、共享内存 + SystemV 信号量使用注意事项
- 共享内存没有自带同步,一定要搭配信号量 / 锁,否则出现数据错乱。
- System V 信号量创建后不会自动初始化,必须手动
SETVAL设置初值。 - IPC 对象(共享内存、消息队列、信号量)内核不会自动回收,进程退出不会自动删除,程序异常退出容易造成内核 IPC 对象泄露,需要手动调用 semctl/shmctl 删除,或者使用
ipcrm命令清理。 - 信号量的
sem_flg默认 0 是阻塞等待,如果设置IPC_NOWAIT,资源不足时不会阻塞,直接返回错误。
四、补充:System V IPC 共性总结
表格
| IPC 对象 | 创建函数 | 删除方式 | 查看命令 |
|---|---|---|---|
| 消息队列 | msgget | msgctl IPC_RMID | ipcs -q |
| 共享内存 | shmget | shmctl IPC_RMID | ipcs -m |
| 信号量集合 | semget | semctl IPC_RMID | ipcs -s |
全部依赖ftok生成 key,都可以通过ipcs/ipcrm命令行调试。
提示:现在新开发项目也会推荐 POSIX 信号量、mmap 共享内存,但是 System V 这套是嵌入式、Linux 后端面试高频考点。
五、面试常考问题
- 共享内存为什么通信速度最快?
数据不需要在内核缓冲区和用户缓冲区之间来回拷贝,进程直接映射物理内存,用户态直接读写物理内存。
- 共享内存既然这么快,还需要信号量干什么?
共享内存只负责数据传输,没有同步互斥能力;信号量负责控制访问时序,防止多个进程同时读写产生数据竞争。
- shmdt 之后共享内存还存在吗?
存在,shmdt 仅仅解除本进程映射;必须 shctl IPC_RMID 才会标记删除,等所有进程全部解除映射才真正释放内存。
- System V 信号量和 Posix 信号量区别?
System V 是信号量集合,多用于进程间;Posix 分为有名 / 无名,无名多用于线程同步。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)