Linux 进程间通信(四)信号量

版权声明:转载请声明 https://blog.csdn.net/qq_40732350/article/details/82830013

1 信号量概述


信号量和其他IPC不同,并没有在进程之间传送数据,信号量用于多进程在存取共享资源时的同步控制就像交通路口的红路灯一样,当信号量大于0,表示绿灯允许通过,当信号量等于0,表示红灯,必须停下来等待绿灯才能通过。

进程间的互斥关系与同步关系存在的根源在于临界资源。

临界资源是在同一个时刻只允许有限个(通常只有一个) 进程可以访问(读) 或修改(写)的资源, 通常包括硬件资源(处理器、 内存、 存储器及其他外围设备等) 和软件资源(共享代码段、 共享结构和变量等)。访问临界资源的代码叫做临界区, 临界区本身也会成为临界资源。

信号量是用来解决进程间的同步与互斥问题的一种进程间通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待列, 以及对信号量进行的两个原子操作(PV操作)。 其中信号量对应于某一种资源, 取一个非负的整型值。 信号量值指的是当前可用的该资源的数量, 若等于 0 则意味着目前没有可用的资源。PV 原子操作的具体定义如下。

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

常见的使用信号量访问临界区的伪代码如下
 

{
/* 设 R 为某种资源, S 为资源 R 的信号量 */
INIT_VAL(S); /* 对信号量 S 进行初始化 */
非临界区;
P(S); /* 进行 P 操作 */
临界区(使用资源 R) ; /* 只有有限个(通常只有一个) 进程被允许进入该区 */
V(S); /* 进行 V 操作 */
非临界区;
}

最简单的信号量只能取 0 和 1 两种值, 这种信号量叫做二维信号量。 这里主要讨论二维信号量。 二维信号量的应用比较容易扩展到使用多维信号量的情况。


2 信号量编程

2.1  函数说明
在 Linux 系统中, 使用信号量通常分为以下几个步骤:
(1) 创建信号量或获得在系统中已存在的信号量, 此时需要调用 semget()函数。 不同进程通过使用同一个信号量键值来获得同一个信号量。
(2) 初始化信号量, 此时使用 semctl()函数的 SETVAL 操作。 当使用二维信号量时, 通常将信号量初始化为 1。
(3) 进行信号量的 PV 操作, 此时调用 semop()函数。 这一步是实现进程间的同步和互斥的核心工作部分。
(4)如果不需要信号量, 则从系统中删除它, 此时使用 semctl ()函数的 IPC_RMID 操作。需要注意的是, 在程序中不应该出现对已经被删除的信号量的操作。

注意:创建了一个信号就会在系统中一直存在,直到我们去删除

1.创建信号量的函数

这里列举了 semget()函数的语法要点。

key:如果为IPC_PRIVATE,则表示创建信号量,如果key不为IPC_PRIVATE且key所对应的信号量已经存在,则返回信号量

nsems: 需要创建的信号量数目, 通常取值为 1

semflg:

如果semget用于创建新的信号量,则的值为IPC_CREAT  |  perm,perm为新创建信号量的存取权限

如果semget用于获得已经存在的信号量则semflg的值为0

实例代码:

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>

int main(void)
{
	int semid = -1;
	semid = semget(222, 1, 0666 | IPC_CREAT);
	if(semid >= 0)
	{
		printf("新建信号的标识符,值为 [%d]\n", semid);		
	}else
	{
		perror("semget");
	}

	return 0;
}

结果:

# ./a.out
新建信号的标识符,值为 [65538]

注意:

创建的标识符的值,不等于在semget函数中给定的key值

 

2.获得和释放信号量的函数:

int semop(int semid, struct sembuf *sops, size_t nsops)

semid semget()函数返回的信号量标识符
sops: 指向信号量操作数组, 一个数组包括以下成员。
nsops: 操作数组 sops 中的操作个数(元素数目) , 通常取值为 1(一个操作)

