0%

《趣谈linux操作系统》小结(三十二) - 管道

管道

这次学习了管道的具体创建与使用。

管道的创建通过调用函数

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

// create
int ret = mkfifo( "/tmp/cmd_pipe" , S_IFIFO | 0666 );

// read
pfp = fopen("/tmp/cmd_pipe", "r");
...
ret = fgets( buffer, MAX_LINE, pfp );


// write
pfp =fopen( "/tmp/cmd_pipe", "w+");

...
ret = fprintf( pfp, "Here’s a test string!\n");


行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。