Linux&Apue(0.5.4):进程间通信的系统IPC方法(信号量)编程

本文深入探讨了信号量在进程间通信中的应用,详细解释了信号量的定义、实现方式及关键函数,如ftok、semget、semctl和semop。并通过一个示例展示了如何使用信号量实现父子进程间的同步。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(一)进程间通信的系统IPC方法编程

系统IPC方法:信号量,消息队列,共享内存

(1) 信号量

1.1 信号量定义

信号量:是包含一个非负整数型的变量(本质:计数器),并且带有两个原子操作wait(P,lock)和signal/post(V,unlock),也叫P/V操作),用来记录对某个资源(如共享内存)的存取状况。
实现方式
①P(wait操作):如果信号量的非负整形变量S>0,wait就将S减1。如果S=0,wait就将调用线程阻塞。
②V(post操作):如果线程在信号量上阻塞(S=0),post就会解除对某个等待线程的阻塞,使其从wait中返回。如果线程没有阻塞在信号量上,则post就将S加1。

1.2 使用信号量的步骤

①创建信号量或获得在系统中已存在的信号量:调用semget()函数。(不同进程通过使用同一个信号量键值来获取同一个信号量)
②初始化信号量:调用semctl()函数的SETVAL操作。(互斥信号量:一般将信号量初始化为1)
③信号量的PV操作:调用semop(0函数。
④不需要信号量时,用semctl()函数的IPC_RMID操作将其从系统中删除。(不应出现:对已删除的信号量的操作)

1.3 相关函数

1.3.1 ftok()函数:获取IPC关键字

ftok()函数:系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。

#include <sys/types.h>		//头文件包含
#include <sys/ipc.h>
key_t ftok( const char * pathname, int id )
参数功能
pathnamepathname:指定的文件名或路径(一般:当前目录))
int idid:子序号(int型,但只有8bits:1~255)

返回值:在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到。(例如:指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。)
key值问题
①为什么需要key值
共享内存,消息队列,信号量都是通过中间介质进行通信,但是这种介质非常多,那么我怎么知道谁用了这种介质呢?所以,就需要key值(身份ID一样)去进行区分。
②key值怎么实现:正好文件的索引节点是唯一的,这样我们只要加上指定的ID值就可以变成我们所要的唯一key值。
拓展
①索引节点:许多类Unix文件系统中的一种数据结构。每个索引节点保存了文件系统中的一个文件系统对象的元信息数据,但不包括数据内容或者文件名。
ftok函数陷阱
①误区:文件的路径,名称,子序列号不变就意味着key值不变?
②情况:访问同一共享内存的多个进程先后调用了ftok(),但是pathname指向的文件或目录被删除又重新创建(文件系统会赋予这个同名文件新的节点信息)。ftok()都能正常返回,但是key值不一定相同。

1.3.2 semget()函数:创建或获取信号量

semget()函数:获取与某个键关联的信号量集标识。

#include <sys/types.h>				//头文件包含
#include <sys/ipc.h>
#include <sys/sem.h>
int semget( key_t key, int nsems, int semflg);
参数功能
key所创建或打开信号量集的键值(key值)(程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键,再由系统生成一个相应的信号标识符(semget()函数的返回值))
nsems指定需要的信号量数目(一般:1)
semflg调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过or表示返回值说明

信号量集两种情况
①当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作,设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。
②IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
返回值
成功:返回信号量集的IPC标识符。
失败:返回-1,并且errno被设定成以下的某个值。

errno值说明errno值说明
EACCES没有访问该信号量集的权限EEXIST信号量集已经存在,无法创建
EINVAL参数nsems的值小于0或者大于该信号量集的限制;或者是该key关联的信号量集已存在,并且nsems大于该信号量集的信号量数ENOENT信号量集不存在,同时没有使用IPC_CREAT
ENOMEM没有足够的内存创建新的信号量集ENOSPC超出系统限制
1.3.3 semctl()函数

semctl()函数:用来执行在信号量集上的控制操作。

int semctl(int semid,int semnum,int cmd, /*union semun arg*/);
参数功能
semidsemget()函数返回的信号量键值
semun操作信号在信号集中的编号,第一个信号是0
cmd在semid指定的信号量集合上执行此命令
/union semun arg/(可选)类型:semun。是多个特定命令参数的联合(union),需自己定义。

参数cmd值
主要分为:SET,GET,IPC_的值,这里只做部分展示。

cmd值功能cmd值功能
SETVAL设置信号量集中的一个单独的信号量的值。则需要传入/union semun arg/SETALL设置信号量集中的所有的信号量的值
GETALL用于读取信号量集中的所有信号量的值。·GETNCNT返回正在等待资源的进程数目
IPC_RMID将信号量集从内存中删除。IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中

返回值
成功:正数
失败:返回-1,并且errno被设定成以下的某个值

errno值说明errno值说明
EACCES权限不够EFAULTarg指向的地址无效
EIDRM信号量集已经删除EINVAL信号量集不存在,或者semid无效
EPERMEUID没有cmd的权利ERANGE信号量值超出范围
1.3.4 semop()函数

semop()函数:操作一个或一组信号。

