【关于Linux中----进程间通信方式之管道】

news2025/1/19 3:39:42

文章目录

  • 一、什么是管道
  • 二、匿名管道
  • 三、命名管道


一、什么是管道

进程间通信主要目的为以下几点

数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程之间通信,就一定会有一个进程把数据交给另一个进程处理的情况。但是,进程是具有独立性的(一个进程时看不到另一个进程的资源的),进行信息交互时成本一定很高。所以,为了解决这个问题,操作系统就会提供一块公共的资源供不同的进程使用。
所以,进程间通信的本质就是操作系统参与,提供一份所有通信进程能看到的公共资源。而这份公共资源的提供方式可能是文件和各种结构体等,而资源提供方式的多样性也是通信方式多样性的根本原因。

如果看过我之前的文章的读者一定知道,每一个进程都有自己的进程控制块(PCB),在这个控制块里存放着一个指针,它指向一个结构体(struct files_struct),这个结构体中包含一个存放文件描述符的数组,每一个文件描述符都指向一个确定的文件结构体(struct file),其中存放文件的属性和文件操作指针集合。
当需要对特定的文件进行操作时,进程就会经过每一层结构体找到对应的文件,进而调用对应的文件接口进行操作。

而当一个进程创建了自己的子进程时,毫无疑问,父子进程是两个独立的进程。
那么需要为子进程创建自己的struct files_struct吗?

答:当然需要。因为struct files_struct是属于进程的,而不是属于文件的,它是为进程和文件之间建立联系的。为了进程的独立性,是一定要为子进程创建自己的结构体的。

那么需要为子进程创建自己的struct file吗?

答:不需要。因为进程跟文件之间是关联关系,而不是拥有关系。所以创建新的进程不需要将原先进程的文件复制一份。但是,子进程和父进程的文件描述符指向的文件是相同的

当我们调用你某一个文件进行写操作时,进程会通过文件描述符找到对应的struct file,调用其中的write函数,而这个函数又会进行两个操作----将数据从用户拷贝到内核以及触发底层的写入函数将其写入磁盘。

这里注意,数据并不是直接写入磁盘的。而是先存入操作系统提供的一个缓冲区中,再经过刷新才到磁盘中的。

而如果不把已经存放到缓冲区中的数据刷新到磁盘中,子进程就可以通过和父进程相同的文件描述符找到对应的struct file,进而找到存放数据的缓冲区。这样也就实现了两个进程共享一份资源。这种通过文件的方式实现进程间通信的就是管道(从一个进程连接到另一个进程的一个数据流)。


二、匿名管道

创建一个管道分为三部分。

  • 首先,父进程创建管道

在这里插入图片描述

  • 然后,创建子进程

在这里插入图片描述
前面说过,创建子进程时,会为子进程创建一个进程控制块,也会为它创建一个struct files_struct,但是这时父子进程中的文件描述符表是相同的,指向的文件也是相同的。所以此时分别有两个进程的两个文件描述符指向一个管道。但是同一个进程是不能同时对一个文件即进行读操作又进行写操作的,所以接下来就进入最后一步

  • 父子进程都关闭一个文件描述符,使得两个进程一个进行读操作,另一个进行写操作

在这里插入图片描述

在Linux中,用来创建管道的函数叫做pipe。
在这里插入图片描述
代码如下:
在这里插入图片描述
对应的Makefile内容如下:
在这里插入图片描述
结果如下:

[sny@VM-8-12-centos practice]$ ls
Makefile  test.c
[sny@VM-8-12-centos practice]$ make
gcc -o test test.c -std=c99
[sny@VM-8-12-centos practice]$ ls
Makefile  test  test.c
[sny@VM-8-12-centos practice]$ ./test
pipefd[0]:3 pipifd[1]:4

因为前三个文件描述符被占用,父子进程打开的文件描述符不相同(一个进行读,一个进行写),所以结果毫无疑问是3和4。

下面创建子进程,并进行父子进程中的一个文件描述符的关闭。
补充:pipefd[0]和pipefd[1]分别表示读和写

在这里插入图片描述

接下来,给父子进程增加一些代码,让它们相互通信:
在这里插入图片描述

