Linux(七)进程间通信

news2024/7/6 19:23:43

进程间是如何进行通信的?

        通过前面的学习之后,我们知道进程间是具有独立性的,在操作系统的层面来看,进程就是一块pcb,是对运行中的程序动态运行过程的描述,在Linux角度下,进程就是一个task_struct结构体,pcb里面的信息存放在虚拟地址空间并通过页表来映射到一块物理内存上,每一个进程都对应着其自己的pcb,所以说进程间是相互独立的,那么如何使这一些相互独立的pcb之间能够形成通信呢?

        Linux中这这么一些通信方式、管道、消息队列、共享内存、信号量……他们建立进程间通信都是通过同样的一种关联关系:能够共同访问的同一块内存

一、管道

1、介绍

管道的灵感来源于生活,管道在生活中随处可见,排水输水管道、自来水管……

Linux中也引入了这一想法:并根据通信传输方向分为

单工通信:单向通信,比如有两端,只能从A->B

双工通信:双向通信,比如有两端,既可以从A->B,也可以从B->A

半双工通信:一种可以选择方向的单向通信,比如有A和B两端,要么从A->B,要么从B->A

                     (同一时间不能同时既发送又接收)

管道的本质:操作系统为进程间通信提供了一个空间交叉点,进程间都能访问这个交叉点,

                     在程序中,管道就是内核的一块缓冲区(缓冲区本质就是内核中的一块内存)

2、匿名管道

定义:没有文件描述符,不能被其他进程找到,所以只能用于具有亲缘关系进程间通信

实现原理概述:

        一个进程创建一个匿名管道,(就是在内核空间中创建一块缓冲区,也就是一块内存或者说一个文件),然后创建之后,给该进程返回一个文件描述符,这时候我们创建一个或多个子进程,那么子进程就会复制父进程大部分的描述信息其中就有该管道文件的描述符,那么子进程就可以与该父进程对同一块文件进程进行读写实现通信。

 如果这时候再创建一个子进程,那么这个子进程和父进程可以进行通信,当然也可以与之前创建的子进程进行通信,该子进程再创建一个子进程(父进程的孙进程)当然,它们之间也能进行通信

接口:

        int pipe(int pipefd[2])

                功能:创建一个管道,并通过参数返回管道的俩个操作句柄

                参数:pipefd - 具有2个整形元素的数组,内部创建管道会将描述符存储在数组中

                        pipefd[0] -- 用于从管道中读数据

                        pipefd[1] -- 用于向管道中写数据

                返回值:成功返回0,失败返回-1

                注意:创建匿名管道前,一定要在创建子进程之前

进行一段代码通信:在第一个子进程中向管道写入数据,然后第二个子进程进行读取数据

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
#include<stdlib.h>

int main()
{
    int pipefd[2];
    // [0] 为读
    // [1] 为写
    int ret = pipe(pipefd);
    if(ret < 0){
      perror("pipe error");
      return -1;
    }

    pid_t pid1 = fork();
    if(pid1==0){
      // 兄 子进程
      const char *str = "我是哥哥,新年好啊\n";
      write(pipefd[1], str, strlen(str));   // 向管道中写入数据
      exit(0);
    }
    pid_t pid2 = fork();
    if(pid2 == 0){
      // 弟 子进程
      char buf[1024] = {0};
      read(pipefd[0], buf, 1023);    // 从管道中读取数据
      printf("%s",buf);
      exit(0);
    }
    wait(NULL);
    wait(NULL);
    return 0;
}

管道特性

1、当管道中没有数据时,读端就会阻塞,直到有数据被写入管道才会进行读取

2、当管道中数据满了,那么写端就会阻塞,等待管道中数据被读取,有空间可以写入了才会进行写入数据。

3、当管道的所有读端被关闭,再次写入数据就会导致程序崩溃

        因为没有进程来进行读取了,那么在进行写入都是没有意义的,所以系统就进行处理

4、当管道的所有写端被关闭,那么读端读取完管道中的所有数据后,将不在阻塞等待,而是返回0

        所以没有意义的事情大佬就会替我们考虑好,读端关闭写入就会报错,写端关闭读取完剩余就会退出返回0

2、命名管道

具有标识符的管道,其他进程可以找到。

实现概述:

进程通过mkfifo创建一个管道文件,这个管道文件的标识符可以被其他进程访问到,当A进程通过这个管道文件描述符访问这个缓冲区,进行写入数据,那么B进程打开通过这个管道文件访问管道,可以进行数据读取

