匿名管道为什么可以在父子进程间通信

网上关于匿名管道的用法已经非常多了,这里就不再过多的介绍,只关注本篇的主题.

匿名管道创建函数pipe()系统调用底层的实现就相当于一个特殊的文件系统,每次调用的时候创建一个inode关联着两个file,一个用于读,一个用于写,从而实现数据的单向流动.

一个管道实际上就是一个无形(只存在于内存中)的文件,对这个文件的操作要通过两个已经打开的文件进行,分别代表该管道的两端 .

每个文件都是有一个inode数据结构代表的。虽然一个管道实际上是一个无形的文件,但是也得有一个inode数据结构。由于这个文件在创建管道之前并不存在,所以需要在创建管道时临时创建一个inode结构。

大致过程是先分配一个内存页面用做管道的缓冲区,再分配一个缓冲区用作pipe_inode_info数据结构。为什么要这么做?用来实现管道的文件是无形的,它并不出现在磁盘或者其他的文件系统存储介质上,而只存在于内存空间,其他进程也无法“打开”或者访问这个文件。所以,这个所谓文件实质上只是一个用作缓冲区的内存页面,只是把它纳入了文件系统的机制,借用了文件系统的各种数据结构和操作加以管理。

读写文件的函数read和write,也适用于匿名管道,他们在底层会作区别,分别是调用pipe_read(),pipe_wrtie().

管道写函数通过将字节复制到 VFS 索引节点指向的物理内存而写入数据,而管道读函数则通过复制物理内存中的字节而读出数据。当然,内核必须利用一定的机制同步对管道的访问,为此,内核使用了锁、等待队列和信号。

当写进程向管道中写入时,它利用标准的库函数write(),系统根据库函数传递的文件描述符,可找到该文件的 file 结构。file 结构中指定了用来进行写操作的函数(即写入函数)地址,于是,内核调用该函数完成写操作。

写入函数在向内存中写入数据之前,必须首先检查 VFS 索引节点中的信息,同时满足如下条件时,才能进行实际的内存复制工作:
    内存中有足够的空间可容纳所有要写入的数据;
    内存没有被读程序锁定。

如果同时满足上述条件,写入函数首先锁定内存,然后从写进程的地址空间中复制数据到内存。否则,写入进程就休眠在 VFS 索引节点的等待队列中,接下来,内核将调用调度程序,而调度程序会选择其他进程运行。

写入进程实际处于可中断的等待状态,当内存中有足够的空间可以容纳写入数据,或内存被解锁时,读取进程会唤醒写入进程,这时,写入进程将接收到信号。当数据写入内存之后,内存被解锁,而所有休眠在索引节点的读取进程会被唤醒。

管道的读取过程和写入过程类似。但是,进程可以在没有数据或内存被锁定时立即返回错误信息,而不是阻塞该进程,这依赖于文件或管道的打开模式。反之,进程可以休眠在索引节点的等待队列中等待写入进程写入数据。当所有的进程完成了管道操作之后,管道的索引节点被丢弃,而共享数据页也被释放.

好吧,写了那么多,查了那么多资料,估计现在还是蒙逼的,直接以我们能明白的方式来说吧:

本质上,匿名管道是操作系统在进程内核空间申请的一块内存(比如一个内存页,一般是 4KB),然后操作系统把这块内存当成一个先进先出(FIFO)的循环队列来存取数据,这一切都由操作系统帮助我们实现了。

也就是说往匿名管道的一端写数据,另一端就会收到数据,就这么简单.在同一个进程中这样中当然是可以的,但是意义不大.

如果一个进程创建了匿名管道,然后fork出子进程,那么好戏就来了.由于子进程共享父进程的某些数据结构,而匿名管道也正好在其中,所以父进程和子进程就可以通过匿名管道通信了.究竟子进程和父进程是怎么共享的,我们就来谈谈这个--进程调用fork与文件描述符的共享(fork,dump)

Linux的进程描述task_struct{}中有一个数组专门用于记录一打开的文件,其中文件描述符作为该数组的下标,数组元素为指向所打开的文件所创建的文件表项。如下图所示,文件表项是用于描述文件当前被某个进程打开后的状态信息,包括文件状态标志,记录当前文件读取的位移量(可以通过接口lseek设置),以及文件的i节点指针(i节点描述文件的具体信息,如:创建,修改时间,文件大小,文件存储的块信息)。不同进程打开同一个文件后,进程表和文件表的关系如下图所示:


进程的所打开文件和在fork后的结构图如下所示,子进程是共享父进程的文件表项:


前面介绍了,匿名管道也以文件的形式来设计,所以父进程fork子进程之后,他们共享匿名管道的文件表项.父子进程操作匿名管道好像就是在同一个进程中进行,当然能实现通信了.

最后我们说一下dup,dup2函数:

当调用dup函数时,内核在进程中创建一个新的文件描述符,此描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。 

dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则先将其关闭。如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。dup2函数返回的新文件描述符同样与参数oldfd共享同一文件表项。  

参考:

https://blog.csdn.net/ordeder/article/details/21716639

https://blog.csdn.net/silent123go/article/details/71108501

https://www.linuxidc.com/Linux/2017-11/148216.htm

https://blog.csdn.net/vonzhoufz/article/details/44494669

https://segmentfault.com/a/1190000009528245

https://blog.csdn.net/cywosp/article/details/38965239

https://blog.csdn.net/u014379540/article/details/53456070

猜你喜欢

转载自blog.csdn.net/zxm342698145/article/details/80022197