执行结果如下:

[sny@VM-8-12-centos practice]$ make clean;make
rm -f test
gcc -o test test.c -std=c99
[sny@VM-8-12-centos practice]$ ./test
pipefd[0]:3 pipifd[1]:4
child to father:hello world!
child to father:hello world!
child to father:hello world!
child to father:hello world!
child to father:hello world!

上面的代码中,子进程每写入一次就sleep一秒,但父进程却一直在读取。接下来,让子进程一直写入,让父进程每读取一次就sleep一秒看一下结果(代码雷同,就不粘贴了):

[sny@VM-8-12-centos practice]$ make clean;make
rm -f test
gcc -o test test.c -std=c99
[sny@VM-8-12-centos practice]$ ./test
pipefd[0]:3 pipifd[1]:4
child to father:hello world!hello world!hello world!hello world!hello world!hel
child to father:lo world!hello world!hello world!hello world!hello world!hello 
child to father:world!hello world!hello world!hello world!hello world!hello wor
child to father:ld!hello world!hello world!hello world!hello world!hello world!
child to father:hello world!hello world!hello world!hello world!hello world!hel
child to father:lo world!hello world!hello world!hello world!hello world!hello 

可以看到,每一次打印出来的内容都是没有规律的。这是因为缓冲区中的内容不会在每一次读取后被刷新,而是要等每一次缓冲区存满之后才刷新,这是管道面向字节流的特性。

下面再对代码稍作修改:
在这里插入图片描述

让子进程每次向缓冲区中写入一个字节的内容,并输出已经写入的字节的个数,而父进程什么也不做,结果如下:
在这里插入图片描述
可见,缓冲区能存储的最大容量为65536字节,也就是64KB。
由于这时,缓冲区已经存满,但是另一个进程并没有进行任何的读操作,所以为了不将没有被读取的信息覆盖,管道的写端会停止写入,直到读端进行读取信息才会继续写入。
所以,管道是有大小的

而如果,当缓冲区满的时候,我们从缓冲区中读取一小段数据,它还是不会立刻就像其中写入信息。只有当读取的字节数比较大时,才会继续写入,这叫做管道的同步机制。(具体可以使缓冲区继续写入的读取字节数是多少,各位读者可以自己动手试一试,一般64字节以下是不足以让缓冲区继续写入的)

接下来看一下管道的大小:

[sny@VM-8-12-centos practice]$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7908
max locked memory       (kbytes, -l) unlimited
max memory size         (kbytes, -m) unlimited
open files                      (-n) 100001
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 7908
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

其中的pipe size=512*8bytes,也就是4KB。
所以,向管道中写书数据的大小是有限制的,也就是不能超过4KB,一旦超过,将不能保证管道的原子性。

而如果管道的写端关闭,那么读端将会读完管道中的内容,然后读端对应的进程将会退出。
而如果管道的读端关闭,这时如果写端继续写入将毫无意义,操作系统不会允许这种浪费时间和资源的事情出现,就会向写端对应的进程发送信号,进而杀死进程。

如果一个进程打开了一个文件,然后文件相关进程退出了,文件会怎么处理呢?

答:与文件相关的引用计数会自动进行–,然后操作系统会将该文件关闭。

总结一下管道的特点:

当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
当管道满的时候
O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。


三、命名管道

管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件。

创建命名管道可以用命令行mkfifo filename来实现:
在这里插入图片描述
下面用一种更通俗易懂的方式介绍一下命名管道:
我们通常标识一个文件时采用路径+文件名的方式,这样的标识使文件具有唯一性。
再思考一个问题:一个文件可以同时被两个进程以读/写的方式打开吗?

答案无疑是可以的,比如stdout和stderr,它们对应的都是显示器,也就是同一个文件。

所以,当我们以某种方式打开文件时,该文件就会被加载到内存中产生一个临时文件,这个文件可以被多个进程读取。而进程找到该文件的方式是通过磁盘中存储的此文件的路径+文件名
而上述在内存中产生的临时文件就是命名管道!

命名管道的打开规则:

如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

