百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

Linux系统编程——进程同步与互斥:System V 信号量

liebian365 2024-11-03 15:47 5 浏览 0 评论

信号量概述

信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。

编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。

在实际应用中两个进程间通信可能会使用多个信号量,因此 System V 的信号量以集合的概念来管理,具体操作和 Posix 信号量大同小异。

信号量集合数据结构:struct semid_ds,此数据结构中定义了整个信号量集的基本属性。

/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct semid_ds
{
	struct ipc_perm	sem_perm;		/* permissions .. see ipc.h */
	__kernel_time_t	sem_otime;		/* last semop time */
	__kernel_time_t	sem_ctime;		/* last change time */
	struct sem	*sem_base;		/* ptr to first semaphore in array */
	struct sem_queue *sem_pending;		/* pending operations to be processed */
	struct sem_queue **sem_pending_last;	/* last pending operation */
	struct sem_undo	*undo;			/* undo requests on this array */
	unsigned short	sem_nsems;		/* no. of semaphores in array */
};

信号量数据结构:struct sem,此数据结构中定义了信号量的基本属性。

/* One semaphore structure for each semaphore in the system. */
struct sem
{
	int	semval;		/* current value *信号量的值*/
	int	sempid;		/* pid of last operation *最后一个操作信号量的进程号*/
	struct list_head sem_pending; /* pending single-sop operations */
};

System V 信号量基本操作

使用 shell 命令操作信号量:

查看信号量:ipcs -s
删除信号量:ipcrm -s semid

以下函数所需头文件如下:

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

1)创建信号量集合

int semget(key_t key, int nsems, int semflg);

功能:

创建或打开一个信号量集合,该集合中可以包含多个信号量。

参数:

key:进程间通信键值,通过调用 ftok() 函数得到的键值

nsems:创建的信号量的个数。如果只是访问而不创建则可以指定该参数为 0,一旦创建了该信号量,就不能更改其信号量个数,只要不删除该信号量,重新调用该函数创建该键值的信号量,该函数只是返回以前创建的值,不会重新创建。

semflg:标识函数的行为及信号量的权限,其取值如下:

  • IPC_CREAT:创建信号量。
  • IPC_EXCL:检测信号量是否存在。
  • 位或权限位:信号量位或权限位后可以设置信号量的访问权限,格式和 open 函数的 mode_ t 一样(open() 的使用请点此链接),但可执行权限未使用。

返回值:

成功:信号量集标识符

失败:返回 -1

2)控制信号量集合、信号量

int semctl(int semid, int semnum, int cmd, ...);

功能:

对信号量集合以及集合中的信号量进行操作。

参数:

semid:信号量集标识符。

semnum:集合中信号量的序号,指定对哪个信号量操作, 只对几个特殊的 cmd 操作有意义。

cmd:信号量控制类型。semctl() 函数可能有3个参数,也可能有4个参数,参数的个数由 cmd 决定。当有4个参数时,第4个参数为联合体:

union semun{
	int			val;	/*信号量的值*/
	struct semid_ds *buf;	/*信号量集合信息*/
	unsigned short  *array;/*信号量值的数组*/
	struct seminfo  *__buf;/*信号量限制信息*/
};

cmd 的取值如下:

GETVAL:获取信号量的值。此时函数有3个参数。semctl() 函数的返回值即为信号量的值。

SETVAL:设置信号量的值。此时函数有4个参数。第4个参数为联合体中的val,其值为信号量的值。

IPC_STAT:获取信号量集合的信息。此时函数有4个参数。第4个参数为联合体中的__buf。

IPC_SET:设置信号量集合的信息。此时函数有4个参数。第4个参数为联合体中的__buf。

IPC_RMID:删除信号量集。此时函数有3个参数,第2个参数semnum不起作用。

GETALL:获取所有信号量的值。此时函数有4个参数,第2个参数semnum不起作用。第4个参数为联合体中的array,其值为用来存放所有信号量值的数组的首地址。

SETALL:设置所有信号量的值 。参数说明同上。

