Linux popen()函数内部实现原理

最近发现了一个函数 popen()/pclose() ,所以现在打算理清下思路…

正文

linux下的 popen 函数,大概就是通过 fork 一个子进程来执行命令并返回执行的结果给父进程.

The popen() function shall execute the command specified by the string command. It shall create a pipe between the calling program and the executed command, and shall return a pointer to a stream that can be used to either read from or write to the pipe.

http://pubs.opengroup.org/onlinepubs/009695399/functions/popen.html

函数声明如下

1
2
3
4
5
6
7
8
9
#include<stdio.h>
/* Create a new stream connected to a pipe running the given command.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern FILE *popen (const char *__command, const char *__modes) __wur;
/* Close a stream opened by popen and return the status of its child.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern int pclose (FILE *__stream);

当然了,更重要的是要知道该函数内部是怎么实现的,大概步骤如下:(此处仅讨论子进程返回结果给父进程!)

  1. 父进程通过 pipe() 创建 读/写匿名管道 ,并关闭写管道pfd[1]
  2. 然后 fork() 创建子进程
  3. 子进程 close() 读管道pfd[0]和标准输出(STDOUT_FILENO=1) ,并 dup() 写管道pfd[1]
  4. 子进程通过 execl() 执行命令,执行的结果为标准输出重定向到了写管道pfd[1]
  5. 父进程 wait() 子进程退出,接着 fdopen() 通过文件描述符返回一个 FILE* . 注意返回文件指针时,不能关闭读管道pfd[0],可以通过后续 fclose() 关闭该描述符
  6. fclose() 关闭该读管道pfd[0]

简单实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
FILE* mypopen(const char*cmd){
//管道文件描述符
int pfd[2];
if(pipe(pfd)!=0){
printf("error: %s\n",strerror(errno));
exit(EXIT_FAILURE);
}
//创建子进程
pid_t pid=fork();
if(pid==0){
close(pfd[0]);

//重定向 标准输出 到 写管道
close(STDOUT_FILENO);
dup(pfd[1]);
char _cmd[50]{0};
sprintf(_cmd,"%s",cmd);
execl("/bin/sh","sh","-c",_cmd,NULL);
exit(0);
}
//关闭写管道
close(pfd[1]);
//等待子进程退出
int p= wait(0);
//printf("%d terminal\n",p);
//直接读取数据
//char buffer[1024*2]={0};
//read(pfd[0],buffer,1024*2);
FILE* pfile =fdopen(pfd[0],"r");
if(!pfile) return NULL;
return pfile;
}
1
2
3
4
5
6
7
8
9
10
int main() {
FILE *file= mypopen("ls -lSah");
char buffer[1024];
while(fgets(buffer,1024,file)){
printf("%s",buffer);
}
//close pfd[0]!
fclose(file);
return 0;
}

其中,较重要的是在子进程中重定向标准输出,这里给出其他方法重定向标准输出到管道描述符

1
2
3
4
5
6
7
8
9
//方法1
//重定向 标准输出 到 写管道
close(STDOUT_FILENO);
dup(pfd[1]);
//方法2,相当于先close(STDOUT_FILENO) ,再dup(pfd[1])
dup2(pfd[1],STDOUT_FILENO);
//方法3
close(STDOUT_FILENO);
int fd=fcntl(pfd[1],F_DUPFD);

结尾

参考文章: http://blog.csdn.net/litingli/article/details/5891726


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!