2011-11-28 45 views
27

因此,我已经使用了fork(),我知道它的作用。作为一个初学者,我非常害怕它(但我仍然不完全理解它)。您可以在网上找到的fork()的一般说明是,它复制当前进程并分配不同的PID,父PID,并且该进程将具有不同的地址空间。但是,所有这些都是好的,但是,考虑到这个功能描述,初学者会想“为什么这个功能如此重要......我为什么要复制我的过程?”。所以我很想知道,最终我发现,您可以通过execve()家族从当前流程中调用其他流程。为什么fork()以这种方式工作

我仍然不明白的是为什么你必须这样做?最顺理成章的事情是有,你可以调用像

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

这将产生新的进程,并开始在主开始运行它(),并返回新的PID功能。

让我感到困惑的是fork/execve解决方案正在进行可能不需要的工作。如果我的流程使用大量内存会怎么样?内核是否复制我的页面表等。我相信它没有真正分配真实的内存,除非我已经触及它。另外,如果我有线程会发生什么?在我看来,这太乱了。

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

This同一个问题线程会谈,但我发现它不是中规中矩:

谢谢。

+0

请在http://unix.stackexchange.com/或http://superuser.com/ – rlemon

+7

上发帖为什么unix?这是一个编程问题,它属于堆栈溢出。 – Petr

+0

解读http://cm.bell-labs.com/who/dmr/hist.html – ninjalj

回答

0

那么就分页/虚拟内存而言,有些技术中fork()并不总是复制整个进程的地址空间。在写分支时,分支进程获取与其父进程相同的地址空间,然后仅复制被更改的一部分空间(通过任一进程)。

2

看看spawn和朋友。

+2

请记住'spawn'是POSIX,而'fork'是纯粹的Unix。并不是说它不能被使用,但是对于一个纯粹的Unix体验,你坚持使用'fork'-'execve' :) –

+0

另外,请注意''spawn'使用'fork'(或'clone')内部。内核中没有任何东西可以提供所需的功能。这意味着它更加用户友好和明显,但无论是开销(复制页表和描述符),开销都是相同的。 – Damon

10

请记住,fork很早就在Unix(早些时候可能在之前)发明的今天看起来很小(例如64K字节的内存)的机器上。

它通过最基本的可能行动提供基本机制而不是政策的整体(原始)理念更为相位。

fork只是创建一个新的过程,最简单的思维方式就是克隆当前过程。所以语义是非常自然的,它是最简单的机制。

其他系统调用(execve)负责加载一个新的可执行文件,等..

将它们分开(并且还提供pipedup2系统调用)给出了很大的灵活性。

并且在当前的系统上,fork被非常有效地实现(通过写入分页技术的懒惰拷贝)。众所周知,fork机制使得Unix进程的创建速度非常快(例如,比Windows或VAX/VMS更快,其中系统调用的创建过程与您提出的更类似)。

还有vfork系统调用,我不打扰使用。

而且posix_spawn API比fork或单独execve要复杂得多,所以说明fork更简单...

+0

所以,我听说过产卵,但是我想知道,哪些创建一个新的进程方法可以做到大型备受推崇的linux应用程序使用(比如Gimp,openoffice,gnome等)。我认为他们中至少有一些人需要这样做。 – user1068779

+0

GTK在'fork'系统调用之上提供了(在Glib库中)调用,就像http://developer.gnome.org/glib/unstable/glib-Spawning-Processes.html –

+0

我认为这最终只是清晰的回答只是说“记住fork很早就发明了Unix”。尽管没有人证实,但我相信可以实现更有效的新函数,除了额外的内存/属性克隆之外,它只会执行“fork()”的功能,仅用于启动一个新的独立进程,该进程几乎不会共享与其父母。 – Petr

2

fork创建通过复制当前进程的新工艺,它执行时拷贝写。这意味着新进程的内存将与父进程共享,直到它被更改。当内存改变时,内存被复制以确保每个进程都有自己的内存有效副本。在fork之后执行execve时,没有内存副本,因为新进程只是加载一个新的可执行文件,因此新的内存空间。

至于为什么要这样做的问题,我不确定,但它似乎是Unix方式的一部分 - 做一件好事。该操作不是创建一个创建新进程并加载新可执行文件的函数,而是分成两个函数。这给了开发者最大的灵活性。尽管我自己还没有使用过任何一种功能......

+0

它由MMU通过标记页面COW完成。 Windows使用相同的机制来启动新进程。系统调用底层fork(clone)与系统调用底层CreateProcess(ZwCreateProcess)非常相似,实际上你可以在ZwCreateProcess之上实现fork。 –

+0