注意点:

        当创建管道文件时,并不是直接就将数据传输管道(内存中的缓冲区)创建好了,而是只有在有进程打开这个管道进行数据访问时,才会真正的创建这个管道。(联想写时拷贝技术)。

命名管道的独有特性:(没读端,只写就会阻塞  ||  没写端,只读就会阻塞)

        只以只写的方式打开管道,写端就会被阻塞,直到管道有以可读的进程访问

        当只以只读方式打开管道,读端就会阻塞,直到管道有以可写的进程访问

原理:因为当一个管道没有构成读写都满足的条件时,这个管道是没有意义的,也就没有必要创建这个缓冲区。(也正好对应上方只有真正满足数据传递的时候才会真正创建这个缓冲区)

 接口:

        int  mkfifo(char* pathname, mode_t mode) 头文件#include<sys/stat.h>

                pathname (创建的管道文件路径  如果当前路径下可以 ./pipe.fifo

                mode (创建文件的访问权限)

        返回值:成功返回0,失败返回-1;

读端进程:

#include<stdio.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>

int main()
{
    umask(0);
    int ret = mkfifo("./named_pipe.fifo", 0664);
    if(ret < 0 && errno!=EEXIST){ // 因为创建管道的文件如果存在那么访问就会出错,加入errno判断可以去掉该报错
      perror("mkfifo error");
      return -1;
    }
    int fd = open("named_pipe.fifo", O_WRONLY);
    if(fd < 0){
      perror("open error");
      return -1;
    }
    while(1)
    {
        printf("A进程说:");
        fflush(stdout);
        char buf[1024] = {0};
        scanf("%s",buf);
        int ret = write(fd, buf, strlen(buf));
        if(ret < 0){
          perror("write error");
          close(fd);
          return -1;
        }
    }
    close(fd);
    return 0;
}

写端进程:

#include<stdio.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>

int main()
{
    umask(0);
    int ret = mkfifo("./named_pipe.fifo", 0664);
    if(ret < 0 && errno!=EEXIST){ // 因为创建管道的文件如果存在那么访问就会出错,加入errno判断可以去掉该报错
      perror("mkfifo error");
      return -1;
    }
    int fd = open("named_pipe.fifo", O_RDONLY);
    if(fd < 0){
      perror("open error");
      return -1;
    }
    while(1)
    {
        char buf[1024] = {0};
        int ret = read(fd, buf, 1023);
        if(ret < 0){
          perror("read error");
          close(fd);
          return -1;
        }else if(ret == 0)
        {
          printf("所有的写端被关闭,读端读取完数据,返回0\n");
          close(fd);
          return -1;
        }
        printf("%s\n", buf);
    }
    close(fd);
    return 0;
}

3、管道学习总结

本质:

        内存中的一块缓冲区,多个进程通过访问同一块缓冲区来实现通信

分类:

匿名管道:只能用于具有亲缘关系的进程间通信(只能通过子进程复制父进程访问获取操作句柄)

命名管道:可用于同一主机的任意进程间通信(通过打开同一个管道文件来访问同一块缓冲区)

特性:

        1)半双工通信

        2)管道声明周期随进程(不需要人为关闭,当所有打开管道的进程退出后,管道释放)

        3)提供字节流传输服务(数据先进进出、按序到达、不会丢失数据、面向连接)

                所有读端被关闭,继续写就会异常;所有写端被关闭,读端读完剩余不在阻塞返回0

        4)自带同步与互斥

                互斥:通过同一时间对共享资源的唯一访问,保证原子性

                      原子性:具有不可分割特性        原子操作:一个操作不会被打断

                同步:通过进程对资源的访问限制,让进程对资源访问更加合理

                      饥饿问题:不会出现A进程一直在写,另外B进程一直等着。

                      数据满了 write进程阻塞,没有数据read阻塞。

二、共享内存

1、简介

作用:实现多个进程之间的数据共享

特性:最快的数据传输方式

           生命周期随内核(删除并非直接删除,而是拒绝后续映射,当映射连接数为0时,表示没有进程访问了,操作系统才会对其回收资源)

原理:开辟出一块物理内存,然后多个进程都将这块内存映射到自己的虚拟地址上,再通过虚拟地址来访问一块空间中的数据。

 通过访问同一块物理内存,将物理地址映射到各自的虚拟地址中,进行数据访问。

管道通信通过在内核中开辟的缓冲区,共同访问这一块缓冲区,数据传输是从发送端拷贝到管道中,接收端再从管道中拷贝取出数据,而共享内存就不需要这俩次拷贝处理,所以它是最快的数据传输方式。

2、流程

1)、创建/打开指定共享内存

