信号量

信号量(semaphore)和信号只有一字之差,却是不同的概念,信号量与之前介绍的IPC不同,它是一个计数器,用于实现进程间的互斥于同步

本文参考:

Linux 的信号量_linux 信号量_行孤、的博客-CSDN博客

【Linux】Linux的信号量集_Yngz_Miao的博客-CSDN博客

Linux进程间通信(九)——信号量_linux 信号量_天山老妖的博客-CSDN博客

信号量函数(semget、semop、semctl)及其范例_semget函数_guoping16的博客-CSDN博客

概念

信号量本质上是一个计数器,用于协调多个进程(但不包括父子进程)对共享数据对象的读/写。它不以传输数据为目的,主要是用来保护临界资源(一次仅允许一个进程使用的资源称为临界资源,很多物理设备都属于临界资源,比如打印机,磁带机),保证临界资源在一个时刻只有一个进程独享。

信号量是描述某一种资源是否可用的变量,信号量的值表示当前可用的资源的数量,若信号量的值等于0则意味着目前没有可用的资源

信号量是一个特殊的变量,只允许进程对它进行等待信号(P)和发送信号(V)操作

  • P操作(拿):等待。如果sv大于0,减小sv。如果sv为0,挂起这个进程的执行
  • V操作(放):发送信号。如果有进程被挂起等待sv,使其恢复执行。如果没有进行被挂起等待sv,增加sv

最简单的信号量是只能取0,1的信号量,这也是信号量最常见的一种形式,叫做二值信号量,而可以取多个正整数的信号量称为通用信号量。 

信号量集

就是由多个信号量组成的一个数组。作为一个整体信号量集中的所有信号量使用同一个等待队列。Linux的信号量集为进程请求多个资源创造了条件。Linux规定,当进程的一个操作需要多个共享资源时,如果只成功获得了其中的部分资源,那么这个请求即告失败,进程必须立即释放所有已获得资源,以防止形成死锁。

 

相关API

Linux下的信号量函数都是在通用的信号量数组(信号量集)上进行操作,而不是在一个单一的二值信号上进行操作。

semget函数

获取或创建信号量组

需要添加的库

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

函数原型

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

函数参数

  • key:IPC键值
  • nsems:信号量集中信号量的个数
  • semflag:取以下值中的一个,若不取0,还要或上新建信号量集的权限
  • 0(取信号量集标识符,若不存在则函数会报错)
  • IPC_CREAT:不存在则创建信号量集
  • IPC_CREAT|IPC_EXCL:不存在则创建信号量集,若存在会报错
  • 返回值:成功返回信号量集ID,失败返回-1

semop函数

对信号量组进行操作,改变信号量的值,也就是对信号量进行P或V操作

需要添加的库

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

函数原型

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

函数参数

  • semid:信号量集ID,semget的返回值
  • sops:一个结构体:
struct sembuf
{
  short sem_num;   // 信号量集的个数,单个信号量设置为0。
  short sem_op;    // 信号量在本次操作中需要改变的数据:-1-等待操作;1-发送操作。
  short sem_flg;   // 把此标志设置为SEM_UNDO,操作系统将跟踪这个信号量。如果设置为IPC_NOWAIT则会直接返回
                   // 如果当前进程退出时没有释放信号量,操作系统将释放信号量,避免资源被死锁。
};
  • nsops:操作信号量的个数,即上一个参数sops结构变量的个数(只有一个就写1)
  • 返回值:成功返回0,失败返回-1

使用举例

封装P操作:
void pGet(int semid)
{
        struct sembuf sops;
        sops.sem_num = 0;
        sops.sem_op = -1;
        sops.sem_flg = SEM_UNDO;

        if(semop(semid,&sops,1) == -1){
                printf("pGet error!\n");
        }else{
                printf("pGet success\n");
        }

}
封装V操作:
void vPut(int semid)
{
        struct sembuf sops;
        sops.sem_num = 0;
        sops.sem_op = 1;
        sops.sem_flg = SEM_UNDO;

        if(semop(semid,&sops,1) == -1){
                printf("vPut error!\n");
        }else{
                printf("vPut success\n");
        }

}

semctl函数

控制信号量(常用于设置信号量的初始值和销毁信号量)

需要添加的库

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

函数原型

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

函数参数

  • semid:信号量集ID,semget的返回值
  • semnum:要操作的目标信号量,第一个就是0,第二个就是1....(只有一个就写0)
  • cmd:对目标信号量采取的命令,取以下值中的一个:

IPC_STAT

IPC_SET

IPC_RMID:销毁信号量,此时没有第四个参数

IPC_INFO

SEM_INFO

SEM_STAT

GETALL

GETNCNT

GETPID

GETVAL

GETZCNT

SETALL

SETVAL:设置信号量的初始值,此时有第四个参数

  • 第四个参数:根据cmd设置的情况决定是否存在这个参数,如果存在,那这个参数的类型就是一个名为semun的联合体:
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) */
};

使用举例

假设我要操作semid是1234的信号量集中的第一个信号量,将这个信号量的值初始化为1:

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 main()
{
    union semun mysemun_1;
    mysemun_1.val = 1; 
    semctl(1234,0,SETVAL,mysemun);

    return 0;
}

