142-Linux进程间通信(信号量)

信号量PV操作

临界资源:同一时刻,只允许被一个进程或线程访问的资源
临界区:访问临界资源的代码段
如:商场试衣间,十字路口
同步:控制,使程序执行的正确性有保证,使用信号量

信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源时,需要对信号量的值进行原子减一,该操作被称为 P 操作

当信号量值为 0 时,代表没有资源可用,P 操作会阻塞住。

释放资源时,需要对信号量的值进行原子加一,该操作被称为 V操作
信号量主要用来同步进程。信号量的值如果只取 0,1,将其称为二值信号量

如果信号量的值大于 1,则称之为计数信号量。比如说有3个试衣间,供3个人使用,第4个人需要等待
PV操作就是对资源的获取和释放
原子性:保证依次通过,不会出现问题,是原子操作

举例子:
n代表火车票剩余的票数
每一个人买票, n-1
两个人都买票,应该n-2,实际上n-1
因为买票是并发的过程
计算机将n值减1需要一个过程,n=5读到处理器,准备减1,还没完成,同时另一个去访问,也把n=5读到处理器,准备减1,最后两个进程都把4写回去
n=4
在这里插入图片描述

此结果4显然有问题!!!

所以我们要进行控制,不要同时对n进行控制
我们需要用到原子操作的信号量,就可以解决这个问题了
通过PV操作0,1

例子2:
访问打印机
在这里插入图片描述
vi a.c
在这里插入图片描述
vi b.c
在这里插入图片描述
在同一个终端同时进行
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
需要通过信号量进行控制
我们设定左边的用户为A用户,右边的用户为B用户
在这里插入图片描述
首先A进行P操作,获取信号量的值(1),减1,为0,可通过
V还没执行之前,A是在使用中,此时如果B访问,执行p操作,遇到信号量为0,就阻塞住!!!(因为p操作把0-1=-1,直接自动就阻塞住了)

现在如下使用信号量解决问题

操作信号量的接口介绍:

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

/*
semget()创建或者获取已存在的信号量
semget()成功返回信号量的 ID, 失败返回-1
key:两个进程使用相同的 key 值,就可以使用同一个信号量
nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号
量的个数,给个1,就是只创建1个信号量
semflg 可选: IPC_CREAT(信号量不存在,需要创建)
 IPC_CREAT IPC_EXCL (全新创建,如果没有就创建,如果有就失败)
*/
int semget(key_t key, int nsems, int semflg);//创建,获取信号量

/*
semop()对信号量进行改变,做 P 操作或者 V 操作
semop()成功返回 0,失败返回-1
struct sembuf
{
unsigned short sem_num; //指定信号量集中的信号量下标
short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
short sem_flg; //SEM_UNDO
};
*/
int semop(int semid, struct sembuf *sops, unsigned nsops);//初始化

/*
semctl()控制信号量
semctl()成功返回 0,失败返回-1
cmd 选项: SETVAL IPC_RMID

union semun//这个联合体没有在头文件被定义,我们要自己定义
{
int val;
struct semid_ds *buf;
unsigned short *array
struct seminfo *_buf;
}; */
第一个参数:信号量的id,第二个参数:结构体 第三个参数:命令
int semctl(int semid, int semnum, int cmd, ...);

在这里插入图片描述
封装信号量的接口:
在这里插入图片描述

vi sem.h

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

union semun{
	int val;//初始值 
};

void sem_init();//创建/或者已存在的信号量
void sem_p();//p 减一
void sem_v();//v 加一
void sem_destroy();//销毁

vi sem.c

#include "sem.h"

static int semid = -1;

void sem_init()//创建/或者已存在的信号量
{
	semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//约定都使用这个key_t值,1个信号量,全新创建,谁先执行,谁先创建,后一个人就直接使用
	if ( semid == -1 )//已存在
	{
		semid = semget((key_t)1234,1,0600);//存在就直接获取,0600是为了占参数位的作用而已
	}
	else//初始化操作
	{
		union semun a;

		a.val = 1;//信号量的初始值
		if ( semctl(semid,0,SETVAL,a) == -1 )//只创建1个,所以下标为0,初始化失败
		{
			perror("semctl error");//#include <unistd.h>
		}
	}
}

void sem_p()//p 减一
{
	struct sembuf buf;
	buf.sem_num = 0;//成员下标,第0个信号量
	buf.sem_op = -1;//p 加的值是-1
	buf.sem_flg = SEM_UNDO;//信号量p操作,然后要执行V操作,如果程序因为某种原因崩溃了,V操作还没来得及执行,其他进程也阻塞的,SEM_UNDO会让
	//操作系统记住你这个P操作,如果你因为崩溃为了V操作,操作系统会帮你执行V操作,把资源释放回去,其他进程就可以使用了

	if ( semop(semid,&buf,1) == -1 )//1是操作的个数
	{
		perror("semop p error");
	}
}

void sem_v()//v 加一
{
	struct sembuf buf;
	buf.sem_num = 0;//成员下标
	buf.sem_op = 1;//v 加的值是1
	buf.sem_flg = SEM_UNDO;

	if ( semop(semid,&buf,1) == -1 )
	{
		perror("semop v error");
	}
}

void sem_destroy()//销毁信号量
{
	if ( semctl(semid,0,IPC_RMID) == -1 )//IPC_RMID代表是移除信号量,移除的是整个信号量集合哦,所以0只是占位的作用
	{
		perror("semctl del error");
	}
}

信号量只能销毁1次哦
vi a.c

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include "sem.h"

int main()
{
	sem_init();//初始化1
	int i = 0;
	for( ;i < 10; i++ )
	{
		sem_p();//如果检测到1,通过,执行减1操作,往下执行
		printf("a");
		fflush(stdout);
		int n = rand() % 3;
		sleep(n);
		printf("a");
		fflush(stdout);
		sem_v();//+1操作

		n = rand() % 3;
		sleep(n);
	}
}

在这里插入图片描述
vi b.c

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

int main()
{
	sem_init();
	int i = 0;
	for( ;i < 10; i++ )
	{
		sem_p();
		printf("b");
		fflush(stdout);
		int n = rand() % 3;
		sleep(n);
		printf("b");
		fflush(stdout);
		sem_v();

		n = rand() % 3;
		sleep(n);
	}
}

在这里插入图片描述
编译运行
在这里插入图片描述
在这里插入图片描述
这个信号量是我们通知内核创建的,内核来维护
我们结束后,内核还在维护
而且信号量可能还有其他人在使用

只能删一次
ipcs可以查看信号量,共享内存,消息队列
ipcs -s查看信号量
ipcs -m查看共享内存
ipcs -q查看消息队列
在这里插入图片描述
使用完,我们必须移除,不然不知道是1还是0(上一次的)
用命令删除
在这里插入图片描述
在这里插入图片描述

拓展提升

三个进程 a、b、c 分别输入“A”、“B”、“C”,要求输出结果必须是“ABCABCABC…”

三个信号量 第一个初始化为1,后两个初始化为0
在这里插入图片描述
ps1第一次通过,ps2 ps3不通过
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述
vi a.c
在这里插入图片描述

vi b.c
在这里插入图片描述

vi c.c
在这里插入图片描述
运行
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值