我正在尝试创建一个minishell项目。我已经完成了将命令解析为列表/令牌的过程。当我使用管道执行命令时,我会遇到一些奇怪的行为。例如,如果我在minishell中键入“echo hi| wc”输出是正确的,但是之后显示了一个空行,我仍然可以在它返回到我原来的进程之前,在其中输入另一个命令来执行。是否挂在一个进程中?
➜ minishell git:() ✗ make; ./minishell
$> echo hi | wc
$> 1 1 3
echo test
test
$>
另外,考虑到我下面的代码,我担心它会等待一个进程完成后再继续下一个进程。例如,如果我执行下面的命令,它应该首先立即返回Makefile的单词计数结果,然后继续睡眠,但似乎我的代码首先睡眠,然后返回结果。如何确保我的代码完全像在bash中一样执行?
➜ minishell git:() ✗ make; ./minishell
$> sleep 6 | cat Makefile | wc
$> 149 499 4252
我提供了下面的片段,如果需要,可以分享任何进一步的信息。由于代码的大小,我不能提供一个最小的可复制的样本。任何帮助将不胜感激!(请忽略注解掉的部分,即“网格”,它不应该影响代码性能。
int GLOBAL_ERROR = 0;
void close_pipes(int pipes[1000][2] , int lines)
{
int i;
i = 0;
while (i < lines - 1)
{
close(pipes[i][0]);
close(pipes[i][1]);
i++;
}
}
int assign_pi(t_mhstruct **mh)
{
t_token *current;
int idx;
current = (*mh)->token;
idx = 0;
while (current)
{
while (current->type != PIPELINE && current)
{
current->pi = idx;
if (current->next)
current = current->next;
else
break;
}
if (current)
current = current->next;
idx++;
}
return (idx);
}
int get_args_size_pipes(t_token *tmp)
{
int i;
i = 0;
if (!tmp)
return (0);
while (tmp && tmp->type != PIPELINE)
{
i++;
tmp = tmp->next;
}
return (i);
}
char **get_args_pipes(t_token *token, t_mhstruct *mh)
{
char **out;
int i;
(void) mh;
i =get_args_size_pipes(token);
out = (char **)malloc((i + 1) * sizeof(char *));
if (!out)
return (NULL);
i = 0;
while (token && token->type != PIPELINE)
{
out[i] = ft_strdup(token->data);
token = token->next;
i++;
}
out[i] = NULL;
return (out);
}
void execve_commands_pipes(t_token *curr, t_mhstruct *mh)
{
char **arg;
char **env;
char *path;
int out;
int pid;
path = NULL;
out = 0;
env = get_env_array(mh);
arg = get_args_pipes(curr, mh);
if (check_path(arg[0], env, &path) == 0)
{
if (access(arg[0], R_OK) == 0)
path = arg[0];
else
{
free(env);
free(arg);
return (pr_err(mh, 127, gemsg(mh->emsg[11], mh->emsg[12], arg[0])));
}
}
pid = fork();
if (pid == 0)
execve(path, arg, env);
waitpid(pid, &GLOBAL_ERROR, 0);
return;
}
void set_pipe(t_token *curr, int pipes[1000][2], int i, int lines, int screen)
{
(void) curr;
(void) screen;
if (i == 0)
{
close(pipes[i][0]);
dup2(pipes[i][1], STDOUT_FILENO);
close(pipes[i][1]);
}
else if (i != 0 && i != lines - 1)
{
dup2(pipes[i - 1][0], STDIN_FILENO);
close(pipes[i - 1][0]);
dup2(pipes[i][1], STDOUT_FILENO);
close(pipes[i][1]);
}
else if (i == lines - 1)
{
close(pipes[i - 1][1]);
dup2(pipes[i - 1][0], STDIN_FILENO);
close(pipes[i - 1][0]);
}
}
void do_pipe_forks(t_mhstruct **mh, int pipes[1000][2], int i, int lines, int screen)
{
int pid;
t_token *curr;
(void) screen;
curr = (*mh)->token;
while (curr && curr->pi != i)
curr = curr->next;
pid = fork();
if (pid == 0)
{
set_pipe(curr, pipes, i, lines, screen);
close_pipes(pipes, lines - 1);
execve_commands_pipes(curr, *mh);
exit(1);
}
}
void do_pipes(t_mhstruct **mh, char **grid, int lines)
{
int screen = dup(STDOUT_FILENO);
int i;
int pipes[1000][2];
(void) grid;
i = 0;
while (i < lines - 1)
{
pipe(pipes[i]);
i++;
}
i = 0;
while (i < lines)
{
do_pipe_forks(mh, pipes, i, lines, screen);
i++;
}
close_pipes(pipes, lines);
}
int launch_pipes(t_mhstruct **mh)
{
int lines;
char **grid = NULL;
lines = assign_pi(mh);
// grid = (char **)malloc(sizeof(char **) * (lines + 1));
// if (!grid)
// return (1);
// create_grid(grid, lines, mh);
do_pipes(mh, grid, lines);
// free_all(grid);
return (0);
}
2条答案
按热度按时间368yc8dk1#
下面是根据你的前两个建议修改的代码,我离开这里,以防有人有同样的问题。重要的是,我通过在do_pipes()的末尾引入一个while循环来等待子进程完成,成功地阻止了launch_pipes过早地退出程序的main()
p3rjfoxz2#
如果我在minishell中输入“echo hi| wc”输出正确
你确定吗?
然而,之后会显示一个空行,我仍然可以在其中输入另一个命令,让它执行,然后它返回到我的原始进程。
不,我不这么认为。仔细看:在
wc
输出行的开头的$>
是什么?这当然不是wc
正常输出的一部分。这不是你shell的提示吗?因此,在打印下一个命令的提示符之前,shell没有等待管道完成。然后在
wc
的输出的末尾有一个换行符,将光标推进到下一行。所以...是否挂在一个进程中?
不完全没有事实上,恰恰相反:你的shell进程太快了,没有等到管道完成。
如果我执行下面的命令,它应该立即返回Makefile的字计数的结果,然后继续睡眠,但似乎我的代码先睡眠,然后返回结果。
显然,在屏幕的这一侧,睡眠延迟相对于
wc
输出的时序并不明显。我在这里确实观察到了我已经讨论过的相同的提示行为,但这并不能解释管道中命令的相对时序。代码中有几个效率低下和小缺陷,尤其是管道中的每个命令都要分叉 * 两次 *:一次在
do_pipe_forks()
中,一次在execve_commands_pipes()
中。此外,如果execve()
调用任何命令失败,则程序福尔斯会失败并执行waitpid(0, &GLOBAL_ERROR, 0)
,这是不需要的。这些都不能解释你描述的行为,尽管它们是相关的。你问的这两个问题都源于同一个一般原因:您正在错误的时间和错误的地点等待子进程。
首先考虑第二个问题,
execve_commands_pipes()
启动一个命令,然后 * 立即 * 等待它完成。管道中的命令应该并发运行,至少如果它们运行的时间足够长的话。除了并行性方面之外,如果其中一个命令(而不是最后一个命令)向其标准输出写入足够的内容以填充管道的缓冲区,则像这样立即等待将导致死锁,因为将使用该输出的进程尚未启动。这也是导致sleep
相对于该管道中后续命令的时序问题的原因。现在,正如我已经观察到的,你为管道中的每个命令fork * 两次 *。您等待
execve_commands_pipes()
开始的子元素完成,但您似乎根本没有等待前一个集合。因此,在主进程中,一旦子进程启动,控制立即返回到命令输入循环,该子进程本身将启动最后一个子进程。因此,主进程在运行最后一个管道命令的进程打印任何内容之前打印提示符也就不足为奇了。如何确保我的代码完全像在bash中一样执行?
我建议(all of):
execve_commands_pipes()
不应该是fork()
也不应该是waitpid()
。但是,如果它的execve()
调用返回,它应该是_exit(1)
。do_pipe_forks()
应该返回主进程中分叉子进程的PID。它在子进程中根本不应该返回,也不会返回,因为对execve_commands_pipes()
的调用不会返回(见上文)。do_pipes()
应该收集子进程的所有PID,并且,* 在 * 关闭所有管道末端的副本后,等待每个子进程。这也提供了捕获最后一个命令的退出状态的机会,以用作管道的退出状态,这实际上是Bash和POSIX shell所做的。