fork函數(shù)用于創(chuàng)建子進程,典型的調(diào)用一次,返回兩次的函數(shù),其中返回子進程的PID和0,其中調(diào)用進程返回了子進程的PID,而子進程則返回了0,這是一個比較有意思的函數(shù),但是兩個進程的執(zhí)行順序是不定的。fork 函數(shù)調(diào)用完成以后父進程的虛擬存儲空間被拷貝給了子進程的虛擬存儲空間,因此也就實現(xiàn)了共享文件等操作。但是虛擬的存儲空間映射到物理存儲空間的過程中采用了寫時拷貝技術(具體的操作大小是按著頁控制的),該技術主要是將多進程中同樣的對象(數(shù)據(jù))在物理存儲其中只有一個物理存儲空間,而當其中的某一個進程試圖對該區(qū)域進行寫操作時,內(nèi)核就會在物理存儲器中開辟一個新的物理頁面,將需要寫的區(qū)域內(nèi)容復制到新的物理頁面中,然后對新的物理頁面進行寫操作。這時就是實現(xiàn)了對不同進程的操作而不會產(chǎn)生影響其他的進程,同時也節(jié)省了很多的物理存儲器。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
int main()
{
char p = 'g';
int number = 11;
if(fork()==0) /*子進程*/
{
p = 'c'; /*子進程對數(shù)據(jù)的修改*/
printf("p = %c , number = %d \n ",p,number);
exit(0);
}
/*父進程*/
number = 14; /*父進程對數(shù)據(jù)修改*/
printf("p = %c , number = %d \n ",p,number);
exit(0);
}
編譯調(diào)試:
[gong@Gong-Computer cprogram]$ gcc -g TestWriteCopyTech.c -o TestWriteCopyTech
[gong@Gong-Computer cprogram]$ ./TestWriteCopyTech
p = g , number = 14 -----父進程打印內(nèi)容
[gong@Gong-Computer cprogram]$ p = c , number = 11 -----子進程打印內(nèi)容
原因分析:
由于存在企圖進行寫操作的部分,因此會發(fā)生寫時拷貝過程,子進程中對數(shù)據(jù)的修改,內(nèi)核就會創(chuàng)建一個新的物理內(nèi)存空間。然后再次將數(shù)據(jù)寫入到新的物理內(nèi)存空間中�?芍�,對新的區(qū)域的修改不會改變原有的區(qū)域,這樣不同的空間就區(qū)分開來。但是沒有修改的區(qū)域仍然是多個進程之間共享。
fork函數(shù)的代碼段基本是只讀類型的,而且在運行階段也只是復制,并不會對內(nèi)容進行修改,因此父子進程是共享代碼段,而數(shù)據(jù)段、Bss段、堆棧段等會在運行的過程中發(fā)生寫過程,這樣就導致了不同的段發(fā)生相應的寫時拷貝過程,實現(xiàn)了不同進程的獨立空間。
但是需要注意的是文件操作,由于文件的操作是通過文件描述符表、文件表、v-node表三個聯(lián)系起來控制的,其中文件表、v-node表是所有的進程共享,而每個進程都存在一個獨立的文件描述符表。父子進程虛擬存儲空間的內(nèi)容是大致相同的,父子進程是通過同一個物理區(qū)域存儲文件描述符表,但如果修改文件描述符表,也會發(fā)生寫時拷貝操作,只有這樣才能保證子進程中對文件描述符的修改,不會影響到父進程的文件描述符表。例如close操作,因為close會導致文件的描述符的值發(fā)生變化,相當于發(fā)生了寫操作,這是產(chǎn)生了寫時拷貝過程,實現(xiàn)新的物理空間,然后再次發(fā)生close操作,這樣就不會產(chǎn)生子進程中文件描述符的關閉而導致父進程不能訪問文件。
測試函數(shù):
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/wait.h>
int main()
{
int fd;
char c[3];
char *s = "TestFs";
fd = open("foobar.txt",O_RDWR,0);
if(fork()==0) //子進程
{
fd = 1;//stdout
write(fd,s,7);
exit(0);
}
//父進程
read(fd,c,2);
c[2]='\0';
printf("c = %s\n",c);
exit(0);
}
[gong@Gong-Computer cprogram]$ gcc -g fileshare2.c -o fileshare2
[gong@Gong-Computer cprogram]$ ./fileshare2
c = fo ----foobar.txt中的內(nèi)容
[gong@Gong-Computer cprogram]$ TestFs ---標準輸出
原因分析:由于父子進程的文件描述符表是相同的,但是在子進程中對fd(文件描述符表中的項)進行了修改,這時會發(fā)生寫時拷貝過程,內(nèi)核在物理內(nèi)存中分配一個新的頁面存儲子進程原文件描述符fd存在頁面的內(nèi)容,然后再進修寫操作,實現(xiàn)將fd修改為1,也就是標準輸出。但是父進程的fd并沒有發(fā)生改變,還是與其他的子進程共享文件描述符表,因此仍然是對文件foobar.txt進行操作。
因此需要主要fork函數(shù)實質(zhì)上是按著寫時拷貝的方式實現(xiàn)文件的映射,并不是共享,寫時拷貝操作使得內(nèi)存的需求量大大的減少了,具體的寫時拷貝實現(xiàn),請參看非常經(jīng)典的“深入理解計算機系統(tǒng)”的第622頁。