回顾联合体的知识:和结构体不同,联合体的成员共用存储空间,所以对多个成员的赋值可能会导致覆盖,产生问题,所以此处只需要对val进行赋值,并不用为了防止野指针而对其他成员赋值

实操演示

需求:创建并初始化一个只有一个信号量的信号量集,然后fork一下,之后通过操控信号量让子进程先打印一句话,打印完才允许父进程打印另一句话

sem1.c:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.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) */
};

void pGet(int semid)
{
	struct sembuf sops;
	sops.sem_num = 0;
	sops.sem_op = -1;
	sops.sem_flg = SEM_UNDO;

	if(semop(semid,&sops,1) == -1){
		printf("pGet error!\n");
	}else{
		printf("pGet success\n");
	}
	
}


void vPut(int semid)
{
        struct sembuf sops;
        sops.sem_num = 0;
        sops.sem_op = 1;
        sops.sem_flg = SEM_UNDO;

        if(semop(semid,&sops,1) == -1){
                printf("vPut error!\n");
        }else{
                printf("vPut success\n");
        }

}



int main()
{
	key_t key;
	key = ftok(".",2);
	int semid;
	pid_t pid;

	semid = semget(key, 1, IPC_CREAT|0666);
	if(semid == -1){
		printf("semget error\n");
		return 1;
	}else{
		printf("get semid = %d\n",semid);
	}

	union semun mysemun_1;
	mysemun_1.val = 0; //初始化为无锁的状态
	semctl(semid,0,SETVAL,mysemun_1);

	pid = fork();
	if(pid>0){
		pGet(semid); //尝试获取锁
		printf("this is father\n");
		semctl(semid,0,IPC_RMID); //销毁信号量
	}else if(pid == 0){
		printf("this is son\n");
		vPut(semid); //放入锁
	}else{
		printf("fork error\n");
		return 1;
	}

	return 0;
}

实现效果:

由于一开始初始化信号的时候将信号量设置为了0,也就是无锁状态:

而父进程首先是想拿锁,所以必然被阻塞,只有等子进程先打印一句话然后将锁放进去之后,父进程才可以拿到锁并打印自己的那句话。

且此时父进程不需要收集子进程退出状态,因为父子进程都是打了一句话就都返回退出了。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/81366.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux RabbitMQ-3.8.5 安装

软件版本操作系统CentOS Linux release 7.9.2009erlangerlang-23.0.2-1.el7.x86_64rabbitMQrabbitmq-server-3.8.5-1.el7 RabbitMQ的安装首先需要安装Erlang,因为它是基于Erlang的VM运行的。 RabbitMQ安装需要依赖:socat和logrotate&#xff0c;logrotate操作系统已经存在了&…

【数据结构】吃透单链表!!!(详细解析~)

目录 前言&#xff1a;一.顺序表的缺陷 && 介绍链表1.顺序表的缺陷2.介绍链表&#xff08;1&#xff09;链表的概念&#xff08;2&#xff09;链表的结构&#xff08;3&#xff09;链表的功能 二.单链表的实现1.创建节点的结构2.头文件函数的声明3.函数的实现&#xff…

33.Netty源码之读写数据

highlight: arduino-light 写数据 写数据的三种方式 md 快递场景(包裹) Netty 写数据(数据) 揽收到仓库 write&#xff1a;写到一个 buffer 从仓库发货 flush: 把 buffer 里的数据发送出去 揽收到仓库并立马发货 (加急件) writeAndFlush&#xff1a;写到 buffer&#xff0c;立马…

搜狗拼音暂用了VSCode及微信小程序开发者工具快捷键Ctrl + Shit + K 搜狗拼音截图快捷键

修改搜狗拼音的快捷键 右键--更多设置--属性设置--按键--系统功能快捷键--系统功能快捷键设置--取消Ctrl Shit K的勾选--勾选截屏并设置为Ctrl Shit A 微信开发者工具设置快捷键 右键--Command Palette--删除行 微信开发者工具快捷键 删除行&#xff1a;Ctrl Shit K 或…

集群、负载均衡集群、高可用集群简介,LVS工作结构、工作模式、调度算法和haproxy/nginx模式拓扑介绍

一.集群的定义 1.定义 2.分类 &#xff08;1&#xff09;负载均衡集群&#xff08;LBC/LB&#xff09; &#xff08;2&#xff09;高可用集群&#xff08;HAC&#xff09; 二.使用集群的意义 1.高性价比和性能比 2.高可用性 3.可伸缩性强 4.持久和透明性高 三.常见的…

什么是单例模式

什么是单例模式 文章目录 什么是单例模式1. 单例(单个的实例)2. 单例模式应用实例3. 饿汉式 VS 懒汉式 1. 单例(单个的实例) 所谓类的单例设计模式&#xff0c;就是采取一定的方法保证在整个的软件系统中&#xff0c;对某个类只能存在一个对象实例&#xff0c;并且该类只提供一…

Windows系统修改域名DNS指向两种方式