接下来用代码制造一个命名管道:
先创建两个源程序文件:
test1.c如下:
在这里插入图片描述
test2.c中只有一个空的main函数,对应的Makefile内容如下:
在这里插入图片描述
执行结果如下:

[sny@VM-8-12-centos practice]$ make clean;make
rm -f test1 test2
gcc -o test1 test1.c
gcc -o test2 test2.c
[sny@VM-8-12-centos practice]$ ls
Makefile  test1  test1.c  test2  test2.c
[sny@VM-8-12-centos practice]$ ./test1
[sny@VM-8-12-centos practice]$ ll
total 36
prw-rw-r-- 1 sny sny    0 Jan 11 15:16 fifo
-rw-rw-r-- 1 sny sny  122 Jan 11 15:14 Makefile
-rwxrwxr-x 1 sny sny 8408 Jan 11 15:16 test1
-rw-rw-r-- 1 sny sny  190 Jan 11 15:16 test1.c
-rwxrwxr-x 1 sny sny 8304 Jan 11 15:16 test2
-rw-rw-r-- 1 sny sny   47 Jan 11 14:59 test2.c

可以看到,成功生成了fifo文件。但是代码中设置的初始权限为0666,结果却和代码不同?

只是因为创建管道文件时,会受到umask的影响,如果不想让规定的权限被改变,可以在创建之前将umask置0,如下:
在这里插入图片描述
再将Makefile中的clean中增加fifo,就可以生成新的管道文件了:

[sny@VM-8-12-centos practice]$ ./test1
[sny@VM-8-12-centos practice]$ ll
total 36
prw-rw-rw- 1 sny sny    0 Jan 11 15:23 fifo
-rw-rw-r-- 1 sny sny  127 Jan 11 15:23 Makefile
-rwxrwxr-x 1 sny sny 8464 Jan 11 15:23 test1
-rw-rw-r-- 1 sny sny  202 Jan 11 15:20 test1.c
-rwxrwxr-x 1 sny sny 8304 Jan 11 15:23 test2
-rw-rw-r-- 1 sny sny   47 Jan 11 14:59 test2.c

权限设置成功!

管道创建成功,如何通信呢?

我们先创建两个源文件。
sever.c如下:

