Linux:进程间通信之信号量

news2025/4/21 19:29:55

system V的进程间通信除了共享内存,还有消息队列和信号量

IPC(进程间通信的简称)

消息队列

  • 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法

  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值

  • 特性方面

  • IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

总之消息队列比较落伍,就是简单提一下

来看一下信号量

信号量

要了解信号量是什么首先我们要了解什么是临界资源

临界资源

能被多个执行流(进程、线程或协程)同时访问的资源就是临界资源,简单理解就是可能会被争抢的资源就是临界资源;例如多进程启动后,同时向显示器打印,显示器就是临界资源。

在进行进程间通信的时候,管道、共享内存、消息队列都是临界资源,那么应该新的问题产生了:临界资源你能用,我能用,但是大家不能一起用(当然了)

所以我们就要进行临界资源的互斥使用,这种进程竞争使用这些资源的关系称为互斥

互斥

互斥:任何时刻只有一个执行流在访问共享资源

在进程中涉及到互斥资源的程序段叫临界区

举个例子,client.c的这部分和server.c的这部分是他们之间的临界区:

client.c:

server.c:

或者多个进程访问显示器的时候,显示器是临界i这样,进程中只有printf区域在访问显示器,printf就叫临界区

深入理解信号量

比如说你要看陶吉吉的演唱会,买到票那么这个座位就是你的;妹买到就不是你的

演唱会的座位票就是临界资源,买票的本质就是对临界资源的预定机制

假如你是米团狗眼的后端程序员,那么你肯定要保证卖出的票不能比座位多吧

所以只能在有票的时候,票才可以被卖,没票的时候,就不能被卖(听上去像废话)

你怎么知道票还有没有?此时就需要引入信号量来维护票的数量,每有一个人买票count--,每有一个人退票count++,但是count本身也是应该临界资源(因为买票和退票判断两端都要看见count是不是>0),count也是临界资源,临界资源怎么保护临界资源?

count多一个就++,少一个就--;如果我们的信号量也是临界资源,那么我们必须保证对count的++和--得是安全的(不会出现负数的情况),怎么保证安全?保证对信号量的操作是原子性的

什么是原子性?

没有中间过程,只有1和0:一件事要么彻底不做,要么就做完

非原子性:做某件事情的时候有中间过程就叫非原子性

我们的count在++和--的时候是不是原子性的呢?

我们的count++和--的时候,要有一个从内存读到CPU的动作,所以本身不是原子性的

还有一个问题,既然我们的信号量可以做++和--,那么他是不是也得被两边看到才能进行对应的++和--?

所以他也得像我之前学的共享内存和管道一样,被两个或多个进程看见

这个信号量他不能做全局变量,因为全局变量不能被多个进程看见

所以一个优秀的信号量应该保证自己的安全(原子性)+可以被别的进程看见

我们的信号量就可以解决生产者-消费者、读-写这种的互斥问题

什么是同步?

这里的同步并不是传统意义的同步。例如父进程获得的数据需要写到管道里,子进程拿走数据做计算并输出是一个正常的顺序的话,那么按照这个顺序的才是同步;也就是说进程之间按照彼此的依赖关系运行才叫同步

实现信号量的操作

信号量有两种状态

当count--的时候叫P(等待):如果信号量的值大于0,就可以进行正常访问资源,并且进行--;如果等于0,就要进行阻塞

当count++的时候叫V(释放):如果信号量绑定的进程或线程完成了一次对共享资源的访问,信号量++

申请信号量的接口:

semget()

  • key:用于标识信号量集的键值。通常可以使用 ftok 函数生成。

  • nsems:指定将创建或访问的信号量集中的信号量数量。

  • semflg:标志参数,用于指定信号量集的创建和访问方式。

fotk()

设置获取信号量的状态、设置信号量的值、删除信号量:

