信号量
###信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
1、原理
2、特点
(1)信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
(2)信号量基于操作系统的== PV 操作==,程序对信号量的操作都是原子操作。
(3)每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
(4)支持信号量组。
注:
PV操作是一种实现进程互斥与同步的有效方法。PV操作与信号量的处理相关,P表示通过的意思,V表示释放的意思。
原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch(切换到另一个线程)。
3、创建
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
常用API
1 #include <sys/sem.h>
2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
3 int semget(key_t key, int nsems, int semflg);
4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
5 int semop(int semid, struct sembuf semoparray[], size_t numops);
6 // 控制信号量的相关信息
7 int semctl(int semid, int sem_num, int cmd, ...);
semget函数
创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
函数原型
int semget(key_t key, int nsems, int semflg);
key:所创建或打开信号量集的键值。
nsems:信号量的个数,通常为1;如果是引用一个现有的集合,则将nsems指定为 0,该参数只在创建信号量集时有效。
semflg:调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过 | 表示。
semop函数
对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
函数原型
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:信号操作结构的数量,恒大于或等于1。
semctl函数
控制信号量的相关信息
函数原型
int semctl(int semid, int semnum, int cmd, ...);
semid:semget()返回的信号量标识符。
semnum:是操作信号在信号集中的编号,第一个信号的编号是0。
cmd:cmd通常是下面的两个值:
SETVAL:
用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:
用于删除一个已经无需继续使用的信号量标识符。
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//设置信号量集中的一个单独的信号量的值。
如果有第四个参数,它通常是一个union semun结构,定义如下:
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) */
};
作用是在信号量第一次使用前对它进行设置。
4、实战
demo.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//int semget(key_t key, int nsems, int semflg);
//int semop(int semid, struct sembuf *sops, unsigned nsops);
//int semctl(int semid, int semnum, int cmd, ...);
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) */
};
//p操作
//将信号量值-1
void pGet(int id)
{
struct sembuf set;
set.sem_num=0;
set.sem_op=-1;
set.sem_flg =SEM_UNDO;
semop(id,&set,1);
printf("get\n");
}
//v操作
//将信号量值+1
void vPut(int id)
{
struct sembuf set;
set.sem_num=0;
set.sem_op=1;
set.sem_flg =SEM_UNDO;
semop(id,&set,1);
printf("put\n");
}
//父进程获取信号量将被阻塞,直到子进程将信号量+1
int main(int argc,char **argv)
{
key_t key;
int semid;
key = ftok(".",2);// 获取key值
semid =semget(key,1,IPC_CREAT|0666);// 创建信号量集,其中只有一个信号量
union semun initsem;
initsem.val = 0;//设置SETVAL的值
semctl(semid,0,SETVAL,initsem);//SETVAL:用来把信号量初始化为一个已知的值。
int pid = fork();//建立进程
if(pid>0){
pGetKey(semid);//父进程将信号量-1
printf("this is father\n");
vPutKey(semid);//子进程将信号量+1
semctl(semid,0,IPC_RMID);//删除信号量
}
else if(pid == 0){
printf("this is child\n");
vPutKey(semid);//子进程将信号量+1
}else{
printf("fork error\n");
}
}