execve 和管道问题 - 如何恢复原始管道?
execve and pipe issues - how to recover original pipe?
我一直在制作简单的 shell 来执行管道。
这里是一些操作管道语法的代码。
int fd[2];
int stdin_copy;
int stdout_copy;
int status;
char * msg;
if (pipe(fd) == -1) {
perror("pipe");
exit(1);
}
// fd[0] : process read from fd[0]
// fd[1] : process write to fd[1]
if (execok(pr_words) == 0) { /* is it executable? */
status = fork(); /* yes; create a new process */
if (status == -1) { /* verify fork succeeded */
perror("fork");
exit(1);
} else if (status == 0) { /* in the child process... */
stdout_copy = dup(1);
close(1); // close standard output
dup(fd[1]);
close(fd[0]);
close(fd[1]); // close and fd[1] will be stdout
pr_words[l_nwds] = NULL; /* mark end of argument array */
status = execve(path, pr_words, environ); /* try to execute it */
perror("execve"); /* we only get here if */
exit(0); /* execve failed... */
}
/*------------------------------------------------*/
/* The parent process (the shell) continues here. */
/*------------------------------------------------*/
else if (status > 0) { // in the parent process....
wait( & status); /* wait for process to end */
if (execok(af_words) == 0) {
if (pipe(fd2) == -1) {
perror("pipe");
exit(1);
}
status = fork();
if (status == -1) {
perror("fork");
exit(1);
} else if (status == 0) { // in the child process...
stdin_copy = dup(0);
close(0);
dup(fd[0]);
close(fd[1]);
close(fd[0]);
read(fd[0], readbuffer, sizeof(readbuffer));
af_words[r_nwds] = NULL; /* mark end of argument array */
status = execve(path, af_words, environ); /* try to execute it */
} else if (status > 0) {
wait( & status);
msg = "over";
write(2, msg, strlen(msg));
close(fd[0]);
close(fd[1]);
dup2(stdin_copy, 0);
dup2(stdout_copy, 1);
close(stdin_copy);
close(stdout_copy);
printf("%s", "hi");
}
} else {
/*----------------------------------------------------------*/
/* Command cannot be executed. Display appropriate message. */
/*----------------------------------------------------------*/
msg = "*** ERROR: '";
write(2, msg, strlen(msg));
write(2, af_words[0], strlen(af_words[0]));
msg = "' cannot be executed.\n";
write(2, msg, strlen(msg));
}
}
} else {
/*----------------------------------------------------------*/
/* Command cannot be executed. Display appropriate message. */
/*----------------------------------------------------------*/
msg = "*** ERROR: '";
write(2, msg, strlen(msg));
write(2, pr_words[0], strlen(pr_words[0]));
msg = "' cannot be executed.\n";
write(2, msg, strlen(msg));
}
pr_words和af_words是包含命令的二维指针,管道的右侧和左侧。 (例如 ls | cat -> pr_words = "ls[=29=]" , af_words = "cat[=30=]")
并且,首先我使用 fork() 创建子进程并为标准输出注册 fd[1]。 (并在关闭 stdin 之前保存 stdin 文件描述符)并在执行命令的左侧后,让其他子进程处理命令的右侧。
同样,我在关闭标准输出之前保存了标准输出文件描述符,并使 fd[0] 成为标准输入。通过使用 execve 函数的第一个结果的输入,我认为每个结果都会保存在 fd[1] 中。 (因为这当前已注册为标准输出)。
最后,将管道输入和输出恢复为标准输出。 (我不想用dup2,但是因为知识匮乏我别无选择)
但是,在执行这段代码时,我输入'ls | cat'后,没有任何输出。此外,我设置终端的每个条目都将打印“#”。 (这意味着 '# ls' 或 '# cat' ...)但是,在输入上面的管道命令后,该程序甚至不打印 '#'。
我猜这个程序的输入和输出流在处理管道命令后完全扭曲了。
我该如何解决?我的意思是,我想将第一个 execve 的结果保存到 fd[1] 中,在使用这个 fd[1] 执行第二个 execve 之后,最终结果将通过 stdout 文件描述打印。
我至少发现您的代码存在一些问题:
首先,您不应该在开始第二个进程之前在第一个进程上等待()。管道中只有几 KB 的缓冲区,之后如果第一个 child 进程试图继续在那里写入,您的 shell 将挂起。在为它们中的每一个等待()之前,您需要启动两个 children 。只需将第一个 wait(&status) 调用向下移动到另一个调用旁边。您可能想稍后使用 waitpid 或其他东西,这样您就知道哪个先完成,哪个状态转到哪个,但是一旦您掌握了基础知识,就可以解决这个问题。
其次,当您 fork() 时,程序中的所有变量和文件描述符映射都会被复制。因此,您不需要在 child 进程中保存 stdin 或 stdout,因为您在 child 进程中所做的更改中有 none 会影响 parent。此外,因为您只在 child 进程中初始化 stdin_copy 和 stdout_copy,所以您在第二个 fork() 之后的 parent 进程中使用的那些变量的版本未初始化。这就是导致 parent shell 的 I/O 在执行此代码后被弄乱的原因。在第二次分叉后,您实际上不需要在 parent 中执行任何操作以在那里维护原始标准输入和标准输出——在那之前,您永远不会在该过程中更改它们。您可能想从 post-fork parent 代码中删除所有这些:
close(fd[0]);
close(fd[1]);
dup2(stdin_copy, 0);
dup2(stdout_copy, 1);
close(stdin_copy);
close(stdout_copy);
第三,为什么在第二个 child 中调用 execve() 之前要从管道读取数据?这只会从管道中删除您的 exec child 永远不会看到的数据。这可能是导致管道本身似乎不起作用的原因。你可能想删除这个:
read(fd[0], readbuffer, sizeof(readbuffer));
最后,这一行可能需要在 execok() 调用之前进行(对于另一个类似的调用也是如此):
pr_words[l_nwds] = NULL; /* mark end of argument array */
代码的框架应该看起来像这样,省略了错误处理和 execok 检查,并演示了 waitpid() 的使用,如果你想知道哪个状态代码是哪个 child:
int child_pid[2];
child_pid[0] = fork();
if (child_pid[0] == 0) {
// first child, close stdout and replace with pipe, then exec
} else {
child_pid[1] = fork();
if (child_pid[1] == 0) {
// second child, close stdin and replace with pipe, then exec
} else {
// parent, and now we have the pids of the children
waitpid(child_pid[0], &status, 0); // wait for first child
waitpid(child_pid[1], &status, 0); // wait for second child
// *do not* mess with stdin/stdout, they are okay here
}
}
我一直在制作简单的 shell 来执行管道。
这里是一些操作管道语法的代码。
int fd[2];
int stdin_copy;
int stdout_copy;
int status;
char * msg;
if (pipe(fd) == -1) {
perror("pipe");
exit(1);
}
// fd[0] : process read from fd[0]
// fd[1] : process write to fd[1]
if (execok(pr_words) == 0) { /* is it executable? */
status = fork(); /* yes; create a new process */
if (status == -1) { /* verify fork succeeded */
perror("fork");
exit(1);
} else if (status == 0) { /* in the child process... */
stdout_copy = dup(1);
close(1); // close standard output
dup(fd[1]);
close(fd[0]);
close(fd[1]); // close and fd[1] will be stdout
pr_words[l_nwds] = NULL; /* mark end of argument array */
status = execve(path, pr_words, environ); /* try to execute it */
perror("execve"); /* we only get here if */
exit(0); /* execve failed... */
}
/*------------------------------------------------*/
/* The parent process (the shell) continues here. */
/*------------------------------------------------*/
else if (status > 0) { // in the parent process....
wait( & status); /* wait for process to end */
if (execok(af_words) == 0) {
if (pipe(fd2) == -1) {
perror("pipe");
exit(1);
}
status = fork();
if (status == -1) {
perror("fork");
exit(1);
} else if (status == 0) { // in the child process...
stdin_copy = dup(0);
close(0);
dup(fd[0]);
close(fd[1]);
close(fd[0]);
read(fd[0], readbuffer, sizeof(readbuffer));
af_words[r_nwds] = NULL; /* mark end of argument array */
status = execve(path, af_words, environ); /* try to execute it */
} else if (status > 0) {
wait( & status);
msg = "over";
write(2, msg, strlen(msg));
close(fd[0]);
close(fd[1]);
dup2(stdin_copy, 0);
dup2(stdout_copy, 1);
close(stdin_copy);
close(stdout_copy);
printf("%s", "hi");
}
} else {
/*----------------------------------------------------------*/
/* Command cannot be executed. Display appropriate message. */
/*----------------------------------------------------------*/
msg = "*** ERROR: '";
write(2, msg, strlen(msg));
write(2, af_words[0], strlen(af_words[0]));
msg = "' cannot be executed.\n";
write(2, msg, strlen(msg));
}
}
} else {
/*----------------------------------------------------------*/
/* Command cannot be executed. Display appropriate message. */
/*----------------------------------------------------------*/
msg = "*** ERROR: '";
write(2, msg, strlen(msg));
write(2, pr_words[0], strlen(pr_words[0]));
msg = "' cannot be executed.\n";
write(2, msg, strlen(msg));
}
pr_words和af_words是包含命令的二维指针,管道的右侧和左侧。 (例如 ls | cat -> pr_words = "ls[=29=]" , af_words = "cat[=30=]")
并且,首先我使用 fork() 创建子进程并为标准输出注册 fd[1]。 (并在关闭 stdin 之前保存 stdin 文件描述符)并在执行命令的左侧后,让其他子进程处理命令的右侧。
同样,我在关闭标准输出之前保存了标准输出文件描述符,并使 fd[0] 成为标准输入。通过使用 execve 函数的第一个结果的输入,我认为每个结果都会保存在 fd[1] 中。 (因为这当前已注册为标准输出)。
最后,将管道输入和输出恢复为标准输出。 (我不想用dup2,但是因为知识匮乏我别无选择)
但是,在执行这段代码时,我输入'ls | cat'后,没有任何输出。此外,我设置终端的每个条目都将打印“#”。 (这意味着 '# ls' 或 '# cat' ...)但是,在输入上面的管道命令后,该程序甚至不打印 '#'。
我猜这个程序的输入和输出流在处理管道命令后完全扭曲了。
我该如何解决?我的意思是,我想将第一个 execve 的结果保存到 fd[1] 中,在使用这个 fd[1] 执行第二个 execve 之后,最终结果将通过 stdout 文件描述打印。
我至少发现您的代码存在一些问题:
首先,您不应该在开始第二个进程之前在第一个进程上等待()。管道中只有几 KB 的缓冲区,之后如果第一个 child 进程试图继续在那里写入,您的 shell 将挂起。在为它们中的每一个等待()之前,您需要启动两个 children 。只需将第一个 wait(&status) 调用向下移动到另一个调用旁边。您可能想稍后使用 waitpid 或其他东西,这样您就知道哪个先完成,哪个状态转到哪个,但是一旦您掌握了基础知识,就可以解决这个问题。
其次,当您 fork() 时,程序中的所有变量和文件描述符映射都会被复制。因此,您不需要在 child 进程中保存 stdin 或 stdout,因为您在 child 进程中所做的更改中有 none 会影响 parent。此外,因为您只在 child 进程中初始化 stdin_copy 和 stdout_copy,所以您在第二个 fork() 之后的 parent 进程中使用的那些变量的版本未初始化。这就是导致 parent shell 的 I/O 在执行此代码后被弄乱的原因。在第二次分叉后,您实际上不需要在 parent 中执行任何操作以在那里维护原始标准输入和标准输出——在那之前,您永远不会在该过程中更改它们。您可能想从 post-fork parent 代码中删除所有这些:
close(fd[0]);
close(fd[1]);
dup2(stdin_copy, 0);
dup2(stdout_copy, 1);
close(stdin_copy);
close(stdout_copy);
第三,为什么在第二个 child 中调用 execve() 之前要从管道读取数据?这只会从管道中删除您的 exec child 永远不会看到的数据。这可能是导致管道本身似乎不起作用的原因。你可能想删除这个:
read(fd[0], readbuffer, sizeof(readbuffer));
最后,这一行可能需要在 execok() 调用之前进行(对于另一个类似的调用也是如此):
pr_words[l_nwds] = NULL; /* mark end of argument array */
代码的框架应该看起来像这样,省略了错误处理和 execok 检查,并演示了 waitpid() 的使用,如果你想知道哪个状态代码是哪个 child:
int child_pid[2];
child_pid[0] = fork();
if (child_pid[0] == 0) {
// first child, close stdout and replace with pipe, then exec
} else {
child_pid[1] = fork();
if (child_pid[1] == 0) {
// second child, close stdin and replace with pipe, then exec
} else {
// parent, and now we have the pids of the children
waitpid(child_pid[0], &status, 0); // wait for first child
waitpid(child_pid[1], &status, 0); // wait for second child
// *do not* mess with stdin/stdout, they are okay here
}
}