15.8 信号量测试

15.8 信号量
(1)概述
	1.信号量是一个计数器
	2.信号量用于多个进程对共享数据的访问。
	3.信号量由内核实现,测试及-1是原子操作。
	4.常用的简单信号量称为二元信号量。
	5.一般信号量的初值可以是任意一个正值,表明有多少个共享资源单位可供共享使用。
	6. 系统对信号量的限制
		SEMMNI:	系统允许的信号量集的上线
		SEMMSL: 单个信号量集中信号量的上限
		SEMMNS: 系统允许的信号量集的上限
	7.信号量集结构
		struct {
			unsigned short semval;	// 信号量值,总是大于等于0
			pid_t		   sempid;	// 是一个执行semop的进程
			unsigned short semncnt; // 返回等待第semnum个信号量增大的进程的个数。
			unsigned short semzcnt; // 返回等待第semnum个信号量变为0的进程的个数。
			...
		}
		
(2)为了获得共享资源,进程需要执行的操作
	1.测试控制该资源的信号量
		若信号量的值为正:则进程可以使用该资源。进程将信号量值-1,表示使用了一个资源
		若信号量的值为0: 则进程进入休眠状态,直至信号量的值大于0,进程被唤醒,然后重新测试该资源的信号量。
	2.当进程不再使用信号量控制共享资源时,信号量的值+1,如果有进程正在等待该信号量,则唤醒他们。
	
(3)创建/获取信号量
	int semget(key_t key, int nsems, int flag)
		nsems:
			该集合中的信号量数。如果创建新信号量集合必须指定nsems;如果引用现有的信号量集合,则将nsems指定为0
		flag: 
			----------------------------------------------------------------
			oflag参数				key不存在(ENOENT)	key已经存在
			无特殊标志				出错,返回-1			成功,获取到已有标识符
			IPC_CREATE				成功				成功,获取到已有标识符
			IPC_CREATE|IPCEXCL		成功				出错,返回-1(EEXIST)
			----------------------------------------------------------------
		ret:
			成功返回信号量标识符(id),失败返回-1。	
		
(4)控制信号量
	int semctl(int semid, int semnum, int cmd, .../* union semun arg*/);
		semid:		信号量标识符
		semnum:		信号量集中第几个信号量,该参数与cmd相关
		cmd:		想要执行的操作
			-------------------------------------------------------------------------------
			IPC_RMID	删除信号量集,所有阻塞在semop函数上的进程被唤醒。semnum参数被忽略	
			IPC_STAT	获取信号量集信息,结果存放在semun.buf中	
			IPC_SET		使用semun->buf中的值来设置信号量集的sem_perm.uid、sem_perm.gid、sem_perm.mode	
			GETVAL		返回信号量集中第semnum个信号量的值,第四个参数被忽略
			SETVAL		信号量集中第semnum个信号量的值为semun.val
			GETALL		获取信号量集中所有信号的值,存放到semun.array中, semnum参数被忽略
			SETALL		用semun.array中的数据初始化信号量集中的所有信号, semnum参数被忽略
			GETPID		返回上一个对第semnum个信号量执行semop的进程ID, 不存在时返回0。
			GETNCNT		返回正在等待信号量的值大于当前值(第semnum个信号量的值)的进程个数
			GETZCNT		正在等待信号量的值变为0的进程个数。
			-------------------------------------------------------------------------------
		第四个参数:
			union semun{
				int 			val;
				struct	senid_ds *buf;
				unsigned short *array;
				struct seminfo *__buf;
			}
			注意: union semun联合体已经被内核注释掉了,需要自己定义一遍
		返回值:
			1.除了GETALL以外的所有GET命令, semctl函数都返回相应的值。
			2.对于其他命令,成功返回0,出错设置error并返回-1;
(5)操作信号量
	int semop(int semid, struct sembuf semoparry[], size_t nops);
		semid: 		信号量标识符
		semoparry:  // 对型号量集中的操作
			struct sembuf {
				unsigned short  sem_num;	// 操作信号量集中的第几个信号量。0表示第一个,1表示第2个,以此类推
				short			sem_op;		// 信号量操作
				short   		sem_flag;	// 操作标记
			};
			--------------------------------------------------------------------------------------------------------------------	
			sem_op			作用						含义
			--------------------------------------------------------------------------------------------------------------------	
			>0				用于释放资源			第sem_num个信号量的信号量值(semval)加上sem_op的值。如有其他进程在等待资源且增加后的信号量满足其要求,则将其唤醒。
			--------------------------------------------------------------------------------------------------------------------	
			=0				用于等待信号			1.如果semval等于0则立刻返回成功
							量值(semval)等于0		2.如果semval不等于0
														如果指定了IPC_NOWAIT, 立刻返回失败
														如果未指定IPC_NOWAIT, 则陷入阻塞,semzcnt的值+1	
			--------------------------------------------------------------------------------------------------------------------	
			<0				用于申请资源。			1.若信号量值(semval)>|sem_num| , 则semval -= |sem_num|,立即返回。
													2.若信号量值(semval)≤|sem_num|,
														如果指定了IPC_NOWAIT,立即返回失败
														如果未指定IPC_NOWAIT,则陷入阻塞,semncnt+1。
			--------------------------------------------------------------------------------------------------------------------	
			sem_flag说明:
				IPC_NOWAIT:
				SEM_UNDO:  // 该标志多用于二值信号量
					1.一般情况下,如果进程申请了资源,修改了信号量的值,却没来得及释放资源就异常退出了,这会造成资源泄露。
					2.如果设置了SEM_UNDO标志(sem_flag |= SEM_UNDO),
						当进程退出时,会撤销对信号量的操作
			
		nops:	// 指定了semoparry数组中的操作个数
		
		系统对semop函数的限制
			SEMOPM:单次调用能够操作信号量的最大值
			SEMVMX: 信号量的上限
