(二)进程间通讯方式-------信号量

  • 临界资源:同一时刻只能被一个进程访问的资源
  • 临界区:访问临界资源的代码区域
  • 原子操作:不能被打断的操作,一旦开始执行,只能到结束,中间不能被打断

信号量

他与其他的IPC机构不一样,它是一个计数器,用于多进程对共享数据对象的访问。因为信号量是对于所有进程都可以访问到的,所以信号量在内核(只有一个内核嘛)中实现。在内核中一个内核对象中有一个信号量集,每个信号量集会有多个信号量,你可以将这个信号量集看成一个数组,其中数组中每个元素就是一个信号量。(数组的元素个数是程序员指定的)我们使用信号量,来解决进程间共享资源引发的同步问题,是进程之间同步控制的一种机制。

对于信号量有两个操作(因为信号量访问具有原子性,所以两个操作都是原子操作)

  • v操作(+1):将信号量的个数加一,进程可以访问资源。
  • p操作(-1):将信号量的个数减一,资源被一个进程占据了,或许当前进程需要等待。

<1>若信号量的值为正,则进程可以使用该资源。进程将信号量的值减一,表示它使用了一个资源单位

<2>若此信号量的值为0,则进程进入休眠状态(等待),直至信号量值大于0,进程被唤醒,可以使用资源。

信号量的使用

Linux提供了一组信号量API,我们来看看

1:要获得一个信号量ID,要调用的第一个函数是semget(创建信号量(集))

#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
int semget(key_t key,int nsems,int flag);  //成功返回信号量ID,出错返回-1

key :信号量键值,可以理解为信号量的唯一性标记。用户态有一个key值,如果获取信号量ID时,使用的key值相同,则访问的信号量就是同一个。(相当于我么key就是文件名,而id就是我们的文件描述符,用id来操作信号量)

nsems:是该信号量集中的信号量数(即数组大小)。如果创建新集合(一般在服务器进程中),则必须指定nsems。如果引用一个现存的集合(一个客户进程),则将nsems指定为0.

sem_flags:有两个值:IPC_CREAT和IPC_EXCL
IPC_CREAT表示若信号量已存在,返回该信号量标识符。
IPC_EXCL表示若信号量已存在,返回错误。

2:信号量的创建与其赋初始值是分开的,赋初始值我们用semctl函数

#include<sys/sem.h>
int semctl(int semid,int semnum,int cmd,..../*union semun arg*/);

semnum:指定信号量集中的一个成员(即数组中的元素),semnum值在0--nsems-1之间(下标)

cmd:常用的有两个 IPC_RMID,GETVAL(其他的cmd值可以查书,这里就不一一列举了)

IPC_RMID:从系统中删除信号量集(IPC开头的cmd操作的是整个信号量集)

GETVAL:返回成员semnum的semval的值(也即计数器的值(信号量)。GET开头的cmd操作的是semnum成员里的数值,而非整个信号量集)

对于第四个参数是可选的,如果使用该参数,其类型是semun,它是一个联合体

union semun{
    int val;
    struct semid_ds *buff;
    unsigned short *array;
}

一般用到的是val,表示要传给信号量的初始值。

3:p/v操作 ,修改信号量的值

#include<sys/sem.h>
int semop(int semid,struct sembuf semoparry[],size_t nops);//成功返回0,出错返回-1

参数semoparry是一个指针,他指向一个信号量操作数组,信号量操作由sembuf结构表示

nops规定该数组中操作的数量(元素数)

struct sembuf{
    unsigned short sem_num;   //要进行操作的信号量集的元素下标 0-nsems-1
    short sem_op;     //信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作另一个是+1,即V(发送信号)操作。 
    short sem_flg;  ////通常为SEM_UNDO,使操作系统跟踪信号,并在进程没有释放该信号量而终止时,操作系统释放信号量(会将信号量自动复原)  

}

实例: 

sem.h

#ifndef __SEM_H
#define __SEM_H

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<fcntl.h>
#include<assert.h>
#include<sys/ipc.h>

typedef union semun
{
	int val;  //我们只用到了val,所以我们就重新定义一下这个结构
}semun;
	
int sem_init(key_t key); //初始化操作

void sem_p(int id,int num); //p操作

void sem_v(int id,int num); //v操作

void sem_del(int id);   //删除信号量

#endif
#include"sem.h"
int sem_init(key_t key) //获取信号量集id函数
{
	int id=semget(key,0,0664);//获取下,看看key值所对应的信号量即是否存
	if(id==-1) //key值对应的信号量集不存在,要创建
	{
		id=semget(key,2,0664|IPC_CREAT); //2是元素个数
		semun un;
		un.val=0;  //信号量数值,这个很关键
		semctl(id,1,GETVAL,un); //根据un值设置1号下标的信号量中的各个属性
	}
	return id;
}

void sem_p(int id,int num) //num是要操作的元素下标
{
	struct sembuf buf;
	buf.sem_num=num;   
	buf.sem_op=-1;
	buf.sem_flg=SEM_UNDO;
	semop(id,&buf,1);   //1是要操作的元素数
}

void sem_v(int id,int num) //num是要操作的元素下标
{
	struct sembuf buf;
	buf.sem_num=num;
	buf.sem_op=1;
	buf.sem_flg=SEM_UNDO;
	semop(id,&buf,1);
}

void sem_del(int id)
{
	semctl(id,1,IPC_RMID);
}

main1.c 

#include"sem.h"

int main()
{
	int semid=sem_init((key_t)2333);
	int fd=open("a.txt",O_WRONLY|O_CREAT,0664);
	assert(fd!=1);
	sleep(3);
	write(fd,"Hello",5);
	sem_v(semid,1);
	close(fd);
}

main2.c 

#include"sem.h"

int main()
{
	int semid=sem_init((key_t)2333);
	sem_p(semid,1);

	int fd=open("a.txt",O_RDONLY|O_CREAT,0664);
	assert(fd!=-1);

	char buff[128]={0};
	int n=read(fd,buff,127);
	printf("%s\n",buff);
	close(fd);
	sem_del(semid);
	exit(0);
}

猜你喜欢

转载自blog.csdn.net/Eunice_fan1207/article/details/83047147