天天看點

環境變量與程序位址空間了解

寫在前面

這個部落客要談一下環境變量和程式位址空間,其中程式位址空間可能有點不好了解,但是這個可以幫助我們解決前面我們遺留的一些問題,以後我們幾乎都要和程式位址空間打交道,很重要.當然,前面的環境變量也解決了我們的指令問題.

環境變量

在談這個之前,我們先來看一個例子,引出這個話題.

#include <stdio.h>    
    
int main()    
{    
  printf("我僅僅是一個 main 函數\n");    
  return 0;    
}      
環境變量與程式位址空間了解
首先我不疑惑結果,這裡我就想問一件事,為何我們執行這個程式需要**./**,這一點才是我感覺到不一樣的.當然我們平常是這樣做的,可能覺得是理所當然,那麼我是知道的Linux是用C語言寫的,一一些指令的本質就是函數.這一對比就出來,我們的ls就不需要指定路徑,為何我們自己寫的程式就需要路徑.

我們先來看看如果使用路徑會怎樣,這樣我們執行不了.

環境變量與程式位址空間了解

這裡面就涉及到環境變量的知道知識點;了,在Windows環境下,我們也是可以檢視自己電腦的環境變量的.我們這裡主要就是看看.

環境變量與程式位址空間了解

env 指令

env是檢視系統環境變量的指令,可以幫助我們檢視系統所有的環境變量.

[bit@Qkj 08_13]$ env      
環境變量與程式位址空間了解

你會發現這些環境變量實在實在太多了,這裡面我們挑幾個比較常用的熟悉一下.

PATH

PATH 指定指令的搜尋路徑 說人話就是我們把一些指令的位址放在PATH種,這個環境變量是我們今天要重點談的,裡面有些可以解決上面我們的疑問.

我們先來看看如何檢視PATH.

第一種方法是使用grep指令,不過這個指令得到的不太簡潔.
[bit@Qkj 08_13]$ env | grep PATH      
環境變量與程式位址空間了解
第二種就是通過echo\指令來獲得,這個倒是很舒服,不過也有要注意的地方.
[bit@Qkj 08_13]$ echo $PATH   # 注意  一定要加$      
環境變量與程式位址空間了解

如果上面我們不加和PATH分離都不會得到這個結果,他隻會它們當作一個字元串直接列印出來.

環境變量與程式位址空間了解

PATH是什麼

前面我們已經談過的,PATH可以看做很多串路徑的集合,其中一個:作為一個分隔符.

環境變量與程式位址空間了解
其中我們主要關注的是/usr/bin目錄,這個目錄放在我們Linux種幾乎所有的指令.這裡我截出來幾個讓大家看看.
[bit@Qkj 08_13]$ ls /usr/bin/      
環境變量與程式位址空間了解
到這裡我們就明白了,如果我們想要執行某一條指令,作業系統會去PATH裡面儲存的路徑中尋找,找到了就執行,找不到就會出現警告,我們 之前學的which指令的本質也是去PATH儲存的路徑中尋找.
環境變量與程式位址空間了解

修改PATH

我們希望可以把自己寫的程式可以直接運作,不用在指明什麼路徑。這裡我們存在兩種方法,這裡我推薦第二種。

  • 把可執行程式直接拷到 /usr/bin/目錄
  • 修改PATH
我們先來看第一種方法,這裡我們需要超級使用者權限
#include <stdio.h>    
    
int main()    
{    
  printf("我僅僅是一個 main 函數\n");    
  return 0;    
}      
[root@Qkj 08_14]# cp mybin /usr/bin/      
環境變量與程式位址空間了解
但是這種方法有一個弊端,就是你這個程式已經是不變的了,如果你要改變,還要再次拷貝才可,我們不建議這麼用.
#include <stdio.h>    
    
int main()    
{    
  printf("我僅僅是一個 main 函數\n");    
  printf("我僅僅是一個 main 函數\n");    
  return 0;    
}      
環境變量與程式位址空間了解
第二種方法就比較簡單了,我們可以直接修改PATH的值,這樣的話就可以知道找到我們對應的程式了
環境變量與程式位址空間了解

如果我們要是直接修改PATH,你就會發現我們一些指令不能用了,因外OS在PATH路徑中找不到我們用的指令.不過也不用擔心,我們退出下使用者再次進入就可以了,這也是因為OS會再次給PATH指派.

環境變量與程式位址空間了解
是以一般情況下我們都是給PATH加上路徑,很少删除路徑的,這裡我們也是加上路徑.
[bit@Qkj 08_14]$ export PATH=$PATH:/home/bit/104/2022/08_14  # export 後面再說      
環境變量與程式位址空間了解

這樣的話我們就可以直接運作自己的可執行程式了

#include <stdio.h>    
    
int main()    
{    
  printf("我僅僅是一個 main 函數\n");    
  return 0;    
}      
環境變量與程式位址空間了解

設定環境變量

我們在想是不是自己可以設定環境變量,畢竟後面我們也可能使用到.Linux是允許我們自己設定的,不過在設定之前我們需要看一下本地變量

本地變量

我們經常拿本地變量和環境變量進行比對,這裡我們先不說它們原理,隻談用法.設定本地變量的方法是很簡單的.

[bit@Qkj 08_14]$ aaa=12345      

上面的aaa就是一個本地變量,本地變量是不會放在環境便裡面的,也就是我們env是查不出本地變量的.

環境變量與程式位址空間了解

設定環境變量

上面我們談過了如何設定本地變量,這裡需要看看環境變量是如何設定的,我們要用到上面的一個指令.export的作用就是設定一個新的環境變量 .

[bit@Qkj 08_14]$ export bbbb=111222333      
環境變量與程式位址空間了解

當然我們也可以把自己設定的本地變量導到環境變量裡面

[bit@Qkj 08_14]$ export aaa      
環境變量與程式位址空間了解

unset 指令

這是清除環境變量的指令.

[bit@Qkj 08_14]$ unset bbbb      
環境變量與程式位址空間了解

set 指令

我們知道了env是不不能産看本地變量的,這裡的set卻可以檢視它們兩個.set顯示本地定義的shell變量和環境變量.我們這裡隻示範檢視本地變量的方法.

[bit@Qkj 08_14]$ set | grep aaa      
環境變量與程式位址空間了解

常見的環境變量

下面我們看一下比較常見的一些環境變量,有助于了解Linux的指令.

HOSTNAME

産看使用者的主機名稱.

[bit@Qkj 08_14]$ echo $HOSTNAME      
環境變量與程式位址空間了解

HOME

檢視家目錄,這裡我們用root使用者和普通使用者做對比,你一看就會明白了為何cd ~會自動跳到自己的家目錄.

[bit@Qkj 08_14]$ echo $HOME
[root@Qkj 08_14]# echo $HOME      
環境變量與程式位址空間了解

PWD

這個環境變量實時記錄目前位置的絕對路徑,和我們之前的pwd指令是有聯系的,可以認為pwd就是取出了這個環境變量的值.

[bit@Qkj 08_14]$ echo $PWD      
環境變量與程式位址空間了解
後面還有一點,這裡我就不示範了,直接出截圖吧.
環境變量與程式位址空間了解

通過代碼如何擷取環境變量

這個也算是我們今天的主要内容吧,不過他衍生出來的知識才是比較重要的.

main函數可以帶參數嗎

我們心裡想這不是廢話嗎,我們寫了這麼長時間的main函數,是一次都沒有帶過參數,肯定也是不能帶參數的.如果你要是這麼認為,那麼你的C語言隻能算是掌握階段,但是一些邊邊角角的知識還是沒有掌握的,這裡我告訴大家,main函數不僅能帶參數,而且還能帶兩個(目前結論).我們測試一下你就知道了.

#include <stdio.h>    
int main(int argc, char *argv[])    
{    
    return 0;                            
}      
環境變量與程式位址空間了解

我們先來看看這兩個參數是什麼,這樣才有利于我們了解後面的知識.

#include <stdio.h>      
      
int main(int argc, char *argv[])      
{  
  int i = 0;  
  for(i=0;i<argc;i++)  
  {
    printf("argv[%d] : %s\n",i,argv[i]);                  
  }
  return 0;
}      
環境變量與程式位址空間了解

這裡面我來解釋一下,這兩個參數究竟是什麼.

環境變量與程式位址空間了解

倒着裡你就明白了這兩個參數的含義,這裡面的字元串可以看作用空格隔開的,其中./可執行程式也算是一個字元串.

環境變量與程式位址空間了解

指令的選項

這裡我們就可以簡單的明白一些東西了,我們前面說過大多數的指令都是函數,這裡的指令的選項和我們現在所看到是多麼的像.我們也可以簡單的了解這些指令的函數也是這麼實作的.

可以給無參的函數傳參嗎

這也是一個問題,我們在之前些=]寫main函數時從來沒有寫過參數,這裡打破了我們的認值.那麼這還有一個打破認值的知識,我們可以給無參的函數傳入參數嗎,這裡是可以的,反正我們函數不接受就可以了.

#include <stdio.h>    
void func()
{
    printf("func()\n");

}
int main(int argc, char *argv[])
{
    func(1,2,3);
    return 0;
}      
環境變量與程式位址空間了解

通過代碼如何擷取環境變量

這裡我們要談一下如何通過代碼來擷取環境變量,這裡一共有三種]方法.

  • 指令行第三個參數
  • 通過第三方變量environ擷取
  • 系統調用擷取 getenv

main函數的第三個參數

是的,這裡面我們更新一下自己的結論,main函數可以帶入第三個參數,這第三個參數就是我們的環境變量.

#include <stdio.h>    
int main(int argc, char *argv[],char*env[])
{                                                       
    int i = 0;
    for(i=0;env[i];i++)
    {      
        printf("env[%d] : %s\n",i,env[i]);
    }      
    return 0;
}      
環境變量與程式位址空間了解
從這裡我們就可以得到一個結論,環境變量是被傳入的程序裡面的,這一點是十分重要的.

通過第三方變量environ擷取

這個變量是一個全局變量,它是指向我們main函數第三個參數的指針.

環境變量與程式位址空間了解

我們來看一下他的作用

#include <stdio.h>
#include <unistd.h>

int main()
{
  extern char** environ;
  int i = 0;
  for(i=0;environ[i];i++)
  {
    printf("environ``[%d] : %s\n",i,environ[i]);
  }                                                              
  return 0;
}      
環境變量與程式位址空間了解

系統調用擷取

上面的兩種方式擷取的是全部,現在這個方法是指定哪個擷取哪個.

#include <stdio.h>  
#include <stdlib.h>

int main()
{
  printf("%s\n",getenv("PATH"));
  return 0;
}      
環境變量與程式位址空間了解

再析環境變量 & 本地變量

我們前面隻是認識了本地變量和環境變量,但是我們還不知道它們的具體差別,這裡我們示範一下。

bash

前面我們已經談過了,在Linux中,幾乎所有指令對應的程序的父程序都是bash.

int main()
{
  while(1)
  {
      printf("hello world  pid : %d,ppid : %d\n",getpid(),getppid());            
      sleep(2);
  }
  return 0;
}      
環境變量與程式位址空間了解

這裡我們就要如果我們kill的bash,你就會發現Linux不能用了,原因就是指令行啟用的程序,父程序都是bash,既然父程序沒了,那麼子程序又如何建立呢,我這裡是直接退出來來了,不過有點系統是指令不能執行.

環境變量與程式位址空間了解

環境變量通常具有全局屬性

這裡我們先下一個結論,環境變量具有全局屬性,它可以被子程序給繼承,那麼也就能被子程序的子程序所繼承。這一點我們示範一下就可以了.

我們來解釋一下,環境變量和本地變量都是存儲在bash程序中,這一點我們先暫時這樣了解,那麼main函數所在的程序也是bash的子程序,但是确可以程序環境變量.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>                                  
                                       
int main()                             
{                                      
  while(1)                             
  {                                    
      printf("hello world:%s\n",getenv("pit_104"));
      sleep(2);                        
  }                                           
  return 0;                                   
}      
環境變量與程式位址空間了解

本地變量不能被繼承

這裡我們就可以示範一下,本地變量是不能被子程序繼承的,他隻能存在bash中.

環境變量與程式位址空間了解

内建指令

我們這裡就有一個問題了,你說本地變量不能被繼承,還說了在Linux中幾乎所有的程序的指令是bash的子程序,那麼下面的指令為何可以執行.

[bit@Qkj 08_14]$ set | grep pit_104      
環境變量與程式位址空間了解
要知道我們的set可是bash的子程序,你不是說子程序拿不到本地變量嗎,這裡為何又拿到了?這不是沖突了嗎?注意了,這裡我要補充一個結論,在Linux中幾乎所有的指令都是按照子程序的形式來完成的,但是仍存在一下指令是通過bash調用自己的函數來完成某些功能的,我們把這類指令稱為内建指令.

位址空間

這個子產品很難了解,主要分為兩種,當然本質是一種,名字不同罷了.

  • 程式位址空間
  • 程序位址空間

程式位址空間

我們先來簡單的,在C語言中我們是學過的C程式虛拟位址空間的,這裡我們要詳細的談一下.我們在C語言中學過指針,也學過變量在記憶體中的存儲,今天我們就要看看記憶體的存儲的實際情況.

記憶體為下面幾個子產品,這張圖叫做虛拟位址空間,我們用代碼驗證一下實際的存儲是不是和我們想的一樣,這裡建議在Linux環境下驗證,VS可能做了優化.這裡注意一下,共享區這裡不好驗證,就不驗證了.
環境變量與程式位址空間了解
#include <stdio.h>
#include <stdlib.h>
int g_val;
int init_g_val = 1;
int main(int argc,char* argv[],char* env[])
{
    printf("code        : %p\n",main);

    printf("init data   : %p\n",&init_g_val);
    printf("uninit data : %p\n",&g_val);

    char* array = (char*)malloc(10);

    printf("heap area   : %p\n",array);
    printf("stack area  : %p\n",&array);

    printf("指令行參數  : %p\n",argv[0]);
    printf("環境變量    : %p\n",env[0]);

    free(array);
    return 0;
}      
環境變量與程式位址空間了解

堆區 & 棧區

我們都知道堆區先上增長,棧區線下增長,這裡示範一下就可以了.

環境變量與程式位址空間了解
#include <stdio.h>
#include <stdlib.h>

int main()
{


  char* arr1 = (char*)malloc(10);
  char* arr2 = (char*)malloc(10);
  char* arr3 = (char*)malloc(10);
  char* arr4 = (char*)malloc(10);

  printf("heap area   : %p\n",arr1);
  printf("heap area   : %p\n",arr2);
  printf("heap area   : %p\n",arr3);
  printf("heap area   : %p\n",arr4);
                                                                                        
  printf("stack area  : %p\n",&arr1);
  printf("stack area  : %p\n",&arr2);
  printf("stack area  : %p\n",&arr3);
  printf("stack area  : %p\n",&arr4);


  free(arr1);
  free(arr2);
  free(arr3);
  free(arr4);
  return 0;
}      
環境變量與程式位址空間了解

static修飾的局部變量

我們也知道static修飾的局部變量在生命周期被放大了,實際上這個變量是存儲在全局變量區.

#include <stdio.h>
#include <stdlib.h>
int g_val;
int init_g_val = 1;
int main()
{
    printf("code        : %p\n",main);

    printf("init data   : %p\n",&init_g_val);
    printf("uninit data : %p\n",&g_val);

    char* array = (char*)malloc(10);                           

    static int a = 10;
    static int b;

    printf("init static : %p\n",&a);
    printf("static      : %p\n",&b);
    printf("heap area   : %p\n",array);
    printf("stack area  : %p\n",&array);


    free(array);
    return 0;
}      
環境變量與程式位址空間了解

程序位址空間

我感覺上面的程式位址空間應該叫做程序位址空間,這個概念是系統層次的概念.我們先來看一下下面的的代碼,你就會明白我們為何這麼說了.

#include <stdio.h>
#include <unistd.h>
int main()                                                                     
{
    int val = 10;
    pid_t id = fork();
    if(id == 0)
    {
        // child
        while(1)
        {
            printf("我是子程序 pid : %d,ppid : %d &val : %p\n",getpid(),getppid(),&val);
            sleep(1);
        }
    }
    else
    {
        while(1)
        {
            printf("我是父程序 pid : %d,ppid : %d &val : %p\n",getpid(),getppid(),&val);
            sleep(1);
        }
    }
    return 0;
}      
環境變量與程式位址空間了解

他們的位址一樣我不感到驚訝,畢竟上面我們說過父子程序共用同一片代碼和空間,這裡位址也是應該相同的,但是下面你就會感到疑惑了.

這裡我們修改一下子程序裡面val的值,你就會發現一個難以了解的東西.

int main()
{
  int val = 10;
  pid_t id = fork();
  if(id == 0)
  {
    // child
    while(1)
    {
      val = 20;
      printf("我是子程序 val = %d &val : %p\n",val ,&val);
      sleep(1);
    }
  }
  else
  {
    while(1)
    {
      printf("我是父程序 val = %d &val : %p\n",val, &val);
      sleep(1);                                                                                      
    }
  }
  return 0;
}      
環境變量與程式位址空間了解

程式位址空間是記憶體嗎

從上面的代碼我們就可以知道,這裡好象出現了一種沖突.我們在C語言中學過,指針指向的資料肯定是唯一确定的,我們在同一片空間怎麼會出現兩個不一樣的資料,除非我們中程式位址空間是假的,不是真正的記憶體.實際上也是如此,我們得到的位址絕對不是真實的實體位址,它是虛拟位址,在Linux中還可以稱為邏輯位址或者線性位址.程式位址空間可以看作是實際記憶體通過一種美化手端得到的.

這裡我們就疑惑了嗎,既然程式位址空間不是記憶體,那麼我們就疑惑OS為何不讓我們看到真實的記憶體.這是由于記憶體隻是一個硬體,它可不會主動做某件事事,隻能被動的被動的進行寫入和讀取.

mm_struct

在Linux中,我們每建立一個程序,首先第一點就是執行個體化一個task_struct,同時我們給這個程序建立一個程序位址空間,這個位址空間就是我們上面說的程序位址空間.

環境變量與程式位址空間了解
這裡我們就可以知道,每一個程序都有一個自己的程序位址空間,既然每一個程序都有一個屬于自己的位址空間,OS就會把他們管理起來,具體來說就是先描述後組織.這個的描述就是一個mm_struct,也就是位址空間就是mm_struct.

什麼是位址空間

前面的準備工作做的差不多了,這裡我們先來解釋一下什麼事位址空間.我們前面說過程序具有獨立性,展現在在資料和代碼等資源的獨立.

這裡我們舉一個例子.假如有一個很有錢的富翁,有十個億的存款.這個富翁有三個私生子,這三個孩子互相不認識,都認為自己是這個富翁的唯一子嗣.這就是展現出了獨立.有一天,這個富翁叫過來大兒子,告訴他,兒子,你好好的學習,等我去世了,這十個億都是你的,後面他又把二兒子和三兒子都獨立的叫過來,說了相應的話.也就是說這個富翁給這三個兒子畫了一張大餅,他們都認為自己可以得到這十個億.一天,大兒子過來說要一千塊錢要買東西,後面其餘的孩子都找過富翁要過錢,富翁都是很爽快的給了,這就有進一步加深了每個孩子可以可以得到這個十個億的信心.

這裡的十個億就是我們真實的實體記憶體,富翁就是作業系統,那麼孩子就是程序,富翁給孩子畫餅就像OS給程序畫餅,讓每個程序都以為自己可以得到所有的實體記憶體,其中這個大餅就是位址空間.位址空間是一個抽象的概念,讓每一個程序都認為自己可以得到所有的實體記憶體,而且隻要程序需要,我們就給程序空間,那麼對于程序來說,他好象是擁有了所有的資源.

頁表

到這裡我們就可以看一看一個可執行程式是如何變化成一個程序的,且還需要看看什麼是頁表.

我們可以把頁表了解成一個函數,其中虛拟位址作為參數,結果作為真實位址.

這裡我們先來說一下過程,我們執行一個程序,第一件事實在真實的實體記憶體開辟出資源,然後程序出現task_strcut,其中task_strcut中有一個指針,這個指針指向的就是位址空間,然後實體記憶體和位址空間裡面出現一個頁表(注意,這可能不是真實的順序,主要是為了好了解).這是一個粗糙的流程.

環境變量與程式位址空間了解

區域

在能夠談清楚上面的過程前,我們需要看一下什麼是區域.假設在一個國小中,一個愛幹淨的女生和一個不修邊幅的男生是同桌.一天女生實在是看不下去男生的邋遢了,就在桌子上劃了一條三八線,告訴男生不要越界.

環境變量與程式位址空間了解

我的位址空間可是劃分了很多區域,那麼他們也是應該按照這個流程來的,我們的通路越界就像是男生不小心把胳膊伸在了女生區域.既然有區域,那麼肯定會有一個start和一個end來表述這塊區域.我們心裡像的應該是是這樣的.

環境變量與程式位址空間了解

我們去源碼裡面看看是不是确實存在這種類似的結構,這個确實存在的,他們變成了一個連結清單.

環境變量與程式位址空間了解

程式是如何變成程序的

到這裡我們還有兩個問題沒有解決,隻要解決這兩個問題,我們今天的程序的原理差不分享了三分之二.

可執行程式存在指針嗎

我們先來談一下,到底我們的道德.exe檔案裡面在沒有被運的時候究竟有沒有指針.這裡我直接給出答案,是的,存在的.要知道我們程序在運作之前是存在一個看一個連結步驟,這個步驟如果我們要調用庫函數,肯定是要拿到函數的位址的,是以可執行程式是存在指針的.

可執行程式有區域嗎

可執行區域是由區域的當然這裡沒有堆區和棧區,這是運作的時候才會有的,不過它存在其他的區域.我們嗎,我們用指令看看就明白了.

[bit@Qkj 08_15]$ readelf -S mybin      
環境變量與程式位址空間了解
現在我們也可以知道了,可執行程式裡面也是存在區域的,而且這個區域的初始部分是從0開始,他們的是相對位址,也成為邏輯位址.

程式是如何變成程序的

現在我們就可以舒服的談程式變成程序的過程了.首先我們在實體記憶體給這個可執行程式開辟好空間,這裡也會發生一個位址的轉變,要知道我們的可執行程式裡面的位址是相對位址,這裡到真實的位址需要加上某個值進行合理的轉換才可以在記憶體中得到真實的位址.

環境變量與程式位址空間了解
這時候程序開始執行了,作業系統執行個體化一個task_struct,了這個對象裡面有一個指針,指向程序空間.
環境變量與程式位址空間了解
這個時候我們需要把虛拟位址課真實的位址連結起來,這樣就需要頁表了,
環境變量與程式位址空間了解

寫時拷貝

現在我們就可以解釋一下為何一位址會出現兩種不一樣的值了,這裡涉及到寫時拷貝,我們需要把前面的代碼拿出再說一下.

#include <stdio.h>
#include <unistd.h>
int main()                                                                     
{
    int val = 10;
    pid_t id = fork();
    if(id == 0)
    {
        // child
        while(1)
        {
            printf("我是子程序 pid : %d,ppid : %d &val : %p\n",getpid(),getppid(),&val);
            sleep(1);
        }
    }
    else
    {
        while(1)
        {
            printf("我是父程序 pid : %d,ppid : %d &val : %p\n",getpid(),getppid(),&val);
            sleep(1);
        }
    }
    return 0;
}      
環境變量與程式位址空間了解
我們這裡父子程序拿到的val變量程序空間的位址的一樣的,那麼對應的實體記憶體是不是一樣的呢.?這裡面的結果是的,至于為何是這樣的機制,我們後面在談.

首先子程序肯定會建立task_struct,,程序空間,頁表,但是這是時候OS不會再次位子程序在實體記憶體中為對應的變量才開辟空間,這也是我們之前說的代碼共享.這裡還要提一嘴,子程序的task_struct,程序空間,頁表大部分資料都是程序父程序的來的,是以開始是指向同一片空間的.

環境變量與程式位址空間了解

寫時拷貝

但是下面的代碼就不一樣的.這時候OS就會從新給變量開辟空間,頁表中虛拟位址空間的部分不變,真實部分位址指向發生變化.這就是我們看到用一片空間,卻出現兩個不同的值的原因.

int main()
{
  int val = 10;
  pid_t id = fork();
  if(id == 0)
  {
    // child
    while(1)
    {
      val = 20;
      printf("我是子程序 val = %d &val : %p\n",val ,&val);
      sleep(1);
    }
  }
  else
  {
    while(1)
    {
      printf("我是父程序 val = %d &val : %p\n",val, &val);
      sleep(1);                                                                                      
    }
  }
  return 0;
}      
環境變量與程式位址空間了解
環境變量與程式位址空間了解
我們這裡要總結以上,上面我們遇到的情就是寫時拷貝,如果我們不修改子程序中對應的資料,代碼等等,這時候兩者共用同一片真實實體空間,一旦子程序中資料代碼等發生了變化,這時候作業系統會會重新開辟空間,并且改變子程序的頁表.

這裡就可以解釋fork為啥會有兩個傳回值了,記住咋在fork中,我們return了兩次,對于每一個,也就是我們子程序會開辟出一塊新的空間用來接受傳回值.

寫時拷貝的意義

這裡我們需要談談為何我們選擇這樣的機制,我們不可以為子程序開辟新的空間嗎?可以的,但是是不時會有的這樣的情況,假設我們的子程序不會修改資料和代碼,他隻是觀察一下這些東西,如果我們的父程序占據的空間很大,我們這個子程序作占據的空間就浪費了,這裡還隻是兩個程序,後面我們遇到的程序個數會更多,到時候浪費的會更多.

位址空間意義

這裡我們需要談一下位址空間為何要這樣的設計.這裡面的有三個理由.

越界通路

假設我們是直接通路實體記憶體,我們假設一個場景.兩個程序連在一起,其中程序一出現了越界,他不小心的把程序二的資料給改了,要是我們出現報錯還好,要是沒有報錯等着整個系統出現bug吧.