1. 程序地址空間回顧
我們在學習語言層面時,會了解到這樣的空間布局圖,我們先對他進行分區了解:
如果以靜態static修飾的變量就會當成已初始化全局變量來看待,存放在已初始化數據區和未初始化數據區之前。
如果不用static修飾test的話,test只是?
?
2.一個例子引入虛擬地址
輸出出來的變量值和地址是?模?樣的,很好理解呀,因為?進程按照?進程為模版
變量值不一樣可以理解,進程之前是具有獨立性的,即便是父子進程。
但是??進程輸出地址是?致的,但是變量內容不?樣的 why ???
但是為什么輸出地址一模一樣卻有兩個不同的值呢?
??進程,輸出地址是?致的,但是變量內容不?樣!能得出如下結論:
3.進程地址空間

?
注:上?的圖就?矣說明問題,同?個變量,地址相同,其實是虛擬地址相同,內容不同其實是被映射到了不同的物理地址!
虛擬地址空間,本質一定是一個內核數據結構!
空間區域劃分:本質其實只要有線性空間的一段開始地址和結束地址表明一段范圍即可。
區域劃分:表明開始地址和結束地址,區域內部的內容都屬于我
4. 虛擬內存管理
?
描述linux下進程的地址空間的所有的信息的結構體是 mm_struct (內存描述符)。每個進程只有?個mm_struct結構,(操作系統給畫的餅)在每個進程的task_struct結構中,有?個指針指向該進程的mm_struct結構體指針?。
struct task_struct
{/*...*/struct mm_struct *mm;//對于普通的??進程來說該字段指向他的虛擬地址空間的??空間部分,對于內核線程來說這部分為NULL。struct mm_struct *active_mm; // 該字段是內核線程使?的。當該進程是內核線程時,它的mm字段為NULL,表?沒有內存地址空間,可也并 不是真正的沒有,這是因為所有進程關于內核的映射都是?樣的,內核線程可以使?任意進程的地址空間。/*...*/
}
可以說,mm_struct結構是對整個??空間的描述。每?個進程都會有??獨?的mm_struct,(把每一個進程映射到不同的物理內存處)這樣每?個進程都會有??獨?的地址空間才能互不?擾。先來看看由task_struct到mm_struct,進程的地址空間的分布情況:
定位mm_struct?件所在位置和task_struct所在路徑是?樣的,不過他們所在?件是不?樣的, mm_struct所在的?件是mm_types.h。
?
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;//...}
那既然每?個進程都會有??獨?的mm_struct,操作系統肯定是要將這么多進程的mm_struct組織起來的!虛擬空間的組織?式有兩種:
1. 當虛擬區較少時采取單鏈表,由mmap指針指向這個鏈表;
2. 當虛擬區間多時采取紅?樹進?管理,由mm_rb指向這棵樹。
linux內核使? vm_area_struct 結構來表??個獨?的虛擬內存區域(VMA),由于每個不同質的虛擬內存區域功能和內部機制都不同,因此?個進程使?多個vm_area_struct結構來分別表?不同類型的虛擬內存區域。上?提到的兩種組織?式使?的就是vm_area_struct結構來連接各個VMA,?便進程快速訪問。
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;
所以我們可以對上圖在進?更細致的描述,如下圖所?:
?
5. 為什么要有虛擬地址空間
這個問題其實可以轉化為:如果程序直接可以操作物理內存會造成什么問題?
在早期的計算機中,要運??個程序,會把這些程序全都裝?內存,程序都是直接運?在內存上的,也就是說程序中訪問的內存地址都是實際的物理內存地址。當計算機同時運?多個程序時,必須保證這些程序?到的內存總量要?于計算機實際物理內存的??。
那當程序同時運?多個程序時,操作系統是如何為這些程序分配內存的呢?例如某臺計算機總的內存??是128M,現在同時運?兩個程序A和B,A需占?內存10M,B需占?內存110。計算機在給程序分配內存時會采取這樣的?法:先將內存中的前10M分配給程序A,接著再從內存中剩余的118M中劃分出110M分配給程序B。
這種分配?法可以保證程序A和程序B都能運?,但是這種簡單的內存分配策略問題很多。
- ?安全?險
每個進程都可以訪問任意的內存空間,這也就意味著任意?個進程都能夠去讀寫系統相關內存區域,如果是?個??病毒,那么他就能隨意的修改內存空間,讓設備直接癱瘓。
- 地址不確定
眾所周知,編譯完成后的程序是存放在硬盤上的,當運?的時候,需要將程序搬到內存當中去運?,如果直接使?物理地址的話,我們?法確定內存現在使?到哪?了,也就是說拷?的實際內存地址每?次運?都是不確定的,?如:第?次執?a.out時候,內存當中?個進程都沒有運?,所以搬移到內存地址是0x00000000,但是第?次的時候,內存已經有10個進程在運?了,那執?a.out的時候,內存地址就不?定了
- ?效率低下
?如果直接使?物理內存的話,?個進程就是作為?個整體(內存塊)操作的,如果出現物理內存不夠?的時候,我們?般的辦法是將不常?的進程拷?到磁盤的交換分區中,好騰出內存,但是如果是物理地址的話,就需要將整個進程?起拷?,這樣,在內存和磁盤之間拷?時間太?,效率較低。
存在這么多問題,有了虛擬地址空間和分?機制就能解決了嗎?當然!
- 地址空間和?表是OS創建并維護的!是不是也就意味著,凡是想使?地址空間和?表進?映射,也?定要在OS的監管之下來進?訪問!!也順便保護了物理內存中的所有的合法數據 ,包括各個進程以及內核的相關有效數據!
- 因為有地址空間的存在和?表的映射的存在,我們的物理內存中可以對未來的數據進?任意位置的加載!物理內存的分配 和 進程的管理就可以做到沒有關系,進程管理模塊和內存管理模塊就完成了解耦合
因為有地址空間的存在,所以我們在C、C++語?上new, malloc空間的時候,其實是在地址空間上申請的,物理內存可以甚??個字節都不給你。?當你真正進?對物理地址空間訪問的時候,才執?內存的相關管理算法,幫你申請內存,構建?表映射關系(延遲分配),這是由操作系統?動完成,??包括進程完全0感知!!
- 因為?表的映射的存在,程序在物理內存中理論上就可以任意位置加載。它可以將地址空間上的虛擬地址和物理地址進?映射,在進程視?所有的內存分布都可以是有序的。