有時會出現父子進程變量的地址一樣,但值不一樣。看下面代碼:
#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.