我在两个子进程之间创建了一个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);
}
}
因此,我有一个“命令”即时
pcommands[i]
它具有:
吹笛的旗帜
fdin,fdout,
和一个字符**(用于实际命令,例如“ ls -l”)
可以说一切都很好,
这意味着:
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
子:dup2到标准输出
可执行文件
第二次:
不创建管道
做叉子
儿童:
pcomands [1]具有pipein == 1
然后:dup2到输入
扩展
..
此命令有效,我的输出是:
errors.log exer2.pdf multipal_try
(所有带有“ r”的东西)
但是它卡住了,并且没有脱离
grep
。在另一个终端中,我可以看到
grep
仍在工作我希望我关闭所有需要关闭的FD ...
我不明白为什么它不起作用,似乎我做对了(嗯,它适用于其他命令。)
有人可以帮忙吗?谢谢
最佳答案
您没有关闭足够的管道文件描述符。
经验法则:
如果使用dup()
或dup2()
将管道文件描述符复制到标准输入或标准输出,则应关闭两个原始管道文件描述符。
您还需要确保,如果父级外壳程序创建了管道,则它会关闭管道文件描述符的两个副本。
另请注意,应允许管道中的进程同时运行。特别是,管道的容量有限,当管道中没有剩余空间时,过程就会阻塞。限制可能很小(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命令”,使其包含两个管道,以及指示器,其中哪个管道包含需要关闭的信息。在许多方面,与您所获得的并没有根本不同;只是在那个孩子中比较整齐,我只需要看
pcommands[i]
。您可以在C Minishell adding pipelines的不同上下文中看到部分答案。