不经意间看到这篇帖⼦,想起实习⾯试的时候再讨论fork时,谈到了这个问题。做⼀个记录吧。原帖见:
COW技术初窥
在linux程序中,fork()会产⽣⼀个和⽗进程完全相同的⼦进程,但⼦进程在此后多会exec系统调⽤,出于效率考虑,linux中引⼊了“写时复制”技术,也就是只有进程空间的各段的内容要发⽣变化时,才将⽗进程的内容复制⼀份给⼦进程。那么⼦进程的物理空间没有代码,怎么去取指令执⾏exec系统调⽤呢??
在fork之后exec之前两个进程⽤的是相同的物理空间(内存区),⼦进程的代码段、数据段、堆栈都是指向⽗进程的物理空间,也就是说,两者的虚拟空间不同,其对应的物理空间是⼀个。当⽗⼦进程中有更改相应段的⾏为发⽣时,再为⼦进程相应的段分配物理空间。如果不是因为exec,内核会给⼦进程的数据段、堆栈段分配相应的物理空间(⾄此两者都有各⾃的进程空间,互不影响),⽽代码段继续共享⽗进程的物理空间(两者的代码完全相同)。⽽如果是因为exec,由于两者执⾏的代码不同,⼦进程的代码段也会分配单独的物理空间。
在⽹上看到的还有个细节问题是:fork之后内核会将⼦进程排在队列的前⾯,以让⼦进程先执⾏,以免⽗进程执⾏导致写时复制,⽽后⼦进程执⾏exec系统调⽤,因⽆意义的复制⽽造成效率的下降。
COW详述
现在有⼀个⽗进程P1,这是⼀个主体,那么它是有灵魂也是有⾝体的。现在在其虚拟地址空间(有相应的数据结构表⽰)上有:正⽂段,数据段,堆,栈这四个部分,相应地,内核要为这四个部分分配给⾃的物理块。即正⽂段块、数据段块、堆块、栈块。⾄于如何分配,这是内核去做的事,在此不详述。
1. 现在P1⽤fork()函数为进程创建⼀个⼦进程P2
内核:
(1) 复制P1的正⽂段,数据段,堆,栈这四个部分,注意是其内容相同。
(2) 为这四个部分分配物理块,P2的:正⽂段(为P1的正⽂段的物理块,其实就是不为P2分配正⽂段块,让P2的正⽂段指向P1的正⽂段块),数据段(P2⾃⼰的数据段块,为其分配对应的块),堆(P2⾃⼰的堆块),栈(P2⾃⼰的栈块)。如下图所⽰,同左到右⼤的⽅向箭头表⽰复制内容:
2. 写时复制技术
写时复制技术:内核只为新⽣成的⼦进程创建虚拟空间结构,它们复制于⽗进程的虚拟空间结构,但是不为这些段分配物理内存,它们共享⽗进程的物理空间,当⽗⼦进程中有更改相应的段的⾏为发⽣时,再为⼦进程相应的段分配物理空间。
3. vfork
vfork的做法更加简单粗暴,内核连⼦进程的虚拟地址空间也不创建了,直接共享了⽗进程的虚拟空间,当然了,这种做法就顺⽔推⾈的共享了⽗进程的物理空间
总结
传统的fork()系统调⽤直接把所有的资源复制给新创建的进程。这种实现过于简单并且效率低下,因为它拷贝的数据也许并不共享,更糟的情况是,如果新进程打算⽴即执⾏⼀个新的映像,那么所有的拷贝将是⽆⽤功。
Linux的fork()使⽤写时拷贝(copy-on-write)页实现。写时拷贝是⼀种可以推迟甚⾄免除拷贝数据的技术。内核此时并不复制整个地址空间,⽽是让⽗进程和⼦进程共享⼀个拷贝。只有在需要写⼊的时候,数据才会复制,从⽽使各个进程拥有各⾃的拷贝。也就是说,资源的复制只有在需要写⼊的时候才进⾏,在此之前,只是以只读⽅式共享。这种技术使地址空间的页的拷贝被推迟到实际发⽣写⼊的时候。
在页根本不会被写⼊的情况下,举例来说,fork()之后⽴即调⽤exec(),它们就⽆需复制了,fork()的实际开销就是复制⽗进程的页表以及给⼦进程创建唯⼀的进程描述符。在⼀般情况下,进程创建后都会马上运⾏⼀个可执⾏的⽂件,这种优化可以避免拷贝⼤量根本不会使⽤的数据(地址空间常常包含数⼗兆的数据)。由于Unix强调进程快速执⾏的能⼒,所以这个优化是很重要的,注:Linux COW和exec没有必然联系
因篇幅问题不能全部显示,请点此查看更多更全内容