进程通讯之信号量

进程通讯之信号量

一、引言:

我们知道在一条单轨铁路上,任何时候在上面只能有一列列车行驶在上面。而管理这条铁路的系统就是信号量,任何一列列车必须等到表明可以行驶的信号确认以后才能进入铁路。当一列列车进入轨道行驶时,需要将轨道改为禁止其他列车进入,从而防止不知情的列车进入轨道,发生冲突。而当列车行驶完这条轨道后,需要将轨道改回原来允许其他列车进入这个轨道。这也可以用交通手势来形象化举例。那么这个信号量到底是什么,它的工作是怎么处理,以及如何运用呢。

二、信号量的理论以及相关知识

信号量在计算机上实际是一个简单的整数,那么这个的整数的作用以及意义是什么呢?
信号量就是一个计数器,是记录一块临界资源能同时被几个进程访问的计数器。比如一个停车场有2个空闲的停车位,这时候来了三辆车,当停车场的管理员允许一辆车通过时,这时候,车位就变成了一个,当最后剩下了一辆车时,停车位满了,那么这时候管理员举会让着辆车等待,直到有一辆车离开为止,剩下的这辆车才能进去。这里的管理员相当于就是信号量,用来通知车辆的进入和车辆的离开。

上面我们提到临界资源,那么临界资源到底是什么呢?
临界资源:多道程序系统中有许多的进程,而这些进程共享着各种资源,但是有很多资源一次只能供一个进程使用。所以在同一时刻,只能供一个进程使用的资源就叫临界资源,比如现实中的打印机。而进程之间采取互斥的方式共享这些资源。比如软件中的:数组,变量,缓冲区等等也是临界资源。

临界区:每个进程中访问临界资源的代码区域就是临界区;

原子操作:众所周知,原子原本是指的是不可再分的最小颗粒,所以原子操作就是不能被分割的指令,原子操作可以保证指令以原子的方式执行,这样执行过程则不会被打断中止,直到指令结束为止。在单处理器上,操作一旦进行执行,则不能停止,直到运行完毕。在多处理器上,操作执行时,其操作中的变量将被锁住,防止其他的进程访问,直到操作运行完毕。

进程同步执行:进程之间存在着相互协作的能力,而进程的同步执行就是一种协作的关系,当一个进程的执行触发了某些条件使得另外一个进程执行的过程叫做同步执行。(当一个进程发送出信息后,接受者才能接受到信息)

比如当进程A向缓冲区写入数据,供给进程B使用,这时候如果缓冲区空了,而进程B执行,则进程B会被阻塞,直到进程A 向缓冲区写数据。反之当缓冲区满了,进程A将会被阻塞,直到进程B从缓冲区读数据,进程A才会被唤醒。

如图图解:


进程异步执行:就是当一个异步调用过程发出后,调用者不能立即得到结果,在完成后通知调用者结果,这与进程同步执行相对。也就是两个进程互不干涉,不影响自身的运行。

进程同步与异步的区别形象化:

进程同步:就是给了小狗一个汉堡,看着小狗把汉堡吃完,走过来对你舔一舔,摇一摇尾巴,这就是进程同步。

进程异步:就是给了小狗一个汉堡吃,然后就走开了,等过一段时间以后小狗去找你给你说汉堡肉少了,不好吃,这就是进程异步,你和小狗之间各干各的互不干扰。

我们说过信号量是进程之间通讯的一种方式,其实不光如此,进程更是一种同步机制,使得更好的去完成进程的同步执行。

三、信号量的操作以及包含元素

信号量集:信号量的一个集合,也就是一个数组。
其实信号量是以集合的形式创建的,一个信号量有一个或多个信号量。
一个信号量包含以下元素:
信号量的当前值
在信号量上操作的最后一个进程的pid
等待该信号量的值大于信号量的当前值的进程数
等待该信号量为0的进程数

系统提供的操作信号量的系统API:
由于信号量是在操作系统内核实现的,所以需要调用系统提供的函数,然后自己进行封装。
信号量函数定义如下:

