目录
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
1.特点:
- 信号量用于进程间同步,若要在进程间传递胡数据需要结合共享内存。
- 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。(P操作:拿锁。V操作:放回锁)
- 每次对信号量的 PV 操作不仅限于对信号量值加 1 或 减1 ,而且可以加加减任意正整数。
- 支持信号量组。
2.函数原型:
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//创建或获取一个信号量组,成功会返回信号量集 ID ,失败返回 -1
int semget(key_t key, int nsems, int semflg);
//对信号量组进行操作,改变信号量的值,成功返回 0,失败返回 -1 (用于 PV 操作)
int semop(int semid, struct sembuf *sops, unsigned nsops);
//控制信号量的相关信息 (用于给信号量初始化)
int semctl(int semid, int semnum, int cmd, ...);
3.semget
原型 : int semget(key_t key, int nsems, int semflg);
参数:
key:一个整型值对应内核中一个信号量对象,可以自己指定,不同信号量的key值不一样。不相关的进程可以通过它访问同一个信号量。程序对所有信号量的访问都是间接的,它先提供一个键,再由系统生成一个响应的信号标识符。
nsems:信号集中信号量的个数
semflg: 由九个权限标志构成,它们的用法和创建文件时使⽤的mode模式标志是一样的
返回值:成功返回⼀一个⾮非负整数,即该信号集的标识码;失败返回-1
4.semop
原型 : int semop(int semid, struct sembuf *sops, unsigned nsops);
参数 :
semid:是该信号量的标识码,也就是semget函数的返回值
sops:是个指向一个结构数值的指针
指向一个结构数组的指针,每个数组元素至少包含以下几个成员:
struct sembuf{
short sem_num; //信号量编号,除非使用一组信号量,否则它的取值为0
short sem_op; //信号量在一次操作中需要改变的数值。通常用到两个值,-1,也就是p操作,表示拿锁;+1,也就是V操作,表示放回锁。
short sem_flg; //通过被设置为SEM_UNDO。表示操作系统会跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量,防止其他进程一直处于等待状态。
};
nsops:信号量的个数
返回值:成功返回0;失败返回-1
5.semctl
系统调用semctl用来执行在信号量集上的控制操作。这和在消息队列中的系统调用msgctl是十分相似的。但这两个系统调用的参数略有不同。
semid: 信号量的标志码(ID),也就是semget()函数的返回值;
semnum: 操作信号在信号集中的编号。从0开始。
cmd: 命令,表示将要进行的操作。
参数cmd中可以使用的命令如下:
·IPC_STAT 读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
·IPC_SET 设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
·IPC_RMID 将信号量集从内存中删除。
·GETALL 用于读取信号量集中的所有信号量的值。
·GETNCNT 返回正在等待资源的进程数目。
·GETPID 返回最后一个执行semop操作的进程的PID。
·GETVAL 返回信号量集中的一个单个的信号量的值。
·GETZCNT 返回正在等待完全空闲的资源的进程数目。
·SETALL 设置信号量集中的所有的信号量的值。
·SETVAL 设置信号量集中的一个单独的信号量的值。【一般用这个】
此函数具有三个或四个参数,具体取决于cmd。当
有四个时,第四个具有union semun类型。
第四个参数:可选。是否使用取决于所请求的命令。如果使用该参数,则其类型是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)
};
一般只使用val这个成员,来为信号量赋初值。当信号量值为0时,进程会阻塞运行。
6.信号量代码demo
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
//int semop(int semid, struct sembuf *sops, unsigned nsops);
//联合体,用于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) */
};
void pGetKey(int id)
{
struct sembuf set;
set.sem_num = 0; //一般默认是 0
set.sem_op = -1; //因为我信号量的初始值设置为0,所以这里-1就是取走锁
set.sem_flg = SEM_UNDO; //一般是 SEM_UNDO ,大概是表示阻塞等待
semop(id,&set,1); //参数2:是个指向⼀一个结构数值的指针
//参数3:信号量的个数
printf("get key\n");
}
void pPutBackKey(int id)
{
struct sembuf set;
set.sem_num = 0; //一般默认是 0
set.sem_op = 1; //因为我信号量的初始值设置为0,所以这里+1就是有锁
set.sem_flg = SEM_UNDO; //一般是 SEM_UNDO ,大概是表示阻塞等待
semop(id,&set,1);
printf("put back the key\n");
}
int main()
{
int semid;
key_t key;
key = ftok(".",2);
//1.获取或创建信号量
semid = semget(key,1,IPC_CREAT|0666); //参数2:信号量集合中有 1 个信号量
//参数3:0666 是信号量的权限
union semun initsem;
initsem.val = 0; //一般只使用val这个成员,来为信号量赋初值。为0时,没有钥匙
//2.初始化信号量
semctl(semid,0,SETVAL,initsem); //参数2:操作第0个信号量
//参数3:SETVAL 设置信号量的初值,此时决定有第四个参数,设置为initsem.
//参数4:是一个联合体。初始化信号量
int pid = fork();
if(pid > 0 ){
//4.去拿锁
pGetKey(semid);
printf("this is father\n");
//5.锁返回去
pPutBackKey(semid);
//6.销毁锁
semctl(semid,0,IPC_RMID);
}
else if(pid == 0){
printf("this is child\n");
//3.放锁
pPutBackKey(semid);
}
else{
printf("fork error\n");
}
return 0;
}
7.代码demo优化
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
//联合体,用于semctl初始化
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
//初始化信号量
int init_sem(int sem_id,int value)
{
union semun tmp;
tmp.val = value;
if(semctl(sem_id,0,SETVAL,tmp) == -1){
perror("init semaphore error");
return -1;
}
return 0;
}
// P 操作
// 若信号量值为 1,获取资源并将信号量值 -1
// 若信号量值为 0,进程挂起等待
int sem_p(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0;
sbuf.sem_op = -1;
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id,&sbuf,1) == -1){
perror("P operation error");
return -1;
}
return 0;
}
// V 操作
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒他们
int sem_v(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; //序号
sbuf.sem_op = 1; // V 操作
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id,&sbuf,1) == -1){
perror("V operation error");
return -1;
}
return 0;
}
//删除信号量集
int del_sem(int sem_id)
{
union semun tmp;
if(semctl(sem_id,0,IPC_RMID,tmp) == -1){
perror("delete semaphore erroe");
return -1;
}
return 0;
}
int main()
{
int sem_id; //信号量集 ID
key_t key;
pid_t pid;
//获取key值
if((key = ftok(".",'z')) < 0){
perror("ftok error");
exit(1);
}
//创建信号量集,其中只有一个信号量
if(sem_id = semget(key,1,IPC_CREAT|0666) == -1){
perror("semget error");
exit(1);
}
//初始化:初值设为 0 资源被占用
init_sem(sem_id,0);
if(pid = fork() == -1){
perror("Fork error");
}
else if(pid == 0){ //子进程
sleep(2);
printf("process child: pid = %d\n",getpid());
sem_v(sem_id); //释放资源
}
else{ //父进程
sem_p(sem_id); //等待资源
printf("process father: pid = %d\n",getpid());
sem_v(sem_id); //释放资源
del_sem(sem_id);//删除信号量集
}
return 0;
}