struct sembuf{ 
short sem_num; /* 信号量编号, 使用单个信号量时, 通常取值为 0 */
short sem_op;/* 信号量操作: 取值为-1 则表示 P 操作, 取值为+1 则表示 V 操作 */
short sem_flg;/* 通常设置为 SEM_UNDO。 这样在进程没释放信号量而退出时, 系统自动释放该进程中未释放的信号量 */
}

sem_op  >  0:那么操作将sem_op加入到信号量的值中,并唤醒等待信号增加的进程

sem_op  ==  0:当信号量的值是0时,函数返回,否则阻塞直到信号量的值为0

sem_op  <  0:则判断   当前信号量   +  sem_op   的值

  1.                   如果为0,唤醒等待信号量为0的进程,
  2.                    如果小于0,调用该函数的进程阻塞,
  3.                    如果大于0,那么信号量减去这个值并返回

获取信号量的代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>

int main(void)
{
	int semid = -1;
	struct sembuf sb;
	semid = semget(222, 1, 0);
	if(semid >= 0)
	{
		sb.sem_num = 0;
		sb.sem_op = -1;  //-1表示获取信号量
		sb.sem_flg = 0;
		printf("值为 [%d]\n", semid);
		if(semop(semid, &sb, 1) == -1)  //如果没得到信号量会阻塞在这
		{
			perror("semop");
			exit(-1);
		}
		printf("值为 [%d]\n", semid);
		
	}
	return 0;
}

释放信号量的代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>

int main(void)
{
	int semid = -1;
	struct sembuf sb;
	semid = semget(222, 1, 0);
	if(semid >= 0)
	{
		sb.sem_num = 0;
		sb.sem_op = 1;  //大于0,表示释放信号量
		sb.sem_flg = 0;
		printf("值为 [%d]\n", semid);
		if(semop(semid, &sb, 1) == -1)
		{
			perror("semop");
			exit(-1);
		}
		printf("值为 [%d]\n", semid);
		
	}
	return 0;
}

结果:

终端1运行获取信号量的代码,如果它没有获取到信号量,就会一直阻塞,直到终端2释放信号量

3.控制信号量操作的函数:

删除信号量实例代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>

int main(void)
{
	int semid = -1;
	int ret = -1;
	struct sembuf sb;
	semid = semget(222, 1, 0);
	if(semid >= 0)
	{
		ret = semctl(semid, 0, IPC_RMID);
		if(ret != -1)
		{
			printf("删除值为 [%d] 信号成功\n", semid);
		}else
		{
			perror("semctl");
		}			
	}
	return 0;
}

完整信号量处理代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };

/* 信号量初始化(赋值) 函数 */
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;
}
int main(void)
{
	pid_t result;
	int sem_id;
	sem_id = semget(ftok(".", 'a'), 1, 0666|IPC_CREAT); /* 创建一个信号量 */
	init_sem(sem_id, 0);
	/* 调用 fork()函数 */
	result = fork();
	if(result == -1)
	{
		perror("Fork\n");
	}else if (result == 0) /* 返回值为 0 代表子进程 */
	{
		sleep(5);
		printf("子进程一下\n");
		sem_v(sem_id);
		sem_p(sem_id);
		sem_v(sem_id);
		printf("子进程再一下\n");
	}else /* 返回值大于 0 代表父进程 */
	{
		sem_p(sem_id);
		printf("父进程一下\n");
		sem_v(sem_id);
		sem_p(sem_id);
		sem_v(sem_id);
		printf("父进程再一下\n");
		del_sem(sem_id);
	}
	exit(0);
}

结果:

# ./a.out 
子进程一下
父进程一下
子进程再一下
父进程再一下

总结:

1.信号量类似于线程中的互斥锁,(一个线程上锁后,另一个线程要等待另一个进程解锁后才能继续运行)

2.定义了一个信号量后,会一直在系统中存在

3.信号量比互斥锁复杂太多了

4.semctl函数中的union semun需要自己定义

5.有一点没搞明白,就是如果多个进程等待一个信号量,该如何处理

猜你喜欢

转载自blog.csdn.net/qq_40732350/article/details/82830013
今日推荐