我在两个子进程之间创建了pipe
,
首先,我运行ls
,写入正确的fd,
然后,我运行grep r
,它从正确的fd读取,
我可以在终端中看到grep
命令工作正常(输出)
问题在于grep
没有退出,即使ls
不再运行,它也会停留在那里
对于其他程序pipe
工作正常..
for (i = 0; i < commands_num ; i++) { //exec all the commands instants
if (pcommands[i]._flag_pipe_out == 1) { //creates pipe if necessary
if (pipe(pipe_fd) == -1) {
perror("Error: \"pipe()\" failed");
}
pcommands[i]._fd_out = pipe_fd[1];
pcommands[i+1]._fd_in = pipe_fd[0];
}
pid = fork(); //the child exec the commands
if (pid == -1) {
perror("Error: \"fork()\" failed");
break;
} else if (!pid) { //child process
if (pcommands[i]._flag_pipe_in == 1) { //if there was a pipe to this command
if (dup2(pcommands[i]._fd_in, STDIN) == -1) {
perror("Error: \"dup2()\" failed");
exit(0);
}
close(pcommands[i]._fd_in);
}
if (pcommands[i]._flag_pipe_out == 1) { //if there was a pipe from this command
if (dup2(pcommands[i]._fd_out, STDOUT) == -1) {
perror("Error: \"dup2()\" failed");
exit(0);
}
close(pcommands[i]._fd_out);
}
execvp(pcommands[i]._commands[0] , pcommands[i]._commands); //run the command
perror("Error: \"execvp()\" failed");
exit(0);
} else if (pid > 0) { //father process
waitpid(pid, NULL, WUNTRACED);
}
}
//closing all the open fd's
for (i = 0; i < commands_num ; i++) {
if (pcommands[i]._fd_in != STDIN) { //if there was an other stdin that is not 0
close(pcommands[i]._fd_in);
}
if (pcommands[i]._fd_out != STDOUT) { //if there was an other stdout that is not 1
close(pcommands[i]._fd_out);
}
}
所以,我有一个&#34;命令&#34;即时pcommands[i]
它有:
pipein,pipeout的旗帜
fdin,fdout,
和一个char **(对于真正的命令,比如&#34; ls -l&#34;)
让我们说一切都很好, 这意味着:
pcommands[0]:
pipein=0
pipeout=1
char** = {"ls","-l",NULL}
pcommands[1]:
pipein=1
pipeout=0
char** = {"grep","r",NULL}
现在,循环将进行两次(因为我有两个命令时刻) 在第一次,它将看到pcommands [0]有pipeout == 1 创建管道 做叉子 pcommands [0]有pipeout == 1 child:dup2到stdout execvp
第二次: 不会创建管道 做叉子 儿童: pcomands [1]有pipein == 1 然后:dup2到输入 exevp ..
此命令有效,我的输出是:
errors.log exer2.pdf multipal_try
(&#39; r&#39;所有的事情)
但后来它被卡住了,并没有离开grep
..
在另一个终端,我可以看到grep
仍在工作
我希望我关闭所有必须关闭的fd ...
我不明白为什么它不起作用,好像我做得对(好吧,它适用于其他命令......)
有人可以帮忙吗?感谢答案 0 :(得分:2)
您没有关闭足够的管道文件描述符。
dup()
或dup2()
将管道文件描述符复制到标准输入或标准输出,则应关闭原始管道文件描述符的两个。您还需要确保如果父shell创建管道,它将关闭管道文件描述符的两个副本。
另请注意,应允许管道中的进程并发运行。特别是,管道的容量有限,并且当管道中没有空间时,过程会阻塞。限制可以非常小(POSIX要求它必须至少为4 KiB,但这就是全部)。如果您的程序处理数兆字节的数据,则必须允许它们在管道中同时运行。因此,waitpid()
应该在启动子项的循环之外发生。您还需要在等待之前关闭父进程中的管道;否则,阅读管道的孩子将永远不会看到EOF(因为从理论上讲,父母可以写入管道,即使它不会。)
您有名称以下划线开头的结构成员。这很危险。以下划线开头的名称保留用于实现。 C标准说:
ISO / IEC 9899:2011§7.1.3保留标识符
- 所有以下划线和大写字母或其他字母开头的标识符 下划线总是保留用于任何用途 - 始终保留以下划线开头的所有标识符作为标识符 在普通名称和标签名称空间中都有文件范围。
这意味着如果遇到问题,那么麻烦就在于你,而不是系统。显然,你的代码是有效的,但你应该意识到可能遇到的问题,最明智的做法是避免它们。
这是一个基于上述代码的固定SSCCE:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
typedef struct Command Command;
struct Command
{
int _fd_out;
int _fd_in;
int _flag_pipe_in;
int _flag_pipe_out;
char **_commands;
};
typedef int Pipe[2];
enum { STDIN = STDIN_FILENO, STDOUT = STDOUT_FILENO, STDERR = STDERR_FILENO };
int main(void)
{
char *ls_cmd[] = { "ls", 0 };
char *grep_cmd[] = { "grep", "r", 0 };
Command commands[] =
{
{
._fd_in = 0, ._flag_pipe_in = 0,
._fd_out = 1, ._flag_pipe_out = 1,
._commands = ls_cmd,
},
{
._fd_in = 0, ._flag_pipe_in = 1,
._fd_out = 1, ._flag_pipe_out = 0,
._commands = grep_cmd,
}
};
int commands_num = sizeof(commands) / sizeof(commands[0]);
/* Allow valgrind to check memory */
Command *pcommands = malloc(commands_num * sizeof(Command));
for (int i = 0; i < commands_num; i++)
pcommands[i] = commands[i];
for (int i = 0; i < commands_num; i++) { //exec all the commands instants
if (pcommands[i]._flag_pipe_out == 1) { //creates pipe if necessary
Pipe pipe_fd;
if (pipe(pipe_fd) == -1) {
perror("Error: \"pipe()\" failed");
}
pcommands[i]._fd_out = pipe_fd[1];
pcommands[i+1]._fd_in = pipe_fd[0];
}
pid_t pid = fork(); //the child exec the commands
if (pid == -1) {
perror("Error: \"fork()\" failed");
break;
} else if (!pid) { //child process
if (pcommands[i]._flag_pipe_in == 1) { //if there was a pipe to this command
assert(i > 0);
assert(pcommands[i-1]._flag_pipe_out == 1);
assert(pcommands[i-1]._fd_out > STDERR);
if (dup2(pcommands[i]._fd_in, STDIN) == -1) {
perror("Error: \"dup2()\" failed");
exit(0);
}
close(pcommands[i]._fd_in);
close(pcommands[i-1]._fd_out);
}
if (pcommands[i]._flag_pipe_out == 1) { //if there was a pipe from this command
assert(i < commands_num - 1);
assert(pcommands[i+1]._flag_pipe_in == 1);
assert(pcommands[i+1]._fd_in > STDERR);
if (dup2(pcommands[i]._fd_out, STDOUT) == -1) {
perror("Error: \"dup2()\" failed");
exit(0);
}
close(pcommands[i]._fd_out);
close(pcommands[i+1]._fd_in);
}
execvp(pcommands[i]._commands[0] , pcommands[i]._commands); //run the command
perror("Error: \"execvp()\" failed");
exit(1);
}
else
printf("Child PID %d running\n", (int)pid);
}
//closing all the open pipe fd's
for (int i = 0; i < commands_num; i++) {
if (pcommands[i]._fd_in != STDIN) { //if there was another stdin that is not 0
close(pcommands[i]._fd_in);
}
if (pcommands[i]._fd_out != STDOUT) { //if there was another stdout that is not 1
close(pcommands[i]._fd_out);
}
}
int status;
pid_t corpse;
while ((corpse = waitpid(-1, &status, 0)) > 0)
printf("Child PID %d died with status 0x%.4X\n", (int)corpse, status);
free(pcommands);
return(0);
}
据我所知,你会怎么做,所以它不会“无可争议地凌乱”?
我可能会保留管道信息,以便孩子不需要担心断言中包含的条件(在管道中之前或之后访问子节点的子信息)。如果每个孩子只需要访问自己数据结构中的信息,那就更清晰了。我重新组织'struct Command',因此它包含两个管道,以及哪个管道包含需要关闭的信息的指示器。在许多方面,与你所得到的并没有根本的不同;只是在那个孩子 i 中更整洁,只需要看pcommands[i]
。
您可以在C Minishell adding pipelines的不同背景下看到部分答案。