【问题标题】:How to wait for exit of non-children processes如何等待非子进程退出
【发布时间】:2010-11-12 13:31:18
【问题描述】:

对于子进程,wait()waitpid() 函数可用于暂停当前进程的执行,直到子进程退出。但是这个函数不能用于非子进程。

还有其他函数可以等待任何进程退出吗?

【问题讨论】:

    标签: c linux process wait


    【解决方案1】:

    没有等同于wait()。通常的做法是使用kill(pid, 0) 进行轮询,并寻找ESRCH 的返回值-1 和errno 以表明进程已结束。

    更新:从 linux 内核 5.3 开始,有一个 pidfd_open 系统调用,它为给定的 pid 创建一个 fd,可以在 pid 退出时对其进行轮询以获取通知。

    【讨论】:

    • 这样的busy-loop可以吗?
    • 好吧,你不想让它太忙;你应该在每个kill() 之后usleep() 一段时间,但没有发现进程消失。然后,您必须在轮询的繁忙程度与在您注意到之前结束该过程的时间之间取得平衡。
    • 哦,usleep() 显然在我不注意的时候已经过时了。看来你现在应该 nanosleep() 代替。
    • @Sam Hocevar:没有任何关于比赛条件的内容或没有它如何做到这一点。没有真正的帮助。
    • @chaos:没有什么能保证kill(pid, 0) 会发出您感兴趣的进程的信号。在您调用nanosleep 期间,它可能已经死亡并被另一个正在运行的进程替换。恐怕我觉得没有必要详细说明:已经提出了三个很好的建议(FIFO、信号量和ptrace 方法,恕我直言,尽管它非常特定于平台,但它优于所有其他方法)。
    【解决方案2】:

    在 BSD 和 OS X 上,您可以使用带有 EVFILT_PROC+NOTE_EXIT 的 kqueue 来做到这一点。无需投票。不幸的是,没有 Linux 等价物。

    【讨论】:

    • 遗憾的是他们没有移植 kqueue。
    【解决方案3】:

    到目前为止,我已经找到了三种在 Linux 上执行此操作的方法:

    • 轮询:您经常检查进程是否存在,或者通过使用kill 或通过测试/proc/$pid 的存在,就像在大多数其他答案中一样
    • 使用ptrace 系统调用像调试器一样附加到进程,以便在它退出时收到通知,如a3nm's answer
    • 使用netlink 接口监听PROC_EVENT_EXIT 消息——这样每次进程退出时内核都会告诉您的程序,您只需等待正确的进程ID。我只看到过这种描述in one place on the internet

    无耻插件:我正在开发一个program(当然是开源的;GPLv2),它可以满足这三个方面的任何一个。

    【讨论】:

      【解决方案4】:

      您还可以创建一个套接字或 FIFO 并读取它们。 FIFO 尤其简单:将您孩子的标准输出与 FIFO 连接并读取。读取将阻塞,直到孩子退出(出于任何原因)或直到它发出一些数据。所以你需要一个小循环来丢弃不需要的文本数据。

      如果您可以访问孩子的来源,请在 FIFO 启动时打开它进行写入,然后就忘记它。当子进程终止并且您等待的“父”进程将唤醒时,操作系统将清除打开的文件描述符。

      现在这可能是一个您没有开始或拥有的过程。在这种情况下,您可以将二进制可执行文件替换为启动真正二进制文件的脚本,但也可以如上所述添加监控。

      【讨论】:

      • 不是一个孩子,尤其是它可能没有考虑到这种跟踪并且无法修改源代码。
      • @Lothar 我认为展示一些显而易见的解决方案是很好的,特别是因为接受的答案是不可靠的。此外,任何进程都可以以某种方式变成一个子进程。例如,您可以将二进制文件替换为监视原始二进制文件并在现在的孩子死亡时发送信号的脚本。
      【解决方案5】:

      您可以使用ptrace(2) 附加到该进程。从外壳来看,strace -p PID >/dev/null 2>&1 似乎可以工作。这避免了忙等待,虽然它会减慢被跟踪的进程,并且不会在所有进程上工作(只有你的,这比只有子进程好一点)。

      【讨论】:

      • 知识永远不会有害,但对于shell,我推荐“标准”方式,定期轮询;见question 1058047。虽然这可能是一种罕见的情况,但 strace 可以使一个繁忙的循环。例如 $(读取)&; strace -p $!。注意 (read) & 本身是无害的。
      【解决方案6】:

      这是一种等待 linux 中的任何进程(不一定是子进程)退出(或被杀死)而不进行轮询的方法:

      使用 inotify 等待 /proc'pid' 被删除将是完美的解决方案,但不幸的是,inotify 不适用于 /proc 之类的伪文件系统。 但是,我们可以将它与进程的可执行文件一起使用。 虽然该过程仍然存在,但该文件仍处于打开状态。 所以我们可以使用 inotify 和 IN_CLOSE_NOWRITE 来阻塞直到文件关闭。 当然它也可以因为其他原因被关闭(例如,如果另一个具有相同可执行文件的进程退出),所以我们必须通过其他方式过滤这些事件。

      我们可以使用kill(pid, 0),但这不能保证它是否仍然是同一个进程。如果我们真的对此感到偏执,我们可以做其他事情。

      这是一种 100% 安全的方法,可以防止 pid 重用问题:我们打开伪目录 /proc/'pid',并保持打开状态直到完成。如果同时创建了具有相同 pid 的新进程,我们持有的目录文件描述符仍将引用原始的(或变为无效,如果旧进程不再存在),但永远不会引用新进程重用的pid。然后我们可以通过检查来检查原始进程是否仍然存在,例如使用 openat() 的目录中是否存在文件“cmdline”。当进程退出或被杀死时,那些伪文件也不再存在,因此 openat() 将失败。

      这是一个示例代码:

      // return -1 on error, or 0 if everything went well
      int wait_for_pid(int pid)
      {
          char path[32];
          int in_fd = inotify_init();
          sprintf(path, "/proc/%i/exe", pid);
          if (inotify_add_watch(in_fd, path, IN_CLOSE_NOWRITE) < 0) {
              close(in_fd);
              return -1;
          }
          sprintf(path, "/proc/%i", pid);
          int dir_fd = open(path, 0);
          if (dir_fd < 0) {
              close(in_fd);
              return -1;
          }
      
          int res = 0;
          while (1) {
              struct inotify_event event;
              if (read(in_fd, &event, sizeof(event)) < 0) {
                  res = -1;
                  break;
              }
              int f = openat(dir_fd, "fd", 0);
              if (f < 0) break;
              close(f);
          }
      
          close(dir_fd);
          close(in_fd);
          return res;
      }
      

      【讨论】:

        【解决方案7】:

        我不知道。除了混乱的解决方案,如果你可以改变你想要等待的程序,你可以使用信号量。

        库函数有sem_open(3), sem_init(3), sem_wait(3), ...

        sem_wait(3) 执行等待,因此您不必像在混乱解决方案中那样忙于等待。当然,使用信号量会使您的程序更加复杂,而且可能不值得这么麻烦。

        【讨论】:

        • 这些信号量实际上是无用的,因为即使没有进程打开它们,它们也会持续存在。我记得我经常调用 ipcrm 来清理一些崩溃进程的剩余部分。
        【解决方案8】:

        也许可以等待 /proc/[pid] 或 /proc/[pid]/[something] 消失?

        还有 poll() 和其他文件事件等待函数,也许有帮助?

        【讨论】:

        • 是的,这是个好主意。除非相同的进程 ID 被如此快速地重用 - 但这种情况可能很少发生
        • @CsTamas,在进程标识符(32768)的数量远大于可以运行的进程数量的情况下,存在保护。因此,除非您睡一会儿,否则您获得相同进程标识符的可能性非常低。
        【解决方案9】:

        从 linux 内核 5.3 开始,有一个 pidfd_open 系统调用,它为给定的 pid 创建一个 fd,可以在 pid 退出时轮询它以获取通知。

        【讨论】:

          【解决方案10】:

          只需轮询 /proc/[PID]/stat 的 22 号和 2 号值。 值 2 包含可执行文件的名称,而 22 包含开始时间。 如果它们发生变化,则其他一些进程已采用相同(已释放)的 PI​​D。因此该方法非常可靠。

          【讨论】:

            猜你喜欢
            • 2011-07-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-09-24
            • 1970-01-01
            • 2021-11-19
            • 1970-01-01
            相关资源
            最近更新 更多