钥匙:信号量
房间:临界资源
信号量集:linuxx API
P操作:拿锁 (获取资源)
V操作:放回锁 (释放资源)
semget函数
semget
是 System V 信号量体系中的一个核心函数,用于创建新的信号量集或获取已存在的信号量集。
函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
参数说明
1. key_t key
- 作用:信号量集的键值,用于唯一标识信号量集
- 生成方式:
- 使用
IPC_PRIVATE
(值为0):每次调用都创建新的信号量集 - 使用
ftok()
函数生成:基于文件路径和项目ID生成唯一键值
2.int nsems
- 使用
- 作用:指定要创建或获取的信号量集中信号量的数量
- 注意事项:
- 如果创建新信号量集,必须指定
nsems > 0
- 如果获取已存在的信号量集,可将
nsems
设为0
3.int semflg
- 如果创建新信号量集,必须指定
- 作用:标志位,控制信号量集的创建和访问权限
- 组成:权限标志(9位)与创建标志的按位或
- 常用标志:
IPC_CREAT
:如果信号量集不存在,则创建IPC_EXCL
:与IPC_CREAT
一起使用,如果信号量集已存在则失败- 权限标志:如
0666
、0644
等(与文件权限类似)
返回值
- 成功:返回信号量集的标识符(非负整数)
- 失败:返回 -1,并设置
errno
semop函数
semop
是 System V 信号量系统中用于执行信号量操作的核心函数,它允许进程对信号量进行原子性的 P(等待)和 V(信号)操作。
函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
参数说明
1. int semid
- 作用:信号量集标识符,由
semget()
调用返回 - 含义:指定要操作的信号量集
struct sembuf *sops
- 作用:指向操作数组的指针,每个元素描述一个信号量操作
- 结构定义:
struct sembuf {
unsigned short sem_num; // 信号量在集合中的索引(0-based)
short sem_op; // 操作类型(正数、负数或零)
short sem_flg; // 操作标志(如IPC_NOWAIT、SEM_UNDO)
};
3. size_t nsops
- 作用:指定操作数组
sops
中的操作数量 - 含义:一次调用中可以同时对多个信号量进行操作
##### 操作类型 (sem_op
)(结构体配置的内容)
- 正数操作 (
sem_op > 0
)
- 作用:执行 V 操作(信号/释放)
- 行为:将信号量的值增加
sem_op
- 效果:通常用于释放资源,唤醒等待的进程
- 负数操作 (
sem_op < 0
)
- 作用:执行 P 操作(等待/获取)
- 行为:
- 如果信号量值 ≥ |
sem_op
|:立即减少信号量值 - 如果信号量值 < |
sem_op
|:阻塞等待,直到条件满足
- 如果信号量值 ≥ |
- 效果:用于获取资源,如果资源不足则等待
- 零操作 (
sem_op == 0
)
- 作用:测试信号量值是否为0
- 行为
- 如果信号量值 = 0:立即返回
- 如果信号量值 ≠ 0:阻塞等待,直到信号量值变为0
- 效果:用于等待特定条件发生 操作标志 (
sem_flg
) 1.IPC_NOWAIT
- 作用:非阻塞模式
- 行为:如果操作不能立即完成,不阻塞进程而是立即返回错误
- 错误码:设置
errno
为EAGAIN
2.SEM_UNDO
- 作用:启用”撤销”功能
- 行为:当进程异常终止时,系统会自动撤销该进程对信号量所做的所有操作
- 用途:防止进程意外终止导致信号量处于不一致状态
返回值 - 成功:返回 0
- 失败:返回 -1,并设置
errno
semctl函数
semctl
是 System V 信号量系统中用于控制信号量操作的函数,它提供了多种对信号量集的管理功能,包括初始化、获取状态、设置值和删除等操作。
函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
参数说明
1. int semid
- 作用:信号量集标识符,由
semget()
返回 - 含义:指定要操作的信号量集
2.int semnum
- 作用:信号量在集合中的编号(索引)
- 取值范围:从 0 到
nsems-1
- 特殊值:对于某些命令(如
IPC_RMID
),此参数被忽略
3.int cmd
- 作用:控制命令,指定要执行的操作类型
- 这是最重要的参数,决定了函数的行为
4....
(可变参数) - 作用:根据不同的
cmd
,可能需要传入额外的参数 - **通常是一个
union semun
类型的联合体union semun
联合体
这个联合体需要用户自己定义,因为它没有在标准头文件中定义:
union semun {
int val; // 用于SETVAL
struct semid_ds *buf; // 用于IPC_STAT和IPC_SET
unsigned short *array; // 用于GETALL和SETALL
struct seminfo *__buf; // 用于IPC_INFO(Linux特定)
};
参数说明2
这个调用非常重要,它的作用是设置信号量的初始值
semctl(semid, 0, SETVAL, arg)
将信号量集 semid
中的第 0 个信号量的值设置为 arg.val
。
- 新创建的信号量值是不确定的
当你使用semget()
创建新的信号量集时,系统不会自动设置信号量的值,它们的初始值是未定义的(可能是任意值)。 - 确保正确的同步行为
信号量的值决定了同步行为:
- 值为 1:二进制信号量(互斥锁),表示资源可用
- 值为 0:表示资源已被占用,需要等待
- 值为 N:计数信号量,表示有 N 个资源可用
如果不初始化,信号量可能从一个随机值开始,导致程序行为不可预测。
参数详解
semid
- 信号量集标识符,由
semget()
返回 - 指定要操作哪个信号量集
0
- 信号量在集合中的索引(从0开始)
- 这里表示操作第一个信号量
SETVAL
- 命令字,表示”设置值”
- 告诉
semctl
我们要设置信号量的值arg
union semun
类型的参数- 必须包含要设置的初始值:
arg.val = 初始值
常用命令 (cmd
)
- 控制整个信号量集的命令
IPC_RMID
– 立即删除信号量集
- 作用:删除信号量集并唤醒所有等待的进程
- 参数:
semnum
被忽略,第四个参数不需要 - 示例:
semctl(semid, 0, IPC_RMID);
IPC_STAT
– 获取信号量集状态信息
- 作用:将信号量集的数据结构复制到提供的缓冲
- 参数:第四个参数是
struct semid_ds *
- 示例:
struct semid_ds ds;
union semun arg;
arg.buf = &ds;
semctl(semid, 0, IPC_STAT, arg);
IPC_SET
– 设置信号量集权限
- 作用:修改信号量集的权限和所有者
- 参数:第四个参数是
struct semid_ds *
- 示例:
struct semid_ds ds;
union semun arg;
// 先获取当前状态
arg.buf = &ds;
semctl(semid, 0, IPC_STAT, arg);
// 修改权限
ds.sem_perm.mode = 0644;
// 设置新权限
semctl(semid, 0, IPC_SET, arg);
- 控制单个信号量的命令
GETVAL
– 获取信号量的当前值
- 作用:返回指定信号量的当前值
- 参数:不需要第四个参数
- 返回值:信号量的当前值(非负整数)
- 示例:
int value = semctl(semid, 2, GETVAL);
printf("信号量2的值: %d\n", value);
SETVAL
– 设置信号量的值
- 作用:设置指定信号量的值
- 参数:第四个参数是
union semun
,使用val
字段 - 示例:
union semun arg;
arg.val = 5; // 设置值为5
semctl(semid, 2, SETVAL, arg);
GETPID
– 获取最后操作进程的PID
- 作用:返回最后对该信号量执行
semop()
的进程ID - 参数:不需要第四个参数
- 示例:
pid_t last_pid = semctl(semid, 1, GETPID);
GETNCNT
– 获取等待进程数
- 作用:返回等待信号量值增加的进程数量
- 参数:不需要第四个参数
- 示例:
int waiting = semctl(semid, 0, GETNCNT);
printf("%d个进程正在等待信号量增加\n", waiting);
GETZCNT
– 获取等待零的进程数
- 作用:返回等待信号量值变为0的进程数量
- 参数:不需要第四个参数
- 示例:
int waiting_zero = semctl(semid, 0, GETZCNT);
- 控制整个信号量集值的命令
GETALL
– 获取所有信号量的值
- 作用:将信号量集中所有信号量的值复制到数组中
- 参数:第四个参数是
unsigned short *
数组 - 示例:
unsigned short values[3]; // 假设信号量集有3个信号量
union semun arg;
arg.array = values;
semctl(semid, 0, GETALL, arg);
for (int i = 0; i < 3; i++) {
printf("信号量%d的值: %d\n", i, values[i]);
}
SETALL
– 设置所有信号量的值
- 作用:使用数组中的值设置信号量集中所有信号量的值
- 参数:第四个参数是
unsigned short *
数组 - 示例:
unsigned short new_values[3] = {1, 0, 5};
union semun arg;
arg.array = new_values;
semctl(semid, 0, SETALL, arg);
返回值
- 成功:取决于执行的命令:
GETVAL
、GETPID
、GETNCNT
、GETZCNT
:返回相应的整数值- 其他命令:返回 0
- 失败:返回 -1,并设置
errno
信号量编程实现一
可以看一下二的,从一基础下写的,注释更好点
#include <stdio.h>
#include <sys/sem.h>
#include <unistd.h>
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int op, ...);
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 key;//
int semid;//信号量的id存储变量
key = ftok(".", '2');//创建key值
semid = semget(key, 1, IPC_CREAT|0666);//key值,1个信号量,IPC_CREAT|0666如果信号量不存在就创建,信号量权限可读可写
union semun initsem;//联合体
initsem.val = 1;
semctl(semid, 0, SETVAL, initsem);//信号量的id,操作第一个信号量(都是从0数起),SETVAL设置值,值设为1(initsem.val = 1;)
int pid = fork();//创建进程
if(pid > 0)//父进程
{
printf("this is father\n");
}
else if(pid == 0)//子进程
{
printf("this is son\n");
}
else//错误
{
printf("fork error\n");
}
return 0;
}
信号量编程实现二
#include <stdio.h>
#include <sys/sem.h>
#include <unistd.h>
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int op, ...);
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) */
};
void pGetKey(int id)
{
struct sembuf set;//semop的操作结构体
set.sem_num = 0;//第一的信号量
set.sem_op = -1;//p操作
set.sem_flg = SEM_UNDO;//进程异常终止时自动撤销操作,防止锁死
semop(id, &set, 1);//信号量id,操作结构体,只有一个信号量
printf("getkey\n");
}
void vPutBackKey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = 1; //V操作
set.sem_flg = SEM_UNDO;
semop(id, &set, 1);
printf("put back the key\n");
}
int main()
{
key_t key;//
int semid;//信号量的id存储变量
key = ftok(".", '2');//创建key值,基于当前目录和字符'2'
semid = semget(key, 1, IPC_CREAT|0666);//key值,1个信号量,IPC_CREAT|0666如果信号量不存在就创建,信号量权限可读可写
union semun initsem;//联合体,给
initsem.val = 0;//无可用资源
semctl(semid, 0, SETVAL, initsem);//信号量的id,操作第一个信号量(都是从0数起),SETVAL设置值,值设为1(initsem.val = 1;)
int pid = fork();//创建进程
if(pid > 0)//父进程
{
pGetKey(semid);//获取资源,没有就等待
printf("this is father\n");
vPutBackKey(semid);//释放资源
}
else if(pid == 0)//子进程
{
printf("this is son\n");
vPutBackKey(semid);//释放资源
}
else//错误
{
printf("fork error\n");
}
return 0;
}
重点
- semget函数 创建信号量
- semop函数 执行P/V操作
- 正数操作 (
sem_op > 0
)- 作用:执行 V 操作(信号/释放)
- 行为:将信号量的值增加
sem_op
- 效果:通常用于释放资源,唤醒等待的进程
- 负数操作 (
sem_op < 0
)- 作用:执行 P 操作(等待/获取)
- 行为:
- 如果信号量值 ≥ |
sem_op
|:立即减少信号量值 - 如果信号量值 < |
sem_op
|:阻塞等待,直到条件满足
- 如果信号量值 ≥ |
- 效果:用于获取资源,如果资源不足则等待
- 正数操作 (
- semctl函数 控制信号量操作