Linux系统编程:信号量Semaphore详解 (IPC)
目录
- 信号量使用流程
- 信号量的优点
- semget函数
- 函数原型
- semctl函数
- 函数原型
- 常用cmd详解
- semop函数
- 函数原型
- `struct sembuf`结构体
- 信号量数组与`struct sembuf`结构体的关系
- 示例
信号量(Semaphore)是一种用于进程间同步的机制,适用于控制对共享资源的访问。
信号量使用流程
1. 创建和初始化信号量
- 使用
semget
创建信号量集。 - 使用
semctl
设置初始值。
2. P/V 操作
- 通过
semop
执行P
(等待)操作,减少信号量值。 - 通过
semop
执行V
(释放)操作,增加信号量值。
3. 删除信号量
- 使用
semctl
和IPC_RMID
删除信号量集。
信号量的优点
1.进程间同步:
- 信号量可以保证多个进程对共享资源的有序访问。
2. 灵活性高:
- 支持多种操作组合,如 P
和 V
。
3. 跨进程使用:
- 通过唯一的 key
,不同进程可共享同一信号量。
semget函数
函数功能:用于创建或获取信号量集的函数。
函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
参数
key
:
- 用于标识信号量集的键值。
- 可以通过
ftok
函数生成,也可以直接指定为整数值。- 如果多个进程需要共享信号量集,必须使用相同的
key
。
nsems
:
- 信号量集中的信号量数量。
- 仅在创建新信号量时有效。
- 如果信号量集已存在,则该参数会被忽略。
semflg
:控制信号量的权限和行为。
IPC_CREAT
:如果信号量不存在,则创建一个新的信号量集。IPC_EXCL
:与IPC_CREAT
一起使用。如果信号量已存在,则返回错误。- 与
IPC_CREAT
一起使用设置权限如0664
返回值
- 成功:返回信号量集的标识符(
semid
),用于后续的信号量操作。- 失败:返回
-1
,并设置
errno
,常见错误包括:
EEXIST
:信号量已存在,但同时指定了
IPC_EXCL
。EACCES
:没有权限访问信号量。ENOSPC
:系统信号量数量超出限制。EINVAL
:nsems
超出系统支持的最大信号量数。
示例
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <errno.h>int main() {key_t key = ftok(".", 'a'); // 生成键值if (key == -1) {perror("ftok");exit(1);}// 创建一个包含 1 个信号量的信号量集int semid = semget(key, 1, IPC_CREAT | 0666);if (semid == -1) {perror("semget");exit(1);}printf("信号量创建成功,ID:%d\n", semid);return 0;
}
semctl函数
函数功能:用于控制信号量集或信号量的属性。它能够初始化、获取信号量的值、删除信号量集等操作。
函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
参数
semid
:信号量标识符
semnum
:操作的信号量的下标
cmd
:操作信号量的命令
IPC_STAT
:获取信号量的信息放到 buf
这个参数中,这个操作需要我们指定信号量拥有读权限SETVAL
:设置信号量的值。GETVAL
:获取信号量的当前值。IPC_RMID
:删除信号量集。GETPID
:获取最后一个操作信号量的进程 ID。GETNCNT
:获取等待信号量增加的进程数量。GETZCNT
:获取等待信号量值变为 0 的进程数量。
arg
(可选):
- 某些命令需要使用该参数传递额外的信息,如设置信号量的值。
- 使用的是一个联合体
union semun
,需显式定义
union semun {int val; // 设置单个信号量的值struct semid_ds *buf; // 获取/设置信号量集的属性unsigned short *array; // 设置多个信号量的值
};
返回值
- 成功:根据命令返回不同的值。
- 失败:返回
-1
,并设置errno
。
常用cmd详解
SETVAL
:设置单个信号量的值
union semun sem_union;
sem_union.val = 1; // 将信号量设置为 1
semctl(semid, 0, SETVAL, sem_union);
GETVAL
:获取信号量的值
int val = semctl(semid, 0, GETVAL);
printf("信号量的当前值:%d\n", val);
GETPID
:获取最后一个操作信号量的进程 ID
int pid = semctl(semid, 0, GETPID);
printf("最后一个操作信号量的进程 ID:%d\n", pid);
IPC_RMID
:删除信号量集
semctl(semid, 0, IPC_RMID);
semop函数
函数功能:用于执行信号量的 P
(等待)和 V
(释放)操作,从而实现对信号量的操作和控制。
函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
参数
semid
:信号量集的标识符,由
semget
返回。
sops
:信号量集合的操作结构体数组,可以一次性操作全部的信号量集合。(要么一次性执行完数组里面的东西要么就不执行,不能只执行里面的一个元素)
nsops
:这个数组有多少个元素
返回值
- 成功时返回
0
。- 失败时返回
-1
,并设置errno
。
struct sembuf
结构体
struct sembuf {unsigned short sem_num; // 信号量下标short sem_op; // 操作值short sem_flg; // 操作标志
};
sem_num
:信号量下标,从0开始(和数组一样)
sem_op
:要对数组元素进行何种操作
>0
:增加信号量(通常表示表示释放资源)<0
:减少信号量(通常表示占用资源)=0
:等待信号量值变为0
sem_flg
:一般写成0代表阻塞等待,IPC_NOWAIT
表示非阻塞等待,如果无法立即完成,返回-1,SEM_UNDO
表示当进程终止时,系统会撤销该操作对信号量的影响。
信号量数组与struct sembuf
结构体的关系
- 信号量数组:
- 通过
semget()
创建的信号量数组是内核中实际维护的信号量集合。例如:
- 通过
semget(ipc_key, 3, IPC_CREAT | 0664);
这会创建一个信号量数组,其中包含 3 个信号量,索引分别为 0
, 1
, 和 2
。
- 操作结构数组 (
struct sembuf semoparr[i]
):
struct sembuf semoparr[i]
表示定义了一个数组,包含i
个struct sembuf
类型的元素。- 每个
semoparr[index]
是一个完整的结构体,包含sem_num
、sem_op
和sem_flg
三个字段。
比如说想操作3个信号量中的两个,那么就要定义semoparr[2]
struct sembuf semoparr[2]; // 定义一个包含 2 个结构体的数组// 配置第一个操作
semoparr[0].sem_num = 0; // 操作信号量数组的第 0 个信号量
semoparr[0].sem_op = -1; // 执行减 1 操作 (P 操作)
semoparr[0].sem_flg = 0; // 阻塞操作// 配置第二个操作
semoparr[1].sem_num = 1; // 操作信号量数组的第 1 个信号量
semoparr[1].sem_op = 1; // 执行加 1 操作 (V 操作)
semoparr[1].sem_flg = 0; // 阻塞操作
示例
#include <stdio.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/wait.h>
#define KEY 1234void P(int semid, int sem_num) {struct sembuf sop = {sem_num, -1, 0}; // P 操作(占用资源)semop(semid, &sop, 1);
}void V(int semid, int sem_num) {struct sembuf sop = {sem_num, 1, 0}; // V 操作(释放资源)semop(semid, &sop, 1);
}int main() {int semid = semget(KEY, 1, IPC_CREAT | 0666); // 创建信号量集semctl(semid, 0, SETVAL, 1); // 初始化信号量值为 1if (fork() == 0) { // 子进程P(semid, 0); // 占用资源printf("Child: Entering critical section\n");sleep(2); // 模拟操作printf("Child: Leaving critical section\n");V(semid, 0); // 释放资源} else { // 父进程P(semid, 0); // 占用资源printf("Parent: Entering critical section\n");sleep(2); // 模拟操作printf("Parent: Leaving critical section\n");V(semid, 0); // 释放资源wait(NULL); // 等待子进程结束semctl(semid, 0, IPC_RMID); // 删除信号量集}return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#include <sys/sem.h>//根据semctl要求,第四个参数必须定义以下共用体,根据不同的cmd(semctl的第三个参数)来决定用里面的哪个元素
union semun {int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */};int main()
{key_t ipc_key;int sem_id;ipc_key = ftok(".",1);sem_id = semget(ipc_key,1,IPC_CREAT|0664);//获取一个信号量,1代表创建的信号量个数(信号量数组元素的个数)//定义共用体并赋值union semun setval = {.val = 0};semctl(sem_id,0,SETVAL,setval);//设置信号量的初值:0代表操作信号量数组的第几个元素,SETVAL代表设置他的初值//定义操作结构体数组,可以一次性操作全部的信号量集合里面的信号struct sembuf semoparr[1];while(1){semoparr[0].sem_num = 0; //操作信号量的下标semoparr[0].sem_op = -1; //对这个信号进行-1操作semoparr[0].sem_flg = 0; //正常的操作信号量,代表如果进行减操作会变成一个负数,则semop函数调用会陷入阻塞semop(sem_id,semoparr,1);//进行PV操作(减加操作),1代表的是上面操作的数组元素个数printf("P(减)操作成功\n");}return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#include <sys/sem.h>//根据semctl要求,第四个参数必须定义以下共用体,根据不同的cmd(semctl的第三个参数)来决定用里面的哪个元素
union semun {int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */};int main()
{key_t ipc_key;int sem_id;ipc_key = ftok(".",1);sem_id = semget(ipc_key,1,IPC_CREAT|0664);//获取一个信号量,1代表创建的信号量个数(信号量数组元素的个数)//定义操作结构体数组,可以一次性操作全部的信号量集合里面的信号struct sembuf semoparr[1];while(1){getchar();//等待键盘锹下去semoparr[0].sem_num = 0; //操作信号量的下标semoparr[0].sem_op = 1; //对这个信号进行+1操作semoparr[0].sem_flg = 0; //正常的操作信号量,代表如果进行减操作会变成一个负数,则semop函数调用会陷入阻塞semop(sem_id,semoparr,1);//进行PV操作(减加操作),1代表的是上面操作的数组元素个数printf("V(加)操作成功\n");}return 0;
}