System V 信号量与共享内存技术解析
System V 信号量与共享内存技术解析
一、System V 信号量
System V 信号量与共享内存是 Linux 系统中用于进程间通信(IPC)的重要机制。以下是关于信号量创建部分的详细解析:
1. semctl
- 信号量的创建
声明:
int semget(key_t key, int nsems, int semflg);
参数详解:
key
:
信号量集的键值,通常通过ftok
函数生成,用于唯一标识信号量集。可以使用IPC_PRIVATE
来创建一个新的、唯一的信号量集。nsems
:
信号量集中信号量的数量。如果只是访问已存在的信号量集,可以将其设为 0。semflg
:
标志位,用于指定信号量集的创建和访问权限。常用的标志包括:
•IPC_CREAT
:如果信号量集不存在,则创建它。
•IPC_EXCL
:与IPC_CREAT
一起使用,如果信号量集已存在,则返回错误。
• 权限模式:如0666
,表示所有用户可读写。
返回值:
• 成功返回信号量集的标识符,失败返回 -1。
2. semctl
- 控制信号量集(通常用于初始化或查看信号量集合状态)
声明:
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
参数详解:
semid
:
信号量集的标识符,由semget
返回。需确保进程对该信号量集有操作权限(如IPC_RMID
需要所有者或超级用户权限)。semnum
:
信号量在集合中的索引(从0
开始)。仅对单信号量操作命令(如SETVAL
、GETVAL
)有效;若cmd
作用于整个集合(如IPC_RMID
),此参数可设为0
。cmd
:
控制命令,决定操作类型及arg
参数用途:
•IPC_STAT
:获取信号量集状态信息,arg
需指向struct semid_ds
结构体。该结构包含内核维护的元数据:struct semid_ds { struct ipc_perm sem_perm; // 权限与所有者信息 time_t sem_otime; // 最后一次 semop() 成功的时间(初始为0) time_t sem_ctime; // 信号量集创建/修改时间 struct sem *sem_base; // 指向信号量数组 unsigned short sem_nsems; // 信号量数量 };
关键字段:
◦sem_otime
:首次创建时初始化为0,仅通过成功执行的semop()
更新为非零值
◦sem_ctime
:记录信号量集的创建或配置修改时间
•IPC_SET
:修改信号量集权限,需struct semid_ds
指针更新sem_perm
字段
•IPC_RMID
:立即删除信号量集
•SETVAL
/GETVAL
:设置或获取单个信号量的值arg
(共用体union semun
):
需用户自定义的联合体,根据cmd
选择成员:union semun { int val; // SETVAL/GETVAL 的值 struct semid_ds *buf; // IPC_STAT/IPC_SET 的状态缓冲区 unsigned short *array; // GETALL/SETALL 的数组指针 };
多进程竞争防护策略:
在信号量集创建与初始化过程中,需防范以下竞争场景:
进程A调用 semget
创建信号量后未完成初始化,进程B获取同一信号量并修改值,随后被进程A的初始化覆盖
解决方案:利用sem_otime
同步机制只让一个进程创建并初始化信号量集合
• 创建者进程:初始化后执行一次空操作(sem_op=0
)的 semop()
,强制更新 sem_otime
为非零值
• 其他进程:循环检查 sem_otime
是否为0,若为0则等待至其变为非零:
struct semid_ds ds;
union semun arg = {.buf = &ds};
do {
semctl(semid, 0, IPC_STAT, arg);
if (ds.sem_otime != 0) break; // 确认初始化完成
usleep(1000); // 避免忙等待
} while (1);
3. semop
- 执行原子操作(PV 操作)
声明:
int semop(int semid, struct sembuf *sops, size_t nsops);
参数:
• semid
:信号量标识符。
• sops
:指向 sembuf
结构数组的指针,每个结构定义一次操作:
struct sembuf {
unsigned short sem_num; // 信号量索引
short sem_op; // 操作值(负数:P 操作;正数:V 操作)
short sem_flg; // 标志(如 `IPC_NOWAIT` 非阻塞,`SEM_UNDO` 防死锁)
};
• nsops
:操作数组的长度。
返回值:
• 成功返回 0,失败返回 -1。
二、System V 共享内存
共享内存允许多进程直接读写同一物理内存区域,无需内核介入,是最高效的 IPC 方式。核心系统调用如下:
1. shmget
- 创建/获取共享内存段
声明:
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
• key
:共享内存标识键,规则同信号量。
• size
:共享内存大小(单位:字节),实际分配按页对齐。
• shmflg
:标志位,如 IPC_CREAT
、IPC_EXCL
及权限模式。
返回值:
• 成功返回共享内存标识符,失败返回 -1。
生命周期管理:
共享内存段的生命周期由内核维护,即使所有进程解除映射,仍需显式调用 shmctl
的 IPC_RMID
命令删除。
2. shmat
- 映射共享内存到进程地址空间
声明:
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
• shmid
:共享内存标识符。
• shmaddr
:建议映射地址(通常设为 NULL
,由系统自动分配)。
• shmflg
:标志位,如 SHM_RDONLY
(只读映射)。
返回值:
• 成功返回映射后的虚拟地址指针,失败返回 (void*) -1
。
注意点:
• 共享内存在每个使用它的进程的附加位置(可以理解为挂载点)不同,因此若共享内存中存储了指针,若希望存储(也只能指向)共享内存的另一处位置,该指针不能存储绝对地址,而是只能存储到目标位置的偏移量。
3. 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
:指向 shmid_ds
结构体的指针,用于存储或修改状态。
返回值:
• 成功返回 0,失败返回 -1。
三、关键注意事项与优化策略
- 同步机制选择:
• 信号量类型:二元信号量(互斥锁)适用于独占资源访问;多元信号量(如连接池)可控制资源数量。
• 性能排序:互斥锁 > POSIX 信号量 > System V 信号量 > 文件锁。 - 资源泄漏防护:
• 显式调用semctl
/shmctl
删除 IPC 对象,避免因进程崩溃导致资源残留。
• 使用ipcs
和ipcrm
命令检查与清理未释放的资源。 - 错误处理与调试:
• 所有系统调用需检查返回值,捕获EACCES
(权限不足)、EINVAL
(无效参数)等错误。
• 通过semctl
的GETNCNT
/GETZCNT
监控信号量等待队列,优化锁粒度。
四、总结
通过合理组合 System V 信号量与共享内存,可实现高效且安全的进程间协作。信号量的原子操作与共享内存的直接内存访问特性,使其在数据库连接池、实时数据处理等高并发场景中表现卓越。开发者需重点关注多进程初始化的竞争防护、资源生命周期管理及错误处理机制,以确保系统稳定性和性能。