1. 创建和初始化信号量
semget
创建信号量集。semctl
设置初始值。2. P/V 操作
semop
执行 P
(等待)操作,减少信号量值。semop
执行 V
(释放)操作,增加信号量值。3. 删除信号量
semctl
和 IPC_RMID
删除信号量集。1.进程间同步:
- 信号量可以保证多个进程对共享资源的有序访问。
2. 灵活性高:
- 支持多种操作组合,如 P
和 V
。
3. 跨进程使用:
- 通过唯一的 key
,不同进程可共享同一信号量。
函数功能:用于创建或获取信号量集的函数。
#include
#include
#include
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
#include
#include
#include
#include
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;
}
函数功能:用于控制信号量集或信号量的属性。它能够初始化、获取信号量的值、删除信号量集等操作。
#include
#include
#include
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
。
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
:获取最后一个操作信号量的进程 IDint pid = semctl(semid, 0, GETPID);
printf("最后一个操作信号量的进程 ID:%d\n", pid);
IPC_RMID
:删除信号量集semctl(semid, 0, IPC_RMID);
函数功能:用于执行信号量的 P
(等待)和 V
(释放)操作,从而实现对信号量的操作和控制。
#include
#include
#include
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
#include
#include
#include
#include
#define KEY 1234
void 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); // 初始化信号量值为 1
if (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
#include
#include
#include
#include
#include
//根据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
#include
#include
#include
#include
#include
//根据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;
}