http://doxygen.scilab.org/5.4/d0/d8f/forkWindows_8c_source.html –

1

假设底层实现使用写入时复制寻址系统,fork()可以用很少的内存分配来实现。通过该优化来实现create_process函数是不可能的。

5

“fork()”是一个杰出的创新,它解决了单个API的全部问题。它是在多处理不常见的时候发明的(在我们今天使用的多处理之前,这种处理大约有20年)。

+0

Err,自1950年代以来一直在进行多处理。 – EJP

+0

辉煌?我会说一个愚蠢的(可以说)解决产生新过程的特定小子集 - 克隆现有的一个。在大多数情况下,你只需要启动一个小帮手程序为你做一些小工作,你得到的只是'叉子'?哎哟!太蹩脚了,从来不喜欢它。在许多情况下克隆确实有意义,但不在这里,请相信我。 – Sergey

0

使用fork的主要原因是执行速度。

如果您按照您的建议使用一组参数启动了流程的新副本,则新流程需要解析这些参数并重复父流程所完成的大部分处理。使用“fork()”,父进程堆栈的完整副本立即可用于孩子,并且所有内容都被解析并格式化。

此外,在大多数情况下,程序将是“.so”或“.dll”,因此可执行指令将不会被复制,只有堆栈和堆存储将被复制。

2

正如其他人所说,fork实施得非常快,所以这不成问题。但为什么不是像create_process()这样的功能?答案是:灵活性的简单性。 全部 unix中的系统调用被编程为只做一件事。像create_process这样的函数可以做两件事:创建一个进程并在其中加载一个二进制文件。

每当您尝试并行化事物时,都可以使用线程 - 或使用fork()打开的进程。在大多数情况下,您通过fork()打开n进程,然后使用IPC机制在这些进程之间进行通信和同步。一些IPC坚持在全球空间中存在变数。

实施例与管道:

  • 创建管
  • 叉它继承的管状手柄
  • 孩子关闭输入侧
  • 父关闭输出侧

Impossible without fork() ...

另一个重要的事实是整个Unix API只有一些功能。每个程序员都可以轻松记住使用过的函数。但是请参阅Windows API:数以千计的功能是人们无法想起的。

所以总结起来,再说一遍:简单灵活

+1

虽然我同意你fork()可以做的事情,“create_process()”我不能强烈反对,即使fork()被实现为非常快,可以使它比一个函数,会做得更快除了内存复制外,fork()还是一样。这总会节省一堆CPU指令,因此速度会更快。 – Petr

+0

@Petr:加载一个新进程主要是通过比较使'fork()'的开销变得微不足道。 – ninjalj

+0

克隆是由MMU通过标记写入时拷贝来完成的。它不吃任何CPU周期。实际上,产生线程是通过用于在Unix和Linux上实现fork的相同的系统调用来完成的,分支并没有比产生线程更高的开销。众所周知,Windows也可以通过分叉来启动一个新进程,虽然它被称为ZwCreateProcess并且隐藏在ntdll.dll中。 CreateProcess与fork的开销来自必须清空并重新初始化克隆以启动一个空进程。 –

1

所以,你主要关注的是:叉()导致不必要的内存复制。

答案是:不,没有记忆浪费。总之,fork()是在内存资源非常有限的情况下诞生的,所以没有人会考虑像这样浪费它。

尽管每个进程都有自己的地址空间,但物理内存页面和进程的虚拟内存页面之间没有一对一的映射关系。相反,可以将一页物理内存映射到多个虚拟页面(有关更多详细信息,请搜索CPU TLB)。

因此,当您使用fork()创建新进程时,它们的虚拟地址空间被映射到相同的物理内存页面。没有内存拷贝是必需的。这也意味着没有重复使用的库,因为它们的代码段标记为只读。

实际内存复制仅在父进程或子进程修改某个内存页面时发生。在这种情况下,新的物理内存页面被分配并映射到修改页面的进程的虚拟地址空间。

+0

CPU浪费怎么样?当流程的某些属性被复制到新流程时,是不是这个操作只是一堆额外的指令,不需要执行,因为我知道我会抛弃它们呢?我的意思是fork()制作一个进程的副本。它复制了许多后来被覆盖并且消耗了一些不需要消耗的CPU的属性,或者不是? – Petr

+0

没有太多的属性会被过度考虑。这样的开销是可接受的 –

1

这是一个很好的问题。我不得不在源代码中进行一些挖掘,看看究竟发生了什么。

fork()通过复制调用过程来创建一个新进程。

在Linux下,fork()是使用写时复制页面实现的,因此唯一的代价是复制父页表所需的时间和内存,并为子项创建独特的任务结构。

