进程间通信之——信号量(一)(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信号量的一些基本概念,目的是能够对信号量有一个基本的初步认识,文中如有不当之处,欢迎大家指正。


  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
信号量是一种用于进程间通信和同步的机制。它是一个计数器,用于保证在共享资源上的互斥访问。在Linux系统中,可以使用信号量来实现进程间的同步和互斥。以下是信号量的基本概念: - 计数器:信号量的值是一个计数器,它可以被多个进程共享。 - P操作:当一个进程需要访问共享资源时,它必须执行P操作,该操作会将信号量的值减1。如果信号量的值为0,则进程将被阻塞,直到信号量的值大于0。 - V操作:当一个进程使用完共享资源后,它必须执行V操作,该操作会将信号量的值加1。如果有进程正在等待该信号量,则唤醒其中一个进程继续执行。 在ZUCC中,可以使用信号量来实现进程的同步和互斥。首先,需要使用semget函数创建一个信号量集合,并使用semctl函数对信号量进行初始化。然后,可以使用semop函数执行P和V操作。例如,下面是一个简单的示例程序,用于演示信号量的使用: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/sem.h> #define SEM_KEY 1234 union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main() { int semid, pid; union semun arg; struct sembuf sb; // 创建信号量集合 semid = semget(SEM_KEY, 1, IPC_CREAT | 0666); if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); } // 初始化信号量 arg.val = 1; if (semctl(semid, 0, SETVAL, arg) == -1) { perror("semctl"); exit(EXIT_FAILURE); } // 创建子进程 pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程执行P操作 sb.sem_num = 0; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop P"); exit(EXIT_FAILURE); } printf("Child process\n"); // 子进程执行V操作 sb.sem_num = 0; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop V"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } else { // 父进程执行P操作 sb.sem_num = 0; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop P"); exit(EXIT_FAILURE); } printf("Parent process\n"); // 父进程执行V操作 sb.sem_num = 0; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop V"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } return 0; } ``` 在上述代码中,创建了一个信号量集合,并将其初始化为1。然后,创建了一个子进程和一个父进程,它们分别执行P和V操作。由于信号量的初始值为1,因此父进程和子进程都可以顺利地执行。如果将信号量的初始值改为0,那么父进程和子进程都将被阻塞,直到有一个进程执行V操作为止。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值