Linux进程间通讯|信号量

信号量

1. 信号量概述

在多任务操作系统环境下,多个进程会同时运行,并且一下进程间可能存在一定的关联。多个进程可能为了完成同一个任务相互协作,这就形成了进程间的同步关系。而且在不同进程间,为了争夺有限的系统资源会进入竞争状态,这就是进程间的互斥关系。
同步和互斥关系存在的根源在于临界资源。临界资源是在同一个时刻只允许有限个进程可以访问或修改资源。访问临界资源的代码就做临界区,临界区本身也会成为临界资源。
信号量是用来解决进程间的同步与互斥问题的一种进程间通讯机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作(PV操作)。其中信号量对应于某一种资源,取一个非负的整型值。信号量值指的是当前可用的该资源的数量,若等于0则意味着目前没有可用的资源。
PV原子操作的具体定义如下:

  • P操作:如果有可用的资源(信号量值 > 0),则占用一个资源(信号量值减1,进入临界区代码);如果没有可用的资源(信号量值 = 0),则被阻塞知道系统将资源分配给该进程
  • V操作:如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞进程;如果没有进程等待它,则释放一个资源(信号量值加1)

最简单的信号量只有0和1两种值,这种信号量被称为二值信号量。

2. 信号量编程

2.1 编程说明

在Linux系统中,使用信号量通常分为以下几个步骤:

  • 创建信号量或获得系统中已存在的信号量,使用 semget() 函数
  • 初始化信号量,二值信号量通常初始化为1,使用 semctl() 函数的SETVAL操作
  • 进行信号量的PV操作,调用 semop() 函数
  • 删除信号量,使用semctl() 函数的IPC_RMID操作
2.2 函数介绍

semget() 函数

/*****semget()函数*****/
函数原型:int semget(key_t key, int nsems, int semflg)
传 入 值:key 信号量的键值,多个进程可以通过它访问同一个信号量。IPC_PRIVATE用于创建私有信号量
		 nsems 需要创建的信号量数目,通常为1
		 semflg 同 open() 函数的权限位
返 回 值:成功:返回信号量标识符,在信号量的其他函数中都会使用该值;
		 失败:返回-1

semctl() 函数

/*****semctl()函数*****/
函数原型:int semctl(int semid, int semnum, int cmd, union semun arg)
传 入 值:semid semget()函数返回的信号量标识符 
		 semnum 信号量编号,使用信号量集时会用到。通常取为0,即使用单个信号量
		 cmd 指定对信号量的各种操作,当使用单个信号量时,常见的操作见下表
		 arg 是union semun结构
返 回 值:成功:根据cmd值的不同返回不同的值;
		 失败:返回-1

//union semun结构的定义
union semun{
    
    
	int val;
	struct semid_ds *buf;
	unsigned short *array;
}
cmd值 对应的操作含义
IPC_STAT 获得该信号量的semid_ds结构,并放在第四个参数arg结构中
IPC_SETVAL 将信号量值设置为arg的val值
IPC_GETVAL 返回信号量的当前值
IPC_RMID 从系统中删除信号量

semop() 函数

/*****semop()函数*****/
函数原型:int semop(int semid, struct sembuf *sops, size_t nsops)
传 入 值:semid semget()函数返回的信号量标识符 
		 sops 指向信号量操作数组
		 nsops 操作数组sops中的操作个数,通常取1
返 回 值:成功:返回信号量标识符,在信号量的其他函数中都会使用该值;
		 失败:返回-1

//sembuf结构体的定义
struct sembuf{
    
    
	short sem_num;	//信号量编号,使用单个信号量时,通常取0
	short sem_op;	//信号量操作,取值-1表示P操作,取值+1表示V操作
	short sem_flg;	//通常设置为SEM_UNDO,表示在进程没释放信号量而退出时,系统自动释放该进程中未释放的信号量
}
2.3 函数实例

因为信号量相关的函数调用比较复杂,可以将他们封装成单个信号量的几个基本函数。

/*****sem_com.c*****/
#include "sem_com.h"
/*信号量初始化函数*/
int init_sem(int sem_id, int init_value){
    
    
	union semun sem_union;
	sem_union.val = init_value;		//init_value为初始值
	if((semctl(sem_id, 0, SETVAL, sem_union)) == -1){
    
    
		perror("Initialize semaphore");
		return -1;
	}
	return 0;
}

/*删除信号量函数*/
int del_sem(int sem_id){
    
    
	union semun sem_union;
	if((semctl(sem_id, 0, IPC_RMID, sem_union)) == -1){
    
    
		perror("Delete semaphore");
		return -1;
	}
}

/*P操作函数*/
int sem_p(int sem_id){
    
    
	struct sembuf sem_b;
	sem_b.sem_num = 0;	//单个信号量的编号为0
	sem_b.sem_op = -1;	//表示P操作
	sem_b.sem_flg = SEM_UNDO;	//系统自动释放将会在系统中残留的信号量
	if(semop(sem_id, &sem_b, 1) == -1){
    
    
		perror("P operation");
		return -1;
	}
	return 0;
}

/*V操作函数*/
int sem_v(int sem_id){
    
    
	struct sembuf sem_b;
	sem_b.sem_num = 0;	//单个信号量的编号为0
	sem_b.sem_op = 1;	//表示V操作
	sem_b.sem_flg = SEM_UNDO;	//系统自动释放将会在系统中残留的信号量
	if(semop(sem_id, &sem_b, 1) == -1){
    
    
		perror("V operation");
		return -1;
	}
	return 0;
}

以下实例说蒙了信号量的概念及基本用法

/*****fork.c*****/
//头文件省略
int main(){
    
    
	pid_t result;
	int sem_id;

	sem_id = semget(ftok(".",'a'), 1, 0666|IPC_CREAT);	//创建一个信号量
	init_sem(sem_id, 0);

	result = fork();	//调用fork()函数
	if(result == -1)
		perror("Fork\n");
	else if(result == 0){
    
    	//子进程
		printf("Child process will wait for some seconds...");
		sleep(3);
		printf("The returned value is %d in the child process (PID = %d)\n",result,getpid());
		sem_v(sem_id);
	}
	else{
    
     	//父进程
		sem_p(sem_id);
		printf("The returned value is %d in the father process (PID = %d)\n",result,getpid());
		sem_v(sem_id);
		del_sem(sem_id);
	}
	exit(0);
}

猜你喜欢

转载自blog.csdn.net/Chuangke_Andy/article/details/108306405