因此,我使用了fork(),我知道它的作用。作为一个初学者,我非常害怕它(而且我仍然不完全了解它)。您可以在线找到的fork()的一般描述是,它复制当前进程并分配不同的PID,父PID且该进程将具有不同的地址空间。一切都很好,但是,鉴于此功能描述,初学者会想知道“为什么这个功能如此重要...我为什么要复制我的过程?”。因此,我确实纳闷,最终我发现这就是您可以通过execve()系列从当前进程中调用其他进程的方式。

我仍然不明白的是,为什么你必须这样做呢?最合乎逻辑的事情是拥有一个可以调用的函数,例如

create_process("executable_path+name",params..., more params);

这将产生一个新进程,并在main()的开头开始运行它并返回新的PID。

让我感到困扰的是,fork/execve解决方案正在做可能不需要的工作。如果我的过程正在使用大量内存怎么办?内核是否复制我的页表等。我确信除非我摸过它,否则它不会真正分配实际内存。另外,如果我有线程怎么办?在我看来,这太乱了。

几乎所有关于fork的描述都说,它只是复制了进程,并且新进程在fork()调用之后开始运行。这确实是发生了什么,但是为什么会这样发生,为什么fork/execve是产生新进程的唯一方法,以及从当前进程创建新进程的最通用的unix方法是什么?还有其他更有效的方法来生成进程吗?**不需要复制更多内存。

This线程讨论相同的问题,但我发现它并不令人满意:

谢谢。

最佳答案

这是由于历史原因。如https://www.bell-labs.com/usr/dmr/www/hist.html所述,很早以前的Unix既没有fork()也没有exec*(),shell执行命令的方式是:

  • 执行必要的初始化(打开stdin/stdout)。
  • 读取命令行。
  • 打开命令,加载一些引导代码并跳转到该命令。
  • bootstrap 代码读取打开的命令(覆盖shell的内存),并跳转到该命令。
  • 命令结束后,它将调用exit(),然后通过重新加载 shell 程序(覆盖命令的内存)并跳转至步骤1来工作。

  • 从那里开始,fork()是一个简单的添加(27条组装线),重用了其余的代码。

    在Unix开发的那个阶段,执行命令成为:
  • 读取命令行。
  • fork()一个子进程,并等待它(通过向其发送消息)。
  • 子进程加载命令(覆盖子内存),并跳转到该命令。
  • 命令结束后,它将调用exit(),它现在更简单了。它只是清除了其流程条目,并放弃了控制。

  • 最初,fork()不会在写入时进行复制。由于这使fork()变得昂贵,并且fork()通常用于产生新进程(因此通常紧随其后是exec*()),因此出现了fork()的优化版本:vfork(),它在父级和子级之间共享内存。在vfork()的那些实现中,父级将被挂起,直到子exec*()'ed或_exit()'ed,从而放弃了父级的内存。后来,fork()经过优化,可以在写入时进行复制,仅当内存页面的父子之间开始有所不同时才进行复制。 vfork()后来对!MMU系统的端口重新产生了兴趣(例如:如果您有ADSL路由器,则它可能在!MMU MIPS CPU上运行Linux),它无法进行COW优化,而且不支持fork()高效处理。
    fork()效率低下的另一个原因是,它最初会复制父级的地址空间(和页表),这可能会使大型程序中运行的短程序相对较慢,或者可能使操作系统以为可能没有足够的内存来拒绝fork()。为此(要解决此问题,您可以增加交换空间,或更改操作系统的内存过量使用设置)。作为轶事,Java 7使用vfork()/posix_spawn()避免了这些问题。

    另一方面,fork()使创建同一流程的多个实例非常有效:例如:Web服务器可能具有为不同客户端提供服务的多个相同流程。其他平台更喜欢线程,因为产生另一个进程的开销比复制当前进程的开销大得多,后者可能比产生新线程的开销大一点。不幸的是,因为所有共享线程都会吸引错误。

    关于linux - 为什么fork()以它的方式工作,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8292217/

    10-11 02:22
    查看更多