linux内核在使用fork创建进程时,基本上会使用Copy-On-Write(COW)技术。这里解释一下COW技术以及为什么在fork中使用。
WIKI上对COW的解释:
Copy-on-write (sometimes referred to as "COW") is an optimization strategy used in computer programming. The fundamental idea is that if multiple callers ask for resources which are initially indistinguishable, they can all be given pointers to the same resource. This function can be maintained until a caller tries to modify its "copy" of the resource, at which point a true private copy is created to prevent the changes becoming visible to everyone else. All of this happens transparently to the callers. The primary advantage is that if a caller never makes any modifications, no private copy need ever be created.
意思上就是:在复制一个对象的时候并不是真正的把原先的对象复制到内存的另外一个位置上,而是在新对象的内存映射表中设置一个指针,指向源对象的位置,并把那块内存的Copy-On-Write位设置为1.
这样,在对新的对象执行读操作的时候,内存数据不发生任何变动,直接执行读操作;而在对新的对象执行写操作时,将真正的对象复制到新的内存地址中,并修改新对象的内存映射表指向这个新的位置,并在新的内存位置上执行写操作。
这个技术需要跟虚拟内存和分页同时使用,好处就是在执行复制操作时因为不是真正的内存复制,而只是建立了一个指针,因而大大提高效率。但这不是一直成立的,如果在复制新对象之后,大部分对象都还需要继续进行写操作会产生大量的分页错误,得不偿失。所以COW高效的情况只是在复制新对象之后,在一小部分的内存分页上进行写操作。
COW在编程中被广泛应用。
特别是在操作系统当中,当一个程序运行结束时,操作系统并不会急着把其清除出内存,原因是有可能程序还会马上再运行一次(从磁盘把程序装入到内存是个很慢的过程),而只有当内存不够用了,才会把这些还驻留内存的程序清出。
而对于Linux内核空间创建进程时的fork,由于在内核空间已经由代码决定不使用COW技术(参见mm/memory.c Line 221)。从而由内核空间的进程0(main)创建进程1(init)不使用COW,系统对此次新进程创建进行了特殊处理(存在疑问,同样是fork,如何实现对这个fork的特殊处理,估计是schedule,看到再解决了)。进程0和进程1同时使用着内核代码区内(<=1M)相同的代码和数据内存页面(640KB),只是执行代码不在一处,因此他们也同时使用着相同的用户堆栈区。在为进程1(init)复制其父进程(进程0)的页目录和页表项时,进程0的640KB页表项的属性没有改动过(仍然可读写),但是进程1的640KB对应的页表项却被设置成只读。因此当进程1(init)开始执行时,对用户堆栈的入栈操作将导致页面写保护异常,从而使得内核的内存管理程序为进程1在主内存区中分配一内存页面,并把进程0中的页面内容复制到新的页面上。从此时开始,进程1开始有自己独立的内存页面,由于此时的内存页面在主内存区,因此进程1中继续创建新的子进程时可以采用COW技术。
在Linux内核首先通过move_to_user_mode转移到用户模式下执行,至此main函数就以进程0的身份运行。而进程0是所有将创建进程的父进程,他创建进程1(init)时,fork的结果就是进程1与进程0拥有完全相同的内存空间、堆栈,这时进程0和进程1的内存还都在Linux内核空间中。
内核调度进程运行时次序是随机的,有可能在进程0创建了进城1之后仍然允许进程0,由于两个进程共享内存空间,为了不出现冲突问题,就必须要求进程0在进程1执行堆栈操作(进程1的堆栈操作会导致页面保护异常,从而使得进程1在主内存区得到新的用户页面区,此时进程1和进程0才算是真正独立,如前面所述)之前禁止使用用户堆栈区。所以进程0在执行了fork(创建了进程1)之后的pause使用内嵌的方式,保证进程0(main)不会弄乱堆栈。
进程1中如果执行fork以及exec,此时的页面空间已经到了主内存区,就可以使用COW了。