Linux:进程间通信之信号量

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;
}

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

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

相关文章

Ray_Tracing_The_Next_Week下

5image Texture Mapping 图像纹理映射 我们之前虽然在交点信息新增了uv属性&#xff0c;但其实并没有使用&#xff0c;而是通过p交点笛卡尔坐标确定瓷砖纹理或者大理石噪声纹理的值 现在通过uv坐标读取图片&#xff0c;通过std_image库stbi_load&#xff08;path&#xff09;…

Kubernetes云原生存储解决方案之 Rook Ceph实践探究

Kubernetes云原生存储解决方案之 Rook Ceph实践探究 除了手动部署独立的 Ceph 集群并配置与Kubernetes进行对接外&#xff0c;Rook Ceph 支持直接在 Kubernetes 集群上部署 Ceph 集群。 通过Rook Ceph云原生存储编排平台&#xff0c;使得 Kubernetes 集群中启用高可用的 Ceph…

【记录】Excel|Excel 打印成 PDF 页数太多怎么办

【记录】Excel&#xff5c;解决 Excel 打印成 PDF 页数过多的问题 文章目录 【记录】Excel&#xff5c;解决 Excel 打印成 PDF 页数过多的问题方法一&#xff1a;调整页边距WPS OfficeMicrosoft Excel 方法二&#xff1a;优化页面布局调整列宽和行高使用“页面布局”视图合并单…

蓝牙定位的MATLAB仿真程序(基于信号强度,平面内的定位,四个蓝牙基站)

这段代码通过RSSI信号强度实现了蓝牙定位,展示了如何使用锚点位置和测量的信号强度来估计未知点的位置。它涵盖了信号衰减模型、距离计算和最小二乘法估计等基本概念。通过图形化输出,用户可以直观地看到真实位置与估计位置的关系。 文章目录 蓝牙定位原理蓝牙定位的原理优缺…

实验5 累加器实验

实验5 累加器实验 6.1实验目的 1、理解累加器的概念和作用。 2、连接运算器、存储器和累加器&#xff0c;熟悉计算机的数据通路。 3、掌握使用微命令执行各种操作的方法。 6.2实验要求 1、做好实验预习&#xff0c;读懂实验电路图&#xff0c;熟悉实验元器件的功能特性和使用…

网络基础 【HTTP】

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux初窥门径⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a; &#x1f4bb;操作环境&#xff1a; CentOS 7.6 华为云远程服务器 &#x1f339;关注我&#x1faf5;带你学习更多Linux知识…

保险丝基础知识

一、简介 保险丝&#xff08;fuse&#xff09;也被称为电流保险丝&#xff0c;它能够在电流异常升高到一定的高度和热度时&#xff0c;自动熔断切断电流&#xff0c;从而保护电路安全运行。 IEC127标准将它定义为“熔断体&#xff08;fuse-link)”。熔断体是由电阻率比较大而熔…

【Linux】进程间关系与守护进程

超出能力之外的事&#xff0c; 如果永远不去做&#xff0c; 那你就永远无法进步。 --- 乌龟大师 《功夫熊猫》--- 进程间关系与守护进程 1 进程组2 会话3 控制终端4 作业控制5 守护进程 1 进程组 之前我们提到了进程的概念&#xff0c; 其实每一个进程除了有一个进程 ID(P…

计算机网络的整体认识---网络协议,网络传输过程

计算机网络背景 网络发展 独立模式: 计算机之间相互独立; 网络互联: 多台计算机连接在一起, 完成数据共享; 局域网LAN: 计算机数量更多了, 通过交换机和路由器连接在一起; 广域网WAN: 将远隔千里的计算机都连在一起;所谓 "局域网" 和 "广域网" 只是一个相…

MetaJUI v0.4 遇到的一些问题及解决办法记录

1、Unity3d 版本 2022.3.29f1。 2、MetaJUI v0.4 的下载&#xff0c;https://download.csdn.net/download/xingchengaiwei/89334848 3、将MetaJUI v0.4解压&#xff0c;用Unity3d 打开项目&#xff0c;会出现如下问题&#xff0c;按照图中提示操作即可。 4、打开工程后会出现…

【2024年最新】基于Spring Boot+vue的旅游管理系统lw+ppt

作者&#xff1a;计算机搬砖家 开发技术&#xff1a;SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;Java精选实战项…

【Linux进程间通信】Linux匿名管道详解:构建进程间通信的隐形桥梁

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;Linux “ 登神长阶 ” &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀Linux进程间通信 &#x1f4d2;1. 进程间通信介绍&#x1f4da;2. 什么是管道&#x1f4dc;3…

如何使用ssm实现民族大学创新学分管理系统分析与设计+vue

TOC ssm763民族大学创新学分管理系统分析与设计vue 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不…

(作业)第三期书生·浦语大模型实战营(十一卷王场)--书生入门岛通关第3关Git 基础知识

任务编号 任务名称 任务描述 1 破冰活动 提交一份自我介绍。 2 实践项目 创建并提交一个项目。 破冰活动 提交一份自我介绍。 每位参与者提交一份自我介绍。 提交地址&#xff1a;https://github.com/InternLM/Tutorial 的 camp3 分支&#xff5e; 安装并设置git 克隆仓库并…

Java中的Junit、类加载时机与机制、反射、注解及枚举

目录 Java中的Junit、类加载时机与机制、反射、注解及枚举 Junit Junit介绍与使用 Junit注意事项 Junit其他注解 类加载时机与机制 类加载时机 类加载器介绍 获取类加载器对象 双亲委派机制和缓存机制 反射 获取类对象 获取类对象的构造方法 使用反射获取的构造方法创建对象 获…

Redis介绍及整合Spring

目录 Redis介绍 Spring与Redis集成 Redis介绍 Redis是内存数据库&#xff0c;Key-value型NOSQL数据库&#xff0c;项目上经常将一些不经常变化并且反复查询的数据放入Redis缓存&#xff0c;由于数据放在内存中&#xff0c;所以查询、维护的速度远远快于硬盘方式操作数据&#…

Yolov8轻量级网络改进GhostNet

1,理论部分 由于内存和计算资源有限,在移动设备上部署卷积神经网络 (CNN) 很困难。我们的目标是通过利用特征图中的冗余,为 CPU 和 GPU 等异构设备设计高效的神经网络,这在神经架构设计中很少被研究。对于类 CPU 设备,我们提出了一种新颖的 CPU 高效 Ghost (C-Ghost) …

国庆普及模拟赛-5

题目链接&#xff1a; file:///C:/Users/Administrator/Desktop/%E4%B8%8B%E5%8F%91%E6%96%87%E4%BB%B61005/20241005.pdf T1&#xff1a; 题目分析&#xff1a;不需要进行模拟&#xff0c;想要获得分数最大化&#xff0c;只需要将大的数据相加&#xff0c;再减去小的数据。 …

Android AMS介绍

注&#xff1a;本文为作者学习笔记&#xff0c;如有误&#xff0c;请各位大佬指点 系统进程运行环境的初始化 Context是一个抽象类&#xff0c;它可以访问application环境的全局信息和各种资源信息和类 context功能&#xff1a; 对Activity、Service生命周期的管理通过Intent发…

DenseNet算法:口腔癌识别

本文为为&#x1f517;365天深度学习训练营内部文章 原作者&#xff1a;K同学啊 一 DenseNet算法结构 其基本思路与ResNet一致&#xff0c;但是它建立的是前面所有层和后面层的密集连接&#xff0c;它的另一大特色是通过特征在channel上的连接来实现特征重用。 二 设计理念 三…