IPC_INFO:获取信号量集合的限制信息。此时函数有4个参数,第2个参数semnum不起作用。第4个参数为联合体中的__buf。

GETPID:获取信号的进程号,即最后操作信号量的进程。此时函数有3个参数。semctl() 函数的返回值即为信号的进程号。

GETNCNT:获取等待信号的值递增的进程数。此时函数有3个参数。semctl() 函数的返回值即为进程数。

GETZCNT:获取等待信号的值递减的进程数。此时函数有3个参数。semctl() 函数的返回值即为进程数。

返回值:

成功:0

失败:-1

3)操作信号量

int semop(int semid, struct sembuf *sops, unsigned nsops);

功能:

操作信号量,主要进行信号量加减操作。

参数:

semid:信号量集标识符。

sops:操作信号量的结构体(struct sembuf)数组的首地址( 结构体定义在 sys/sem.h ),此结构体中的数据表明了对信号量进行的操作。

struct sembuf{
	unsigned short  sem_num;	/*信号量的序号*/
	short       sem_op;		/*信号量的操作值*/
	short       sem_flg;	/*信号量的操作标识*/
};

结构体成员使用说明如下:

sem_num:信号量集中信号量的序号

sem_op 取值如下:

sem_op > 0:信号量的值在原来的基础上加上此值。

sem_op < 0:如果信号量的值小于 semop 的绝对值,则挂起操作进程。如果信号量的值大于等于 semop 的绝对值,则信号量的值在原来的基础上减去 semop 的绝对值。

sem_op = 0:对信号量的值进行是否为 0 测试。若为 0 则函数立即返回,若不为 0 则阻塞调用进程。

sem_flag 取值如下:

IPC_NOWAIT:在对信号量的操作不能执行的情况下使函数立即返回。

SEM_UNDO:当进程退出后,该进程对信号量进行的操作将被撤销。

nsops:操作信号量的结构体数组中元素的个数。

返回值:

成功:0

失败:-1

使用示例

示例一:

#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <stdio.h>
 
int main(int argc, char *argv[])
{
	key_t key;
 
	//创建key值
	key = ftok(".", 'a');
	if(key == -1)
	{
		perror("ftok");
	}
	
	//查看信号量
	system("ipcs -s");
	
	int semid;
	
	//1: 创建的信号量的个数
	semid = semget(key, 1, IPC_CREAT|0666); //创建信号量
	if(semid == -1)
	{
		perror("semget");
	}
	
	system("ipcs -s"); //查看信号量
	
	//删去信号量
	// 0: 代表对第0个信号量进行操作
	// IPC_RMID:删除信号量集
	semctl(semid, 0, IPC_RMID);
	
	system("ipcs -s"); //查看信号量
	
	return 0;
}

运行结果如下:

示例二:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <stdio.h>
 
/*解决编译出错的问题*/
#define IPC_INFO 3
 
int main(int argc, char *argv[])
{
	key_t key;
 
	//创建key值
	key = ftok(".", 'a');
	if(key == -1)
	{
		perror("ftok");
	}
	
	system("ipcs -s"); //查看信号量
	
	int semid;
	//1: 创建的信号量的个数
	semid = semget(key, 1, IPC_CREAT|0666);//创建信号量
	if(semid == -1)
	{
		perror("semget");
	}
	
	system("ipcs -s"); //查看信号量
	
	struct seminfo buf;
	/*
	//struct seminfo相关成员
	struct seminfo {
		int semmap;
		int semmni;
		int semmns;
		int semmnu;
		int semmsl;
		int semopm;
		int semume;
		int semusz;
		int semvmx;
		int semaem;
	};
	
	*/
	
	//IPC_INFO:获取信号量集合的限制信息。
	//此时函数有4个参数,第2个参数semnum不起作用。
	semctl(semid, 0, IPC_INFO, &buf);
	
	printf("buf.semmni = %d\n", buf.semmni);
	printf("buf.semmns = %d\n", buf.semmns);
	printf("buf.semmnu = %d\n", buf.semmnu);
	printf("buf.semmsl = %d\n", buf.semmsl);
	printf("buf.semopm = %d\n", buf.semopm);
	printf("buf.semume = %d\n", buf.semume);
	printf("buf.semusz = %d\n", buf.semusz);
	printf("buf.semvmx = %d\n", buf.semvmx);
	printf("buf.semaem = %d\n", buf.semaem);
 
	//删去信号量
	// 0: 代表对第0个信号量进行操作
	// IPC_RMID:删除信号量集
	semctl(semid, 0, IPC_RMID);
	
	system("ipcs -s"); //查看信号量
	
	return 0;
}

