Linux 基础入门操作 第九章 进程之间通讯信号量

system V 的进程间通信 之信号量

9.3 信号量

信号量与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制。相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。信号量有以下两种类型:

二值信号量:最简单的信号量形式,信号灯的值只能取 0 或 1,类似于互斥锁。

计算信号量:信号量的值可以取任意非负值(当然受内核本身的约束)

信号量只能进行两种操作等待和发送信号,即 P(sv)和 V(sv),他们的行为是这样的:

P(sv):如果 sv 的值大于零,就给它减 1;如果它的值为零,就挂起该进程的执行

V(sv):如果有其他进程因等待 sv 而被挂起,就让它恢复运行,如果没有进程因等待 sv 而挂起,就给它加 1.

9.3.1 创建函数

它的作用是创建一个新信号量或取得一个已有信号量:

c 复制代码
sys/sem.h
int semget(key_t key, int num_sems, int sem_flags);

第一个参数 key 是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用 semget 函数并提供一个键,再由系统生成一个相应的信号标识符(semget 函数的返回值),只有 semget 函数才直接使用信号量键,所有其他的信号量函数使用由 semget 函数返回的信号量标识符。如果多个程序使用相同的 key 值,key 将负责协调工作。

第二个参数 num_sems 指定需要的信号量数目,它的值几乎总是 1。

第三个参数 sem_flags 是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT 做按位或操作。设置了 IPC_CREAT 标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而 IPC_CREAT | IPC_EXCL 则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

semget 函数成功返回一个相应信号标识符(非零),失败返回-1.

9.3.2 修改信号值

c 复制代码
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
c 复制代码
sem_id 是由 semget 返回的信号量标识符,sembuf 结构的定义如下:
struct sembuf{
	short sem_num;//除非使用一组信号量,否则它为 0
	short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即 P(等待)操作,
//一个是+1,即 V(发送信号)操作。
	short sem_flg;//通常为 SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};

9.3.3 控制信号量信息

semctl 函数

c 复制代码
int semctl(int sem_id, int sem_num, int command, ...);
如果有第四个参数,它通常是一个 union semum 结构,定义如下:
union semun{
	int val;
	struct semid_ds *buf;
	unsigned short *arry;
};

前两个参数与前面一个函数中的一样,command 通常是下面两个值中的其中一个

SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过 union semun 中的 val 成员设置,其作用是在信号量第一次使用前对它进行设置。IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。

源文件为 seml.c,代码如下:

在 main 函数中调用 semget 来创建一个信号量,该函数将返回一个信号量标识符,保存于全局变量sem_id 中,然后以后的函数就使用这个标识符来访问信号量。

c 复制代码
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>
union semun
{
	int val;
	struct semid_ds *buf;
	unsigned short *arry;
};
static int sem_id = 0;
static int set_semvalue();
static void del_semvalue();
static int semaphore_p();
static int semaphore_v();
int main(int argc, char *argv[])
{
	char message = 'X';
	int i = 0;
	//创建信号量
	sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
	if(argc > 1)
	{
		//程序第一次被调用,初始化信号量
		if(!set_semvalue())
		{
		fprintf(stderr, "Failed to initialize semaphore\n");
		exit(EXIT_FAILURE);
		}
	//设置要输出到屏幕中的信息,即其参数的第一个字符
		message = argv[1][0];
		sleep(2);
	}
	for(i = 0; i < 10; ++i)
	{
		//进入临界区
		if(!semaphore_p())
		exit(EXIT_FAILURE);
		//向屏幕中输出数据
		printf("%c", message);
		//清理缓冲区,然后休眠随机时间
		fflush(stdout);
		sleep(rand() % 3);
		//离开临界区前再一次向屏幕输出数据
		printf("%c", message);
		fflush(stdout);
		//离开临界区,休眠随机时间后继续循环
		if(!semaphore_v())
		exit(EXIT_FAILURE);
		sleep(rand() % 2);
	}
	sleep(10);
	printf("\n%d - finished\n", getpid());
	if(argc > 1)
	{
	//如果程序是第一次被调用,则在退出前删除信号量
		sleep(3);
		del_semvalue();
	}
	exit(EXIT_SUCCESS);
}

static int set_semvalue()
{
	//用于初始化信号量,在使用信号量前必须这样做
	union semun sem_union;
	sem_union.val = 1;
	if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
	return 0;
	return 1;
}
static void del_semvalue()
{
	//删除信号量
	union semun sem_union;
	if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
	fprintf(stderr, "Failed to delete semaphore\n");
}

static int semaphore_p()
{
//对信号量做减 1 操作,即等待 P(sv)
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;
}

static int semaphore_v()
{
	//这是一个释放操作,它使信号量变为可用,即发送信号 V(sv)
	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;
}

同时运行一个程序的两个实例,注意第一次运行时,要加上一个字符作为参数,例如本例中的字符'O',它用于区分是否为第一次调用,同时这个字符输出到屏幕中。因为每个程序都在其进入临界区后和离开临界区前打印一个字符,所以每个字符都应该成对出现,正如你看到的上图的输出那样。在main 函数中循环中我们可以看到,每次进程要访问 stdout(标准输出),即要输出字符时,每次都要检查信

号量是否可用(即 stdout 有没有正在被其他进程使用)。所以,当一个进程 A 在调用函数 semaphore_p 进入了临界区,输出字符后,调用 sleep 时,另一个进程 B 可能想访问 stdout,但是信号量的 P 请求操作失败,只能挂起自己的执行,当进程 A 调用函数 semaphore_v 离开了临界区,进程 B 马上被恢复执行。然后进程A 和进程 B 就这样一直循环了 10 次。

相关推荐
Kusunoki_D15 分钟前
速查 Linux 常用指令 II
linux·运维·服务器
xmweisi0236 分钟前
Ansible内置模块之 group
linux·运维·ansible·rhce·rhca·红帽认证
小猪写代码43 分钟前
Ubuntu 系统默认已安装 python,此处只需添加一个超链接即可
linux·python·ubuntu
孤寂大仙v1 小时前
【Linux笔记】——Linux线程理解与分页存储的奥秘
linux·运维·笔记
有谁看见我的剑了?2 小时前
ubuntu 22.04 wifi网卡配置地址上网
linux·运维·ubuntu
码农新猿类2 小时前
Ubuntu摄像头打开失败
linux·运维·ubuntu
PWRJOY3 小时前
Ubuntu磁盘空间分析:du命令及常用组合
linux·运维·ubuntu
ASDyushui3 小时前
Shell 编程之正则表达式与文本处理器
linux·正则表达式
zuozewei3 小时前
安全扫描之 Linux 杀毒软件 Clamav 安装
linux·运维·安全
wangchen_03 小时前
linux-信号保存和处理
linux·运维·服务器