根据我的问题标题中的假设“Fork创建一个与其父级完全相同的新进程”。我想知道操作系统是如何实现fork的。
考虑到一个繁重的进程(巨大的RAM占用空间),它自己完成一项小任务(将文件列入目录)。从假设出发,我希望子进程与第一个进程一样大。然而,我的常识告诉我,情况并非如此。
它在现实世界中如何运作?
答案 0 :(得分:4)
正如评论中提到的其他人所说,一种名为 Copy-On-Write 的技术减轻了复制父进程的整个内存空间的沉重成本。写时复制意味着内存页面在父级和子级之间以只读方式共享,直到它们中的任何一个决定写入 - 此时页面被复制并且每个进程都获得自己的私有副本。这种技术很容易防止大量的复制,在很多情况下会浪费时间,因为孩子会exec()
或做一些简单的事情并退出。
以下是详细情况:
当您致电fork(2)
时,您所承担的唯一直接成本是分配新的唯一流程描述符的成本以及复制父页面表的成本。在Linux中,fork(2)
由clone(2)
系统调用实现,这是一种更通用的系统调用,允许调用者控制新进程的哪些部分与父进程共享。从fork(2)
调用时,传递一组标志以指示不共享任何内容(您可以选择共享内存,文件描述符等 - 这是线程的实现方式:通过调用clone(2)
使用CLONE_VM
,这意味着"共享内存空间")。
在幕后,每个进程的内存页面都有一个位标志,即 copy-on-write 标志,指示在写入之前是否应该复制该页面。 fork(2)
使用该位标记进程中的每个可写页面。每个页面还保留一个引用计数。
因此,当进程分叉时,内核会在该进程的每个非私有可写页面上设置 copy-on-write 位,并将引用计数增加1。子进程有指向这些相同页面的指针。
然后,每个页面都标记为只读,以便尝试写入页面会产生页面错误 - 这需要唤醒内核,以便它有机会看到发生了什么以及需要做什么
当任一进程写入仍在共享的页面并因此被标记为只读时,内核会唤醒并尝试找出出现页面错误的原因。假设父/子进程正在写入合法位置,内核最终会看到生成页面错误,因为页面被标记为 copy-on-write 并且对该页面有多个引用
然后内核分配内存,将页面复制到新位置,然后写入就可以继续了。
各种叉子有什么不同
您说fork(2)
创建的新流程与其父完全相同。这不是真的。父母与子女之间存在一些差异:
关于vfork
vfork(2)
系统调用与fork()
非常相似,但绝对不会复制 - 它甚至不会复制父页面表。随着写入时复制的引入,它不再被广泛使用,但历史上它被分叉后调用exec()
的进程使用。
当然,在vfork()
导致混乱之后,尝试在子进程中写入内存。
答案 1 :(得分:1)
当进程分叉时,子进程使用与其父进程父对象或子进程相同的页表写入其空间!所以