引子:在Linux系统的终端上输入ps aux | grep bash
,会在终端上输出:
lapvir@ubuntu:~$ ps aux | grep bash
lapvir 3330 0.0 0.2 29568 5060 pts/0 Ss+ 15:47 0:00 /bin/bash
lapvir 3507 0.0 0.2 29476 4988 pts/4 Ss 15:58 0:00 bash
lapvir 5482 0.0 0.0 21292 968 pts/4 S+ 19:48 0:00 grep --color=auto bash
这里的|
就是管道的意思,ps aux
将数据写入到终端上,grep bash
从终端上读取并搜索出含有bash
的进程。下面我将剖析一下这背后的过程。
Linux系统中默认了前三个文件标识符,指向dev/tty
这个文件,如下图所示:
而我将ps aux | grep bash
理解为下图所示的过程。
接下来我将要实现的功能是父进程通过管道写入ps aux
,子进程通过管道读取数据并搜索出含有bash
的进程。为了实现这个功能需要将输入输出的文件标识符从终端指向管道的两端,这自然就引出了dup2()
函数。下面是是这个过程的示意图:
dup2函数的函数原型为:
#include <unistd.h>
int dup2(int oldfd, int newfd);
这个函数的功能是让文件标识符newfd指向文件标识符oldfd所指向的文件。
举例:
int fd = open("temp", O_CREAT | O_RDWR, 0664);
dup2(fd, STDOUT_FILENO);
close(fd);
我们打开了一个叫temp的新文件,并创建一个文件描述符fd指向temp,我们调用dup2函数,这会让我们用fd这个文件描述符替换掉STDOUT_FILENO这个文件描述符所指向的位置,这样原本任何从终端写出的东西,都会写到fd这个文件描述符所指向的文件中,即temp。需要注意的是,dup2函数在将fd复制到STDOUT_FILENO后,就会将fd给关闭。理解了dep2这个函数,接下来的就容易理解了。
下面我将结合代码来具体讲解这一个过程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main(int argc, const char* argv[]){
int fd[2];
int ret = pipe(fd);//创建管道
if(ret == -1){
perror("pipe error");
exit(1);
}
pid_t pid = fork();//创建子进程,这样子进程也有两个文件描述符,读端和写端
if(pid == -1){
perror("fork error");
exit(1);
}
if(pid>0){
close(fd[0]);//父进程关闭读端
dup2(fd[1],STDOUT_FILENO);//数据重定向,任何stdout写出去的东西,现在都将写到fd[1]
execlp("ps", "ps", "aux", NULL);//加载一个进程
perror("execlp error");
exit(1);
}else if(pid == 0){
close(fd[1]);
dup2(fd[0], STDIN_FILENO);//数据重定向任何stdin读到的东西,现在都被fd[0]读取
execlp("grep", "grep", "bash", NULL);//grep bash,搜索管道里的内容bash
}
printf("pipe[0] = %d\n", fd[0]);
printf("pipe[1] = %d\n", fd[1]);
close(fd[0]);
close(fd[1]);
return 0;
}
关于stdout是写入,stdin是读取,这一个地方我感觉是挺难理解的,我得想法是这样的:
站在终端的角度上,stdout把东西写到终端上,stdin是终端读取东西。
例如在终端上输入ps aux
,通过文件描述符STDOUT_FILENO
把数据直接写到当前终端上。
非常感谢这个视频中老师对这一块的讲解,感兴趣的小伙伴可以看一下。