进程间通信-信号量

时间:2021-08-09 15:12:51

信号量:以保护进程互斥与同步为目的,本质上为计数器,记录与统计临界资源的数目。

当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用:

1.大于0,资源可以请求,等于0,无资源可用,进程会进入睡眠状态直至资源可用;

2.当进程不再使用一个信号量控制的共享资源时,信号量的值+1(对信号量的值进行的增减操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。而在信号量的创建及初始化上,不能保证操作均为原子性)。


二原信号量:信号量的值为1或0,任一时刻只允许一个进程访问临界资源,其+-操作必须为原子操作。

信号量保护进程访问临界资源的互斥与同步,在进程申请临界资源时,先要申请信号量,所以信号量也成为临界资源。


P、V操作:

P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行;
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂
起,就给它加1.


Linux下信号量命令:

ipcs -s ;          //查看信号量
ipcrm -s <semid> //删除信号量


信号量机制函数:

1.创建信号量:

函数:

int semget(key_t key,int nsems,int semflg);
参数:

key:标识信号量的键值,由ftok()函数初始;

nsems:申请的信号量数目;

semflg:IPC_CREAT(单独使用,存在打开,不存在创建新的)IPC_EXCL(结合使用,不存在创建,存在返回错误码);

返回值:

返回信号量集标识符。


2.删除信号量

函数:

int semctl(int semid,int semnum,int cmd,...);
参数:

semid:信号量集标识符;

semnum:信号量集中要操作的信号量下标;

cmd:删除设为IPC_IMRD;

... :可变参数列表,此处设为NULL;

返回值:

<0,失败,==0,成功删除。


3.信号量初始化:

函数:

int semctl(int semid,int semnum,int cmd,...)'
参数:

semid:信号量集标识符;

semnum:信号量集中要操作的信号量下标;

cmd:此处设为SETVAL,对semnum信号量进行此控制操作;

...  :可变参数设为一个联合体,如下:

union semun {
int val; // 使用的值
struct semid_ds *buf; // IPC_STAT、IPC_SET 使用缓存区
unsigned short *array; // GETALL,、SETALL 使用的数组
struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区
};
将此联合中val初始化值。


4.信号量操作P()、V():

函数:

int semop(int semid,struct sembuf* sops,unsigned nsops);
参数

semid:信号量集标识符;

sops:结构体指针,结构体如下:

struct sembuf
{
unsignea short sem_num; //表示在信号量集哪一个信号量上操作
short sem_op; //P操作设为-1,V操作设为1;
short sem_flg; //此处代码实现设为缺省0
};

初始化时初始化此结构体,并传入其地址为函数参数;

nsops:前一个参数可视为一个数组,其表示数组中元素的个数;

以上结构体成员sem_flg取值:

(1).0               (2).IPC_NOWAIT

(3).SEM_UNDO:

有一种情况:如当有两个进程同时申请一个信号量去使用临界资源时,其中第一个进程在P()操作申请以后,还没有进行V()释放操作就异常退出了,所以此时另一个进程再对此信号量进行P()操作时,因为第一个进程没有释放信号量但已经退出,此进程则会一直申请不到信号量,从而造成死锁。

解决办法:将sem_flg取值为SEM_UNDO时,它将使操作系统跟踪当前进程对信号量的修改情况,若此进程在没有释放信号量的情况下异常退出终止,它将取消先前的P()操作,使信号量恢复原值,即操作系统会自动释放该进程持有的信号量,使其它进程可以正常工作。


代码实现:模拟实现将显示器作为临界资源,若实现信号量机制父子进程则分别成对向显示器打印‘BB’‘AA’,若没有信号量,则父子进程会以无规律形式向显示器打印BA.

test_sem.c

#include "comm.h"
int main()
{
//创建二原信号量
int semid=createSem(1);
//printf("create sucess:%d\n",semid);

if(initSem(semid,0,1)<0) //初始化
{
perror("initSem");
return -1;
}

pid_t id=fork();
if(id==0) //child
{
int _semid=getSem(0); //获取信号量集
while(1)
{
P(_semid,0); //申请信号量

printf("A");
fflush(stdout);
usleep(123456);
printf("A");
fflush(stdout);
usleep(321456);

V(_semid,0); //释放
}
}
else //father
{
while(1)
{
P(semid,0);

printf("B");
fflush(stdout);
usleep(102456);
printf("B");
fflush(stdout);
usleep(311456);

V(semid,0);
}
}
destroySem(semid); //删除信号量集
return 0;
}


comm.c

#include "comm.h"

static int commSemMessage(int nums,int flags)
{
key_t _key=ftok(PATH,PROJ_ID);
if(_key<0)
{
perror("ftok");
return -1;
}
else
{
int semid=semget(_key,nums,flags); //以flag模式得到nums个信号量返回键值为_key的信号量集标识符semid
if(semid<0)
{
perror("semget");
return -2;
}
return semid;
}
}

int createSem(int nums) //创建
{
return commSemMessage(nums,IPC_CREAT|IPC_EXCL|0666);
}

int getSem(int nums) //获取
{
return commSemMessage(nums,IPC_CREAT);
}

int destroySem(int semid) //删除
{
if(semctl(semid,0,IPC_RMID,NULL)<0) //IPC_RMID立即删除semid信号量集中数组下标为第0个信号量
{
perror("destroy:semctl");
return -1;
}
return 0;
}

int initSem(int semid,int nums,int initVal)
{
union semun _un;
_un.val=initVal;
if(semctl(semid,nums,SETVAL,_un)<0) //对semid信号量集中第nums个信号量进行SETVAL操作将联合中_un成员val设为初始值initVal
{
perror("semctl");
return -1;
}
return 0;
}

static int commPV(int semid,int nums,int initop)
{
struct sembuf _buf;
_buf.sem_num=nums; //信号量集中哪一个信号量
_buf.sem_op=initop; //P:-1, V:1
_buf.sem_flg=0; //此处默认为0

if(semop(semid,&_buf,1)<0)
{
perror("semop");
return -1;
}
return 0;
}

int P(int semid,int nums)
{
return commPV(semid,nums,-1);
}

int V(int semid,int nums)
{
return commPV(semid,nums,1);
}

comm.h

#ifndef COMM_H__
#define COMM_H__

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <errno.h>


#define PATH "."
#define PROJ_ID 0x6666

union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};

int createSem(int nums);
int getSem(int nums);
int initSem(int semid,int nums,int initVal);
int destroySem(int semid);

int P(int semid,int nums);
int V(int semid,int nums);

#endif

结果如下:

进程间通信-信号量