在 C 中使用 execve 加载程序时子进程如何终止

Posted

技术标签:

【中文标题】在 C 中使用 execve 加载程序时子进程如何终止【英文标题】:how child processes get terminated when use execve loader in C 【发布时间】:2018-11-15 08:21:57 【问题描述】:

我是 C 的初学者,我正在努力理解 C 中的 execve 函数来调用子进程来加载和运行可执行对象文件。

我们知道execve只有在出现找不到文件名之类的错误时才会返回调用程序,所以它只被调用一次,永远不会返回。

这是我的问题,如果我们 fork 一个子进程来调用 execve 但由于 execve 永远不会返回,如果一切正常,它将始终执行某些操作,这意味着子进程永远不会终止,所以父进程如何获得这个子进程?下面是示例代码

if ((pid = Fork()) == 0)  /* Child runs user job */
   if (execve(argv[0], argv, environ) < 0)       -------->line 2
      printf("%s: Command not found.\n", argv[0]);
      exit(0);
   


/* Parent waits for foreground job to terminate */
if (waitpid(pid, &status, 0) < 0)   ------------> but the child process never terminated
   printf("waitpid error");

所以在第 2 行,execve(argv[0], argv, environ) 永远不会返回,所以子进程永远不会终止?

【问题讨论】:

当您加载的程序终止子进程时(通过从其main 函数返回或通过调用exit),子进程将被终止。就像任何其他过程一样。 @Someprogrammerdude 谢谢你,现在我明白了。但为什么 execve 设计为永不返回?我们不能设计成这样:如果子进程终止,则返回 1? 为此您必须询问 UNIX 的原始设计者。至于知道子进程何时终止(并可能获得其返回码),请使用例如wait. @amjad 如果子进程终止,它不能返回 1,因为它是子进程。一旦exec 成功,你的代码就不再运行,也没有什么可返回的。 【参考方案1】:

您的程序foo,将启动一些子进程来运行其他程序bar, 并且您希望它使用基本的系统调用 forkexecve 来做到这一点

让我们调用您的初始foo 进程p1。 (这代表一些pid。)

首先,您将致电fork。 这会创建一个 p1 的子进程,该进程正在运行 foo另一个实例 调用那个子进程p1.1

p1.1 正在运行 foo。但是你想运行bar。所以立即在 p1.1 中,foo 调用 execve(path/to/bar ...)。 这将 p1.1 正在运行的foo 实例替换为bar 实例。然后你的 子进程 p1.1 正在运行 bar,如您所愿。

要清楚这一点:-

execve(path/to/bar ...) 是否在新的子进程中启动bar p1.1,并让 p1.1 仍在运行foo 的分叉后实例。相反,execve(path/to/bar ...) 替换 foo 的实例与 bar 的实例在进程 p1.1 中。在fork 之后,但在execve 之前, 我们有:

p1[foo] -> p1.1[foo]

execve 之后我们有:

p1[foo] -> p1.1[bar]

不是:

p1[foo] -> p1.1[foo] -> p1.1.1[bar]

你可以看到execve不能返回成功给它的调用者,p1.1[foo], 因为如果execve 成功,那么p1.1[foo] 不再存在。 当然execve 不能将成功返回给p1[foo],因为p1[foo] 没有调用它

由于 execve 永远不会返回,如果一切正常,它将始终执行某些操作

没有。 execvep1.1[foo] 替换为 p1.1[bar] 并且不返回,因为调用者不再存在。然后 p1.1[bar] 一直运行直到它终止。

p1.1[bar] 迟早会以其中一种方式终止 any 程序终止:它将运行到正常的exit,或者它将 被信号杀死,或者它可能会主动调用abort

父进程(p1)如何获取这个子进程(p1.1)?

首先,不必。一旦 p1[foo] 启动了 p1.1,它就可以, 如果这就是你想要的,那就别管 p1.1,继续做其他事情 如果有的话,最后是exit。如果 p1p1.1 之前终止,则 p1.1 变成orphan process。 一个孤儿进程立即被init process 采用为子进程。所以 如果在此期间没有任何东西终止它,p1.1 将在init 终止时获得,在系统关闭时。

但很可能,您不想放弃孤儿,而您确实希望foo 知道孩子bar 的退出状态。在这种情况下, p1[foo] 迟早要致电wait/waitpid 来学习p1.1 结束,然后采取相应的行动。

与此同时,p1[foo] 很可能正在使用一些 inter-process communication 的形式。和/或 p1[foo] 可能 注意 p1.1[bar] 还没有结束时经过的时间。在这些方式中的一种或其他方式中,p1[foo] 可能会确定 p1.1[bar] 有麻烦了,持续了太久,决定killp1.1 自己。 当 p1.1 被杀 - 无论是谁干的 - 或根据自己的意愿结束,wait/waitpid 将把该信息返回给 p1[foo],然后它 可能会自行退出,或继续做其他事情。

在您询问的评论中:

我们不能像这样设计 [execve]:如果子进程终止则返回 1?

这样的系统调用当然可以设计,并且已经存在,但它不能 是一个替换调用进程的非阻塞系统调用,这就是execve 是。这将是一个运行调用的子进程的阻塞系统调用 进程并将子进程的退出状态返回给父进程。这样做的人是system

【讨论】:

您好,请问您也可以看看这个问题吗? ***.com/questions/53331232/…【参考方案2】:

来自execve的手册页:

成功时,execve() 不返回,错误时返回 -1,并且 errno 设置得当。

要从子进程接收返回值,需要使用wait

【讨论】:

你能解释一下子进程是如何被终止的吗? ***.com/questions/12239645/…【参考方案3】:

在简单的情况下,无论你调用exec是否成功,子进程最终都会终止。所以这很容易......只需为它使用fork 创建的每个孩子设置父母wait

但是,有一种技术可以将故障从exec 传播到父级。该技术的工作原理如下:

    创建管道。 叉子。 在子进程中,关闭管道的读取端。将写入端标记为 close-on-exec。 给孩子打电话exec。如果失败,则将失败消息写入管道,然后退出。 从父进程,关闭管道的写端,从读端读取。

如果exec成功,父进程将不读取任何数据,只获取EOF。如果失败,父级将读取错误消息。

【讨论】:

以上是关于在 C 中使用 execve 加载程序时子进程如何终止的主要内容,如果未能解决你的问题,请参考以下文章

Linux进程启动过程分析do_execve(可执行程序的加载和运行)---Linux进程的管理与调度

如何在vfork()之后恢复父级

如何使用 execv 生成后台程序

如果在使用管道时子进程的数量大于处理器,进程会被阻塞吗?

Linux0.11内核--加载可执行二进制文件之3.exec

Linux 进程创建二(execve)