新进程称为子进程,与调用进程完全相同(称为父进程)。不包括:

  • 孩子有其自己唯一的进程ID,并且此PID不匹配 任何现有进程组的ID。
  • 孩子的父进程ID与父进程ID相同。
  • 孩子不继承父母的记忆锁。
  • 处理资源利用率和CPU时间计数器在子中重置为零 。
  • 孩子的待决信号集最初是空的。
  • 该子项不会从其父项继承信号量调整。
  • 孩子不从其父母继承记录锁。
  • 孩子不从其父母继承定时器。
  • 该子项不从其父项继承未完成的异步I/O操作 ,也不从其父项继承任何异步I/O上下文。

结论:叉

主要目的是分裂的父母进程的任务分成更小的子任务,而不会影响父母的唯一的任务结构。这就是叉克隆现有流程的原因。

来源:

http://www.quora.com/Linux-Kernel/After-a-fork-where-exactly-does-the-childs-execution-start http://learnlinuxconcepts.blogspot.in/2014/03/process-management.html

+1

+1用于挖掘fork()的工作方式。但是,现在还没有比克隆现有技术更好的方法来开始新的过程吗?我只是没有看到这一点。如果你想开始新的,单独的过程,为什么你想先克隆现有的过程? – Petr

+0

为了回应您的评论,我已对我的回答进行了更改。 –

+0

如果你产生一个新的过程,你将不得不从main()开始并设置所有东西。线程通常也是这种情况,线程从自己的threadproc开始,后者必须解码void指针提供的数据(它唯一的参数)。用叉子不需要初始化任何东西。 –

17

这是由于历史的原因。截至https://www.bell-labs.com/usr/dmr/www/hist.html解释,UNIX很早就没既无fork()也不exec*(),壳执行的命令的样子:

  • 做必要的初始化(开标准输入/输出)。
  • 阅读命令行。
  • 打开命令,加载一些引导代码并跳转到它。
  • 引导程序代码读取打开的命令(覆盖shell的内存)并跳转到它。
  • 一旦命令结束,它会调用exit(),然后通过重新加载外壳(覆盖命令的内存),并跳转到它,回去工作步骤1

从那里,fork()是一个易于添加(27条装配线),重用其余的代码。

在Unix的发展阶段,执行命令变成了:

  • 阅读的命令行。
  • fork()一个子进程,并等待它(通过发送一条消息给它)。
  • 子进程加载命令(覆盖孩子的记忆),并跳转到它。
  • 一旦命令结束,它会调用exit(),现在更简单了。它只是清理了它的流程条目,并放弃了控制权。

最初,fork()没有做写上复制。由于这使得fork()非常昂贵,并且fork()经常用于产生新的进程(因此经常紧接着是exec*()),fork()的优化版本出现了:vfork()它共享父和子之间的内存。在那些vfork()的实施中,父母将被暂停,直到儿童exec*()'ed或_exit()'编辑,从而放弃父母的记忆。后来,fork()被优化,以便在写入时进行复制,仅当父母和子女之间的差异开始时才复制内存页面。后来又看到了对MMU系统端口的重新兴趣(例如:如果你有一个ADSL路由器,它可能在一个MMU MIPS CPU上运行Linux),它不能进行COW优化,而且不能支持fork()'ed有效地处理。

fork()效率低下的其它来源,它最初可以复制地址空间(和页表)的母公司,这可能使运行从庞大的计划短期课程相对缓慢,或可能使OS否认fork()思维有可能没有足够的内存(要解决这个问题,可以增加交换空间,或者更改操作系统的内存过量使用设置)。作为一个轶事,Java 7使用vfork()/posix_spawn()来避免这些问题。

另一方面,fork()使创建几个相同过程的实例非常高效:例如:一个Web服务器可能有几个相同的进程服务于不同的客户端。其他平台更倾向于使用线程,因为产生不同进程的成本比重复当前进程的成本要大得多,这可能比产生新线程稍微大一点。这是不幸的,因为共享的所有线程都是错误的诱因。

+0

在所有的答案中,这看起来应该是这里唯一的答案:^) –

+0

链接已经死亡。任何正在寻找该文件的人: 标题:“Unix分时系统的演变” 作者:“Dennis M. Ritchie” – Sidervs

0

你可以想到这有点像在Windows中产生一个线程,除了进程不共享除文件句柄,共享内存和其他明确可继承的东西之外的资源。因此,如果您有新任务要做,则可以在克隆负责新任务时分叉和一个进程继续其原始作业。

如果您想要执行并行计算,您的进程可以将其自身分割为循环上方的多个克隆。每个克隆都会执行计算的一个子集,而父级则等待它们完成。操作系统确保它们可以并行运行。在Windows中,您可以需要使用OpenMP才能获得相同的可表达性。

