进程间通信之信号量

        信号量主要用于进程间同步,避免并发访问共享资源。
        信号量集合数据结构struct semid_ds:在此数据结构中定义了整个信号量集合的基本属性,如访问权限。
信号量数据结构struct sem:信号量集合使用指针指向一个由数组组成的信号量单元,在此信号量单元中存储了各信号量的值。

它们的定义:

信号量集合数据结构:

struct semid_ds{
	struct ipc_perm sem_operm;		//权限
	__kernel_time_t sem_otime;		//最近semop时间。
	__kernel_time_t sem_ctime		//最近修改时间
	struct sem *sem_base;			//队列第一个信号量
	struct sem_queue *sem_pending 		//阻塞信号量
	struct sem_queue **sem_pending_last;	//最后一个阻塞的信号量
	struct sem_undo *undo;			//undo 队列
	unisigned short sem_nsems;
}

每个信号量结构:

struct sem{
	int semval;	//信号量当前值
	int sempid	//最近一个操作的进程号PID。
}

信号量使用步骤:
1、创建信号量或获得在系统已存在的信号量,此时需要调用semget()函数。不同的进程通过使用同一个信号量键值来获得同一个信号量。
2、初始化信号量,此时使用semctl()函数的SETVAL操作,当使用二维信号量时,通常将信号量初始化为1。
3、进行信号量的PV操作,此时调用semop()函数。这一步是实现进程之间的同步和互斥的核心工作部分。
4、如果不需要信号量,则从系统中删除它,此时使用semctl()函数的IPC_RMID操作。此时需要注意,在程序中不应该出现对已经被删除的信号量的操作。

信号量/信号量集合操作:
1、semget()函数创建信号量集合:
原型:int semget(key_t key,int nsems,int semflg)
参数:
1)key:信号量的键值,多个进程可以通过访问同一个信号量,其中有个特殊的键值IPC_PRIVATE。它用于创建当前进程的私有数据信号量。
2)nsems:需要创建的信号量数目,通常取值为1。

3)semflg:同open()函数的权限位。其中使用IPC_CREAT标志创建新的信号量,信号量已经存在也不报错。同时使用IPC_EXCL创建一个新的唯一的信号量,若信号量已存在则返回出错。

2、semctl()函数控制信号量或信号量集合:

原型:int semctl(int semid,int semnum,int cmd,union semun arg)
参数:
1)semid:semget()函数返回的信号量集标识符。
2)semnum:信号量在信号集中的编号,操作信号量集时,此参数无意义。单个信号量中取为0。单个信号量也即只用一个信号量的信号量集。
3)cmd:对信号量集或信号量集中某些信号量的操作方式。
如果操作信号量集,cmd可取IPC_RMID、IPC_SET、IPC_STAT和IPC_INFO、GETALL、SETALL等。
含义(同msgctl的相关操作):
IPC_STAT:获取信号量集的semid_ds结构,并存放在由第四个参数arg的buf指向的semid_ds结构中。semid_ds是在系统中描述信号量集的数据结构。
IPC_RMID:从系统中删除信号量集。
如操作单个信号量,cmd可取IPC_SETVAL、IPC_GETVAL等。
含义:
IPC_SETVAL:将信号量值设置为arg的val值
IPC_GETVAL:返回信号量的当前值
4)arg:是union semun结构,该结构可能在某些系统中并不给出定义,此时必须由程序员自己定义。
union semun
{
        int val;
        struct semid_ds *buf;
        unsigned short *array;
}

关于semctl()函数,更详细的信息请man一下semctl,使用这个函数很简单的,只是内容有点多而已。

3、semop()函数:
        多个进程中使用此函数修改同一个信号量集合中的各个信号量的值(在原信号量值的基础上进行加/减,即信号量的PV操作,注意PV操作都是原子操作),各进程根据获取的信号量值来决定自己的行为,实现进程间通信。
原型:int semop(int semid,struct sembuf *sops,size_t nsops)
参数:
nsops:操作数组sops(第二个参数)中的操作个数(元素数目),通常取值为1(一个操作)
sops:指向信号量的操作数组,一个操作包括以下成员:
struct sembuf
{
        short sem_num;
        short sem_op;
        short sem_flg;
}
成员含义:
1)sem_num:要操作的信号量在信号量集合中的编号。
2)sem_op:整数,表示作用于信号量的操作方式,该值如果为正数则表示增加信号量的值(例如sem_op为1,表示在原来基础上加1,如果为3,表示在原来基础上加3),如果为负整数则表示减小信号量的值,如果为0则表示对信号量的当前值进行是否为0的测试。
3)sem_flg:操作标识,可选以下各值:
IPC_NOWAIT:在对信号量集合的操作不能执行的情况下,调用立即返回,对某信号量的操作,即使其中一个操作失败,也不会导致修改集合中的其他信号量。