#include "comm.h"
  2 
  3 int main()
  4 {
  5   umask(0);
  6   if(mkfifo(MY_FIFO,0666)<0)
  7   {
  8     perror("mkfifo");
  9     return 1;
 10   }
 11   //进行文件操作
 12   int fd=open(MY_FIFO,O_RDONLY);
 13   if(fd<0)
 14   {
 15     perror("open");
 16     return 2;
 17   }
 18   while(1)
 19   {
 20     char buf[64]={0};
 21     ssize_t s=read(fd,buf,sizeof(buf)-1);
 22     if(s>0)
 23     {
 24       buf[s]=0;
 25       printf("client:%s\n",buf);
 26     }
 27     else if(s==0)
 28     {
 29       printf("client finish\n");                                       
 30       break;
 31     }
 32     else
 33     {
 34       perror("read");
if(s>0)
 23     {
 24       buf[s]=0;
 25       printf("client:%s\n",buf);
 26     }
 27     else if(s==0)
 28     {
 29       printf("client finish\n");                                       
 30       break;
 31     }
 32     else
 33     {
 34       perror("read");
 35       break;
 36     }
 37   }
 38   return 0;
 39 }

client.c如下:

#include "comm.h"
  2 #include <string.h>
  3 
  4 int main()
  5 {
  6   int fd=open(MY_FIFO,O_WRONLY);
  7   if(fd<0)
  8   {
  9     perror("open");
 10     return 1;
 11   }
 12   while(1)
 13   {                                                                    
 14     char buf[64]={0};
 15     //先把数据从标准输入拿到进程内部                     
 16     ssize_t s=read(0,buf,sizeof(buf)-1);
 17     if(s>0)                             
 18     {      
 19       buf[s-1]=0;
 20       printf("%s\n",buf);
 21       write(fd,buf,strlen(buf));
 22     }                           
 23   }  
 24   close(fd);
 25   return 0; 
 26 }          

它们包含的头文件comm.h如下:

#pragma once 
  2  #include <stdio.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/stat.h>
  6 #include <fcntl.h>
  7 
  8 #define MY_FIFO "./fifo"   

对应的Makefile如下:
在这里插入图片描述
执行结果如下:
在这里插入图片描述
可见,两个进程成功实现通信!

补充:命名管道和匿名管道一样,也是面向字节流的,所以需要通信双方“制定协议”,这里不具体详谈。
另外,命名管道中的内容不会被刷新到磁盘中,这样做是为了保证效率。

命名管道与匿名管道的区别:

匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开用open。
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。


本篇结束!坚持!努力!

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

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

相关文章

STL forward_list 模拟实现

forward_list 概述 forward_list 是 C 11 新增的容器&#xff0c;它的实现为单链表。 forward_list 是支持从容器中的任何位置快速插入和移除元素的容器&#xff0c;不支持快速随机访问。forward_list 和 list 的主要区别在于&#xff0c;前者的迭代器属于单向的 Forward Ite…

二分法讲解

目录 一、前言 二、二分法理论 1、引导&#xff1a;猜数游戏 2、理论背景&#xff1a;非线性方程的求根问题 1&#xff09;非线性方程的近似解 2&#xff09;搜索法和二分法 3、用二分的两个条件 4、二分法复杂度 三、整数二分 1、在单调递增序列中找 x 或者 x 的后继…

使用python-pptx创建PPT演示文档功能实践

python对PPT演示文档读写&#xff0c;是通过第三方库python-pptx实现的&#xff0c;python-pptx是用于创建和更新 PowerPoint&#xff08;.pptx&#xff09;文件的 Python 库。 关于PPT演示文档与幻灯片模板的内容不是本文的重点&#xff0c;在此略过。 1. PPT基本结构在pyth…

Sophus降维、升维与欧拉角、旋转向量的爱恨情仇

0. 简介 在面对二维与三维之间的转换时&#xff0c;我们常常会困惑该如何去转换&#xff0c;在G2O中存在有理想的坐标转换工具&#xff0c;但是在Sophus中却缺乏这样的手段。之前在Sophus处简要的介绍了一下SE(2)与SE(3)的转换&#xff0c;最近发现之前的文章这部分需要拿出来…

Leetcode:654. 最大二叉树(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 直接模拟&#xff08;递归)&#xff1a; 原理思路&#xff1a; 索引版本&#xff1a; 问题描述&#xff1a; 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&a…

Thymeleaf从入门到清晰使用

文章目录什么是thymeleaf&#xff1f;第一个Thymeleaf程序Thymeleaf详解配置常用标签最后什么是thymeleaf&#xff1f; 模板引擎&#xff1a; 前端交给我们的页面&#xff0c;是html页面&#xff0c;如果是以前开发&#xff0c;我们需要把他们转成jsp页面&#xff0c;jsp的好处…

ABB AC500 系列 PLC 与上位机iFix 的通讯配置

ABB PLC IP 及 MODBUS TCP/IP 协议设置 通过 IP config tool 配置设备 IP 在 软件中&#xff0c;有 3 种方式可以进入 IP config tool 的配置界面  双击 IP_settings&#xff0c;点击 IP config tool&#xff0c;即可进入 IP config tool 界面 点击菜单栏的 Tool→IP confi…

k8s 微服务spring boot JVM 监控

目录 1.前言 1.1 JVM监控方案 1.2 接入操作步骤 2. 服务开启actuator prometheus监控 2.1 示例参考添加依赖 2.2 配置prometheus监控 3 配置 prometheus 监控 4 配置jvm grafana dashboard 1.前言 服务 部署环境 监控 spring-demo k8s v1.22 Prometheus Operator …

进程相关概念

1、什么是程序&#xff0c;什么是进程&#xff0c;有什么区别 程序是静态的概念。例如&#xff1a;gcc xxx.c -o pro&#xff0c;磁盘中生成pro文件&#xff0c;叫做程序&#xff08;还未运行起来&#xff09; 进程是程序的一次运行活动&#xff0c;也就是程序跑起来了&#xf…

【Linux】信号机制(非实时信号)

目录 前言 一.信号的概念以及产生 1.什么是信号 2.信号分为两类 3.查看信号的命令 4.信号如何产生 1).通过软件产生 2).通过硬件产生 3).通过键盘组合键产生 二.信号的发送以及保存 1.信号如何发送 2.信号如何保存 1).概念 2).底层实现结构&&内核中的实现…