semctl()

  • semid:目标信号量集的标识符。

  • semnum:用于指定要操作的具体信号量的索引。对于整个信号量集的操作,此参数一般设置为 0。

  • cmd:指定要执行的控制操作。

  • semctl() 函数的第四个参数为可选参数,根据不同的 cmd 命令,它的类型和用法可能不同。

  • 常用的 cmd 控制命令如下:

  • IPC_STAT:获取信号量集的状态信息,将结果存储在 semun 结构体中。

  • SETVAL:设置单个信号量的值。第四个参数为一个新的值,可以是整数或结构体 semun

  • GETVAL:获取单个信号量的值。

  • IPC_RMID:删除整个信号量集。

插播一下,我抢到陶吉吉的演唱会票了

sem.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>        
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sem.h>
#define MY_FIFO "./fifo"
#include<sys/shm.h>
#define PATH_NAME "./"
#define PROJ_ID 0X6667
#define SIZE 4097

union semun{
    int val;
    struct semid_ds *buf;
};//联合体,联合体内的成员共享同一块空间

int main(){
    //创建key
    int key=ftok(PATH_NAME,PROJ_ID);
    if(key==-1){
        perror("ftok");
        return 1;
    }
    //创建信号量
    int semid= semget(key,3,IPC_EXCL|IPC_CREAT);
    if(semid<0){
        perror("semget");
        return -1;
     }
    //设置信号量的值
    union semun argv;
    argv.val=1;
    int result=semctl(semid,0,SETVAL,argv);
    if(result<0){
        perror("semctl");
        return -1;
    }
    //获取信号量的值
    int val=semctl(semid,0,GETVAL);
    if(val<0){
        perror("semctl val");
        return -1;
    }
    printf("val==%d\n",val);
    //删除信号量
     result = semctl(semid, 0, IPC_RMID);
    if (result == -1) {
        perror("semctl del");
        return -1;
    }

    printf("key==%d\nsemid==%d\n",key,semid);
   
    
    return 0;
}

一开始写成这样发现提示:semctl: Permission denied

于是给semctl加上权限0666:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>        
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sem.h>
#define MY_FIFO "./fifo"
#include<sys/shm.h>
#define PATH_NAME "./"
#define PROJ_ID 0X6667
#define SIZE 4097

union semun{
    int val;
    struct semid_ds *buf;
};//联合体,联合体内的成员共享同一块空间

int main(){
    //创建key
    int key=ftok(PATH_NAME,PROJ_ID);
    if(key==-1){
        perror("ftok");
        return 1;
    }
    //创建信号量
    int semid= semget(key,3,IPC_EXCL|IPC_CREAT|0666);
    if(semid<0){
        perror("semget");
        return -1;
     }
    //设置信号量的值
    union semun argv;
    argv.val=1;
    int result=semctl(semid,0,SETVAL,argv);
    if(result<0){
        perror("semctl");
        return -1;
    }
    //获取信号量的值
    int val=semctl(semid,0,GETVAL);
    if(val<0){
        perror("semctl val");
        return -1;
    }
    printf("val==%d\n",val);
    //删除信号量
     result = semctl(semid, 0, IPC_RMID);
    if (result == -1) {
        perror("semctl del");
        return -1;
    }

    printf("key==%d\nsemid==%d\n",key,semid);
   
    
    return 0;
}

这样就好啦

我们也可以用命令查看我们的信号状态:

ipcs -s

有信号量的时候是这样:

删除是:

ipcrm -s semid

 对信号集的进行等待和释放操作(实现PV操作):

semop()

  • semid:目标信号量集的标识符。

  • sops:指向 sembuf 结构体数组的指针,每个结构体表示一个信号量操作。

  • nsops:指定要执行的信号量操作的数量。

sem.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>        
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sem.h>
#define MY_FIFO "./fifo"
#include<sys/shm.h>
#define PATH_NAME "./"
#define PROJ_ID 0X6667
#define SIZE 4097

union semun{
    int val;
    struct semid_ds *buf;
};//联合体,联合体内的成员共享同一块空间
struct sembuf{
    unsigned short sem_num;//信号量的索引
    short sem_op;//信号量操作,<0则表示P,>0则表示V,0表示等待
    short sem_flg;//操作标志,IPC_NOWAIT(非阻塞)和SEM_UNDO(系统恢复时撤销操作)
};



