管道
这次学习了管道的具体创建与使用。
管道的创建通过调用函数
1 | int pipe(fd[2]); |
返回了两个文件描述符,这表示管道的两端,一个是管道的读取端描述符 fd[0],另一个是管道的写入端描述符 fd[1]。先读后写。
pipe
系统调用,最终是调到一个pipe2
的函数.这里面要创建一个数组 files,用来存放管道的两端的打开文件,另一个数组 fd 存放管道的两端的文件描述符。
匿名管道
所谓的匿名管道,其实就是内核里面的一串缓存。读写管道的文件的操作就是对这段缓存的操作。创建的两个文件描述符都是在一个进程里面的,通过fork创建子进程的方式,使得子进程也可以访问相同的文件,最终实现父子进程间的通信。
父进程和子进程都可以写入,也都可以读出,通常的方法是父进程关闭读取的 fd,只保留写入的 fd,而子进程关闭写入的 fd,只保留读取的 fd,如果需要双向通行,则应该创建两个管道。具体的操作需要程序员自己掌控。
好了,那么shell命令怎么实现的管道呢。其实,就是先fork一个进程A,进程A保留写入的fd。此时shell保留读端的fd。 然后再fork一个进程B,这样进程B就可以有读端的fd,shell进程关掉自己的读端功能。A和B就分别控制管道的两端了。至于输入输出传输,需要把读端的fd和进程B的标准输入fd关联起来,这是调用的
1 | int dup2(int oldfd, int newfd); |
将老的文件描述符赋值给新的文件描述符,让 newfd 的值和 oldfd 一样。 具体的就是进程A调用dup2(fd[1],STDOUT_FILENO),写端对应A的标准输出。进程B调用dup2(fd[0],STDIN_FILENO),读端对应B的标准输入。这样,就实现了读写的无缝衔接。 A以后往标准输出写入的任何东西,都会写入管道文件。B以后从标准输入读取的任何东西,都来自于管道文件。
命名管道
命名管道需要事先通过命令 mkfifo,进行创建。如果是通过代码创建命名管道,也有一个函数,但是这不是一个系统调用,而是 Glibc 提供的函数,也叫mkfifo。
1 | int mkfifo (const char *path, mode_t mode) |
Glibc 的 mkfifo 函数会调用 mknodat 系统调用,还记得咱们学字符设备的时候,创建一个字符设备的时候,也是调用的 mknod。这里命名管道也是一个设备,因而我们也用 mknod。
接下来,要打开这个管道文件,我们还是会调用文件系统的 open 函数。所谓的命名管道,其实是也是内核里面的一串缓存。
命名管道的创建与使用伪代码:
1 |
|
行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。