如果您需要阅读或写入文件,但无法等待,您可以分叉并且您的克隆执行I/O,同时继续执行原始任务。在Windows上,你可能会考虑产生线程或者在很多情况下使用重叠的I/O,在Unix中一个简单的fork就可以完成。特别是,进程并不像线程那样具有相同的可调度性问题。这在32位系统上尤其如此。只是分叉比处理错综复杂的I/O更加方便。虽然进程拥有自己的内存空间,但线程仍处于相同的状态,因此对于应该考虑放入32位进程的线程数有限制。使用fork制作32位服务器应用程序非常简单,而使用线程制作32位服务器应用程序可能是一场噩梦。所以,如果你在32位Windows上编程,你将不得不求助于其他解决方案,如重叠I/O,这是一个PITA的工作。

因为进程不会像线程一样共享全局资源(例如malloc中的全局锁),所以这是更具可扩展性的。虽然线程经常会彼此阻塞,但进程独立运行。

在Unix上,因为fork为您的进程创建写时复制克隆,所以它不会比在Windows中产生新线程更重量级。

如果您处理的是解释型语言,通常有一个全局解释器锁(Python,Ruby,PHP ...),那么赋予您fork功能的操作系统是必不可少的。否则,您利用多个处理器的能力将受到更多限制。

另一件事就是在这里有一个安全问题。进程不共享内存空间,不能混淆每个其他内部细节。这导致更高的稳定性。如果您有一台使用线程的服务器,则一个线程中的崩溃将导致整个服务器应用程序崩溃。分叉崩溃只会取消分叉克隆。这也使错误处理更加简化。分叉克隆通常已经足够,因为它对原始应用程序没有任何影响。

还有一个安全问题。如果分叉进程注入恶意代码,则不会进一步影响父级。现代的网页浏览器利用这个例如保护一个标签与另一个标签。如果您有叉式系统调用,所有这些都可以方便编程。

-1

其他的答案已经做了解释为什么fork比它似乎更快了,怎么了最初来存在的一个好工作。但是,保持fork + exec组合也是一个很好的例子,这就是它提供的灵活性。

通常情况下,产卵一个子进程的时候,也有执行儿童前采取的准备步骤。例如:您可以使用pipe(读取器和写入器)创建一对管道,然后将子进程的stdoutstderr重定向到写入器,或者将读取器用作进程的stdin或任何其他文件描述符。或者,您可能需要设置环境变量(但仅限于小孩)。或者使用setrlimit设置资源限制来限制孩子可以使用的资源量(不限制父母)。或用setuid/seteuid更改用户(不更改父级)。等等等等

当然,你可以做到这一切与一个假想create_process功能。但是,这是一个需要覆盖的东西!为什么不提供运行的灵活性fork,做任何你想设置的孩子,然后运行exec

此外,有时你实际上并不需要一个子进程可言。如果您当前的程序(或脚本)仅用于执行这些设置步骤中的一部分,并且它将要执行的最后一件事是运行新流程,那么为什么有两个流程呢?您可以使用exec来替换当前进程,释放自己的内存和PID。

的分岔还允许有关只读数据集一些有用的行为。例如,你可以有一个父进程来收集和索引大量的数据,然后派生出子工来根据这些数据执行遍历和计算。父母不需要将它保存在任何地方,孩子们不需要阅读它,并且不需要对共享内存做任何复杂的工作。 (例如:有些数据库使用这种方式将子内存数据库转储到磁盘,而不会阻塞父进程。)

上面还包括任何读取配置,数据库,和/或一组代码文件,然后继续分离子进程以处理请求并更好地使用多核CPU。这包括web服务器,但也包括web(或其他)应用程序本身,特别是如果这些应用程序只是在阅读和/或编译更高级代码时花费大量启动时间。

的分岔,也可以来管理内存,并避免碎片,特别是对于使用自动内存管理(垃圾收集),并没有对他们的记忆布局直接控制高级语言的有效途径。如果您的进程短暂地需要大量内存用于特定操作,则可以进行分叉并执行该操作,然后退出,释放刚刚分配的所有内存。相比之下,如果您在父级执行操作,则可能会在整个过程中持续存在大量内存碎片 - 对于长时间运行的进程来说不是很好。

最后:一旦你接受forkexec两个都有自己的用法,相互独立,问题就变成了 - 为什么还要创建一个单独的函数来结合这两个函数呢?据说Unix的理念是让它的工具“做一件事,做得很好”。通过将forkexec作为单独的构建块 - 并使每个构建块尽可能快速和高效 - 它们允许比单个功能更具灵活性。

相关问题