通信管网工程
乐鱼体育直播:图解 Linux 管道通信的原理?
  发表时间:2022-07-06 | 来源:乐鱼体育直播下载 作者:乐鱼体育app靠谱 

  处于安全的考虑,不同进程之间的内存空间是相互隔离的,也就是说 进程A 是不能访问 进程B 的内存空间,反之亦然。如果不同进程间能够相互访问和修改对方的内存,那么当前进程的内存就有可能被其他进程非法修改,从而导致安全隐患。

  但某些场景下,不同进程间需要相互通信,比如: 进程A 负责处理用户的请求,而 进程B 负责保存处理后的数据。那么当 进程A 处理完请求后,就需要把处理后的数据提交给 进程B 进行存储。此时, 进程A 就需要与 进程B 进行通信。如下图所示:

  由于不同进程间是相互隔离的,所以必须借助内核来作为桥梁来进行相互通信,内核相当于岛屿之间的轮船,如下图所示:

  内核提供多种进程间通信的方式,如: 共享内存 , 信号 , 消息队列 和 管道(pipe) 等。本文主要介绍 管道 的原理与实现。

  由于管道分为读端和写端,所以需要两个文件描述符来管理管道: fd[0] 为读端, fd[1] 为写端。

  每个进程的用户空间都是独立的,但内核空间却是共用的。所以,进程间通信必须由内核提供服务。前面介绍了 管道(pipe) 的使用,接下来将会介绍管道在内核中的实现方式。

  在内核中, 管道 使用了环形缓冲区来存储数据。环形缓冲区的原理是:把一个缓冲区当成是首尾相连的环,其中通过读指针和写指针来记录读操作和写操作位置。如下图所示:

  在 Linux 内核中,使用了 16 个内存页作为环形缓冲区,所以这个环形缓冲区的大小为 64KB(16 * 4KB)。

  当向管道写数据时,从写指针指向的位置开始写入,并且将写指针向前移动。而从管道读取数据时,从读指针开始读入,并且将读指针向前移动。当对没有数据可读的管道进行读操作,将会阻塞当前进程。而对没有空闲空间的管道进行写操作,也会阻塞当前进程。

  offset :如果进程正在读取当前内存页的数据,那么 offset 指向正在读取当前内存页的偏移量。

  管道的环形缓冲区实现方式与经典的环形缓冲区实现方式有点区别,经典的环形缓冲区一般先申请一块地址连续的内存块,然后通过读指针与写指针来对读操作与写操作进行定位。

  但为了减少对内存的使用,内核不会在创建管道时就申请 64K 的内存块,而是在进程向管道写入数据时,按需来申请内存。

  从 经典的环形缓冲区 中读取数据时,首先通过读指针来定位到读取数据的起始地址,然后判断环形缓冲区中是否有数据可读,如果有就从环形缓冲区中读取数据到用户空间的缓冲区中。如下图所示:

  而 管道的环形缓冲区 与 经典的环形缓冲区 实现稍有不同, 管道的环形缓冲区 其读指针是由 pipe_inode_info 对象的 curbuf 字段与 pipe_buffer 对象的 offset 字段组合而成:

  pipe_buffer 对象的 offset 字段表示读操作要从内存页的哪个位置开始读取数据。

  从缓冲区中读取到 n 个字节的数据后,会相应移动读指针 n 个字节的位置(也就是增加 pipe_buffer 对象的 offset 字段),并且减少 n 个字节的可读数据长度(也就是减少 pipe_buffer 对象的 len 字段)。

  我们来看看管道读操作的代码实现,读操作由 pipe_read 函数完成。为了突出重点,我们只列出关键代码,如下所示:

  通过 pipe_inode_info 对象的 curbuf 字段获取读操作应该从环形缓冲区的哪个内存页处读取数据。

  通过 pipe_buffer 对象的 offset 字段获取真正的读指针, 并且从管道中读取数据到用户缓冲区。

  如果当前内存页的数据已经被读取完毕,那么移动 pipe_inode_info 对象的 curbuf 指针,并且减少其 nrbufs 字段的值。

  经典的环形缓冲区 写入数据时,首先通过写指针进行定位要写入的内存地址,然后判断环形缓冲区的空间是否足够,足够就把数据写入到环形缓冲区中。如下图所示:

  但 管道的环形缓冲区 并没有保存 写指针 ,而是通过 读指针 计算出来。那么怎么通过读指针计算出写指针呢?

  然后再通过 pipe_buffer 对象的 offset 字段和 len 字段来定位到,应该写入到内存页的哪个位置。

  // 2. 如果最后写入的 pipe_buffer 空闲空间不足, 那么申请一个新的内存页来存储数据

  如果上次写操作写入的 pipe_buffer 还有空闲的空间,那么就将数据写入到此 pipe_buffer 中,并且增加其 len 字段的值。

  如果上次写操作写入的 pipe_buffer 没有足够的空闲空间,那么就新申请一个内存页,并且把数据保存到新的内存页中,并且增加 pipe_inode_info 的 nrbufs 字段的值。

  这是因为父子进程通过 pipe 系统调用打开的管道,在内核空间中指向同一个管道对象( pipe_inode_info )。所以父子进程共享着同一个管道对象,那么就可以通过这个共享的管道对象进行通信。

  因为使用 pipe 系统调用打开管道时,并没有立刻申请内存页,而是当有进程向管道写入数据时,才会按需申请内存页。当内存页的数据被读取完后,内核会将此内存页回收,来减少管道对内存的使用。

Copyright (C) 2019  乐鱼体育直播下载-乐鱼体育app靠谱  乐鱼体育直播下载-乐鱼体育app靠谱   All rights reserved   版权所有
联系地址:长沙市芙蓉区马王堆中路蔚蓝天空大厦九楼    电话:0731-8235888  传真:0731-82610000

湘ICP备13006809号