目录
信号量与已经介绍过的 IPC 结构不同,它是一个计数器,是一种用于在多个进程或线程之间同步和通信的机制。它通常用于控制对共享资源的访问,以防止竞态条件的发生。
1、信号量的特点
- 信号量的操作是原子的,这意味着对信号量的操作是不可分割的。这确保了在多进程环境中对信号量的操作是线程安全的。
- 信号量是一个计数器,它可以表示可用资源的数量。当进程获取资源时,信号量的计数减少;当进程释放资源时,信号量的计数增加。
- 多个信号量可以被组合成一个信号量集。这样的信号量集可以一次性操作,简化了对多个信号量的管理。
- 信号量常常用于进程间的同步,确保在多进程环境中资源的正确共享和协调。信号量还可以用于控制对一定数量资源的访问。通过适当设置信号量的初值,可以限制对资源的并发访问。
- 与其他 进程通信IPC 对象一样,信号量具有持久性,它们在系统中创建并且可以一直存在,直到显式地被删除。
2、信号量的使用介绍
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
1.函数介绍
1.创建或获取一个信号量集
int semget(key_t key, int nsems, int semflg);
2.对信号量进行控制操作,其中
cmd
为SETVAL
时用于初始化信号量的值。int semctl(int semid, int semnum, int cmd, ...);
3.对信号量进行操作改变信号量的值
int semop(int semid, struct sembuf *sops, size_t nsops);
1.1semget函数
当semget创建新的信号量集合时,必须指定集合中信号量的个数(即nsems),通常为1; 如果是引用一个现有的集合,则将nsems指定为 0 。
semflg为标志位
- IPC_CREAT: 表示创建一个新的 IPC 对象(例如,信号量集、共享内存、消息队列)。
- IPC_EXCL 用于创建一个新的唯一键值,如果已存在会使得创建失败,需与IPC_CREAT连用表示创建新的唯一键值
- 一般我们使用 IPC_CREAT | 0666 来创建或获取信号量,存在时获取,不存在则创建
1.2semctl函数
semctl函数用于对信号量集进行控制操作的函数。它提供了多种功能,通过传递不同的 cmd
参数来执行不同的操作。一般我们使用SETVAL用于设置信号量的初始值,IPC_RMID从系统中删除信号量集。
1.3 semop函数
nsops: sops数组中结构体的数量
struct sembuf *sops:是一个结构体
struct sembuf
结构体定义如下:struct sembuf {
unsigned short sem_num; // 信号量在信号量集中的索引
short sem_op; // 操作类型(负值表示 P 操作,正值表示 V 操作,
short sem_flg; // 操作标志
};当 sem_op的值为0时,P 操作是一种特殊情况。这时,P 操作只会检查信号量的当前值,而不进行实际的减法操作。如果信号量的值为正数,P 操作将成功执行,信号量的值不变。如果信号量的值为0,P 操作将被阻塞,等待信号量的值变为正数。
3、代码示例
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// P 操作(等待信号量值变为 0)
void Pgetsem(int semid) {
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = -1;
sops.sem_flg = SEM_UNDO;
if (semop(semid, &sops, 1) == -1) {
perror("P 操作失败");
exit(-1);
}
printf("----- P 操作\n");
}
// V 操作(增加信号量值)
void Vgetsem(int semid) {
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = 1;
sops.sem_flg = SEM_UNDO;
if (semop(semid, &sops, 1) == -1) {
perror("V 操作失败");
exit(-1);
}
printf("++++++++ V 操作\n");
}
int main() {
int key = ftok(".", 5);
if (key == -1) {
perror("ftok 失败");
exit(-1);
}
int semid = semget(key, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget 失败");
exit(-1);
}
union semun tmp;
if (semctl(semid, 0, SETVAL, 0) == -1) {
perror("semctl SETVAL 失败");
exit(-1);
}
int pid = fork();
if (pid == -1) {
perror("fork 失败");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程
sleep(5);
printf("子进程\n");
Vgetsem(semid);
} else {
// 父进程
Pgetsem(semid);
printf("父进程\n");
// 删除信号量,注意要确保在删除信号量集之前没有其他进程正在使用它
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("semctl IPC_RMID 失败");
exit(-1);
}
}
return 0;
}