进程间通信方式--信号量semaphore

信号量semaphore


定义


信号量主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。
进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。
信号量有以下两种类型:
二值信号量: 最简单的信号量形式,信号量的值只能取0或1,类似于互斥锁。 注:二值信号量能够实现互斥锁的功能,但两者的关注内容不同。信号量强调共享资源,只要共享资源可用,其他进程同样可以修改信号量的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
计算信号量:信号量的值可以取任意非负值(当然受内核本身的约束)。
和消息队列一样,通过struct kern_ipc_perm的指针可以找到相应的信号量,在
System V IPC
信号量中,我们的每一个条目为一个信号量,定义在/include/linux/sem.h:

struct sem_array {
	struct kern_ipc_perm	____cacheline_aligned_in_smp
				            sem_perm;	/* permissions .. see ipc.h */
	time_t			        sem_otime;	/* last semop time */
	time_t			        sem_ctime;	/* last change time */
	struct sem		        *sem_base;	/* ptr to first semaphore in array */
	struct list_head	    sem_pending;	/* pending operations to be processed */
	struct list_head	    list_id;	/* undo requests on this array */
	int			            sem_nsems;	/* no. of semaphores in array */
	int			            complex_count;	/* pending complex operations */
};

其中struct sem *sem_base为信号量列表的头指针。struct sem是一个简单的数据结构:

struct sem {
		int	semval;		/* current value */
		int	sempid;		/* pid of last operation */
		struct list_head sem_pending; /* pending single-sop operations */
};

它维持一个当前值,最后操作的进程ID以及一个阻塞队列。在用户空间可以通过struct sembufsem中的信号量的值进行改变**(SETVAL操作)** 或者通过通过联合体union semun 对整个信号量进行改变 (IPC_STAT SETVAL等操作) ,两个结构分别如下:

/*semop system calls takes an array of these. */
struct sembuf {
	unsigned short  sem_num;	/* semaphore index in array */
	short		sem_op;		/* semaphore operation */
	short		sem_flg;	/* operation flags */
};
/* arg for semctl system calls. */
union semun {
	int val;			/* value for SETVAL */
	struct semid_ds __user *buf;	/* buffer for IPC_STAT & IPC_SET */
	unsigned short __user *array;	/* array for GETALL & SETALL */
	struct seminfo __user *__buf;	/* buffer for IPC_INFO */
	void __user *__pad;
};

include/linux/ipc.h文件中定义相应的操作:

操作 含义
#define SEMOP 1 改变信号量的值
#define SEMGET 2 打开或者创建一个信号量
#define SEMCTL 3 消息量控制
#define SEMTIMEDOP 4 好像是内部使用吧,没有仔细去看

信号量操作函数


信号量主要是实现进程间共享资源访问的控制。
对信号量的操作主要有P()V(),假设sv是一个信号量变量:

  • P(sv):如果sv的值大于0,就减1,如果sv的值等于0,则挂起进程的执行。
  • V(sv):如果有其他进程因为等待 sv而挂起,就让它恢复运行;如果没有因sv等待而挂起的进程,就对该信号量进行加1操作。
    对信号量的操作有:
操作 含义
SEMOP 改变信号量的值
SEMGET 打开或者创建一个信号量
SEMCTL 信号量控制

对信号量的操作流程是:

SEMGET —> SEMOP(-1) —> (操作临界区) —> SEMOP(+1)(SEMCTL删除或者修改信号量参数等)

创建或打开信号量


#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
 
int semget(key_t key, int nsems, int semflg);

参数1

key是一个键值,唯一标识一个信号灯集,用法与msgget()中相同。

参数2

nsems指定打开或者新创建的信号灯集中将包含信号量的数目,一般情况下,都是取值为1。

参数3

semflg是一些标志位。参数keysemflg的取值,以及何时打开已有信号灯集或者创建一个新的信号灯集与msgget()中的对应部分相同,不再祥述。该调用返回与健值key相对应的信号灯集描述字。

返回:成功返回信号灯集描述字semid,否则返回-1。

对信号量进行PV操作

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

参数1

semidsemget()返回的信号量描述符

参数2

如果nsems参数不为1,此时semid指向的是一个信号量集,而不是单独的一个信号量。因此每次对该信号集进行操作时候必须指定需要操作的信号量数目,即nsops大小。struct sembuf *sops指向的是一个struct sembuf结构体数组,数组大小即为nsops
如果我们的信号量集只有一个信号量,此时,nsops=1,我们的sops就直接指向一个struct sembuf类型的指针。
struct sembuf数据结构:

struct sembuf {
	unsigned short  sem_num;	/* semaphore index in array */
	short		sem_op;		/* semaphore operation */
	short		sem_flg;	/* operation flags */
};

