物理虛擬記憶體
有網友問到小編是什麼,那麼到底是什麼呢?小編為大家介紹下。供大家參考!
前一段時間在面試總監的時候,總監問了我這樣的一個問題:你跟我說說實體記憶體和虛擬記憶體到底是怎麼一回事?
其實之前我看過這個問題,據我理解的,當時是這麼回答的“程序在執行的時候,作業系統都為其分配一個4GB的地址空間,即所謂的虛擬地址空間,一般情況下,當我們的程式很大的時候,實際的實體記憶體根本不能滿足我們的需求的時候,這個時候作業系統就會藉助磁碟空間來做虛擬的記憶體空間,把當前程序不需要的資料放在磁碟上,等到用到的時候,在利用排程演算法把所需要的資料從磁碟空間上排程到記憶體,虛擬記憶體就是為了擴大記憶體的容量,每當我們要執行一個程式的時候經過編譯以後形成的僅僅是邏輯上的空間,根本不是可以直接執行的記憶體空間,所以它還存在一個地址對映的概念。”當時感覺回答的很是籠統,只見總監在最後說了一句,你下去還是把這一塊的內容在好好看看,所以今天就好好的把這個概念理一理。
首先我從最基本的概念說起,什麼是實體記憶體的概念,虛擬記憶體的概念?
實體記憶體,在應用中,自然是顧名思義,物理上,真實的插在板子上的記憶體是多大就是多大了。而在CPU中的概念,實體記憶體就是CPU的地址線可以直接進行定址的記憶體空間大小。比如8086只有20根地址線,那麼它的定址空間就是1MB,我們就說8086能支援1MB的實體記憶體,及時我們安裝了128M的記憶體條在板子上,我們也只能說8086擁有1MB的實體記憶體空間。同理我們現在大部分使用的是32位的機子,32位的386以上CPU就可以支援最大4GB的實體記憶體空間了。
先說說為什麼會有虛擬記憶體和實體記憶體的區別。正在執行的一個程序,他所需的記憶體是有可能大於記憶體條容量之和的,比如你的記憶體條是256M,你的程式卻要建立一個2G的資料區,那麼不是所有資料都能一起載入到記憶體實體記憶體中,勢必有一部分資料要放到其他介質中比如硬碟,待程序需要訪問那部分資料時,在通過排程進入實體記憶體。所以,虛擬記憶體是程序執行時所有記憶體空間的總和,並且可能有一部分不在實體記憶體中,而實體記憶體就是我們平時所瞭解的記憶體條。有的地方呢,也叫這個虛擬記憶體為記憶體交換區。關鍵的是不要把虛擬記憶體跟真實的插在主機板上的記憶體條相掛鉤,虛擬記憶體它是“虛擬的”不存在,假的啦,它只是記憶體管理的一種抽象!
那麼,什麼是虛擬記憶體地址和實體記憶體地址呢。假設你的計算機是32位,那麼它的地址匯流排是32位的,也就是它可以定址0~0xFFFFFFFF4G的地址空間,但如果你的計算機只有256M的實體記憶體0x~0x0FFFFFFF256M,同時你的程序產生了一個不在這256M地址空間中的地址,那麼計算機該如何處理呢?回答這個問題前,先說明計算機的記憶體分頁機制。
計算機會對虛擬記憶體地址空間32位為4G分頁產生頁page,對實體記憶體地址空間假設256M分頁產生頁幀page frame,這個頁和頁幀的大小是一樣大的,所以呢,在這裡,虛擬記憶體頁的個數勢必要大於實體記憶體頁幀的個數。在計算機上有一個頁表page table,就是對映虛擬記憶體頁到實體記憶體頁的,更確切的說是頁號到頁幀號的對映,而且是一對一的對映。但是問題來了,虛擬記憶體頁的個數 > 實體記憶體頁幀的個數,豈不是有些虛擬記憶體頁的地址永遠沒有對應的實體記憶體地址空間?不是的,作業系統是這樣處理的。作業系統有個頁面失效page fault功能。作業系統找到一個最少使用的頁幀,讓他失效,並把它寫入磁碟,隨後把需要訪問的頁放到頁幀中,並修改頁表中的對映,這樣就保證所有的頁都有被排程的可能了。這就是處理虛擬記憶體地址到實體記憶體的步驟。
現在來回答什麼是虛擬記憶體地址和實體記憶體地址。虛擬記憶體地址由頁號與頁表中的頁號關聯和偏移量組成。頁號就不必解釋了,上面已經說了,頁號對應的對映到一個頁幀。那麼,說說偏移量。偏移量就是我上面說的頁或者頁幀的大小,即這個頁或者頁幀到底能存多少資料。舉個例子,有一個虛擬地址它的頁號是4,偏移量是20,那麼他的定址過程是這樣的:首先到頁表中找到頁號4對應的頁幀號比如為8,如果頁不在記憶體中,則用失效機制調入頁,否則把頁幀號和偏移量傳給MMUCPU的記憶體管理單元組成一個物理上真正存在的地址,接著就是訪問實體記憶體中的資料了。總結起來說,虛擬記憶體地址的大小是與地址匯流排位數相關,實體記憶體地址的大小跟實體記憶體條的容量相關。
第一層理解
1. 每個程序都有自己獨立的4G記憶體空間,各個程序的記憶體空間具有類似的結構
2. 一個新程序建立的時候,將會建立起自己的記憶體空間,此程序的資料,程式碼等從磁碟拷貝到自己的程序空間,哪些資料在哪裡,都由程序控制表中的task_struct記錄,task_struct中記錄中一條連結串列,記錄中記憶體空間的分配情況,哪些地址有資料,哪些地址無資料,哪些可讀,哪些可寫,都可以通過這個連結串列記錄
3. 每個程序已經分配的記憶體空間,都與對應的磁碟空間對映
問題:
計算機明明沒有那麼多記憶體n個程序的話就需要n*4G記憶體
建立一個程序,就要把磁碟上的程式檔案拷貝到程序對應的記憶體中去,對於一個程式對應的多個程序這種情況,浪費記憶體!
第二層理解
1. 每個程序的4G記憶體空間只是虛擬記憶體空間,每次訪問記憶體空間的某個地址,都需要把地址翻譯為實際實體記憶體地址
2. 所有程序共享同一實體記憶體,每個程序只把自己目前需要的虛擬記憶體空間對映並存儲到實體記憶體上。
3. 程序要知道哪些記憶體地址上的資料在實體記憶體上,哪些不在,還有在實體記憶體上的哪裡,需要用頁表來記錄
4. 頁表的每一個表項分兩部分,第一部分記錄此頁是否在實體記憶體上,第二部分記錄實體記憶體頁的地址如果在的話
5. 當程序訪問某個虛擬地址,去看頁表,如果發現對應的資料不在實體記憶體中,則缺頁異常
6. 缺頁異常的處理過程,就是把程序需要的資料從磁碟上拷貝到實體記憶體中,如果記憶體已經滿了,沒有空地方了,那就找一個頁覆蓋,當然如果被覆蓋的頁曾經被修改過,需要將此頁寫回磁碟
總結:
優點:
1.既然每個程序的記憶體空間都是一致而且固定的,所以連結器在連結可執行檔案時,可以設定記憶體地址,而不用去管這些資料最終實際的記憶體地址,這是有獨立記憶體空間的好處
2.當不同的程序使用同樣的程式碼時,比如庫檔案中的程式碼,實體記憶體中可以只儲存一份這樣的程式碼,不同的程序只需要把自己的虛擬記憶體對映過去就可以了,節省記憶體
3.在程式需要分配連續的記憶體空間的時候,只需要在虛擬記憶體空間分配連續空間,而不需要實際實體記憶體的連續空間,可以利用碎片。
另外,事實上,在每個程序建立載入時,核心只是為程序“建立”了虛擬記憶體的佈局,具體就是初始化程序控制表中記憶體相關的連結串列,實際上並不立即就把虛擬記憶體對應位置的程式資料和程式碼比如.text .data段拷貝到實體記憶體中,只是建立好虛擬記憶體和磁碟檔案之間的對映就好叫做儲存器對映,等到執行到對應的程式時,才會通過缺頁異常,來拷貝資料。還有程序執行過程中,要動態分配記憶體,比如malloc時,也只是分配了虛擬記憶體,即為這塊虛擬記憶體對應的頁表項做相應設定,當程序真正訪問到此資料時,才引發缺頁異常。
補充理解:
虛擬儲存器涉及三個概念: 虛擬儲存空間,磁碟空間,記憶體空間
可以認為虛擬空間都被對映到了磁碟空間中,事實上也是按需要對映到磁碟空間上,通過mmap,並且由頁表記錄對映位置,當訪問到某個地址的時候,通過頁表中的有效位,可以得知此資料是否在記憶體中,如果不是,則通過缺頁異常,將磁碟對應的資料拷貝到記憶體中,如果沒有空閒記憶體,則選擇犧牲頁面,替換其他頁面。
mmap是用來建立從虛擬空間到磁碟空間的對映的,可以將一個虛擬空間地址對映到一個磁碟檔案上,當不設定這個地址時,則由系統自動設定,函式返回對應的記憶體地址虛擬地址,當訪問這個地址的時候,就需要把磁碟上的內容拷貝到記憶體了,然後就可以讀或者寫,最後通過manmap可以將記憶體上的資料換回到磁碟,也就是解除虛擬空間和記憶體空間的對映,這也是一種讀寫磁碟檔案的方法,也是一種程序共享資料的方法 共享記憶體
在核心態申請記憶體比在使用者態申請記憶體要更為直接,它沒有采用使用者態那種延遲分配記憶體技術。核心認為一旦有核心函式申請記憶體,那麼就必須立刻滿足該申請記憶體的請求,並且這個請求一定是正確合理的。相反,對於使用者態申請記憶體的請求,核心總是儘量延後分配實體記憶體,使用者程序總是先獲得一個虛擬記憶體區的使用權,最終通過缺頁異常獲得一塊真正的實體記憶體。
1.實體記憶體的核心對映
IA32架構中核心虛擬地址空間只有1GB大小從3GB到4GB,因此可以直接將1GB大小的實體記憶體即常規記憶體對映到核心地址空間,但超出1GB大小的實體記憶體即高階記憶體就不能對映到核心空間。為此,核心採取了下面的方法使得核心可以使用所有的實體記憶體。
1.高階記憶體不能全部對映到核心空間,也就是說這些實體記憶體沒有對應的線性地址。不過,核心為每個物理頁框都分配了對應的頁框描述符,所有的頁框描述符都儲存在mem_map陣列中,因此每個頁框描述符的線性地址都是固定存在的。核心此時可以使用alloc_pages和alloc_page來分配高階記憶體,因為這些函式返回頁框描述符的線性地址。
2.核心地址空間的後128MB專門用於對映高階記憶體,否則,沒有線性地址的高階記憶體不能被核心所訪問。這些高階記憶體的核心對映顯然是暫時對映的,否則也只能對映128MB的高階記憶體。當核心需要訪問高階記憶體時就臨時在這個區域進行地址對映,使用完畢之後再用來進行其他高階記憶體的對映。
由於要進行高階記憶體的核心對映,因此直接能夠對映的實體記憶體大小隻有896MB,該值儲存在high_memory中。核心地址空間的線性地址區間如下圖所示:
從圖中可以看出,核心採用了三種機制將高階記憶體對映到核心空間:永久核心對映,固定對映和vmalloc機制。
2.實體記憶體管理機制
基於實體記憶體在核心空間中的對映原理,實體記憶體的管理方式也有所不同。核心中實體記憶體的管理機制主要有夥伴演算法,slab快取記憶體和vmalloc機制。其中夥伴演算法和slab快取記憶體都在實體記憶體對映區分配實體記憶體,而vmalloc機制則在高階記憶體對映區分配實體記憶體。
夥伴演算法
夥伴演算法負責大塊連續實體記憶體的分配和釋放,以頁框為基本單位。該機制可以避免外部碎片。
per-CPU頁框快取記憶體
核心經常請求和釋放單個頁框,該快取包含預先分配的頁框,用於滿足本地CPU發出的單一頁框請求。
slab快取
slab快取負責小塊實體記憶體的分配,並且它也作為快取記憶體,主要針對核心中經常分配並釋放的物件。
vmalloc機制
vmalloc機制使得核心通過連續的線性地址來訪問非連續的物理頁框,這樣可以最大限度的使用高階實體記憶體。
3.實體記憶體的分配
核心發出記憶體申請的請求時,根據核心函式呼叫介面將啟用不同的記憶體分配器。
3.1 分割槽頁框分配器
分割槽頁框分配器 zoned page frame allocator ,處理對連續頁框的記憶體分配請求。分割槽頁框管理器分為兩大部分:前端的管理區分配器和夥伴系統,如下圖:
管理區分配器負責搜尋一個能滿足請求頁框塊大小的管理區。在每個管理區中,具體的頁框分配工作由夥伴系統負責。為了達到更好的系統性能,單個頁框的申請工作直接通過per-CPU頁框快取記憶體完成。
該分配器通過幾個函式和巨集來請求頁框,它們之間的封裝關係如下圖所示。
這些函式和巨集將核心的分配函式__alloc_pages_nodemask封裝,形成滿足不同分配需求的分配函式。其中,alloc_pages系列函式返回實體記憶體首頁框描述符,__get_free_pages系列函式返回記憶體的線性地址。
3.2 slab分配器
slab 分配器最初是為了解決實體記憶體的內部碎片而提出的,它將核心中常用的資料結構看做物件。slab分配器為每一種物件建立快取記憶體。核心對該物件的分配和釋放均是在這塊快取記憶體中操作。一種物件的slab分配器結構圖如下:
可以看到每種物件的快取記憶體是由若干個slab組成,每個slab是由若干個頁框組成的。雖然slab分配器可以分配比單個頁框更小的記憶體塊,但它所需的所有記憶體都是通過夥伴演算法分配的。
slab快取記憶體分專用快取和通用快取。專用快取是對特定的物件,比如為記憶體描述符建立快取記憶體。通用快取則是針對一般情況,適合分配任意大小的實體記憶體,其介面即為kmalloc。
3.3 非連續記憶體區記憶體的分配
核心通過vmalloc來申請非連續的實體記憶體,若申請成功,該函式返回連續記憶體區的起始地址,否則,返回NULL。vmalloc和kmalloc申請的記憶體有所不同,kmalloc所申請記憶體的線性地址與實體地址都是連續的,而vmalloc所申請的記憶體線性地址連續而實體地址則是離散的,兩個地址之間通過核心頁表進行對映。
vmalloc的工作方式理解起來很簡單:
1.尋找一個新的連續線性地址空間;
2.依次分配一組非連續的頁框;
3.為線性地址空間和非連續頁框建立對映關係,即修改核心頁表;
vmalloc的記憶體分配原理與使用者態的記憶體分配相似,都是通過連續的虛擬記憶體來訪問離散的實體記憶體,並且虛擬地址和實體地址之間是通過頁表進行連線的,通過這種方式可以有效的使用實體記憶體。但是應該注意的是,vmalloc申請實體記憶體時是立即分配的,因為核心認為這種記憶體分配請求是正當而且緊急的;相反,使用者態有記憶體請求時,核心總是儘可能的延後,畢竟使用者態跟核心態不在一個特權級。
物理首地址是什麼