ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【Linux】管道 [打印本页]

作者: 小小小幸运    时间: 2024-6-22 12:59
标题: 【Linux】管道
头脑导图


学习内容

        进程间通讯的一些知识点:是什么、为什么和怎么办??之后就是理解管道中的匿名管道的一些知识点:会创建匿名管道、匿名管道的四种环境……最后,就是进程池的代码编写,也是最难的一部门。
学习目标

过上面的学习目标,我们可以列出要学习的内容:
一、进程间通讯

1.1 进程间通讯的概念

       每个进程各自有不同的用户地点空间任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要互换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通讯(IPC,InterProcess Communication)
   一些易错的知识点:
  
  1.2 进程间通讯的作用


1.3 进程间通讯的本质(如何进行通讯)

       进程间通讯的本质是:让不同的进程看到同一份资源。进程是具有独立性的,我们每一个进程不能看到其他进程的代码和数据,进程 = 内核数据结构 + 代码 + 数据。因此,我们需要借助第三方资源实现进程间的通讯。
       进程间的通讯本钱较高,我们需要先将某一个进程需要通讯,在OS操作系统中创建一个共享资源,OS操作系统必须提供许多的系统调用,由于OS操作系统创建的共享资源的不同,函数调用的不同,进程间通讯会有不同的类型。让不同的进程看到同一份(操作系统)资源(内存)
 
   总结:
  
  1.4 进程间为什么要进行通讯??

       进程之间是需要进行某种协同的,而协同的前提条件是通讯。通讯时的数据是有类别的:通知就绪的数据,控制相干信息的数据,单纯地通报给我的数据……
       事实上:进程是具有独立性的,在进程间通讯的本质中说到:进程间通讯的本质是什么?以及进程 = 内核数据结构 + 代码 + 数据
   协同的意思:
         协同是指和谐的两个大概两个以上的不同资源的个体协同同等地完成某个任务的过程或本领。
  二、管道

2.1 什么是管道

       一说到管道,我们大概想到的是下水道的管道,但是这里讲的管道是计算机中的管道,在详细一点,是Linux操作系统中的管道,那么管道到底是什么呢??
       管道技能还是比较重要的,我们每天都可以用到。在计算机中,由于进程之间是相互独立的,信息无法进行交互。而计算机中的管道,就是一种解决进程间信息交互的本领
2.2 匿名管道

       为什么叫匿名管道呢??由于不需要文件路径和文件名
2.2.1 解释一种现象,为什么父子进程向同一个显示器终端打印数据??

       进程会默认打开三种标准输入和输出,0,1,2  三个文件描述符代表的是标准输入端,标准输出端和标准错误流。我们可以通过bash来进行理解,bash如果打开了3端口,那么bash其所有的子进程都会默认打开3端口。
2.2.2 匿名管道的工作原理

   匿名管道仅用于具有血缘关系的进程之间的通讯,只允许单向通讯。
  为什么管道要单向通讯??? 由于简朴。
         进程间通讯的本质是,让不同的进程看到同一份资源,使用匿名管道实现父子进程间通讯的原理是,让两个父子进程先看到同一份被打开的文件资源,然后父子进程就可以对该文件进行写入或读取操作,进而实现父子进程间的通讯。

   理解一种现象:为什么父子进程会向同一个显示器打印数据???
       进程默认会打开三个标准输入输出流:0,1,2。 bash打开,bash的子进程默认也就打开了,由于标准输入输出流都是bash的子进程,所以会默认打开。
  为什么我们主动关闭子进程0,1,2文件描述符,不会影响父进程继续使用显示器文件??
          由于struct file 是通过内存级的引用计数,在主动关闭子进程0,1,2文件描述符,会使struct file 的引用计数减一。当引用计数为0时,才会开释struct file。
  注意:

2.2.3 pipe函数

  1. #include <unisted.h>
  2. int pipe(int pipefd[2])
复制代码
       pipe()函数的参数部门是一个巨细为2的数组类型的指针,该函数成功时返回0,并将一对打开的文件描述符值填入pipefd参数指向的数组。该函数失败时返回-1。
       通过pipe函数创建的这两个文件描述符 fd[0] 和 fd[1] 分别构成管道的两头,往 fd[1] 写入的数据可以从 fd[0] 读出。并且 fd[1] 一端只能进行写操作fd[0] 一端只能进行读操作,不能反过来使用。要实现双向数据传输,可以使用两个管道。
2.2.3.1 pipe函数的参数部门

        pipe函数的参数是输出型参数,数组pipefd[2]表现的是对于一个管道的读端写端的文件描述符。
数组元素数组元素中代表的寄义
pipefd[0]管道读端的文件描述符
pipefd[1]管道写端的文件描述符
这里有一个小扩展:
          


2.2.3.2 pipe函数的返回部门

        当pipe函数成功调用时返回 0,pipe函数调用失败后返回 -1
2.2.4 匿名管道的使用步调

2.2.4.1 创建一个管道

       一般都是结合fork()函数进行使用,由于匿名管道需要在创建子进程之前创建,由于只有如许才气复制到管道的操作句柄,与具有亲缘关系的进程实现访问同一个管道通讯。所以我们需要先在创建子进程之前,利用pipe()函数创建一个匿名管道。