int semop(int semid, struct sembuf * sops, unsigned nsops);
参数功能
semid信号集的识别码,可通过semget获取
sops指向存储信号操作结构的数组指针
nsops信号操作结构的数量,恒大于或等于1.

结构体sembuf

struct sembuf
{
	unsigned short sem_num; 	//操作信号在信号集中的编号,第一个信号的编号是0,最后一个为nsems-1
	short sem_op;	 //值为负数(P),且绝对值大于信号的现有值:操作会阻塞直到信号值大于或等于其绝对值。
					//值为正数(V):该值会加到现有的信号内含值中。
					//值为0:如果,没有设置IPC_NOWAIT,则调用该操作的进程或者线程将暂时睡眠,直到信号量的值为0。否则,,进程或者线程不会睡眠,函数返回错误EAGAIN
	short sem_flg; 	// 信号操作标识,有两种选择:
					//IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息
					// SEM_UNDO:程序结束时(正常退出或异常终止),保证信号值会被重设为semop()调用前的值。避免程序在异常情况下结束时未解锁锁定的资源,早成资源被永远锁定。造成死锁。
};

返回值
成功:返回0
失败:返回-1,并且errno被设定成以下的某个值。

errno值说明errno值说明
E2BIG一次对信号的操作数超出系统的限制EACCES调用进程没有权能执行请求的操作,并且不具有CAP_IPC_OWNER权能
EAGAIN信号操作暂时不能满足,需要重试EFAULTsops或timeout指针指向的空间不可访问
EIDRM信号集已被移除EFBIGsem_num指定的值无效
EINTR系统调用阻塞时,被信号中断EINVAL参数无效
ENOMEM内存不足ERANGE信号所允许的值越界

(2) 用信号量实现父子进程同步示例

2.1 信号量实现父子进程流程图

在这里插入图片描述

2.2 信号量实现父子进程代码

#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#define FTOK_PATH "/dev/zero"
#define FTOK_PROJID 0x23

int semaphore_init(void);
void semaphore_term(int semid);
int semaphore_p(int semid);
int semaphore_v(int semid);
union semun
{
	int	val;
	struct	semid_ds *buf;
	unsigned short*arry;
};

int main(int argc,char **argv)
{
	int	semid;
	pid_t	pid;
	int	i;

	if((semid=semaphore_init())<0)
	{
		printf("semaphore initial failure :%s",strerror(errno));
		return -1;
	}
	//pid=fork()
	if((pid=fork())<0)
	{
		printf(" child fork() create failure :%s\n",strerror(errno));
		return -2;
	}
	else if(0==pid)
	{
		printf("child  process start running and do sth ...\n");
		sleep(2);

		printf("child process do sth...\n");
		semaphore_v(semid);

		sleep(1);

		printf("child process exit now\n");
		exit(0);
	}
	else
	{
		printf("parent process P operator wait child process ...\n");
		semaphore_p(semid);

		printf("parent process destroy semaphore and exit\n");
		sleep(2);

		printf("child process eixt and clear semaphore");
		semaphore_term(semid);
		return 0;
	}
}
//将获取key值,信号量集,信号量标识封装成一个函数
int semaphore_init(void)	
{
	key_t		key;
	int		semid;
	union	semun	sem_union;
	//1.用ftok获取key值
	if((key=ftok(FTOK_PATH,FTOK_PROJID))<0)
	{
		printf("ftok() get IPC token failure :%s\n",strerror(errno));
		return -1;
	}
	//2.调用semget函数并代入key值获取信号量集
	semid=semget(key,1,IPC_CREAT|0644);
	if(semid<0)
	{
		printf("semget() get semid failure:%s\n",strerror(errno));
		return -2;
	}

	sem_union.val=0;
	//3.调用semctl函数并代入信号量集获取信号量标识符
	if(semctl(semid,0,SETVAL,sem_union)<0)
	{
		printf("semctl () set initial value failure :%s\n",strerror(errno));
		return -3;
	}
	else
	{
		printf("semaphore get key_t[0x%x] and semid[%d]\n",key,semid);
		return semid;
	}
}
//清除信号量
void semaphore_term(int semid)
{
	union semun	sem_union;
	if(semctl(semid,0,IPC_RMID,sem_union)<0)
	{
		printf("semctl () delete semaphore ID failure :%s\n",strerror(errno));
	}
	return ;
}
//P操作
int semaphore_p(int semid)
{
	struct sembuf	_sembuf;

	_sembuf.sem_num=0;
	_sembuf.sem_op=-1;
	_sembuf.sem_flg=SEM_UNDO;

	if(semop(semid,&_sembuf,1)<0)
	{
		printf("semop P operator failure:%s\n",strerror(errno));
		return -1;
	}

	return 0;
}
//V操作
int semaphore_v(int semid)
{
	struct sembuf	_sembuf;

	_sembuf.sem_num=0;
	_sembuf.sem_op=1;
	_sembuf.sem_flg=SEM_UNDO;

	if(semop(semid,&_sembuf,1)<0)
	{
		printf("semop V operator failure:%s\n",strerror(errno));
		return -1;
	}

	return 0;
}

2.3运行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值