根据维基百科(这可能是错误的)
当发出 fork() 系统调用时,会创建与父进程对应的所有页面的副本,并由操作系统为子进程加载到单独的内存位置。但在某些情况下不需要这样做。考虑这样的情况:子进程执行“
exec
”系统调用(用于执行 C 程序中的任何可执行文件)或在fork()
.当子进程只需要执行父进程的命令时,不需要复制父进程的页面,因为exec
用要执行的命令替换了调用它的进程的地址空间。在这种情况下,会使用一种称为写入时复制 (COW) 的技术。使用此技术,当发生分叉时,不会为子进程复制父进程的页面。相反,页面在子进程和父进程之间共享。每当进程(父进程或子进程)修改页面时,都会为执行修改的进程(父进程或子进程)单独创建该特定页面的单独副本。然后,此过程将在所有将来的引用中使用新复制的页面而不是共享的页面。另一个进程(未修改共享页面的进程)继续使用该页面的原始副本(现在不再共享)。这种技术称为写时复制,因为当某个进程写入该页面时,该页面就会被复制。
似乎当任何一个进程尝试写入页面时,都会分配该页面的新副本并将其分配给生成页面错误的进程。之后原始页面被标记为可写。
我的问题是:如果fork()
在任何进程尝试写入共享页面之前多次调用 ,会发生什么情况?
答案1
没有什么特别的事情发生。所有进程都共享同一组页面,并且每个进程在想要修改页面时都会获得自己的私有副本。
答案2
fork() 的行为取决于 *nix 系统是否有 MMU。在非 MMU 系统(如早期的 PDP-11)上,fork() 系统调用为每个子进程复制所有父进程的内存。在基于 MMU 的 *nix 系统上,内核将所有非堆栈页面标记为 R/O 并在父级和子级之间共享它们。然后,当任何一个进程写入任何页面时,MMU 都会捕获该尝试,然后内核分配一个可写页面并更新 MMU 页表以指向现在可写页面。这种写时复制行为提供了加速,因为最初只需要为每个子进程分配和克隆一个私有堆栈。
如果您在每次 fork() 调用之间执行一些父代码,则生成的子进程将因父进程更改的页面而有所不同。另一方面,如果父进程只是发出多个 fork() 调用(例如在循环中),则子进程将几乎相同。如果使用局部循环变量,那么每个子堆栈中的变量都会不同。
答案3
当系统执行分叉时,通常(这可能取决于实现)它还会将页面标记为只读,并将父进程标记为这些页面的主进程。
当尝试写入这些页面时,会发生页面错误,操作系统接管,复制整个页面列表或仅复制已更改的页面(同样,取决于实现),因此写入过程将具有可写副本。
当有多个进程从同一个进程派生时,当“主”进程写入其内存时,其他进程复制其等效页面。