【信号量】
本质:信号量本质就是一个计数器,用于统计临界资源数目的计数器,信号量是用来调协进程 对共享资源的访问。
【信号量的工作原理 】
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行。
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1。
注意:
当进程要访问临界资源时,要先去访问信号量,所以信号量就相当于一个临界资源,所以为了保证信号量的一致性,则要保证对信号量的操作都是原子操作。
【信号量的操作】
在systemV版本中,信号量的申请是直接申请一个信号量集,并且对于信号量的操作都是原子的。
- 创建
int semget(key_t key, int nsems, int semflg);
参数:
key:由ftok()生成
nsems:信号量的个数
semflg:IPC_CREAT 存在打开,不存在创建;PC_CREAT|IPC_EXCL 不存在创建,存在出错返回
- 删除和初始化
int semctl(int semid, int semnum, int cmd, ...);
参数:
semid:标识要操作那个信号量集
sennum:初始化的是信号量集中的那个信号量
cmd:设置为SETVAL表示需要进行初始化;设置为IPC_RMID表示要进行删除信号量集
...:可变参数,一般为union semun的一个实例,注意删除的时候不用给出。
如果需要使用第四各参数时,第四个参数的类型是 union,并且需要像以下一样定义出,
union semun {
int val; // 使用的值
struct semid_ds *buf; // IPC_STAT、IPC_SET 使用缓存区
unsigned short *array; // GETALL,、SETALL 使用的数组
struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区
};
因为结构没有在头文件中声明,所以使用时要自己定义。
- 信号量的操作
int semop(int semid, struct sembuf *sops, unsigned nsops);//这个操作是原子性的
参数:
semid:标识是那个信号量集
nsops:标识是信号量集的那个信号量
sops:在介绍这个参数时先对struct sembuf这个结构体进行介绍
信号量在system V版本中是以信号量集形式出现的,在内部就是以数组形式对信号进行保存的,也就是说这块这个sops其实是一个结构体数组,每个结构体中间保存了不同信号量的信息。
struct sembuf
{
unsigned short sem_num; //表示是那个信号量
short sem_op;//对信号量的操作(P操作,这个值就是-1;V操作,这个值就是1)
short sem_flg;//一般为0
}
注意:sem_flg:信号操作标志,可能的选择有两种
IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
SEM_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
【实例代码】
利用信号量实现进程间的通信。
父子进程分别向屏幕打印“AA”“BB”,要是不进行信号量的设置,让他们随意打印会造成AA与BB的不完整输出,为了避免这样的情况发生,利用信号量实现进程对显示器资源的互斥访问。
comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<string.h>
#define PATHNAME "."
#define PJIO_ID 0x666
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;
};
int createSem(int nums);
int initSemSet(int semid,int which);
int getSem(int nums);
int P(int semid, int who);
int V(int semid, int who);
int destorySem(int semid);
#endif
comm.c
#include"comm.h"
static int commSem(int nums,int flags)
{
key_t key = ftok(PATHNAME, PJIO_ID);
if(key < 0)
{
perror("fotk");
return -1;
}
int semid = semget(key, nums, flags);
if(semid < 0)
{
perror("semget");
return -2;
}
return semid;
}
//创建信号量
int createSem(int nums)
{
return commSem(nums, IPC_CREAT|IPC_EXCL|0666);
}
//获取到信号量
int getSem(int nums)
{
return commSem(nums, IPC_CREAT);
}
//初始化信号量
int initSemSet(int semid, int which)
{
union semun un;
un.val = 1;
//因为是初始化,所以semctl这个函数需要第四个参数,这个参数类型需要自己定义
int ret = semctl(semid, which, SETVAL, un);
if(ret < 0)
{
perror("semctl");
return -1;
}
return 0;
}
//销毁信号量
int destorySem(int semid)
{
if(semctl(semid, 0, IPC_RMID) < 0)
{
perror("semctl");
return -1;
}
return 0;
}
//因为p、v操作调用相同的函数,只是传入的参数不同,对其进行一次封装
static int ChangeSem(int semid, int who, int flags)
{
struct sembuf sf;
sf.sem_num = who;
sf.sem_op = flags;
sf.sem_flg = 0;
if(semop(semid, &sf, 1) < 0)
{
perror("semop");
return -1;
}
return 0;
}
//信号量的p操作
int P(int semid, int who)
{
return ChangeSem(semid, who, -1);
}
//信号量的v操作
int V(int semid, int who)
{
return ChangeSem(semid, who, 1);
}
mysem.c
#include"comm.h"
int main()
{
//创建有一个信号量的信号量集
int semid = createSem(1);
if(semid < 0)
{
perror("createSem");
return -1;
}
initSemSet(semid, 0);
int id = fork();
while(1)
{
if(id == 0) //子进程
{
//获取信号量
int semid = getSem(1);
//申请临界资源
P(semid, 0);
printf("A");
fflush(stdout);
usleep(123456);
printf("A");
fflush(stdout);
usleep(213456);
//释放临界资源
V(semid, 0);
}
else //父进程
{
//申请资源
P(semid, 0);
usleep(123456);
printf("B");
fflush(stdout);
usleep(231456);
printf("B");
fflush(stdout);
//释放资源
V(semid, 0);
}
}
wait(NULL);
//销毁信号量
destorySem(semid);
return 0;
}