int main(){
    //创建key
    int key=ftok(PATH_NAME,PROJ_ID);
    if(key==-1){
        perror("ftok");
        return 1;
    }
    //创建信号量
    int semid= semget(key,3,IPC_EXCL|IPC_CREAT|0666);
    if(semid<0){
        perror("semget");
        return -1;
     }
    //设置信号量的值
    union semun argv;
    argv.val=1;
    int result=semctl(semid,0,SETVAL,argv);
    if(result<0){
        perror("semctl");
        return -1;
    }
    //等待信号量的值为0
    struct sembuf sop;
    sop.sem_num=0;
    sop.sem_op=0;
    sop.sem_flg=0;
    result=semop(semid,&sop,1);
    if(result<0){
        perror("semop");
        return -1;
    }
    printf("Semaphore operation complete.\n");
    //对信号量进行P操作
    sop.sem_op=-1;
    result=semop(semid,&sop,1);
      if(result<0){
        perror("semop");
        return -1;
    }
    //进行V操作
    sop.sem_op=1;
    result=sempo(semid,&sop,1);
      if(result<0){
        perror("semop");
        return -1;
    }
    //删除信号量
     result = semctl(semid, 0, IPC_RMID);
    if (result == -1) {
        perror("semctl del");
        return -1;
    }
    printf("key==%d\nsemid==%d\n",key,semid); 
    return 0;
}

我们现在学了这么多通信方式,那么操作系统是怎么管理这些文件的?

通过结构体:

struct ipc_id_ary
{
    int size;
    struct kern_ipc_pern* p[0];
    ...
}

我们之前学了shmid就是数组的下标,那么把它们的第一个元素都存到struct kern_ipc_perm *XXX[n],就意味着我们把IPC的资源统一管理了

来写一个利用信号量的完整流程:

sem.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>        
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sem.h>
#define MY_FIFO "./fifo"
#include<sys/shm.h>
#include <errno.h>
#define PATH_NAME "./"
#define PROJ_ID 0X6667

union semun{
    int val;
    struct semid_ds *buf;
};//联合体,联合体内的成员共享同一块空间
/*struct sembuf{
    unsigned short sem_num;//信号量的索引
    short sem_op;//信号量操作,<0则表示P,>0则表示V,0表示等待
    short sem_flg;//操作标志,IPC_NOWAIT(非阻塞)和SEM_UNDO(系统恢复时撤销操作)
};
*/
void mysemop(int num,int op,int semid){
    struct sembuf buf;
    buf.sem_num=num;
    buf.sem_op=op;
    buf.sem_flg=0;
    semop(semid,&buf,1);
}

