在 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
,
并且您希望它使用基本的系统调用 fork
和 execve
来做到这一点
让我们调用您的初始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 永远不会返回,如果一切正常,它将始终执行某些操作
没有。 execve
将 p1.1[foo] 替换为 p1.1[bar] 并且不返回,因为调用者不再存在。然后 p1.1[bar] 一直运行直到它终止。
p1.1[bar] 迟早会以其中一种方式终止
any 程序终止:它将运行到正常的exit
,或者它将
被信号杀死,或者它可能会主动调用abort
。
父进程(p1)如何获取这个子进程(p1.1)?
首先,不必。一旦 p1[foo] 启动了 p1.1,它就可以,
如果这就是你想要的,那就别管 p1.1,继续做其他事情
如果有的话,最后是exit
。如果 p1 在 p1.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进程的管理与调度