semnum: 当前需要操作的信号量在信号集中编号,从0开始
sem_flg: IPC_NOWAITSEM_UNDO,如果设置了SEM_UNDO标志,那么在进程结束时,相应的操作将被取消,这是比较重要的一个标志位。

sem_op: PV操作,其值为正,加到现有的信号内含值。通常用于释放所控资源的使用权;值为负,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。

信号量控制函数


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

semnum为需要控制的信号量在信号集中的编号,如果信号集只有一个元素,该值为0。cmd为控制类型,对于有些操作,需要第四个参数,即为一个union semun联合体,根据cmd不同,使用联合体中不同的字段:

union semun {
	int val;			/* value for SETVAL */
	struct semid_ds __user *buf;	/* buffer for IPC_STAT & IPC_SET */
	unsigned short __user *array;	/* array for GETALL & SETALL */
	struct seminfo __user *__buf;	/* buffer for IPC_INFO */
	void __user *__pad;
};
cmd 描述
SETVAL 用于把信号量初始化为一个已知的值,用于第一次使用该信号量时,完成信号量值的初始化。此时使用的是union semun 中val字段。
IPC_RMID 用于删除已经不再继续使用的信号量标识符,该操作会解除所有在该信号量上的挂起进程。
IPC_STAT/IPC_SET 和消息队列相似。

信号量实例


实现字符成对打印

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
 
#include <sys/sem.h>//包含信号量定义的头文件
 
//联合类型semun定义
union semun{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
};
 
//函数声明
//函数:设置信号量的值
static int set_semvalue(void);
//函数:删除信号量
static void del_semvalue(void);
//函数:信号量P操作
static int semaphore_p(void);
//函数:信号量V操作
static int semaphore_v(void);
 
static int sem_id;//信号量ID
 
 
int main(int argc,char *argv[])
{
	int i;
	int pause_time;
	char op_char = 'O';
 
	srand((unsigned int)getpid());
 
	//创建一个新的信号量或者是取得一个已有信号量的键
	sem_id = semget((key_t)1234,1,0666 | IPC_CREAT);
 
	//如果参数数量大于1,则这个程序负责创建信号和删除信号量
	if(argc > 1)
	{
	    if(!set_semvalue())
	    {
	    	fprintf(stderr,"failed to initialize semaphore\n");
		exit(EXIT_FAILURE);
	    }
 
	    op_char = 'X';//对进程进行标记
	    sleep(5);
	}
 
	//循环:访问临界区
	for(i = 0;i < 10;++i)
	{
	    //P操作,尝试进入缓冲区
	    if(!semaphore_p())
		exit(EXIT_FAILURE);
	    printf("%c",op_char);
	    fflush(stdout);//刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上
 
	    pause_time = rand() % 3;
	    sleep(pause_time);
 
	    printf("%c",op_char);
	    fflush(stdout);
 
	    //V操作,尝试离开缓冲区
	    if(!semaphore_v())
		exit(EXIT_FAILURE);
	    pause_time = rand() % 2;
	    sleep(pause_time);
	}
 
	printf("\n %d - finished \n",getpid());
 
	if(argc > 1)
	{
		sleep(10);
		del_semvalue();//删除信号量
	}
}
 
 
//函数:设置信号量的值
static int set_semvalue(void)
{
	union semun sem_union;
	sem_union.val = 1;
 
	if(semctl(sem_id,0,SETVAL,sem_union))
	    return 0;
 
	return 1;
}
 
//函数:删除信号量
static void del_semvalue(void)
{
	union semun sem_union;
 
	if(semctl(sem_id,0,IPC_RMID,sem_union))
		fprintf(stderr,"Failed to delete semaphore\n");
}
 
//函数:信号量P操作:对信号量进行减一操作
static int semaphore_p(void)
{
	struct sembuf sem_b;
 
	sem_b.sem_num = 0;//信号量编号
	sem_b.sem_op = -1;//P操作	
	sem_b.sem_flg = SEM_UNDO;
 
	if(semop(sem_id,&sem_b,1) == -1)
	{
	    fprintf(stderr,"semaphore_p failed\n");
	    return 0;
	}
 
	return 1;
}
 
//函数:信号量V操作:对信号量进行加一操作
static int semaphore_v(void)
{
	struct sembuf sem_b;
 
	sem_b.sem_num = 0;//信号量编号
	sem_b.sem_op = 1;//V操作	
	sem_b.sem_flg = SEM_UNDO;
 
	if(semop(sem_id,&sem_b,1) == -1)
	{
	    fprintf(stderr,"semaphore_v failed\n");
	    return 0;
	}
 
	return 1;
 
}

运行结果
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_44715649/article/details/88884685