int main(){
    //创建key
    int key=ftok(PATH_NAME,PROJ_ID);
    if(key==-1){
        perror("ftok");
        return 1;
    }
    //创建信号量
    int semid= semget(key,2,IPC_EXCL|IPC_CREAT|0666);
    if(semid<0){
        if(errno==17){
            semget(key,2,0666);
        }else{
             perror("semget");
             exit(0);
            }
     }
    printf("key==%d\nsemid==%d\n",key,semid);
    //设置信号量的值
    union semun sem;
    sem.val=0;
    int result=semctl(semid,0,SETVAL,sem);
    if(result<0){
        perror("semctl");
        return -1;
    }
    sem.val=10;
    result=semctl(semid,1,SETVAL,sem);
    if(result<0){
        perror("semctl");
        return -1;
    }
    //对信号量进行PV操作
    int i=6;
    while(i--){
        mysemop(1,-1,semid);
        printf("sem==%d\n",semctl(semid,1,GETVAL));//获取信号量的值
    }
    //删除信号量
    result = semctl(semid, 0, IPC_RMID);
    if (result <0) {
        perror("semctl del");
        return -1;
    }
 
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Electron 使⽤ electron-builder 打包应用

electron有几种打包方式&#xff0c;我使用的是electron-builder。虽然下载依赖的时候让我暴躁&#xff0c;使用起来也很繁琐&#xff0c;但是它能进行很多自定义&#xff0c;打包完成后的体积也要小一些。 安装electron-builder&#xff1a; npm install electron-builder -…

cherry-markdown开源markdown组件详细使用教程

文章目录 前言开发定位目标调研技术方案前提工作量安排数据库表设计实现步骤1、引入依赖2、实现cherry-markdown的vue组件&#xff08;修改上传接口路径&#xff09;3、支持draw.io组件4、支持展示悬浮目录toc前端使用&#xff1a;编辑状态使用cherry-markdown的vue组件前端使用…

图像转3D视差视频:DepthFlow

参看: https://github.com/BrokenSource/DepthFlow 通过深度图实现图像3d效果 安装 https://brokensrc.dev/get/pypi/#installing pip insatll depthflow shaderflow broken-source pianola spectronote turbopipe 使用 1、下载项目 git clone https://github.com/BrokenS…

巧用armbian定时任务控制开发板LED的亮灭

新买了个瑞莎 3E 开发板,号称最小SBC,到了之后简直玩开了花,各种折腾后 安装好armbian系统,各种调优。 不太满意的地方:由于太小的原因,导致两个USBTYPEC的接口距离很近,所以买的OTG转接口如果有点宽的话 会显得特别拥挤。 还有就是每天晚上天黑了之后,卧室里的…

大数据处理从零开始————4.认识HDFS分布式文件系统

1.分布式文件系统HDFS 1.1 认识HDFS 当单台服务器的存储容量和计算性能已经无法处理大文件时&#xff0c;分布式文件系统应运而生。什么是分布式系统&#xff0c;分布式系统是由多个独立的计算机或节点组成的系统&#xff0c;这些计算机通过网络连接&#xff…

Map: 地图

对全国2023年各省市的人口分布情况&#xff0c;做出地图展示效果 参考&#xff1a;Map - Map_base - Document (pyecharts.org) 1、模板 # -*- coding: gbk -*- from pyecharts import options as opts from pyecharts.charts import Map from pyecharts.faker import Faker…

如何安全地大规模部署 GenAI 应用程序

大型语言模型和其他形式的生成式人工智能(GenAI) 的广泛使用带来了许多组织可能没有意识到的安全风险。幸运的是&#xff0c;网络和安全提供商正在寻找方法来应对这些前所未有的威胁。 随着人工智能越来越深入地融入日常业务流程&#xff0c;它面临着泄露专有信息、提供错误答…

交换排序:冒泡排序、递归实现快速排序

目录 冒泡排序 1.冒泡排序的核心思想 2.冒泡排序的思路步骤 3.冒泡排序代码 4.代码分析 5.对冒泡排序的时间复杂度是O(N^2)进行解析 6.冒泡排序的特性总结 递归实现快速排序(二路划分版本) 1.快速排序基本思路 2.代码思路步骤 3.代码实现 4.代码分析 (1)递归终止条…

队列的实现与讲解

一.概念与结构 1.概念 只允许在⼀端进行插⼊数据操作&#xff0c;在另⼀端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出FIFO(First In First Out) ​ 入队列&#xff1a;进⾏插⼊操作的⼀端称为队尾 ​ 出队列&#xff1a;进⾏删除操作的⼀端称为队头 注意&…

美联储巨亏背后的秘密

听说美联储报告称亏损已破2000亿美元&#xff0c;这一数字无疑触动了市场的敏感神经。 亏损的直接原因是美联储在加息周期期间&#xff0c;为了维持短期利率在目标水平&#xff0c;向金融机构支付的利息超过了其持有债券的利息收入。 然而&#xff0c;美联储官员强调&#xff…

学习C语言(23)

整理今天的学习内容 1.文件的概念 使用文件是为了将数据永久化地保存 按照文件功能&#xff0c;在程序设计中一般把文件分成两类&#xff1a; 每个文件都有一个唯一的文字标识&#xff0c;文字标识常被称为文件名&#xff0c;文件名包含文件路径&#xff0c;文件名主干和文件…

如何快速切换电脑的ip地址

在当今的数字化时代&#xff0c;IP地址作为网络身份的重要标识&#xff0c;其重要性日益凸显。无论是出于保护个人隐私的需要&#xff0c;还是为了访问特定的网络服务等&#xff0c;快速切换电脑的IP地址已成为许多用户的迫切需求。本文将为你介绍几种实用的方法&#xff0c;帮…

【Hadoop】改一下core-site.xml和hdfs-site.xml配置就可以访问Web UI

core-site.xml&#xff1a; hdfs-site.xml&#xff1a; 所有的都改为0.0.0.0 就可以访问Web UI 原因&#xff1a; 使用 0.0.0.0 作为绑定地址时&#xff0c;实际会将服务监听在所有可用的网络接口上。这意味着&#xff0c;任何从外部访问的请求都可以通过任何网络适配器连接到…

黑神话:仙童,数据库自动反射魔法棒

黑神话&#xff1a;仙童&#xff0c;数据库自动反射魔法棒 Golang 通用代码生成器仙童发布了最新版本电音仙女尝鲜版十一及其介绍视频&#xff0c;视频请见&#xff1a;https://www.bilibili.com/video/BV1ET4wecEBk/ 此视频介绍了使用最新版的仙童代码生成器&#xff0c;将 …

算法笔记(六)——链表

文章目录 两数相加两两交换链表中的节点重排链表合并 K 个升序链表K个一组翻转链表 技巧: 画图观察指针指向&#xff1b;添加虚拟头节点&#xff1b;可以多定义几个节点&#xff1b;快慢双指针&#xff1b; 常见操作&#xff1a; 定义new head逆序时&#xff0c;头插 ListNode*…

带你深入浅出设计模式:八、适配器模式:代码世界中的万能转换器

此为设计模式第八谈&#xff01; 用总-分-总的结构和生活化的例子给你讲解设计模式&#xff01; 码农不易&#xff0c;各位学者学到东西请点赞收藏支持支持&#xff01; 开始部分&#xff1a; 总&#xff1a;适配器模式主要解决的问题是已有类的接口与所需的接口不匹配的问题…

[Python学习日记-38] Python 中的函数的名称空间

[Python学习日记-38] Python 中的函数的名称空间 简介 名称空间 作用域查找顺序 简介 在前面学习函数的时候我们发现&#xff0c;函数内部也有一个内存空间是用于存储函数自己的一些变量的&#xff0c;及时这个变量名与外部的变量名一样是也没关系&#xff0c;Python 会优先…

CGLib动态代理和JDK动态代理Demo、ASM技术尝鲜

本文主要介绍CGLib和JDK动态代理的使用&#xff0c;不对源码进行深入分析。代码可直接复制使用。 类型 机制 回调方式 适用场景 效率 JDK动态代理 委托机制。代理类和目标类都实现了同样的接口。InvocationHandler持有目标类。代理类委托InvocationHandler去调用目标类原…

令牌主动失效机制范例(利用redis)注释分析

介绍该机制 令牌生成 在需要限流的场景中&#xff0c;系统会根据一定的速率生成令牌&#xff0c;存储在 Redis 中。可以设定每秒生成的令牌数量。 令牌获取 当用户请求时&#xff0c;系统会从 Redis 中获取令牌。可以使用原子性操作&#xff08;如 DECR&#xff09;来确保令牌…

SHAP分析

SHAP分析&#xff08;SHapley Additive exPlanations&#xff09;是一种基于博弈论的解释机器学习模型输出的方法。它提供了一种统一的方式来解释模型的预测结果&#xff0c;量化每个特征对模型预测的贡献&#xff0c;能够为复杂的机器学习模型&#xff08;如随机森林、梯度提升…