C语言 进程分叉后会发生什么



假设一个进程是从另一个进程分叉出来的。换句话说,我们通过 fork 函数调用复制一个进程。现在,由于分叉是一种写入时复制机制,因此每当分叉进程或原始进程写入页面时,它们都会获得一个新的物理页面进行写入。因此,据我所知,当分叉进程和原始进程都在执行时,事情会像这样。

-

-> 分叉时,原始进程和分叉进程的所有页面都被赋予只读访问权限,以便内核知道写入了哪个页面。发生这种情况时,内核将新的物理页面映射到写入过程,将之前的内容写入其中,然后授予对该页面的写入访问权限。现在我不清楚的是,如果分叉和原始进程都写入同一页面,其中一个是否仍将保留原始物理页面(在分叉之前),或者两者都将获得新的物理页面。其次,我的假设是否正确,即分叉和原始流程中的所有页面在分叉时都被授予只读访问权限?

-

-> 现在,由于每个页面错误都会触发中断,这意味着每次写入原始或分叉进程都会减慢执行速度。假设我们知道该应用程序,并且我们知道将写入大量连续的内存页面,那么当写入组中的某个页面时,向多个页面(一组页面)授予写入权限不是更好吗?这将减少由于页面错误处理而导致的中断次数。不是吗?当然,在这种情况下,我们有时可能会不必要地复制副本,但我认为中断比写入 512 个 long 类型的变量(页面的 4096 字节)要多得多。我的理解是正确的还是我错过了什么?

如果我

没记错的话,其中一个进程将被视为首先写入页面。即使您拥有多个内核,我相信页面错误也会串行处理。在这种情况下,第一个被捕获的页面将解耦两个进程的页面,因此当第二个写入它时,不会有错误,因为它现在将拥有自己的可写页面。

我相信完成后,现有页面由一个进程保留(并设置回读/写),并为另一个进程创建一个新副本。

我认为您的第三点围绕着一个简单的观点:"假设我们是否知道该应用程序......"。这就是问题所在:操作系统不知道应用程序。从本质上讲,它"知道"的唯一事情将是间接的,通过内核编码人员的观察。他们无疑会观察到 fork 通常后面跟着 exec ,所以他们无疑会为此进行优化。是的,情况并非总是如此,你显然关心其他情况 - 我在这里说的是它们足够不寻常,以至于我猜在它们身上花费的精力很少。

不太确定我是否遵循4096字节页面中512个长长的逻辑或数学 - 第一次写入页面时,它会在进程之间复制和解耦。从那时起,进一步写入该页面的任一进程副本都不会导致任何进一步的页面错误(至少与写入时的副本有关 - 当然,如果进程站点空闲很长时间,则数据可能会分页到页面文件,或该顺序上的内容,但在这里无关紧要)。

Fork 在

语义上制作流程的副本。写入时复制是一种优化,可以使其更快。优化通常有一些隐藏的权衡。有些案件做得更快,但另一些案件却受到影响。写时复制是有成本的,但我们希望通常会有节省,因为大多数复制的页面实际上不会由孩子写入。在理想情况下,孩子会立即执行。

因此,我们遭受少量页面的页面错误异常,这比预先复制所有页面便宜。

大多数"惰性求值"类型的优化都具有这种性质。

包含

100 万个项目的延迟列表比包含 100 万个项目的常规列表更昂贵。但是,如果列表的使用者仅访问前 100 个项目,则惰性列表获胜。

好吧,如果fork()不使用COW,初始成本将非常高。如果您查看典型的top显示,RSS/VSIZE 的比例非常小(例如,典型 vi 会话为 2MB/56MB)。

克隆没有 COW 的进程会导致巨大的内存压力,这实际上会导致其他进程丢失其附加的页面(必须移动到辅助存储,然后可能恢复)。而且这种分页实际上会导致每页 1-2 个磁盘 I/O(仅当页面是新的或脏的时才需要换,只有在页面再次被其他进程引用时才需要换入

另一点是粒度:在MMU不存在的时代,必须交换整个进程以产生内存,导致系统实际上冻结一秒钟左右。基于每页的页面错误会导致更多的陷阱,但这些陷阱很好地分布开来,允许进程实际竞争物理 RAM。没有先验知识,很难击败LRU计划。

最新更新