Ric*_*ral 2 c fork pipe exec dup2
几天我就提出了一个问题.我的解决方案符合接受的答案中的建议.但是,我的一个朋友提出了以下解决方案:
请注意,代码已更新几次(查看编辑修订版)以反映下面答案中的建议.如果您打算给出新的答案,请考虑这个新代码,而不是那些有很多问题的旧代码.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]){
int fd[2], i, aux, std0, std1;
do {
std0 = dup(0); // backup stdin
std1 = dup(1); // backup stdout
// let's pretend I'm reading commands here in a shell prompt
READ_COMMAND_FROM_PROMPT();
for(i=1; i<argc; i++) {
// do we have a previous command?
if(i > 1) {
dup2(aux, 0);
close(aux);
}
// do we have a next command?
if(i < argc-1) {
pipe(fd);
aux = fd[0];
dup2(fd[1], 1);
close(fd[1]);
}
// last command? restore stdout...
if(i == argc-1) {
dup2(std1, 1);
close(std1);
}
if(!fork()) {
// if not last command, close all pipe ends
// (the child doesn't use them)
if(i < argc-1) {
close(std0);
close(std1);
close(fd[0]);
}
execlp(argv[i], argv[i], NULL);
exit(0);
}
}
// restore stdin to be able to keep using the shell
dup2(std0, 0);
close(std0);
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这通过像bash这样的管道模拟一系列命令,例如:cmd1 | cmd2 | ...... | cmd_n.我说"模拟",因为,正如您所看到的,命令实际上是从参数中读取的.只是为业余时间编写一个简单的shell提示符......
当然,有一些问题需要修复,并添加像错误处理,但这不是重点.我觉得我有点得到代码,但它仍然让我很困惑这整个事情的工作原理.
我错过了什么或这真的有效,这是解决问题的一个很好的清洁解决方案吗?如果没有,有人能指出这个代码的关键问题吗?
看起来合理,虽然它确实需要修复泄漏std和aux孩子以及循环后,父母的原始文件stdin永远丢失.
颜色可能会更好......
./a.out foo bar baz <stdin >stdout
std = dup(stdout) || |+==========================std
|| || ||
pipe(fd) || || pipe1[0] -- pipe0[1] ||
|| || || || ||
aux = fd[0] || || aux || ||
|| XX || || ||
|| /-------++----------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
close(fd[1]) || || || XX ||
|| || || ||
fork+exec(foo) || || || ||
XX || || ||
/-----++-------+| ||
dup2(aux, 0) // || || ||
|| || || ||
close(aux) || || XX ||
|| || ||
pipe(fd) || || pipe2[0] -- pipe2[1] ||
|| || || || ||
aux = fd[0] || || aux || ||
|| XX || || ||
|| /-------++----------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
close(fd[1]) || || || XX ||
|| || || ||
fork+exec(bar) || || || ||
XX || || ||
/-----++-------+| ||
dup2(aux, 0) // || || ||
|| || || ||
close(aux) || || XX ||
|| || ||
pipe(fd) || || pipe3[0] -- pipe3[1] ||
|| || || || ||
aux = fd[0] || || aux || ||
|| XX || || ||
|| /-------++----------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
close(fd[1]) || || || XX ||
|| XX || ||
|| /-------++-----------------+|
dup2(std, 1) || // || ||
|| || || ||
fork+exec(baz) || || || ||
foo得到stdin=stdin,stdout=pipe1[1]bar得到stdin=pipe1[0],stdout=pipe2[1]baz得到stdin=pipe2[0],stdout=stdout我的建议是不同的,因为它避免了修改父级,stdin并且stdout只在子级内操纵它们,并且永远不会泄漏任何FD.但是,图表有点难度.
for cmd in cmds
if there is a next cmd
pipe(new_fds)
fork
if child
if there is a previous cmd
dup2(old_fds[0], 0)
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
close(new_fds[0])
dup2(new_fds[1], 1)
close(new_fds[1])
exec cmd || die
else
if there is a previous cmd
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
old_fds = new_fds
Run Code Online (Sandbox Code Playgroud)
parent
cmds = [foo, bar, baz]
fds = {0: stdin, 1: stdout}
cmd = cmds[0] {
there is a next cmd {
pipe(new_fds)
new_fds = {3, 4}
fds = {0: stdin, 1: stdout, 3: pipe1[0], 4: pipe1[1]}
}
fork => child
there is a next cmd {
close(new_fds[0])
fds = {0: stdin, 1: stdout, 4: pipe1[1]}
dup2(new_fds[1], 1)
fds = {0: stdin, 1: pipe1[1], 4: pipe1[1]}
close(new_fds[1])
fds = {0: stdin, 1: pipe1[1]}
}
exec(cmd)
there is a next cmd {
old_fds = new_fds
old_fds = {3, 4}
}
}
cmd = cmds[1] {
there is a next cmd {
pipe(new_fds)
new_fds = {5, 6}
fds = {0: stdin, 1: stdout, 3: pipe1[0], 4: pipe1[1],
5: pipe2[0], 6: pipe2[1]}
}
fork => child
there is a previous cmd {
dup2(old_fds[0], 0)
fds = {0: pipe1[0], 1: stdout,
3: pipe1[0], 4: pipe1[1],
5: pipe2[0], 6: pipe2[1]}
close(old_fds[0])
fds = {0: pipe1[0], 1: stdout,
4: pipe1[1],
5: pipe2[0] 6: pipe2[1]}
close(old_fds[1])
fds = {0: pipe1[0], 1: stdout,
5: pipe2[0], 6: pipe2[1]}
}
there is a next cmd {
close(new_fds[0])
fds = {0: pipe1[0], 1: stdout, 6: pipe2[1]}
dup2(new_fds[1], 1)
fds = {0: pipe1[0], 1: pipe2[1], 6: pipe2[1]}
close(new_fds[1])
fds = {0: pipe1[0], 1: pipe1[1]}
}
exec(cmd)
there is a previous cmd {
close(old_fds[0])
fds = {0: stdin, 1: stdout, 4: pipe1[1],
5: pipe2[0], 6: pipe2[1]}
close(old_fds[1])
fds = {0: stdin, 1: stdout, 5: pipe2[0], 6: pipe2[1]}
}
there is a next cmd {
old_fds = new_fds
old_fds = {3, 4}
}
}
cmd = cmds[2] {
fork => child
there is a previous cmd {
dup2(old_fds[0], 0)
fds = {0: pipe2[0], 1: stdout,
5: pipe2[0], 6: pipe2[1]}
close(old_fds[0])
fds = {0: pipe2[0], 1: stdout,
6: pipe2[1]}
close(old_fds[1])
fds = {0: pipe2[0], 1: stdout}
}
exec(cmd)
there is a previous cmd {
close(old_fds[0])
fds = {0: stdin, 1: stdout, 6: pipe2[1]}
close(old_fds[1])
fds = {0: stdin, 1: stdout}
}
}
您更新的代码确实修复了以前的FD泄漏...但是添加了一个:您现在正在泄漏std0给孩子们.正如乔恩所说,这对大多数程序来说可能并不危险......但是你仍然应该写一个比这更好的shell.
即使它是临时的,我强烈建议不要修改你自己的shell标准输入/输出/错误(0/1/2),只在exec之前的子进程中这样做.为什么?假设您printf在中间添加了一些调试,或者由于错误情况需要挽救.如果你不先清理乱糟糟的标准文件描述符,你就会遇到麻烦.请为了让事情在意外情况下按预期运行,在你需要之前不要捣乱.
正如我在其他评论中提到的那样,将其拆分为更小的部分会使其更容易理解.这个小助手应该很容易理解并且没有错误:
/* cmd, argv: passed to exec
* fd_in, fd_out: when not -1, replaces stdin and stdout
* return: pid of fork+exec child
*/
int fork_and_exec_with_fds(char *cmd, char **argv, int fd_in, int fd_out) {
pid_t child = fork();
if (fork)
return child;
if (fd_in != -1 && fd_in != 0) {
dup2(fd_in, 0);
close(fd_in);
}
if (fd_out != -1 && fd_in != 1) {
dup2(fd_out, 1);
close(fd_out);
}
execvp(cmd, argv);
exit(-1);
}
Run Code Online (Sandbox Code Playgroud)
应该这样:
void run_pipeline(int num, char *cmds[], char **argvs[], int pids[]) {
/* initially, don't change stdin */
int fd_in = -1, fd_out;
int i;
for (i = 0; i < num; i++) {
int fd_pipe[2];
/* if there is a next command, set up a pipe for stdout */
if (i + 1 < num) {
pipe(fd_pipe);
fd_out = fd_pipe[1];
}
/* otherwise, don't change stdout */
else
fd_out = -1;
/* run child with given stdin/stdout */
pids[i] = fork_and_exec_with_fds(cmds[i], argvs[i], fd_in, fd_out);
/* nobody else needs to use these fds anymore
* safe because close(-1) does nothing */
close(fd_in);
close(fd_out);
/* set up stdin for next command */
fd_in = fd_pipe[0];
}
}
Run Code Online (Sandbox Code Playgroud)
你可以看到猛砸的execute_cmd.c#execute_disk_command被称为从execute_cmd.c#execute_pipeline,XSH的process.c#process_run被称为从jobs.c#job_run,甚至每一个人的BusyBox的各种 小 和 最小的 贝壳拆分起来.
| 归档时间: |
|
| 查看次数: |
9670 次 |
| 最近记录: |