信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
1、特点
-
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
-
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
-
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
-
支持信号量组。
2、原型
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);
当semget
创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems
),通常为1; 如果是引用一个现有的集合,则将num_sems
指定为 0 ;sem_flags:获取或者创建信号量。
semctl初始化信号量,semid获取信号量id,sem_num操作第几个信号量,cmd:对前面的信号(sem_num)进行操作。
semctl:semid获取信号量id,struct sembuf semoparray[]:配置信号量(怎么写?看手册),numops:第二项(struct sembuf semoparray[])的个数(信号量集的个数)
在semop
函数中,sembuf
结构的定义如下:
struct sembuf
{
short sem_num; // 信号量组中对应的序号,0~sem_nums-1
short sem_op; // 信号量值在一次操作中的改变量
short sem_flg; // IPC_NOWAIT, SEM_UNDO
}
例子:
#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 semctl(int semid, int semnum, int cmd, ...);
//int semop(int semid, struct sembuf *sops, unsigned nsops);
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;//拿完钥匙让钥匙减1
set.sem_flg=SEM_UNDO;//进程终止时自动撤消
semop(id,&set,1);
printf("getKey\n");
}
void vPutBackKey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = 1;//获得1个钥匙
set.sem_flg=SEM_UNDO;
semop(id,&set,1);
printf("putBackKey\n");
}
int main(int argc ,char *argv[])
{
key_t key;
int semid;
key=ftok(".",1);
//1的意思:信号量集合中有一个信号
semid=semget(key,1,IPC_CREAT|0666);//获取/创建信号量
union semun initsem;
initsem.val=0;//配置0把钥匙
semctl(semid,0,SETVAL,initsem);//初始化信号量
//信号量集有多个信号量,因为上面创造了1个信号,这个填0的意思是操作第0个信号量,跟数组一样从0下标开始
// SETVAL,设置信号量的初值,设置为initsem
//cmd设置SETVAL,要有第4个参数,第4个参数需要定义联合体(man手册找),也就是这个代码最开始的那个联合体
int pid = fork();
if(pid>0){
pGetKey(semid);
printf("this is father\n");
vPutBackKey(semid);
semctl(semid,0,IPC_RMID);//销毁钥匙
}
else if(pid ==0){
printf("this is son\n");
vPutBackKey(semid);
}else{
printf("pid fail\n");
}
return 0;
}
刚开始钥匙为0,父进程没有钥匙卡住了,子进程得到钥匙,子进程先运行,运行完(钥匙放回去),父进程再得到钥匙,才可运行。