信号量描述
信号量和信号完全不同,虽然它们只相差一个字。管道、消息队列、共享内存、信号都可以携带消息,而信号量不可以携带数据,那信号量的作用是什么?
以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。在此场景中,车位就是临界资源,而看门人就起信号量的作用。
临界资源:多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机、磁带机等。
信号量定义
最简单的信号量是一个只有0与1两个值的变量,二值信号量。这是最为通常的形式。具有多个正数值的信号量被称之为通用信号量。
P与V的定义出奇的简单。假定我们有一个信号量变量sv,两个操作定义如下:
P(sv) 如果sv大于0,减小sv。如果sv为0,挂起这个进程的执行。
V(sv) 如果有进程被挂起等待sv,使其恢复执行。如果没有进行被挂起等待sv,增加sv。
在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)
注:原子操作:单指令的操作称为原子的,单条指令的执行是不会被打断的
semget函数
用于创建信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key,int nsems,int flags)
key:信号量键值,可以理解为信号量的唯一性标记。
num_sems:信号量的数目,一般为1
sem_flags:有两个值,IPC_CREATE和IPC_EXCL,
IPC_CREATE表示若信号量已存在,返回该信号量标识符。
IPC_EXCL表示若信号量已存在,返回错误。
返回值:相应的信号量标识符,失败返回-1
semop函数
用于修改信号量的值
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
sem_id:信号量标识符
nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作
sembuf的定义如下:
struct sembuf{
short sem_num; //除非使用一组信号量,否则它为0
short sem_op; //锁加一或者减一
short sem_flg; //通常为SEM_UNDO,使操作系统跟踪信号量,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
semctl函数
用于信号量的初始化和删除
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);//参数...是不是下了一跳
sem_id:是由semget返回的信号量标识符
semnum:当前信号量集的哪一个信号量
cmd:有两个值SETVAL,IPC_RMID,分别表示初始化和删除信号量(删除的话就不需要缺省参数,只需要三个参数即可)。
如有需要第四个参数一般设置为union semnu arg;定义如下
union semun
{
int val; //使用的值
struct semid_ds *buf; //IPC_STAT、IPC_SET 使用的缓存区
unsigned short *arry; //GETALL,、SETALL 使用的数组
struct seminfo *__buf; // IPC_INFO(Linux特有) 使用的缓存区
};//一般用到的是val,表示要传给信号量的初始值。
代码实现
利用信号量让子进程先运行 放锁之后父进程再运行
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
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;
set.sem_op = -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;//锁加一
set.sem_flg=SEM_UNDO;
semop(id, &set ,1);
printf("put back the key\n");
}
int main(int argc, char const *argv[])
{
key_t key;
int semid;
key = ftok(".",2);
//1是信号量的个数
semid = semget(key, 1, IPC_CREAT|0666);//获取/创建信号量
union semun initsem;
initsem.val = 0;
//操作第0个信号量
semctl(semid, 0, SETVAL, initsem);//初始化信号量
//SETVAL设置信号量的值 设置为initsem
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 child\n");
vPutBackKey(semid);//子进程放锁
}else{
printf("fork error\n");
}
return 0;
}
编译运行