【问题标题】:Fork and dup2 - Child process is not terminating - Issues with file descriptors?Fork 和 dup2 - 子进程未终止 - 文件描述符问题?
【发布时间】:2014-10-10 22:34:11
【问题描述】:

我正在为家庭作业编写自己的 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);
        }
    }
}
  • args 将包含命令的参数。
  • count 是我需要创建的命令数。
  • pos 是命令在输入中的位置

我无法找出问题所在。在此之前,我对硬编码值使用了相同的方法并且它正在工作。

我对@9​​87654333@/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]);
        }
}

【问题讨论】:

  • 请检查您调用的每个系统调用的返回值(例如forkwaitpiddup2execvp)。如果他们返回错误代码,请使用perrorstrerror(errno) 打印错误。请更新问题,说明没有错误。请在execvp 之后调用abort(); 以确保子进程不会继续运行。
  • 请将您的整个程序 (sscce.org) 发布在一个 .c 文件中。

标签: c linux fork dup2


【解决方案1】:

第一次诊断

你说:

每个新进程将其管道的读取端与前一个进程复制其标准输入,并将其管道的写入端与下一个进程复制其标准输出。

你没有提到close()这个神奇的词。

当您使用dup()dup2() 将其连接到标准输入时,您需要确保关闭每个管道的读取端和写入端。这意味着使用 2 个管道,您可以对 close() 进行 4 次调用。

如果您没有正确关闭管道,正在读取的进程将不会获得 EOF(因为有一个进程,可能是它自己,可以写入管道)。对close() 进行足够多(不要太少,不要太多)的呼叫至​​关重要。


我在 dup2 通话后拨打 close_fds()。该函数将遍历fd[][2] 数组并对数组中的每个fd 执行close() 调用。

好的。这很重要。这意味着我的主要诊断可能没有准确。

二次诊断

其他几项:

  1. execvp() 之后应该有代码,如果execvp() 返回(这意味着它失败),则报告错误并退出。

  2. 您不应立即致电waitpid()。应该允许管道中的所有进程同时运行。您需要启动所有进程,然后等待最后一个进程退出,并在其他进程死亡时清理它们(但不必担心管道中的所有进程在继续之前退出)。

    如果您确实在启动第二个命令之前强制执行第一个命令,并且如果第一个命令生成的输出超出管道的容量,您将遇到死锁 - 第一个进程无法退出,因为它被阻塞写入,并且第二个进程无法启动,因为第一个进程尚未退出。中断和重启以及宇宙的终结都会稍微粗暴地解决问题。

  3. 在递归之前,您减少 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”命令创建管道的代码。

我不再清楚递归是如何工作的——或者它是否在您的示例中被调用。我认为它未使用,实际上在您的示例中,这可能也是因为您最终会多次执行相同的命令,AFAICS。

【讨论】:

  • +Jonathan - 我在 dup2 调用后调用 close_fds() 。该函数将遍历 fd[][] 数组并对数组中的每个 fd 执行 close() 调用。
  • Jonathan... 我为 close_fds() 添加了代码。我现在修复了一个错误。现在 grep 命令未执行,控制台输出为 - grep: (standard input): Bad file descriptor。编辑问题
  • 正如我已经问过的,请在代码中添加错误检查和错误报告,更新问题中的代码,并指定打印了哪些错误。
  • @pts:这个评论是给我的还是给 OP 的?如果是针对 OP,也许它属于问题而不是我的答案。不过,我 100% 同意您的观点——如果没有足够的代码来查看问题所在,就无法解决问题。管道是如何创建的?孩子们是如何分叉的?等等。
【解决方案2】:

grep 未终止的最可能原因:

  • 你没有用正确的 PID 调用waitpid(即使你的代码中有这样的调用,它也可能由于某种原因没有被执行),所以grep 变成了一个僵尸进程。也许您的父 shell 进程首先等待另一个进程(无限期,因为另一个进程永远不会终止),并且它不会调用 PID 为grepwaitpid。如果grep 是僵尸,您可以在ps 的输出中找到Z

  • grep 在其标准输入 (fd 0) 上未收到 EOF,某些进程保持其管道的写入端打开。您是否关闭了父 shell 进程中 fd 数组中的所有文件描述符?如果没有在所有地方关闭,grep 将永远不会收到 EOF,并且永远不会终止,因为它将被阻止(永远)在其标准输入上等待更多数据。

【讨论】:

  • +pts - 1. 对于 grep 进程,我无法在 ps 的输出中看到 Z。 grep 似乎在等待。 2.我关闭了父进程中的所有fds,但第二个进程(本例中为grep)并没有在控制台上打印任何内容并一直等待。
  • 在您将整个程序发布为 sscce.org 之前,我无法为您提供更多帮助
猜你喜欢
  • 2014-07-14
  • 1970-01-01
  • 1970-01-01
  • 2012-10-16
  • 2014-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多