运行结果如下:

示例三:

#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <stdio.h>
 
int main(int argc, char *argv[])
{
	key_t key;
 
	//创建key值
	key = ftok(".", 'a');
	if(key == -1)
	{
		perror("ftok");
	}
	
	//查看信号量
	system("ipcs -s");
	
	int semid;
	
	//1: 创建的信号量的个数
	semid = semget(key, 1, IPC_CREAT|0666); //创建信号量
	if(semid == -1)
	{
		perror("semget");
	}
	
	system("ipcs -s"); //查看信号量
	
	int ret;
	
	/*
	//SETVAL: 设置信号量的值。此时函数有4个参数。第4个参数为联合体中的val,其值为信号量的值。 
	union semun{
		int			val;		//信号量的值
		struct semid_ds *buf;	//信号量集合信息
		unsigned short  *array;	//信号量值的数组
		struct seminfo  *__buf;	//信号量限制信息
	};
	*/
	ret = semctl(semid, 0, SETVAL, 20);
	if(ret == -1)
	{
		perror("semctl");
	}
	
	//GETVAL:获取信号量的值。函数返回值即为信号量的值。
	ret = semctl(semid, 0, GETVAL);
	if(ret == -1)
	{
		perror("semctl");
	}
	printf("ret = %d\n", ret);
 
	// 0: 代表对第0个信号量进行操作
	// IPC_RMID:删除信号量集
	semctl(semid, 0, IPC_RMID);
	
	system("ipcs -s");
	
	return 0;
}

运行结果如下:

示例四:

#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
 
int main(int argc, char *argv[])
{
	key_t key;
 
	//创建key值
	key = ftok(".", 'a');
	if(key == -1)
	{
		perror("ftok");
	}
	
	//查看信号量
	system("ipcs -s");
	
	int semid;
	
	//2: 创建的信号量的个数
	semid = semget(key, 2, IPC_CREAT|0666); //创建信号量
	if(semid == -1)
	{
		perror("semget");
	}
	
	system("ipcs -s"); //查看信号量
	
	int ret;
 
	unsigned short sem_arry[2] = {30,20};
	
	/*
	//SETALL: 设置所有信号量的值。此时函数有4个参数,第2个参数semnum不起作用。第4个参数为联合体中的array,其值为用来存放所有信号量值的数组的首地址。
	union semun{
		int			val;		//信号量的值
		struct semid_ds *buf;	//信号量集合信息
		unsigned short  *array;	//信号量值的数组
		struct seminfo  *__buf;	//信号量限制信息
	};
	*/
	ret = semctl(semid, 0, SETALL, sem_arry);
	if(ret == -1)
	{
		perror("semctl");
	}
	
	
	bzero(sem_arry, sizeof(sem_arry));
	//GETALL:获取所有信号量的值。此时函数有4个参数,第2个参数semnum不起作用。第4个参数为联合体中的array,其值为用来存放所有信号量值的数组的首地址。
	ret = semctl(semid, 0, GETALL, sem_arry);
	if(ret == -1)
	{
		perror("semctl");
	}
	printf("sem_arry[0] = %d\n", sem_arry[0]);
	printf("sem_arry[1] = %d\n", sem_arry[1]);
 
	// IPC_RMID:删除信号量集
	semctl(semid, 0, IPC_RMID);
	system("ipcs -s");
	
	return 0;
}

