我正在为自己的家庭作业编写自己的shell,并且遇到了问题。
我的shell程序从控制台 获取输入cat scores | grep 100
并按预期打印输出,但grep
命令没有终止,我可以看到它无限运行来自ps
命令。
编辑 - 关闭fds时出错。现在grep命令没有执行,控制台输出是 -
grep :(标准输入):错误的文件描述符
我正在从控制台读取命令的数量并创建必要的管道并在分叉第一个进程之前将它们存储在二维int array fd[][]
中。
fd[0][0]
将包含第一个管道的读取结束,fd[0][1]
将包含第一个管道的写入结束。 fd[1][0]
将包含第二个管道的读取结束,fd[1][1]
将包含第二个管道的写入结束,依此类推。
每个新进程使用前一个进程的管道读取结束复制其stdin
,并使用其管道的写入端与下一个进程重复其stdout
。
以下是我的功能:
void run_cmds(char **args, int count,int pos)
{
int pid,status;
pid = fork();
if ( pid == 0 )
{
if(pos != 0) dup2(fd[pos-1][0],0); // not changing stdin for 1st process
if(pos != count) dup2(fd[pos][1],1); //not changing stdout for last process
close_fds(pos);
execvp(*args,args);
}
else
{
waitpid(pid,&status,0);
count--;
pos++;
//getting next command and storing it in args
if(count > 0)
run_cmds(args,count,pos);
}
}
}
我无法弄清楚问题所在。在此之前我使用了相同的方法来处理硬编码值,并且它正在工作。
我对dup2
/ fork
的理解/实施缺少什么,为什么命令会无限期等待?
任何输入都会非常有用。在过去的几天里对此深有感触!
编辑:close_fds()函数如下 - 对于任何流程,我都关闭了连接流程的管道。
void close_fds(int pos)
{
if ( pos != 0 )
{
close(fd[pos-1][0]);
close(fd[pos-1][1]);
}
if ( pos != count)
{
close(fd[pos][0]);
close(fd[pos][1]);
}
}
答案 0 :(得分:3)
你说:
每个新进程使用前一个进程的管道读取端复制其stdin,并使用下一个进程的管道写入端复制其stdout。
你没有提到神奇的单词close()
。
当您使用dup()
或dup2()
将其连接到标准输入时,您需要确保关闭每个管道的读取和写入端。这意味着使用2个管道,您可以拨打close()
进行4次调用。
如果你没有正确关闭管道,那么正在阅读的过程就不会获得EOF(因为那里有一个可能写入管道的进程,可能本身)。对close()
进行足够(不是太少,不要太多)的调用至关重要。
我在dup2调用后调用
close_fds()
。该函数将遍历fd[][2]
数组并对数组中的每个close()
进行fd
调用。
行。这很重要。这意味着我的主要诊断可能并未被发现。
其他几项:
您应该在报告错误的execvp()
之后拥有代码,如果execvp()
返回(这意味着它失败),则会退出。
您不应立即致电waitpid()
。应允许管道中的所有进程同时运行。你需要启动所有进程,然后等待最后一个进程退出,在他们死亡时清理其他任何进程(但不一定要担心管道中的所有内容都在退出之前)。
如果在启动第二个命令之前强制第一个命令完全执行,并且第一个命令产生的输出多于输入管道的输出,则会出现死锁 - 第一个进程无法退出因为它被阻止了写作,第二个过程无法启动,因为第一个过程没有退出。中断和重新启动以及宇宙的终结都会有点粗暴地解决问题。
在递归之前递减count
并递增pos
。那可能不好。我想你应该增加pos
。
更新后显示close_fds()
功能。
我回到"关闭管道有问题" (虽然等待和错误报告问题仍然存在问题)。如果管道中有6个进程,并且在运行任何进程之前创建了所有5个连接管道,则每个进程都必须关闭所有10个管道文件描述符。
另外,请不要忘记,如果管道是在父shell中创建的,而不是在执行管道中的某个命令的子shell中创建的,那么父级必须关闭 all 之前的管道描述符等待命令完成。
请制作MCVE(How to create a Minimal, Complete, and Verifiable Example?)或 SSCCE(Short, Self-Contained, Correct Example) - 两个相同基本想法的名称和链接。
您应该创建一个程序来制作您传递给调用run_cmds()
的代码的数据结构。也就是说,您应该创建解析代码创建的任何数据结构,并显示为' cat score | grep 100
' {{1}}'创建管道或管道的代码。命令。
我不再清楚递归是如何工作的 - 或者是否在您的示例中调用它。我认为它是未使用的,事实上在你的例子中,这可能也是因为你最终会被执行多次相同的命令,AFAICS。
答案 1 :(得分:2)
grep
未终止的最可能原因:
您没有使用正确的PID调用waitpid
(即使您的代码中存在此类调用,但由于某种原因可能无法执行),因此grep
变为僵尸进程。也许你的父shell进程首先等待另一个进程(无限期,因为另一个进程永远不会终止),并且它不会使用waitpid
的PID调用grep
。如果Z
是僵尸,您可以在ps
的输出中找到grep
。
grep
在其stdin(fd 0)上没有收到EOF,某些进程正在保持其管道的写入结束。您是否关闭了父shell进程中fd
数组中的所有文件描述符?如果没有在任何地方关闭,grep
将永远不会收到EOF,它将永远不会终止,因为它将被阻止(永远)等待其stdin上的更多数据。