目錄
Ⅰ、什么是程序地址空間?
Ⅱ、虛擬地址空間是什么樣的?
一、虛擬地址空間和頁表
1、什么是頁表?
2、什么是虛擬地址空間?
3、什么是vm_area_struct?
Ⅲ、為什么要用虛擬地址空間?
一、進程的獨立性
二、保護數據安全
三、管理模塊和內存模塊的解耦合
Ⅰ、什么是程序地址空間?
在我們之前學習C語言/C++的時候可能我們看過下面圖片,當時有人告訴我們不同的數據和代碼存放在了內存的什么地方,當我們現在學習了操作系統了以后我們對這個圖片產生了一些疑問,我們我們在之前的博客說過進程的產生如果我們的內存是這樣排布的我們的進程應該怎么去存放?現在我們將提出虛擬地址空間也叫程序地址空間。
那什么是虛擬地址空間?
說的形象一點,假設操作系統是老板,進程是員工,那么虛擬地址空間就是老板給員工畫的大餅,操作系統告訴進程,這個內存里邊只有你和我在這里邊,你只要好好進行這里邊的內存全部是你的。這樣進程以為他可以擁有全部的內存。這就是虛擬地址空間。
下面我們來看一段代碼:
#include<stdio.h>2 #include<unistd.h>3 #include<stdlib.h>4 5 6 int num = 0;7 8 int main()9 {10 pid_t id = fork();11 if(id == 0)12 {13 //子進程14 num = 1;15 printf("我是一個子進程,num地址:%p, num = %d \n", &num , num); 16 }17 else18 {19 20 printf("我是一個父進程,num地址:%p, num = %d \n", &num , num);21 }22 return 0;23 }
~
~
看上面這段代碼我們可以發現在同一個地址下不同的進程的值竟然是不一樣的,我們在之前的學習我們知道一個地址只能有一個值,這是為什么?這其實就是虛擬地址空間的作用下面我們來詳細了解虛擬地址空間。
Ⅱ、虛擬地址空間是什么樣的?
一、虛擬地址空間和頁表
我們先來解釋一下上文的問題,為什么一個地址父子進程的值卻不一樣?這時以為在操作系統創建進程的時候不僅僅是創建PCB,去創建虛擬地址空間和頁表,還要去加載代碼和數據。這樣一個進程才真正的被創建。
1、什么是頁表?
下面我們先來解釋頁表的功能:頁表可以把虛擬地址一一對應真實的物理地址。
我們上面說過虛擬程序地址就像操作系統給進程畫的一張大餅,操作系統告訴進程你可以把你的數據按照一定的順序放,進程把自己的代碼和數據按照不同的特性放在了不同的區域,但實際上內存中不止一個進程當然一個進程也不可能擁有一整塊內存,所以操作系統要靠一個東西把進程的代碼和數據在虛擬地址空間和物理空間一一對應這個東西就是頁表。
2、什么是虛擬地址空間?
虛擬地址空間被分為了很多部分,比如堆區,棧區,等等。那么如何去做區域劃分的呢?我們知道整個虛擬地址空間(假設是32位機器)被統一編址從0X00000000~0Xffffffff。如果我們可以記錄每個區域的起始和結束那么就可以實現了區域劃分,那么在操作系統內核中是如何做的呢?在Linux中用了一個內核數據結構來實現這個就是mm_struct。
struct mm_struct
{
struct vm_area_struct *mmap; /* 指向虛擬區間(VMA)鏈表 */
struct rb_root mm_rb; /* red_black樹 */
unsigned long task_size; /*具有該結構體的進程的虛擬地址空間的??*/
/*...*/
// 代碼段、數據段、堆棧段、參數段及環境段的起始和結束地址。
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
}
3、什么是vm_area_struct?
我們知道在我們申請空間的時候是在堆上開空間,那么我們每次申請的空間都是不連續的那么mm_struct里只有兩個變量去區分堆的開始和結束,這里我們就有一個疑問:我們開的空間是不連續的但我們只有兩個變量去確定區間,也不能把每個空間的開始和結束都表示出來啊?
這個時候我們用vm_area_struct去解決這個問題
struct vm_area_struct {
unsigned long vm_start; //虛存區起始
unsigned long vm_end; //虛存區結束
struct vm_area_struct *vm_next, *vm_prev; //前后指針
struct rb_node vm_rb; //紅?樹中的位置
unsigned long rb_subtree_gap;
struct mm_struct *vm_mm; //所屬的 mm_struct
pgprot_t vm_page_prot;
unsigned long vm_flags; //標志位
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} shared;
struct list_head anon_vma_chain;
struct anon_vma *anon_vma;
const struct vm_operations_struct *vm_ops; //vma對應的實際操作
unsigned long vm_pgoff; //?件映射偏移量
struct file * vm_file; //映射的?件
void * vm_private_data; //私有數據
atomic_long_t swap_readahead_info;
#ifndef CONFIG_MMU
struct vm_region *vm_region; /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
struct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
} __randomize_layout;
我們看上面的代碼我們可以看見start,end,next,prev,其實mm_struct中只記錄每個區域總的開始和結束而具體的每個空間(比如我們在堆上開辟的空間)的?開始和結束是由vm_area_struct記錄,然后將vm_area_struct再組織起來。
組織虛擬空間有兩種方式:
1、當虛擬區比較少的時候用單鏈表,mmap指向這個鏈表
2、當虛擬區比較多的用紅黑樹進行組織,mm_rb指向這顆紅黑樹
Ⅲ、為什么要用虛擬地址空間?
一、進程的獨立性
每個進程都有一個task_struct , mm_struct , vm_area_struct等相互獨立,頁表將虛擬地址和物理地址一一對應的,當創建子進程被創建的時候操作系統會先拷貝父進程的task_struct , mm_struct , vm_area_struct然后將一部分,比如pid,ppid等改變,最后交給子進程,當父進程或者子進程對數據進行改變的時候操作系統會進行寫時拷貝,重新開辟一個新的物理空間然后把數據拷貝進去再去改動,然后操作系統改變子進程的頁表中這個變量對應的物理地址而虛擬地址不改變,這也就解釋了為什么一個地址會有兩個值的問題。如果沒有虛擬地址空間就沒有頁表也就不能實現獨立性。
二、保護數據安全
頁表是由操作系統去維護的如果想要去訪問一個不合法的地址或者是數據的話當查詢頁表的時候操作系統就可以去拒絕進而對數據形成了保護。
三、管理模塊和內存模塊的解耦合
task_struct,mm_struct等這是屬于管理模塊,物理內存等都是屬于內存模塊,以為有虛擬地址空間和頁表才會實現無論數據在物理內存是如何存儲的在虛擬內存空間都是有序的,這樣兩個模塊就可以解耦合。阻塞掛起也可以很好的展示管理模塊和內存模塊解耦合的好處,當進程是掛起狀態的時候,操作系統會把進程的代碼和數據換出到磁盤中,然后把頁表的物理地址刪除當進程調用數據和代碼的時候操作系統再換回到內存中,這也很好展示了管理和內存模塊是解耦合的。