2.2.4.2 创建一个子进程 

       利用fork()函数创建一个子进程,将子进程和父进程对应的读端大概写端关闭。由于匿名管道只支持半双工通讯,因此,匿名管道只能有一个读端和一个写端。将对应的读端或写端的文件描述符进行关闭。
2.2.4.3 子进程和父进程各自创建读端大概是写端

       创建一个读端和一个写端的函数进行通讯即可。

下面来进行详细地先容过程:
   在创建子进程之前将匿名管道创建完成。
  

    创建子进程,将父子进程看到同一份文件缓冲区,进而进行通讯。 
  

    父进程关闭fd[0],子进程关闭fd[1]。
  

    创建读端的相干操作。 
  利用read()函数来进行读出操作,注意read()函数的用法。
  
  read()函数用于从文件描述符(通常是套接字、文件等)读取数据。
  1. #include <unisted.h>
  2. ssize_t read(int fd, void *buf, size_t count);
复制代码
read()函数的fd参数:
  
  read()函数的buf参数:
  
  read()函数的count参数:
  
  read()函数的返回值为:
  
  

    创建写端的相干操作。
  利用write()函数来进行写入操作,注意write()函数的用法。
  
  wrtie()函数用于将数据写入文件描述符(通常是套接字、文件等)。
  1. #include <unisted.h>
  2. ssize_t write(size_t fd, void* buf, int count);
复制代码
write()函数的fd参数:
  
  write()函数的buf参数:
  
  write()函数的count参数:
  
  write()函数的返回值:
  
                

    父子既然要关闭不需要的id,为什么刚开始要打开呢??可以不关闭吗??
          为了使子进程继承下去。也可以不关闭不需要的id。文件描述符数组中的文件描述符是有限的,而且还会导致文件描述符泄漏,但是还是建议关闭,万一误写!!!!!
  2.3 管道的四种环境


       其中前面两种环境就能够很好的分析,管道是自带同步与互斥机制的,读端进程和写端进程是有一个步调和谐的过程的,不会说当管道没有数据了读端还在读取,而当管道已经满了写端还在写入。读端进程读取数据的条件是管道里面有数据,写端进程写入数据的条件是管道当中另有空间,若是条件不满意,则相应的进程就会被挂起,直到条件满意后才会被再次唤醒。
       第三种环境也很好理解,读端进程已经将管道当中的所有数据都读取出来了,而且今后也不会有写端再进行写入了,那么此时读端进程也就可以执行该进程的其他逻辑了,而不会被挂起。
       第四种环境也不难理解,既然管道当中的数据已经没有进程会读取了,那么写端进程的写入将没故意义,因此操作系统直接将写端进程杀掉。而此时子进程代码都还没跑完就被终止了,属于异常退出,那么子进程必然收到了某种信号。
2.4 管道的五种特征


       字节流,我们来做一个实行:子进程不断地向管道中输入数据,父进程不断地向管道吸收数据,我们可以看出,写入的次数与读入的次数不是逐一匹配的。
  1. // 子进程进行写入
  2. void subprocessWrite(int fd)
  3. {
  4.     std::string message = "father, I am child";
  5.     while (true)
  6.     {
  7.         // 不停地在往管道中打印数据
  8.         std::cerr << "+++++++++++++++++++++++++++++" << std::endl;
  9.         std::string info = message + getOtherMessage() + std::to_string(getpid());
  10.         std::cerr << info << std::endl;
  11.         write(fd, info.c_str(), info.size());
  12.         sleep(1);
  13.     }
  14. }
  15. // 父进程进行读取
  16. void fatherprocessread(int fd)
  17. {
  18.     char inbuffer[size]; //
  19.     while (true)
  20.     {
  21.         sleep(5); // 等待读取
  22.         ssize_t n = read(fd, inbuffer, sizeof inbuffer);
  23.         if(n > 0)
  24.         {
  25.             inbuffer[n] = '\0';
  26.             std::cout << "get message:" << inbuffer << std::endl;
  27.             sleep(1);
  28.         }
  29.         else if(n == 0)
  30.         {
  31.             std::cout << "client quit, father get return val:" << n << "father quit tool!" << std::endl;
  32.             break;
  33.         }
  34.         else if(n < 0)
  35.         {
  36.             std::cout << "read error" << std::endl;
  37.             break;
  38.         }
  39.         //break;
  40.     }
  41. }
复制代码

2.5 管道的巨细

       管道是一个文件,既然是一个文件,必然会有容量的巨细,那么管道的巨细是多少呢??当管道容量变满后,会进行壅闭。那么如何计算出管道的巨细呢?
2.5.1 使用man手册

       根据man手册,在2.6.11之前的Linux版本中,管道的最大容量与系统页面巨细相同,从Linux 2.6.11今后,管道的最大容量是65536字节。
2.5.2 使用ulimit下令

我们可以使用 ulimit -a 下令来检察当前资源限制的设定。

根据显示,管道的最大容量是 512 × 8 = 4096 512\times8=4096512×8=4096 字节。
2.5.3 自行测试

       我们可以将写端的文件描述符打开,一直进行写入操作,知道管道满为止。由图,我们可以知道我的管道的容量巨细是65536。

代码如下:

2.6 有关匿名管道的一些知识点
 


下令行中的 “|” ,就是本日学习的匿名管道: 
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4