semget函数:创建一个新信号量或者取得一个已有信号量的键
int semget(key_t key, int num_sems, int sem_flags);
第一个参数key是整数值,不相关的进程可以通过它访问同一个信号量。程序对所有的信号量访问都是间接的,它先提供一个键,再由系统生成相应的信号量标识符。只有semget函数才能直接使用信号量键,其他的信号量函数都是使用semget函数的返回的信号量标识符。
num_sems参数是指定需要的信号量数目。
sem_flags参数是一组标志,它与open函数的标志非常类似,它低端的9个比特是该信号量的权限,类似于文件访问权限。sem_flags包括:IPC_CREAT和IPC_EXCL。
操作权限:Onnn
IPC_CREAT:创建一个新的信号量
IPC_EXCL:创建出来的是一个新的、唯一的信号量。
返回值:成功返回一个正数(非零值),其他信号量函数的信号量的标识符
如果失败,则返回-1.

semop函数:用于改变信号量的值
int semop(int sem_id,  struct sembuf *sem_ops,  size_t num_sem_ops);
第一个参数:sem_id是semget函数返回的信号量标识符;
第二个参数:sem_ops是一个指向结构数组的指针。
结构体定义如下:
struct sembuf
{
short sem_num;
short sem_op;
short sem_flg;
}
第一个成员sem_num是信号量标号,除非你需要使用一组信号量,否则它取值一般为0;
第二个成员sem_op是信号量的P  V 操作
第三个成员sem_flg通常设置为SEM_UNDO,它使得操作系统跟踪当前进程对这个信号量的修改,当进程退出时取消正在执行的pv操作。
第三个参数num_sem_ops是sem_op数组的元素个数。
返回值:成功返回0,失败返回-1;

semctl函数:用来控制信号量的信息
int semctl(int sem_id, int sem_num,int command,···);
第一个参数:sem_id是semget函数返回的信号量标识符;
第二个参数sem_num是信号量标号,一般取值为0,表示这是第一个也是唯一的一个信号量;
第三个参数command是将要采取的动作。
如果还有第四个参数,它将会是一个union semun结构,由程序员自己定义
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
}

信号量操作:
创建(初始化) 或者 是获取

p操作  :-1  阻塞运行

v操作  :+1       释放资源

删除

对信号量的操作的封装:
void sem_get()     // 创建并初始化     或者获取
void sem_p()        //P操作
void sem_v()        //V操作
void sem_del()     //删除

封装代码实现如下:
头文件:

sem.h文件

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

int semid;

union sembuff
{
	int val;
};

void sem_get();
void sem_p();
void sem_v();
void sem_del();


#endif
函数实现:

sem.c 文件

#include "sem.h"

void sem_get()
{
	//获取信号量集
	semid = semget((key_t)1234,1,0666);
	if (semid == -1)
	{
		//完成创建信号量集
		semid = semget((key_t)1234, 1, 0666 | IPC_CREAT);
		assert(semid != -1);
		if (semid == -1)
		{
			printf("error\n");
			exit(0);
		}
		//完成初始化
		union sembuff data;
		data.val = 1;
		semctl(semid,0,SETVAL,data);

	}
}

void sem_p()
{
	struct sembuf buff;
	buff.sem_num = 0; //要操作的信号量的下标pos
	buff.sem_op = -1;//p操作的核心
	buff.sem_flg = SEM_UNDO;

	semop(semid,&buff,1);
}

void sem_v()
{
	struct sembuf buff;
	buff.sem_num = 0;
	buff.sem_op = 1;//v操作的核心
	buff.sem_flg = SEM_UNDO;
	semop(semid,&buff,1);
}

void sem_del()
{
	semctl(semid,0, IPC_RMID);
}
四、练习

A 进程接受用户输入, 当用户输入为“baby” 时, B 进程开始计算 100 以内所有的素
数, 假设 B 进程执行时间需要 5 秒。 A 在这 5 秒内不能接受用户输入。

Linux终端下:gcc编译命令为:
a.c文件:gcc -o   a   a.c   sem.c
b.c文件:gcc -o   b   b.c    sem.c
代码如下:

a.c文件

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

void main()
{
	sem_get();
	sem_p();
	while(1)
	{
		char buff[128] = {0};
		printf("请输入正确口令:\n");
		fflush(stdout);
		fgets(buff,127,stdin);
		if (strncmp(buff,"baby",4) == 0)
		{
			sem_v();
			sem_p();
		}
	}

	exit(0);

}

b.c文件

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

#define MAX 100
void main()
{
	sem_get();
	sem_p();
	while(1)
	{
		int i = 1;
		int j = 1;
		for (; i<MAX+1; i++)
		{
			if (i%2 != 0)
			{
				printf("%d以内的第%d次素数是 : %d\n",MAX,j,i);
				j++;
			}
		}
		sleep(5);
		sem_v();
		sem_p();
	}
}

结果如下:





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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值