子進程復制了父進程的什么


如果你對代碼段、數據段、棧、堆存放哪些數據還不是很清楚,請先看我寫和Linux 內存管理

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

#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之后內核會
通過將子進程放在隊列的前面,以讓子進程先執行,以免父進程執行導致寫時復制,而后子進程執行exec系統調用,因無意義的復制而造成效率的下降。

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

每個進程都有自己的虛擬地址空間,不同進程的相同的虛擬地址顯然可以對應不同的物理地址。因此地址相同(虛擬地址)而值不同沒什么奇怪。
具體過程是這樣的:
fork子進程完全復制父進程的棧空間,也復制了頁表,但沒有復制物理頁面,所以這時虛擬地址相同,物理地址也相同,但是會把父子共享的頁面標記為“只讀”(類似mmap的private的方式),如果父子進程一直對這個頁面是同一個頁面,知道其中任何一個進程要對共享的頁面“寫操作”,這時內核會復制一個物理頁面給這個進程使用,同時修改頁表。而把原來的只讀頁面標記為“可寫”,留給另外一個進程使用。

這就是所謂的“寫時復制”。正因為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.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM