目錄
- 一、回顧我們以前學習的地址空間
- 二、進程地址空間
- 三、進程地址空間的作用
- 四、解決一個地址出現兩個值的問題
一、回顧我們以前學習的地址空間
這個內存布局真是的我們實實在在的內存嘛? 答案是不是的
下面我們來驗證
1 #include<stdio.h>2 #include<assert.h>3 #include<unistd.h>4 5 int myval=100;6 7 int main()8 {9 pid_t id=fork();10 assert(id>=0);11 if(id==0)12 {13 //子進程14 myval=200; //修改myval的值15 while(1)16 {17 printf("我是子進程,我的pid是:%d,我的父進程是:%d,myval: %d, &myval: %p\n",getpid(),getppid(),myval,&myval);18 sleep(1);19 }20 }else if(id>0)21 {22 //父進程23 while(1)24 {25 printf("我是父進程,我的pid是:%d,我的父進程是:%d,myval: %d, &myval: %p\n",getpid(),getppid(),myval,&myval); 26 sleep(1);27 }28 }29 30 return 0;31 }
可以看到,父進程和子進程中的g_val的地址是一摸一樣的,那么按理說將子進程中的g_val改變后,由于他們使用的是一塊空間,所以父進程中的g_val的值也應該改變,可這里為什么沒有變化??
如果C/C++打印出來的地址是物理內存的地址,這種現象絕不可能存在!而這里使用的地址是虛擬地址。
在用C/C++語言所看到的地址,全部都是虛擬地址!物理地址,用戶一概看不到,由OS統一管理
所以最上面那張圖應該叫做,進程虛擬地址空間。
二、進程地址空間
每個進程都有一個地址空間,都認為自己在獨占物理內存。而這個地址空間在內核中是一個結構體 struct mm_struct.
mm_struct 中的分布類似下面這種:
struct mm_struct {unsigned int code_start; //地址空間上進行區域劃分時,對應的線性位置,稱為虛擬地址unsigned int code_end;unsigned int init_data_start;unsigned int init_data_end;unsigned int uninit_data_start;unsigned int uninit_data_end ;unsigned int heap_start;unsigned int heap_end;unsigned int stack_start;unsigned int stack end;
}
?
雖然這里只有start和end,但每個進程都可以認為mm_struct代表整個內存的所有的地址為0x0000…000~0xFFFF…FFF(即每個進程都認為自己擁有4GB的空間,至于到底有沒有,是OS要做的事)
真實內存的樣子
頁表:是一種特殊的數據結構,放在系統空間的頁表區,存放邏輯頁與物理頁幀的對應關系。 每一個進程都擁有一個自己的頁表,PCB表中有指針指向頁表。
三、進程地址空間的作用
1.防止訪問權限越界
通過添加一層軟件層,完成有效的對進程操作內存進行權限管理,本質目的是為了保護物理內存以及各個進程的數據安全。
?
2.將內存申請和內存使用的概念劃分清楚
通過虛擬地址空間,來屏蔽底層申請內存的過程,達到進程讀寫內存和OS申請內存管理操作,進行軟件上面的分離。
進程A想申請1000字節空間,進程A馬上就能使用這1000字節嗎?這是不一定的,可能會存在暫時不會全部使用的情況。
在OS角度,如果空間馬上給進程A,就意味著整個系統會有一部分空間本來可以給其他進程立即使用,先在卻被進程A閑置著。
這樣就會存在空間浪費的情況。操作系統不允許出現浪費和不高效的行為
所以在這種情況下,OS會在進程A使用空間的時候才將內存申請給進程A。(相當于是類似寫時拷貝的思想)
?
3.站在CPU和應用層的角度,進程同意可以看作統一使用4GB空間,而且每個空間區域的相對位置是比較確定的。
如果同時存在多個進程,而每個進程代碼的其實位置是不確定的,那么CPU在執行時,需要找到代碼在哪里,比較混亂。
而使用虛擬地址空間和頁表的方式,將內存劃分為代碼段、常量區、堆、棧等區域,CPU執行進程時,每次從同一個位置開始即可,而不同的進程通過不同的頁表映射到自己的物理內存中存放代碼和數據的位置,提高了CPU的執行效率。
所以通過虛擬地址和頁表,程序的代碼和數據可以被加載到物理內存的任意位置!!極大的減少內存管理的負擔。
OS最終這樣的目的,為了達到一個目標:每個進程都認為自己是獨占系統資源的。
?
四、解決一個地址出現兩個值的問題
在開始那段代碼中,我們可以看到myval的值在被子進程修改后,父進程值沒有改變,同時打印出來的myval的地址相同,出現了一個地址兩個值的情況,我們來解決。
子進程在創建時會以父進程為模板,即能夠拷貝父進程的地方就拷貝,例如虛擬地址,只讀區的映射關系(代碼共享)。
所以子進程和父進程的虛擬地址是相同的,而頁表的映射關系是不同的,所以他們的物理地址也不同。
所以就出現了,子進程改變myval的值,而父進程不變,但打印出的地址卻是一樣的情況了。