【问题标题】:waiting for popen subprocess to terminate before reading在读取之前等待 popen 子进程终止
【发布时间】:2015-06-15 17:39:51
【问题描述】:

如何判断 popen 启动的进程何时完成?

我将描述符(来自在 popen 返回的 FILE * 上调用的 fileno())传递给调用 fstat() 并使用返回的值来确定要读取多少的函数。令人惊讶的是,如果存在延迟(例如,逐步通过调试器),这实际上可以工作,但如果在 popen 之后立即调用,则返回文件大小为零。所以我需要一些方法来等到所有输出都准备好 - 我该怎么做?我假设我不能调用 pclose() 即使它会等待,因为这样描述符就不再有效了。

更新:实际上,如果我假装 fstat() 在管道调用中失败,代码似乎可以正常工作 - 问题似乎是 fstat() 没有失败,正如预期的那样。所以我真正想要的是一种判断描述符是否是管道的方法 - fstat 返回 st_mode=0 (期待 S_IFIFO!)

【问题讨论】:

    标签: c macos popen


    【解决方案1】:

    在类 unix 操作系统中,父进程在其子进程终止时收到 SIGCHLD。见this tutorial

    您也可以在管道描述符上select() 来检查是否有要读取的数据。当管道关闭时,带有零大小数据的“read-ready”事件is generated。

    【讨论】:

    • 为此目的拦截SIGCHLD 通常是一个非常糟糕的主意。
    • 好吧,也许吧。事实上,这取决于孩子的预期行为和程序的一般逻辑。我可以想象孩子退出代码比他们可能的输出更有意义的情况,反之亦然。
    【解决方案2】:

    这行不通(至少在 Linux 系统上不行)。

    popen(3)-ed 命令的文件描述符(由fileno(3) 给出)是pipe(7)。所以它是不可搜索的,fstat(2) 不会给出任何显着的大小。

    如果您需要如此细粒度的信息,请不要使用popen,而是调用底层系统调用pipe(2)、fork(2)、execve(2)、waitpid(2),然后您可以使用poll(2)。

    如果你坚持使用popen你仍然可以poll它的fileno;但是没有标准的方法来获取它的 pid(使用waitpid)这就是为什么你应该直接使用syscalls(2)。

    我猜fstat-ing 管道不应该给出一个普通文件,而是一个 fifo(7) 如果它成功,那么在 st.st_mode & S_IFMT == S_IFIFO 上的特殊情况来检测这种情况。

    【讨论】:

    • 这也是我的期望。显然 OS X (BSD) 在这方面有所不同,因为 fstat() 将返回输出的确切大小,如果我等到该过程完成。
    • @Michael:这不可靠,因为管道没有无限大小。它将限制在最大管道缓冲区大小,此时程序将阻塞等待您读取。
    • @R.. 有没有办法判断 fstat() 是否返回有意义的信息?如果输入是管道,我实际上希望它失败,但它不是。
    • @Michael:当S_ISREG(st_mode) 为假(即当fd 不是常规文件时)时,忽略st_size(或将整个操作视为失败)。
    【解决方案3】:

    也许您有一些特殊原因想要确定来自popen 的管道上可用的数据量,但这通常不可用,也不是预期的用例。 popen(在读取模式下)旨在与尽可能快地输出数据流的程序一起使用(注意:如果您不立即读取,当缓冲区填满时,它们会阻塞管道)完成后退出,为您的读者生成EOF。这给了你很大的灵活性;您可以使用fgets 或getline 等基于行的读取函数,读取while 流直到EOF,或使用fread 读取大块并准备好在最后一个上进行短读。

    【讨论】:

    • 我想我只是希望 fstat() 失败并以不同的方式处理这种情况 - 问题似乎是它没有失败。如果我假装是这样,那么当输入描述符是管道时一切正常。
    • 如果您想使用popen,请正确使用它并通过EOF 在FILE 流上检测完成。如果您想等待进程终止而不读取管道,那么您需要自己创建子进程并且不要使用popen,但是当管道缓冲区填满并且进程永远不会遇到麻烦无需您先从管道中读取一些数据即可自行退出。
    【解决方案4】:

    如果您不从管道中读取数据,则子进程很有可能从不终止。管道两端之间的缓冲区相当小。因此,当子进程写入输出时,缓冲区将被填满,子进程将在write() 调用中阻塞。那么你就会陷入僵局。父进程在从管道读取之前等待子进程终止,子进程被阻塞,直到父进程从管道读取,因此无法终止。

    【讨论】:

      【解决方案5】:

      我不知道这是否会有所帮助,但在类似的情况下,我想存储 popen() 的输出。

      void live(void) { // scan and read the dongle
        FILE *dong;  // dongle
        char *c;
        char cmd[] = "rtl_power -f 88M:108M:5k -1 -";
        c = buf;  // externally allocated buffer
        dong = popen(cmd,"r");
        while (!feof(dong)) {
          *c = fgetc(dong);
          c++;
        }
        pclose(dong);
        textsize = (int) (c-buf);
        printf("Read %i bytes\n",textsize);
      }
      

      它有点拖尾文件,将其复制到 buf[]。它已经工作了几天,我什至在 Sourceforge 上发布了该程序。运行 Linux、Debian 和 Raspbian。

      【讨论】:

      • Why is “while (!feof(file))” always wrong? feof() 在达到 EOF 条件之后之前不会返回非零值,并且直到 fgetc() 失败时才会发生。请注意fgetc() 返回int,而不是char。在您发布的代码中,到达 EOF 时失败的最后一个 fgetc() 返回一个 int typed-EOF 值,该值被截断为 char,并错误地填充到 buf 中。只有在那之后,feof() 才会返回 true。
      • feof() 条件直到导致它的读取之后才为真。 fgets 也会发生同样的情况,只要在使用数据之前再次检查 feof() 就很安全。循环直到 feof 但意识到您可能刚刚读取了 EOF,因此如果 feof() 为真,请不要使用它。 EOF 是 0xFFFF 我认为,转换为 char 它只是 0xFF。在上面的代码中,当管道进程结束时,feof() 为真。
      • 实际上,一旦管道进程停止并且您已经读取了它生成的所有数据,上面的 feof() 就是真的。假设它在某个地方缓冲但有一些小缓冲区,我不想通过假设我以后可以阅读它而丢失任何东西。这几乎是鸡或蛋的情况。 C 语言有很多不漂亮的地方。
      • EOF 可能真的是 -1,所以它可以显示为 0xff、0xffff、0xffffffff,具体取决于它的位置。许多与文件相关的函数在错误时返回 -1,也许这就是 EOF。它是否存在于磁盘上的文件中?从来没有看过,但我不会依赖它。我认为这是您用完数据时存在的一种情况。
      猜你喜欢
      • 1970-01-01
      • 2010-09-25
      • 1970-01-01
      • 1970-01-01
      • 2017-12-27
      • 1970-01-01
      • 2012-11-08
      • 1970-01-01
      • 2020-09-09
      相关资源
      最近更新 更多