进程间通信—信号量

            信号量的本质是一种数据结构操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识,信号量在此过程中负责数据结构的互斥,同步等功能。

       当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用。大于0,资源可以请求,等于0,无资源可用,进程会进入睡眠状态直至资源可用。当进程不再使用一个信号量控制共享资源时,信号量的值+1,对信号量的值进行的增减均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。而在信号量的创建及初始化上,不能保证操作均为原子性。

      信号量本质上是一个计数器,信号量是描述临界资源当中临界树木的计数器。

      二元信号量则就是非0即1的信号量,通常实现互斥功能。

      信号量的工作原理:由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行。

V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1。

也就相当于,当两个进程共享信号量w,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号,这是第二个进程可以恢复。

下面是信号量的一些操作接口:

int semget(key_t key,int nsems,int semflg);  创建一个信号量,返回值成功返回一个信号量集,第二个参数代表信号量集的个数。

int semctl(int semid,int semnum,int cmd,...);  semctl()在semid标识的信号量集上,或者该集合的第semnum个信号量上执行cmd指定的控制命令。(信号量集合索引起始于0)根据cmd不同,这个函数有三个或四个参数。当有四个参数,第四个参数类型是union联合体。

union semun{

      int  val;                                //使用的值

      struct semid_ds *buf;          //IPC_STAT, IPC_SET使用缓存区

      unsigned short *array         //GETALL, SETALL使用的数组

      struct seminfo* _buf           //IPC_INFO使用缓冲区

}

该联合体没有定义在任何系统头文件中,因此得用户自己声明。

int semop(int semid,struct sembuf *sops,unsigned nops);  P,V操作调用接口,其中第一个参数是信号量集标号,第二个参数定义一个struct sembuf*类型的操作数组,第三个参数是数组的个数。semop允许调用一次对多个信号量进行操作。

查看信号量集命令:ipcs -s

删除信号量集命令:ipcrm -s semid

我们来看下面的程序:

#include"comm.h"

int main()
{
//	int semid=createSemSet(1);
//	initSemSet(semid,0,1);
	pid_t id=fork();
	if(id==0){ 
//		int _semid=getSemSet(1);
		while(1){
//			P(_semid,0);
			printf("A");
			fflush(stdout);
			usleep(123456);
			printf("A ");
			fflush(stdout);
			usleep(123456);
//			V(_semid,0);
		}
	}else{
		//father
		while(1){
//			P(semid,0);
			printf("B");
			fflush(stdout);
			usleep(23456);
			printf("B ");
			fflush(stdout);
			usleep(23456);
//			V(semid,0);
		}
	}
//	destroySemSet(semid);
	return 0;
}

当没有信号量一些操作函数时,程序结果如下:AB乱序打印


下面程序实现了信号量的操作:

// comm.h
#ifndef _COMM_H_
#define _COMM_H_

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

#define PATHNAME "."
#define PROJ_ID 0x0666



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

int createSemSet(int nums);
int getSemSet(int nums);
int initSemSet(int semid,int which,int val);
int P(int semid,int num);
int V(int semid,int num);
int destroySemSet(int semid);


#endif

// comm.c
#include"comm.h"

static int commSemSet(int nums,int flags)
{
	key_t _k=ftok(PATHNAME,PROJ_ID);
	if(_k<0){
		perror("ftok");
		return -1;
	}
	int semid=semget(_k,nums,flags);
	if(semid<0){
		perror("semget");
		return -2;
	}
	return semid;
}

int createSemSet(int nums)
{
	return commSemSet(nums,IPC_CREAT|IPC_EXCL|0666);
}

int getSemSet(int nums)
{
	return commSemSet(nums,IPC_CREAT);
}

int initSemSet(int semid,int which,int val)
{
	union semun _un;
	_un.val=val;
	int ret=semctl(semid,which,SETVAL,_un);
	if(ret!=0){
		perror("semctl");
		return -3;
	}
	return 0;
}

static int commPV(int semid,int num,int op)
{
	struct sembuf _sf;
	_sf.sem_num=num;
	_sf.sem_op=op;
	_sf.sem_flg=0;

	if(semop(semid,&_sf,1)!=0){
		perror("semop");
		return -1;
	}
	return 0;
}

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

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

int destroySemSet(int semid)
{
	if(semctl(semid,0,IPC_RMID)<0){
		perror("semctl");
		return -6;
	}
	return 0;
}

// test测试用例
#include"comm.h"

int main()
{
	int semid=createSemSet(1);
	initSemSet(semid,0,1);
	pid_t id=fork();
	if(id==0){
		//child
		int _semid=getSemSet(1);
		while(1){
			P(_semid,0);
			printf("A");
			fflush(stdout);
			usleep(123456);
			printf("A ");
			fflush(stdout);
			usleep(123456);
			V(_semid,0);
		}
	}else{
		//father
		while(1){
			P(semid,0);
			printf("B");
			fflush(stdout);
			usleep(23456);
			printf("B ");
			fflush(stdout);
			usleep(23456);
			V(semid,0);
		}
	}
	destroySemSet(semid);
	return 0;
}

我们来看实现信号量操作的程序运行结果:AB有序打印


 当我们再次运行程序时,则发生创建失败,查看信号量,则发现系统已存在了刚刚创建的信号量。如下图:


从而说明,当信号量被创建时,进程运行结束时,信号量不会随进程消失,所以信号量的生命周期是随内核。

那么这里有一个问题:当进程异常退出时,没有进行V操作,该如何解决?

       这时,我们可以将信号量semflg标志位设置成SEM_UNDO,当操作信号量时,将semflg设置成SEM_UNDO标识,SEM_UNDO将用于修改的信号量值在进程正常退出时(调用exit退出或main执行完)或异常退出时(如段异常,除0异常,收到KILL信号等)时归还给信号量。

如信号量初始值是20,进程以SEM_UNDO方式操作信号量减2,减5,加1;在进程未退出时,信号量变成20-2-5+1=14;在进程退出时,将修改的值归还给信号量,信号量变成14+2+5-1=20。



    

      

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值