2)、将内存映射到自己的虚拟地址空间

3)、内存操作……

4)、解除映射关系

5)、删除共享内存

1)创建/打开指定共享内存

头文件 #include<sys/shm.h>

int  shmget (key_t key, size_t size, int shmflg)

        key:共享内存的标识符(名字)

        size:要创建的共享内存大小,最好是PAGE_SIZE的整数倍

        shmfg:IPC_CREAT | IPC_EXCL | 0664

                IPC_CUEAT:如果共享内存不存在则创建打开,存在则直接打开

                IPC_EXCL:   与IPC_CREAT搭配使用,共享内存不存在则创建打开,存在则报错返回

                mode_flags: 共享内存的访问权限 0664

        返回值:成功返回一个操作句柄(非负整数)、失败返回-1;


2)将共享内存映射到当前进程的虚拟地址空间

获取到首地址后,就可以通过首地址访问内存中的数据,以及可以修改内存中的数据

 头文件 #include<sys/shm.h>

void *shmat(int shmid,const void *shmaddr, int shmflg);

        shmid:之前shmget打开共享内存返回的操作句柄

        addr:映射首地址,通常置为NULL,让操作系统进行分配

        shmflag:默认为0表示可读可写,SHM_RDONLY 表示只读

                        (前提该共享内存创建的权限允许)

        返回值:成功返回映射首地址,失败返回(void*)-1

3)解除映射关系

int  shmdt(const void *shmaddr);

        shmaddr:映射首地址,也就是shmat的返回值

4)数据交互

对内存内容进行修改或者读取

5)删除共享内存

int shmctl(int shmid, int cmd,struct shmid_ds *buf)

        shmid:创建共享内存的返回值

        cmd:对共享内存指向的操作命令

                使用IPC_RMID:标记一个共享内存段需要被删除

        buf:当cmd命令为接收获取共享内存信息时的容器(删除命令不需要用)

真的会实际删除这个共享内存嘛?

        不会的,正如上面命令所说的,只会进行一次标记,标记的共享内存将不会接受新的映射而是等当前的映射连接计数为0时,再实际删除。

        真正的删除是由系统执行的,进程所做的删除操作,只是进程标记一下。

3、代码模拟通信

给共享内存中写入数据

#include<stdio.h>
#include<sys/shm.h>
#include<unistd.h>

