为什么vfork或fork的子代调用_exit()而不是exit()?


12

从手册页vfork()

vfork()与fork()的不同之处在于,父级被挂起,直到子级对execve(2)或_exit(2)进行调用。子级与其父级(包括堆栈)共享所有内存,直到子级发出execve()为止。子级不得从当前函数返回或调用exit(),而可以调用_exit()。

为什么孩子应该使用an _exit()而不是简单地打电话exit()?我希望这对vfork()和都适用fork()


Answers:


11

正如前面看到的vfork不允许子进程来访问父进程的内存。exit是C库函数(这就是为什么通常将其编写exit(3))的原因。它执行各种清理任务,例如刷新和关闭C流(通过中声明的函数打开文件stdio.h)以及执行向用户注册的用户指定函数atexit。所有这些任务都涉及读取和写入过程存储器。

_exit退出而不进行清理。它是直接的系统调用(这就是为什么将其编写为_exit(2)),通常是通过将系统调用号放在处理器寄存器中并执行特定的处理器指令(分支到系统调用处理程序)来实现的。这不需要访问进程内存,因此在执行操作之后是安全的vfork

之后fork,就没有这种限制:父进程和子进程现在是完全自治的。


vfork不允许子进程访问父进程的内存?但是我认为他们共享相同的地址空间,因此孩子可以访问父母的地址空间。那是错误的理解吗?

分叉之后,就没有这种限制:父进程和子进程现在完全自治。这是否意味着我可以从fork的子级调用exit()
森,

1
@Sen:不允许孩子访问父母的记忆。如果尝试使用它,则可能会起作用,因为内核无法保护您。但是效果可能不是您想要的。例如,如果您的编译器决定在寄存器中保留一些值,则父进程将看不到它。
吉尔(Gilles)“所以,别再邪恶了”,

@Sen:分叉后,您可以执行调用退出或任何其他功能。每个进程都在派生之后开始(在Linux上,即使是初始进程init也由内核派生)。
吉尔(Gilles)“所以,别再邪恶了”,

3

exit做额外的清理工作,例如调用由其注册的函数,atexit因此它访问复制部分之外的数据。_exit直接执行系统调用,而无需清除内核内的任何清理。


...并且应该注意,fork()确实复制了所有内容,因此您可以调用exit(),并且可以肯定从当前函数返回。
derobert

3

子调用_exit()可以避免在子进程退出时刷新stdio(或其他)缓冲区。由于子进程构成了父进程的精确副本,因此子进程仍具有父进程“ stdout”或“ stderr”(<stdio.h>中的缓冲区)中的内容。当父进程中的缓冲区已满并被刷新时,您可以(并且会在不适当的时间)通过调用exit()获得双倍输出,其中一个来自子进程的atexit处理程序,另一个来自父进程。

我意识到上面的答案集中在stdio.h细节上,但是这个想法可能会延续到其他缓冲的I / O,正如上面的答案之一所示。


1

exit():-执行一些清理任务,例如关闭I / O流和许多流,然后返回内核。 _exit():-直接进入内核(不执行任何清理任务)。

fork() :父级和子级都有不同的文件表,因此,子级所做的更改不会影响父级的环境参数,反之亦然。

vfork():父级和子级都使用相同的文件表,因此子级所做的更改会影响父级的环境参数。例如,一些变量var=10,现在var++由child运行,然后由parent运行,您也可以var++在parent输出中看到的效果。

就像我说的那样,如果使用exit()vfork()则所有I / O已经关闭。因此,即使parent运行正常,您也无法获得正确的输出,因为所有变量均已清空,所有流均已关闭。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.