进程间通信之——信号量(一)(system V)

system v信号量又被称为system v信号量集。它的本质就是一个计数器,用来为多个进程共享的数据结构提供受控访问。

信号量支持的操作有:


使用最广泛的信号量为二元信号量,它控制单个资源,对于这种信号量而言,它只有两种合法值: 0 和 1 ,对应一个可用的资源。若当前有资源可用,则与之对应的二值信号量的值为 1 ;若资源已被占用,则与之对应的二值信号量的值为 0 。当进程申请资源时,如果当前信号量的值为 0 ,那么进程会陷入阻塞,直到有其他进程释放资源,将信号量的值加 1 才能被唤醒。

内核为每个信号量集维护者一个semid_ds结构,该结构定义了此信号量的权限、指针、最近修改时间和队列中信号量队列信息:/usr /src/kernels/2.6.32-431.el6.i686/include/Linux/sem.h


——sem_perm权限;

——sem_otime最近semop时间;

——sem_ctime最近修改时间;

——sem_base队列第一个信号量;

——sem_pending阻塞信号量;

——sem_pending_last最后一个阻塞信号量;

——undo表示undo队列;

——sem_nsems表示信号量个数;

每一个信号量还有一个结构:


——semval表示信号量值;

——sempid表示最近一个操作的进程号pid;

当然信号量仍然存在系统限制


——SEMMNI系统容许的信号量集的上限;

——SEMMSL 单个信号量集中信号量的上限;

——SEMMNS 系统容许的信号量的上限

——SEMOPM 单次 semop 调用能够操作的信号量的最大值;

——SEMVMX 信号量值的上限;

信号量集函数

semget

功能:用来创建或打开一个信号量集;

原型:int semget(key_t key, int nsems, int semflg);

参数:key表示信号集的名字,一般由ftok函数产生;

         nsems表示信号集中信号量的个数;

         semflg用来标识信号量集合的权限,和创建文件类似,由九个权限标志为构成如0644,他还可以和以下参数一起使用:


——IPC_CREAT表示如果key不存在就创建;

——IPC_EXCL表示如果key存在就返回失败;

——IPC_NOWAIT表示如果需要等待,则直接返回错误;

返回值成功返回一个非负整数即该信号量的标识码;失败返回-1;

semctl

功能:用来控制信号量集;

原型:int semctl(int semid, int semnum, int cmd, ...);

参数:semid由semget返回的信号量标识;

          semnum信号集中信号量的序号;

          cmd表示将要采取的操作;

          最后一个参数根据命令不同而不同,他是一个类型为senum的联合(下文会有介绍);

cmd可采取的操作有:IPC_RMID、IPC_SET、IPC_STAT和IPC_INFO,定义在:/usr/include/linux/ipc.h中


IPC_RMID、IPC_SET、IPC_STAT和IPC_INFO这四个参数都是system v的通用参数;

——IPC_RMID表示删除信号集

——IPC_STAT表示获取ipc_perm的参数

——IPC_INFO表示获取系统信息

——IPC_SET表示设置ipc_prem的参数,对于这个参数,semctl有单独的规定参数


——GETPID获取信号量拥有者的pid的值;

——GETVAL获取信号量的值;

——GETALL获取所有信号量的值;

——GETNCNT获取等待信号量的值递增的进程数;

——GETZCNT获取等待信号量的值递减的进程数;

——SETVAL设置信号量的值;

——SETALL设置所有信号的值;

第四个参数的senum的联合体定义如下:


接下来介绍几个常用的操作

|cmd操作为GETVAL

semctl第二个参数为信号量编号,若执行成功,semctl返回当前信号量的值,失败返回-1;

|cmd操作为SETVAL

semctl第二个参数为信号量编号,第四个参数为要设置的val

返回值:成功返回0;失败返回-1;

semop

功能:修改集合中一个或多个信号量值;

原型:int semop(int semid, struct sembuf *sops, unsigned nsops);

参数:semid信号领=量标识码;

          sops是一个sembuf类型的指针;

          nsops标识信号量个数;

sembuf结构体定义如下:


——sem_num标识信号量编号;

——sem_op信号量一次pv操作时加减的数值,一般会用到两个值:

                   -1,p操作,等待信号量变得可用

                   +1,v操作,发出的信号量变得可用

——sem_flg操作标识,有以下值: IPC_NOWAIT和SEM_UNDO

       IPC_NOWAIT对某一信号量操作,即使其中一个操作失败,也不会导致修改其他信号量的值;

       SEM_UNDO当进程退出后,该进程对sem进行的操作将被撤销;


返回值成功返回0;失败返回-1;

测试案例一:哲学家就餐问题

问题描述:

(由Dijkstra首先提出并解决)5个哲学家围绕一张圆桌而坐,桌子上放着5支筷子,每两个哲学家之间放一支;哲学家的动作包括思考和进餐,进餐时需要同时拿起他左边和右边的两支筷子,思考时则同时将两支筷子放回原处。如何保证哲学家们的动作有序进行?如:不出现相邻者同时要求进餐;不出现有人永远拿不到筷子;

解决思路:五只筷子相当于资源,保证每次会有两位哲学家先进餐,进餐完后在换其他哲学家,能够保证五位哲学家都能进餐

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

int semid;

union semun{
	int value;
};
//获得资源
void get_sour(int num)
{
	struct sembuf sb[2]={
		{num,-1,0},
		{(num+1)%5,-1,0}
	};
	semop(semid,sb,2);
}

//释放资源
void free_sour(int num)
{
	struct sembuf sb[2]={
		{num,1,0},
		{(num+1)%5,1,0}
	};
	semop(semid,sb,2);

}

void phil(int num)
{
	while(1){
		printf("%d is thinking...\n",num);
		sleep(rand()%5);
		get_sour(num);
		printf("%d start eating...\n",num);
		sleep(rand()%3);
		printf("%d end eating...\n",num);
		free_sour(num);
	}
}

int main()
{
	srand(getpid());
	semid=semget(1234,5,IPC_CREAT|0600);//创建信号量
	if(semid == -1){
		perror("semget");
		exit(1);
	}
	union semun s;
	s.value=1;
	//五个信号量分别设初值
	int i=0;
	for(i=0;i<5;i++){
		semctl(semid,i,SETVAL,s);
	}
	//创建五个进程——一个父进程+4个子进程
	int num=0;//为每个进程编号,父进程初始化编号为0
	for(i=1;i<5;i++){
		pid_t pid=fork();
		if(pid == 0){
			num=i;
			break;
		}
	}


	phil(num);
}

运行结果:


本文主要介绍了system v信号量的一些基本概念,目的是能够对信号量有一个基本的初步认识,文中如有不当之处,欢迎大家指正。


猜你喜欢

转载自blog.csdn.net/l__xing/article/details/80217842