(6)测试
	场景:
		1.现某个中转站有2个仓库
		2.每一次来货占用一个仓库
		3.仓库中的货存放一天后发出(腾空厂库)
		4.需要中转的客户陆续的送货来,只有有空余的仓库时送的货才能被中转
	解析:
		1.两个仓库用信号量的值2来表示。
		2.接收货物时信号量-1,表示暂用一个仓库
		  发出货物时信号量+1,表示腾出一个仓库
		3.没有厂库时,来货进程被阻塞。直到有仓库空闲  

 

测试代码 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/wait.h>
#include <sys/acct.h>
#include <errno.h>
#include <sys/times.h>
#include <pthread.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include "mysem.h"
/*
	场景:
		1.现某个中转站有2个仓库
		2.每一次来货占用一个仓库
		3.仓库中的货存放一天后发出(腾空厂库)
		4.需要中转的客户陆续的送货来,只有有空余的仓库时送的货才能被中转
	解析:
		1.两个仓库用信号量的值2来表示。
		2.接收货物时信号量-1,表示暂用一个仓库
		  发出货物时信号量+1,表示腾出一个仓库
		3.没有厂库时,来货进程被阻塞。直到有仓库空闲  

	测试结果:
		storeid=65537
		handle_goods: customer1 goods 1
		handle_goods: customer3 goods 1
		handle_goods: customer2 goods 1
		handle_goods: customer1 goods 2
		handle_goods: customer3 goods 2
		handle_goods: customer2 goods 2
		handle_goods: customer1 goods 3
*/

store_t store1;	// 仓库1
store_t store2;	// 仓库2

//注意: union semun联合体已经被内核注释掉了,需要自己定义一遍
union semun{
	int 			val;
	struct	senid_ds *buf;
	unsigned short *array;
	struct seminfo *__buf;
};

// 初始化仓库管理
int init_store_manage(const int num)		// num:仓库个数
{
	int semid;
	key_t key;
	union semun arg;
	arg.val = num;

	assert((key_t)-1 != (key = ftok(FTOK_PATH, PRJ_NUM)));
	// 信号量集中只有1个信号量(及一个中转站)
	assert(-1 != (semid = semget(key, 1, IPC_CREAT | S_IRUSR| S_IWUSR))); // 不存在该消息队列就返回错误
	//  0: 操作信号量集中的第一个信号量
	assert(0 == semctl(semid, 0, SETVAL, arg));
	memset(&store1, 0, sizeof(store_t));
	memset(&store2, 0, sizeof(store_t));
	return semid;
}

void get_store(const int storeid)	// 获得一个仓库
{
	int ret;
	
	struct sembuf semoparry[1];
	semoparry[0].sem_num = 0;
	semoparry[0].sem_op = -1;
	semoparry[0].sem_flg |= SEM_UNDO;

	 
	if(-1 == (ret == semop(storeid, semoparry, 1))){
		printf("ERROR: semop1, errno=%d\n",errno);
		exit(0);
	}
}

void put_store(const int storeid) // 腾出一个仓库
{
	int ret;
	struct sembuf semoparry[1];
	
	semoparry[0].sem_num = 0;
	semoparry[0].sem_op = 1;
	semoparry[0].sem_flg |= SEM_UNDO;
	
	if(-1 == (ret == semop(storeid, semoparry, 1))){
		printf("ERROR: semop2, errno=%d\n",errno);
		exit(0);
	}
}

// 处理来货
void handle_goods(const int storeid, const char *goods)
{
	store_t *s;
	
	get_store(storeid);	// 获得一个空闲仓库
	if (!store1.noempty){
		s = &store1;
		memcpy(store1.buf, goods,strlen(goods));
		store1.noempty = true;
	}
	else if (!store2.noempty){
		s = &store2;
		memcpy(store2.buf, goods,strlen(goods));
		store2.noempty = true;
	}
	else
	{
		printf("ERROR: store_goods\n");
		exit(-1);
	}
	sleep(1);		// 在厂库存放一天
	
	printf("handle_goods: %s\n", goods);	// 从仓库中取出货物发出
	memset(s, 0, sizeof(store_t));
	put_store(storeid);
}

int main()
{
	int storeid;
	key_t key;
	pid_t pid;
	pid_t pid2;
	int i;
	char goods[255];
	
	storeid = init_store_manage(2);	// 有2个仓库
	printf("storeid=%d\n", storeid);

	pid =fork();
	assert(pid >= 0);
	if (pid > 0){		// 客户1
		while(1)
		{
			i++;
			sprintf(goods, "customer1 goods %d", i);
			handle_goods(storeid, goods);
		}
	}
	
	else if (pid == 0){	
		pid2 =fork();
		assert(pid2 >= 0);
		if (pid2 == 0){		// 客户2
			while(1)
			{
				i++;
				sprintf(goods, "customer2 goods %d", i);
				handle_goods(storeid, goods);
			}
		}
		else if (pid2 > 0){	// 客户3
			while(1)
			{
				i++;
				sprintf(goods, "customer3 goods %d", i);
				handle_goods(storeid, goods);
			}
		}
	}
	pause();

}


头文件 

 

#ifndef MYSEM_H
#define MYSEM_H

#include <stdbool.h>


#define FTOK_PATH "/data1/zhouhouping/nfs/testCode/chapter15/sem/ftokpath"
#define PRJ_NUM 333333
#define NOFLAG 0


typedef struct {
	bool noempty;		// 仓库是否空闲
	char buf[255];  // 仓库里
}store_t;


void handle_goods(const int storeid, const char *goods);

#endif // MYSEM_H

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值