WampServer服务器设置控件

WampServer服务器设置控件 WampServer是一个web开发环境&#xff0c;是一个用于Windows操作系统的类似服务器的软件&#xff0c;由Romain Bourdon构建。该工具允许您开始构建web应用程序&#xff0c;并在Windows上使用Apache服务器2、PHP编程语言和MySQL数据库在本地网络上启动…

java 实现 springboot项目 使用socket推送消息,前端实时进行接收后端推送的消息

1 后端 1.1 添加依赖 在我们的springboot项目里面&#xff0c;添加依赖&#xff1b; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.7.0</version>…

企业微信在职继承功能如何理解?怎么使用?

企业微信的在职继承功能就是企业管理员将在职员工的客户分配给其他在职员工跟进的一种方式&#xff0c;很方便&#xff0c;可以在工作出现变更时使用。 前言 企业微信之前的离职继承功能受到很多企业的青睐&#xff0c;企业能够将离职员工的客户分配给其他员工接手&#xff0c…

云天励飞在科创板获准注册:计划募资30亿元,陈宁为实际控制人

2023年1月10日&#xff0c;证监会发布公告&#xff0c;同意深圳云天励飞技术股份有限公司&#xff08;下称“云天励飞”&#xff09;首次公开发行股票注册。据贝多财经了解&#xff0c;云天励飞于2020年12月8日在科创板递交上市申请&#xff0c;2021年8月6日过会。 本次冲刺上市…

蓝队攻击的四个阶段(一)

蓝队的攻击是一项系统的工作&#xff0c;整个攻击过程是有章可循、科学合理的涵盖了从前期准备、攻击实施到靶标控制的各个步骤和环节。按照任务进度划分&#xff0c;一般可以将蓝队的工作分为4个阶段:准备工作、目标网情搜集、外网络向突破和内网横向拓展。第一阶段准备工作&a…

App开发提效法宝之插件技术

近年来技术革新频率越来越高&#xff0c;最近工作中经常有小伙伴问到插件技术的相关内容&#xff0c;今天就来跟大家系统的说清楚什么是插件技术以及它的好处。欢迎评论区交流哦&#xff01; 什么是插件技术&#xff1f; 插件技术指的是一种应用程序&#xff0c;遵循程序接口…

高温热水解预处理对厌氧消化期间污泥腐殖化的调控机制

期刊&#xff1a;Water Research 影响因子&#xff1a;9.13 发表时间&#xff1a;2022样本类型&#xff1a;污泥客户单位&#xff1a;同济大学凌恩生物客户同济大学发表在《Water Research》上的文章“The neglected effects of polysaccharide transformation on sludge humif…

振弦采集模块参数配置工具VMTool 的使用

振弦采集模块参数配置工具VMTool 的使用 准备工作 &#xff08; 1&#xff09; 将 VMXXX 模块的 UART_TTL、 RS232&#xff08; 或 RS485&#xff09; 接口与计算机的 COM 端口连接&#xff1b; &#xff08; 2&#xff09; 连接振弦传感器及温度传感器到 VMXXX 的对应接口&…

三菱FX3U系列PLC运动控制_伺服回原点的3种方法示例

三菱FX3U系列PLC运动控制_伺服回原点的3种方法示例 方法1: 运动的方向为圆形、环形、电机往一个方向转动; 只有一个原点开关,没有极限开关 如下图所示, 原点回归的方式为:启动回原点后,电机开始寻找原点开关,在找到原点开关的瞬间,开始减速;在离开原点开关的瞬间,电…

23. 反爬案例:不登录不给,要数据请先登录我的站点

登录之后&#xff0c;可以查看数据&#xff0c;是部分站点常用规则&#xff0c;本篇博客将在爬虫训练场中实现该需求。 文章目录安装必备模块建立 models建立 login_form 表单文件flask_wtf 中 FlaskForm 类建立登录视图函数配置 login.html 页面安装必备模块 实现 Python Fla…