SEM_UNDO:当进程退出后,该进程对sem进行的操作将被取消。

使用示例:(假设信号量集中的各信号量的值已经初始化)
struct sembuf sops[4];
sops[0].sem_num = 1;
sops[0].sem_op = -1;
sops[0].sem_flg = 0;
sops[1].sem_num = 2;
sops[1].sem_op = 3;
sops[1].sem_flg = 0;
semop(mysemid,sops,2);

调用了semop后,上面的代码进行了这样的操作:
第一个sop即sops[0],用于操作信号集中的第二个信号量(sem_num=1),对其信号量进行减1操作(sem_op=-1)。
第二个sop即sops[1],用于操作信号集中的第三个信号量(sem_num=2),对其信号量进行加3操作(sem_op=3)。

信号量实现生产消费模型:

        这里用的不是二元信号量,不是用信号量来实现互斥访问其他资源。这里用了两个信号量,一个信号量的值表示产品数,一个信号量的值表示仓库空间。消费者和生产者都能修改这两个值,生产者每生产一个产品,产品数加1,仓库空间减1,消费者每消费一个产品,产品数减1,空间仓库空间加1。重点是信号量的PV操作。

sem_productor.c:

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

int sem_id;

void init()
{
	key_t key;
	int ret;
	unsigned short sem_array[2];
	union semun
	{
		int val;
		struct semid_d *buf;
		unsigned short *array;	
	}arg;
	key = ftok(".",'s');
	sem_id = semget(key,2,IPC_CREAT|0644);
	sem_array[0] =0;	//identify the productor
	sem_array[1] = 100;	//identify the space
	arg.array = sem_array;
	ret = semctl(sem_id,0,SETALL,arg);	//初始化
	
	if(ret == -1)
	{
		printf("SETAL failed (%d)\n",errno);
	}
	printf("productor init is %d\n",semctl(sem_id,0,GETVAL));//打印初始化结果,产品数
	printf("space init is %d\n\n",semctl(sem_id,1,GETVAL));	//空间数
}

void del()
{
	semctl(sem_id,IPC_RMID,0);//完成操作后删除,在此程序是一个死循环,不会执行此操作	
}

int main(int argc,char *argv[])
{
	struct sembuf sops[2];	//操作两个信号使用的结构体
	sops[0].sem_num = 0;
	sops[0].sem_op = 1;	//执行加1操作,每生产一个产品,对空间数加1
	
	
	sops[1].sem_num = 1;
	sops[1].sem_op = -1;	//执行减1操作,每生产一个产品,对空间数减1
	sops[1].sem_flg = 0;
	init();
	printf("this is productor\n");
	while(1)
	{
		printf("\n\nbefore produce:\n");
		printf("productor number is %d\n",semctl(sem_id,0,GETVAL));
		printf("space number is %d\n",semctl(sem_id,1,GETVAL));
		semop(sem_id,(struct sembuf*)&sops[1],1);	//get the space to instore the productor
		
		printf("now producing....\n");
		semop(sem_id,(struct sembuf*)&sops[0],1);	//now tell the customr canbu cusume
		printf("\nafter produce\n");
		printf("spaces number is %d\n",semctl(sem_id,1,GETVAL));
		printf("productor number is %d\n",semctl(sem_id,0,GETVAL));
		sleep(4);	
	}
	del();
}

sem_customer.c:

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

int sem_id;
void init()
{
	key_t key;
	key = ftok(".",'s');
	sem_id = semget(key,2,IPC_CREAT|0644);	//获取id,必须先执行生产者初始化	
}

int main(int argc,char *argv[])
{
	init();
	struct sembuf sops[2];
	sops[0].sem_num = 0;
	sops[0].sem_op = -1;	//执行减1操作,每消费一个产品,对产品数减去1
	sops[0].sem_flg = 0;
	
	sops[1].sem_num = 1;
	sops[1].sem_op = 1;	//执行加1操作,每消费一个产品,对空间数加1
	sops[1].sem_flg = 0;
	init();
	
	printf("the is customer\n");
	while(1)
	{
		printf("\n\nbefore consume:");	
		printf("procductor is %d\n",semctl(sem_id,0,GETVAL));
		printf("space si %d\n",semctl(sem_id,1,GETVAL));
		semop(sem_id,(struct sembuf *)&sops[1],1);	//now tell the productor canbu produce
		
		printf("\nafter consume\n");
		printf("products number is %d\n",semctl(sem_id,0,GETVAL));
		printf("space number is %d\n",semctl(sem_id,1,GETVAL));
		sleep(3);
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值