子進程到底複製了父進程的什麼內容

有時會出現父子進程變量的地址一樣,但值不一樣。看下面代碼:

#include<stdio.h>

#include<string.h>

#include<stdlib.h>

#include<unistd.h>

 

main(){

char str[4]="asd";

pid_t pid=fork();

if(pid==0){

str[0]='b';

printf("子進程中str=%s\n",str);

printf("子進程中str指向的首地址:%x\n",(unsigned int)str);

}

else{

sleep(1);

printf("父進程中str=%s\n",str);

printf("父進程中str指向的首地址:%x\n",(unsigned int)str);

}

}

輸出:

 

子進程中str=bsd

子進程中str指向的首地址:bfc224dc

父進程中str=asd

父進程中str指向的首地址:bfc224dc

這裏就涉及到物理地址和邏輯地址(或稱虛擬地址)的概念。

從邏輯地址到物理地址的映射稱爲地址重定向。分爲:

靜態重定向--在程序裝入主存時已經完成了邏輯地址到物理地址和變換,在程序執行期間不會再發生改變。

動態重定向--程序執行期間完成,其實現依賴於硬件地址變換機構,如基址寄存器。

邏輯地址:CPU所生成的地址。CPU產生的邏輯地址被分爲 :p (頁號) 它包含每個頁在物理內存中的基址,用來作爲頁表的索引;d (頁偏移),同基址相結合,用來確定送入內存設備的物理內存地址。

物理地址:內存單元所看到的地址。

用戶程序看不見真正的物理地址。用戶只生成邏輯地址,且認爲進程的地址空間爲0到max。物理地址範圍從R+0到R+max,R爲基地址,地址映射-將程序地址空間中使用的邏輯地址變換成內存中的物理地址的過程。由內存管理單元(MMU)來完成。

 

  fork()會產生一個和父進程完全相同的子進程,但子進程在此後多會exec系統調用,出於效率考慮,linux中引入了“寫時複製“技術,也就是隻有進程空間的各段的內容要發生變化時,纔會將父進程的內容複製一份給子進程。在fork之後exec之前兩個進程用的是相同的物理空間(內存區),子進程的代碼段、數據段、堆棧都是指向父進程的物理空間,也就是說,兩者的虛擬空間不同,但其對應的物理空間是同一個。當父子進程中有更改相應段的行爲發生時,再爲子進程相應的段分配物理空間,如果不是因爲exec,內核會給子進程的數據段、堆棧段分配相應的物理空間(至此兩者有各自的進程空間,互不影響),而代碼段繼續共享父進程的物理空間(兩者的代碼完全相同)。而如果是因爲exec,由於兩者執行的代碼不同,子進程的代碼段也會分配單獨的物理空間。

       

fork時子進程獲得父進程數據空間、堆和棧的複製,所以變量的地址(當然是虛擬地址)也是一樣的。

每個進程都有自己的虛擬地址空間,不同進程的相同的虛擬地址顯然可以對應不同的物理地址。因此地址相同(虛擬地址)而值不同沒什麼奇怪。

 

具體過程是這樣的:

fork子進程完全複製父進程的棧空間,也複製了頁表,但沒有複製物理頁面,所以這時虛擬地址相同,物理地址也相同,但是會把父子共享的頁面標記爲“只讀”(類似mmap的private的方式),如果父子進程一直對這個頁面是同一個頁面,知道其中任何一個進程要對共享的頁面“寫操作”,這時內核會複製一個物理頁面給這個進程使用,同時修改頁表。而把原來的只讀頁面標記爲“可寫”,留給另外一個進程使用。fork之後內核會通過將子進程放在隊列的前面,以讓子進程先執行,以免父進程執行導致寫時複製,而後子進程執行exec系統調用,因無意義的複製而造成效率的下降。

 

這就是所謂的“寫時複製”。正因爲fork採用了這種寫時複製的機制,所以fork出來子進程之後,父子進程哪個先調度呢?內核一般會先調度子進程,因爲很多情況下子進程是要馬上執行exec,會清空棧、堆。。這些和父進程共享的空間,加載新的代碼段。。。,這就避免了“寫時複製”拷貝共享頁面的機會。如果父進程先調度很可能寫共享頁面,會產生“寫時複製”的無用功。所以,一般是子進程先調度滴。

子進程複製了父進程的行緩衝

#include <stdio.h>

#include <unistd.h>

int main(void)

{

printf("one");

fork();

printf("two/n");

return 0;

}

輸出

onetwo

onetwo

首先要清楚stdin和stdout都是行緩衝,stderr是無緩衝。"one"存放在父進程的行緩衝裏,子進程複製了父進行程的行緩衝,所以子進程也會打印輸出"one"。

#include <stdio.h>

#include <unistd.h>

int main(void)

{

printf("one");

fflush(stdout);

fork();

printf("two/n");

return 0;

}

輸出

onetwo

two

想消除行緩衝所帶來的困擾,方法如下

1、輸出數據後加換行符

2、使用fflush之類的函數強制刷新

3、使用setbuf,setvbuf函數設置緩衝區大小

4、使用非緩衝的的流,如stderr.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章