一、直接打开对应文件进行修改 1、进入hosts文件目录&#xff1a;C:\Windows\System32\drivers\etc 2、右键打开HOSTS文件进行编辑&#xff0c;将需要对应的域名和IP地址进行配置 编写完成后 Ctrl s 进行保存即可。 二、使用DOS命令进行修改 1、按住键盘win键 r 打开命令…

基于MOEA/D求解电力系统中环境经济调度问题(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

操作符详解下(非常详细)

这里写目录标题 下标访问[ ]、函数调用()[ ]下标引用操作符函数调用操作符 操作符的属性&#xff1a;优先级、结合性优先级结合性 表达式求值整型提升整型提升的意义如何进行整体提升 算术转换问题表达式解析表达式1表达式2表达式3表达式4表达式5 总结 下标访问[ ]、函数调用()…

TiDB 多集群告警监控-中章-融合多集群 Grafana

作者&#xff1a; longzhuquan 原文来源&#xff1a; https://tidb.net/blog/ac730b0f 背景 随着公司XC改造步伐的前进&#xff0c;越来越多的业务选择 TiDB&#xff0c;由于各个业务之间需要物理隔离&#xff0c;避免不了的 TiDB 集群数量越来越多。虽然每套 TiDB 集群均有…

excel逻辑函数篇2

1、IF(logical_test,[value_if_true],[value_if_false])&#xff1a;判断是否满足某个条件&#xff0c;如果满足返回一个值&#xff0c;如果不满足则返回另一个值 if(条件,条件成立返回的值,条件不成立返回的值) 2、IFS(logical_test1,value_if_true1,…)&#xff1a;检查是否…

【深度学习 | 数据可视化】 视觉展示分类边界: Perceptron模型可视化iris数据集的决策边界

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

基于Three.js的WebXR渲染入门

1、Three.js 渲染管线快速概览 我不会花太多时间讨论 Three.JS 渲染管道的工作原理,因为它在互联网上有详细记录(例如,此链接)。 我将在下图中列出基础知识,以便更容易理解各个部分的去向。 2、WebXR 设备 API 入门 在我们深入了解 WebXR API 本身之前,您应该知道 WebX…

AutoSAR系列讲解(深入篇)13.7-Mcal Adc配置(上)

目录 一、AdcGeneral 二、AdcSafety 三、AdcConfigSet 在之前的章节中,咱们在Port的配置中讲解了工具的初步使用与一些技巧;在Dio的配置中讲解了生成的代码的内容;在mcu的配置里讲解了外部一些 第三方简便工具的使用。这一次咱们配合ADC模块,就详细的讲讲每个配置项的作…

STM32F407使用Helix库软解MP3并通过DAC输出,最精简的STM32+SD卡实现MP3播放器

只用STM32单片机SD卡耳机插座&#xff0c;实现播放MP3播放器&#xff01; 看过很多STM32软解MP3的方案&#xff0c;即不通过类似VS1053之类的解码器芯片&#xff0c;直接用STM32和软件库解码MP3文件&#xff0c;通常使用了labmad或者Helix解码库实现&#xff0c;Helix相对labm…

Kubernetes网络模型

Kubernetes 用来在集群上运行分布式系统。分布式系统的本质使得网络组件在 Kubernetes 中是至关重要也不可或缺的。理解 Kubernetes 的网络模型可以帮助你更好的在 Kubernetes 上运行、监控、诊断你的应用程序。 网络是一个很宽泛的领域&#xff0c;其中有许多成熟的技术。对于…

学习总结(TAT)

项目写完了&#xff0c;来写一个总的总结啦&#xff1a; 1.后期错误 Connection&#xff0c;Statement&#xff0c;Prestatement&#xff0c;ResultSet都要记得关闭接口&#xff1b;&#xff08;一定要按顺序关闭&#xff09;&#xff1b; 在写群聊的时候写数据库名的时候不要…

Spring项目使用Redis限制用户登录失败的次数以及暂时锁定用户登录权限

文章目录 背景环境代码实现0. 项目结构图&#xff08;供参考&#xff09;1. 数据库中的表&#xff08;供参考&#xff09;2. 依赖&#xff08;pom.xml&#xff09;3. 配置文件&#xff08;application.yml&#xff09;4. 配置文件&#xff08;application-dev.yml&#xff09;5…

Windows10上VS2022单步调试FFmpeg 4.2源码

之前在 https://blog.csdn.net/fengbingchun/article/details/103735560 介绍过通过VS2017单步调试FFmpeg源码的方法&#xff0c;这里在Windows10上通过VS2022单步调试FFmpeg 4.2的方法&#xff1a;基于GitHub上ShiftMediaProject/FFmpeg项目&#xff0c;下面对编译过程进行说明…

.netcore grpc身份验证和授权

一、鉴权和授权&#xff08;grpc专栏结束后会开启鉴权授权专栏欢迎大家关注&#xff09; 权限认证这里使用IdentityServer4配合JWT进行认证通过AddAuthentication和AddAuthorization方法进行鉴权授权注入&#xff1b;通过UseAuthentication和UseAuthorization启用鉴权授权增加…