#define SHM_KEY 0x12345678
int main()
{
    // 创建/打开共享内存
    int shmid = shmget(SHM_KEY, 4096, IPC_CREAT | 0664);
    if(shmid < 0){
      perror("shget error");
      return -1;
    }

    // 进行组织映射
    void *start = shmat(shmid, NULL, 0);
    if(start == (void*)-1){
      perror("shmat error");
      return -1;
    }

    // 进行数据交互
    int id = 0;
    while(1)
   {
      sprintf(start, "已经传输了%d内容到共享内存中\n", id++);
      sleep(1);
    }

    // 解除映射
    shmdt(start);
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

使用共享内存读取数据

#include<stdio.h>
#include<sys/shm.h>
#include<unistd.h>

#define SHM_KEY 0x12345678
int main()
{
    // 创建/打开共享内存
    int shmid = shmget(SHM_KEY, 4096, IPC_CREAT | 0664);
    if(shmid < 0){
      perror("shget error");
      return -1;
    }

    // 进行组织映射
    void *start = shmat(shmid, NULL, 0);
    if(start == (void*)-1){
      perror("shmat error");
      return -1;
    }

    // 进行数据交互
    while(1)
    {
      printf("%s\n", start);// 之前进行映射后返回的就是共享内存的地址
      sleep(1);
    }

    // 解除映射
    shmdt(start);
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

三、进程间通信所学命令小结

  ipcs 查看进程间通信资源/ipcrm 删除进程间通信资源

  -m 针对共享内存的操作

  -q 针对消息队列的操作

  -s 针对信号量的操作

  -a 针对所有资源的操作

 使用ipcs -m命令获取共享内存通信方式信息

ipcsrm -m shmid  删除shmid对应的共享内存。

注意删除的时候,如果没有其他进程访问会直接删除共享内存,如果有其他进程访问则会将共享内存键值设置为一个无法访问的量。

ipcs 获取当前进程间通信所有方式信息

底行模式下使用  :1,2s/shmread/shmwrite/g

将1-2行中的shmread修改为shmwrite。

 四、消息队列

功能:实现进程间通信

本质:内核中的一个优先级队列

实现:多个进程通过访问同一个消息队列,以添加数据结点和获取数据结点方式实现通信

        结点中一个结构为type类型(身份标识、优先级比较方式)

        第二个结构为data存放数据

特性:

        声明周期随内核

        以数据块传输

        自带同步与互斥

五、信号量

作用:用于实现进程间的同步与互斥

本质:计数器 + pcb队列

P、V操作:

        P操作:对计数器进行-1操作,判断计数是否大于等于0,正确则返回;失败则阻塞

        V操作:对计数器进行+1操作,判断计数是否小于等于0,释放一个资源,如果有进程等待中,则唤醒一个等待的进程

同步实现:

        获取资源之前进行P操作,判断是否有资源可使用,如果条件满足则访问,否则阻塞

        当产生一个资源时,进行V操作,唤醒阻塞的进程

互斥实现:

        初始计数器为1,表示资源只有一个

        访问资源之前进程P操作、访问资源完毕后进行V操作。

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

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

相关文章

2-2JVM-GC垃圾回收

GC垃圾回收 了解什么是垃圾回收掌握垃圾会回收的常见算法学习串行、并行、并发、G1垃圾收集器学习GC日志的可视化查看 1.什么是垃圾回收&#xff1f; 程序的运行必然需要申请内存资源&#xff0c;无效的对象资源如果不及时处理就会一直占有内存资源&#xff0c;最终将导致内…

YOLOv5/v7 Falsk Web 监测平台 | YOLOv5/v7 Falsk Web 部署

YOLOv7 Falsk Web 监测平台图片效果展示 YOLOv7 Falsk Web 监测平台视频效果展示 YOLOv7 Flask Web 检测平台 什么是Flask? 简介 Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开…

HTB_Jerry tomcat弱口令war包getshell

文章目录信息收集Tomcat war 包 getshell信息收集 扫描发现 8080 端口存在 tomcat 服务&#xff0c;访问 manage app 管理接口&#xff0c;需要输入账号密码 点击取消&#xff0c;看到默认账号密码&#xff0c;重新登录&#xff0c;登录成功&#xff0c;未修改 一般就是上传或…

JVM类加载

作用&#xff1a;负责从硬盘/网络中加载字节码信息&#xff0c;加载到内存中&#xff08;运行时数据区的方法区中&#xff09; 类加载过程&#xff1a; 加载 使用IO读取字节码文件&#xff0c;转换并存储 为每个类创建一个Class类的对象 存储在方法区中 链接&#xff08;…

Jackson注解使用分析

文章目录Jackson常用注解1. 常用注解汇总2. 注解使用分析JsonIncludeJsonAnyGetterJsonAnySetterJsonNamingJsonAutoDetectJacksonInjectJsonAliasJsonValueJsonMergeJsonRawValueJsonEnumDefaultValueJsonFilterJsonSerializeJsonDeserializeJacksonAnnotationJacksonAnnotati…

excel合并技巧:查找函数遇到合并单元格怎么应对

大家都在期盼奖金的到来&#xff0c;可是核算奖金的同事正在苦恼&#xff0c;因为以前用得好好的VLOOKUP函数突然不合适了&#xff0c;很多人的奖金计算出来都变成了乱码&#xff1a;使用VLOOKUP函数每个部门只有第一行正确&#xff0c;其他都是乱码。看到这个表&#xff0c;相…

【服务器数据恢复】raid5硬盘离线后热备盘未启用的数据恢复案例

服务器数据恢复环境&#xff1a; 某品牌X3850服务器&#xff0c;组建的raid5磁盘阵列&#xff0c;该raid5磁盘阵列包含4块成员盘和1块热备盘。 服务器故障&#xff1a; 服务器在运行过程中由于未知原因突然崩溃&#xff0c;用户方工程师检查后发现该故障服务器raid5阵列中2块磁…

Electron + Vue 开发环境搭建

1.安装nodejs&#xff0c;下载网址&#xff1a;https://nodejs.org/en/ 点击安装程序&#xff0c;一路next即可 安装完成之后打开cmd测试&#xff0c;输入node -v查看node版本&#xff0c;输入npm -v查看npm版本 安装完成后&#xff0c;.msi格式的安装包已经将node.exe添加到…

Excel连接openGauss数据库实操

目录 前言 一、通过excel 添加数据源访问openGauss 1、查看Excel版本 2、下载 ODBC驱动 3、安装ODBC驱动 4、添加ODBC数据源 5、在excel中添加数据源&#xff08;访问openGauss&#xff09; 二、通过excel 的VBA&#xff08;宏&#xff09;访问openGauss 1、宏权限设置…

2.1 java基础 day02 流程控制 创建类和对象 栈堆元空间

1流程控制 流程控制&#xff1a; 1.1.计算机在执行代码时对指令代码执行顺序的控制 1.2.Java 流程控制主要分三种&#xff1a; 顺序执行 分支执行 重复执行 1.3.顺序执行 按代码和语法出现的先后顺序执行 1.4.分支执行 根据判断条件执行分支逻辑 可选分支、必选分支&#…

活体识别6:小视科技开源的静默活体检测

说明 该项目为小视科技的静默活体检测项目。开源地址在 https://github.com/minivision-ai/Silent-Face-Anti-Spoofing。 由于不是论文衍生项目&#xff0c;所以只有一个公众号文章的介绍&#xff1a;https://mp.weixin.qq.com/s/IoWxF5cbi32Gya1O25DhRQ 方案详情 该方案是…

【安全】端口复用:远程遥控iptablesSSLH工具

目录 基础知识点 链的概念 表的概念 表链关系 远程遥控iptables进行端口复用 Ⅰ、利用ICMP做遥控开关 ①创建端口复用链 ②创建端口复用规则&#xff0c;将流量转发至 22 端口 ③开启开关&#xff0c;如果接收到一个长为 1139 的 ICMP 包&#xff0c;则将来源 IP 添加到…

使用Oracle VM VirtualBox安装Centos

1.下载安装Oracle VM VirtualBox 2.下载Centos 下载地址 旧版本 3.新建 选择镜像填写账户信息之后&#xff0c;我这边不知道什么原因&#xff0c;并不能完成所有工作&#xff0c;所以我一般不选择 4.选择镜像 5.安装 通过方向键和回车键选择 6.设置 选择中文 等待一些自动…

2023年“华数杯”国际大学生数学建模B题完整思路

2023华数杯如期开赛&#xff0c;本次比赛作为美赛的模拟赛&#xff0c;赛题和比赛时间都和美赛高度相似&#xff0c;因此大家 完全可以当作一次美赛之前的练习赛进行。美赛的发题时间与华数杯一致&#xff0c;都是早晨六点&#xff0c;现已经将机器翻译的初步翻译 结果进行了分…

c语言通讯录max——数据的持久化处理(详解)

用文件操作升级通讯录前言1.实现逻辑2.用哪种文件存储数据2. save_contact函数设计3. load_contact 函数设计5.代码总览contact.hcontact.ctext.c前言 在有关通讯录的上一篇博客中&#xff0c;作者用柔性数组实现了动态改变通讯录占用空间的功能&#xff0c;但是在最后还是留下…

【Go基础】Http编程

文章目录1. http协议1.1 请求方法1.2 URL1.3 协议版本1.4 请求头1.5 请求正文1.6 http response1.7 https2. go语言http标准库3. http router4. 请求校验5. http中间件6. GIN6.1 路由6.2 参数获取6.3 利用postman提交http请求6.4 生成response6.5 参数检验6.6 中间件6.7 会话7.…

互联网分层模型

互联网的逻辑实现被分为好几层。每一层都有自己的功能&#xff0c;就像建筑物一样&#xff0c;每一层都靠下一层支持。用户接触到的只是最上面的那一层&#xff0c;根本不会感觉到下面的几层。要理解互联网就需要自下而上理解每一层的实现的功能。如上图所示&#xff0c;互联网…

55.Isaac教程--Livox 激光雷达

Livox 激光雷达 ISAAC教程合集地址文章目录Livox 激光雷达支持的硬件和固件在桌面上设置和运行示例应用程序在机器人上设置和运行示例应用程序查看正在运行的应用程序将来Livox 激光雷达 Isaac SDK 支持使用 Livox LIDAR&#xff0c;包括兼容的驱动程序和示例应用程序。 支持的…

Android马甲包的那些事儿

制作Android马甲包最简单的方式就是使用 productFlavors 机制。本文就是在productFlavors机制的基础上制作的马甲包&#xff0c;每个马甲只需要在build.gradle文件中配置一下包名、各种key、签名文件配置启动页、logo、app名等资源配置服务器域名、微信分享回调Activity等代码此…

Windows上tensorflow的GPU死活引用不了(tensorflow 2.11无法调用GPU)

tensorflow对于gpu的支持只到2.10&#xff0c;如果你装了最新的tf(2.11)&#xff0c;需要先卸载2.11。 安装代码&#xff1a; pip install tensorflow2.10 -i https://pypi.tuna.tsinghua.edu.cn/simple/解决过程&#xff1a; 查看CUDA与cuDNN配套版本&#xff1a; https:/…