运行结果如下:

示例五:

//此范例使用信号量来同步共享内存的操作
#include <stdio.h> 
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h> 
#include <sys/shm.h>
#include <unistd.h>
#include <sys/wait.h>
 
#define SHM_KEY 0x33 
#define SEM_KEY 0x44 
 
union semun 
{ 
	int val; 
	struct semid_ds *buf; 
	unsigned short *array; 
}; 
 
int P(int semid) 
{ 
	struct sembuf sb;
	/*
	//操作信号量的结构体
	struct sembuf{
		unsigned short  sem_num;//信号量的序号
		short       sem_op;		//信号量的操作值
		short       sem_flg;	//信号量的操作标识
	};	
	*/
	sb.sem_num = 0; 
	sb.sem_op = -1;
	//SEM_UNDO:当进程退出后,该进程对信号量进行的操作将被撤销。
	sb.sem_flg = SEM_UNDO; 
 
	//操作1个信号量
	if(semop(semid, &sb, 1) == -1){ 
		perror("semop"); 
		return -1; 
	} 
 
	return 0; 
} 
 
int V(int semid) 
{ 
	struct sembuf sb;
	/*
	//操作信号量的结构体
	struct sembuf{
		unsigned short  sem_num;//信号量的序号
		short       sem_op;		//信号量的操作值
		short       sem_flg;	//信号量的操作标识
	};	
	*/
	
	sb.sem_num = 0; 
	sb.sem_op = 1;
	//SEM_UNDO:当进程退出后,该进程对信号量进行的操作将被撤销。
	sb.sem_flg = SEM_UNDO; 
 
	//操作1个信号量
	if(semop(semid, &sb, 1) == -1){ 
		perror("semop"); 
		return -1; 
	} 
	
	return 0; 
} 
 
int main(int argc, char **argv) 
{
	pid_t pid; 
	int i, shmid, semid; 
	int *ptr = NULL; 
	union semun semopts; 
	/*
	union semun{
		int			val;		//信号量的值
		struct semid_ds *buf;	//信号量集合信息
		unsigned short  *array;	//信号量值的数组
		struct seminfo  *__buf;	//信号量限制信息
	};
	*/
 
	//创建一块共享内存, 存一个int变量
	if ((shmid = shmget(SHM_KEY, sizeof(int), IPC_CREAT | 0600)) == -1) { 
		perror("msgget"); 
		return -1;
	} 
 
	//将共享内存映射到进程, fork后子进程可以继承映射
	ptr = (int *)shmat(shmid, NULL, 0);
	if (ptr == (int *)-1) { 
		perror("shmat");
		return -1;
	}
	
	*ptr = 0; //赋值为0
 
	// 创建一个信号量用来同步共享内存的操作 
	if ((semid = semget(SEM_KEY, 1, IPC_CREAT | 0600)) == -1) { 
		perror("semget"); 
		return -1;
	} 
 
	//初始化信号量  
	semopts.val = 1; 
	if (semctl(semid, 0, SETVAL, semopts) < 0) { 
		perror("semctl"); 
		return -1;
	} 
 
	if ((pid = fork()) < 0) { //创建进程
		perror("fork");
		_exit(0);
	}else if (pid == 0){ // Child
		// 子进程对共享内存加1 
		for (i = 0; i < 100000; i++) { 
			P(semid); 
			(*ptr)++; 
			V(semid); 
			printf("child: %d\n", *ptr); 
		} 
		
	} else { //Parent
		// 父进程对共享内存减1 
		for (i = 0; i < 100000; i++) { 
			P(semid); 
			(*ptr)--; 
			V(semid); 
			printf("parent: %d\n", *ptr); 
		} 
		
		//如果子进程结束,回收其资源
		wait(NULL);
		
		//如果同步成功, 共享内存的值为0 
		printf("finally: %d\n", *ptr); 
	} 
 
	return 0; 
} 

运行结果如下:

需要C/C++ Linux服务器架构师学习资料私信“资料”(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: