Linux 操作系統原理 — 內存 — 頁式管理、段式管理與段頁式管理

目錄

前文列表

Linux 操作系統原理 — 內存 — 物理存儲器與虛擬存儲器
Linux 操作系統原理 — 內存 — 基於 MMU 硬件單元的虛/實地址映射技術
Linux 操作系統原理 — 內存 — 基於局部性原理實現的內/外存交換技術
Linux 操作系統原理 — 內存 — 內存分配算法

頁式管理

頁式存儲管理是一種把主存按頁分配的存儲管理方式,主存-輔存間信息傳送單位是定長的頁。對比塊式管理而言,因爲管理的粒度更細緻,所以造成內存頁碎片的浪費也會小很多。而缺點也正好相反,由於頁不是程序獨立模塊對應的邏輯實體,所以處理、保護和共享都不及段來得方便。同時也因爲頁要比段小得多,在 Linux 下通常默認設置爲 4KB,所以頁在進行交換時,不會出現段交換那般卡頓。所以,頁式存儲管理方式會更加的受到歡迎,Linux 操作系統採用的就是頁式存儲管理方式。

在這裏插入圖片描述

更進一步的,頁式存儲管理方式使得加載程序的時候,不再需要一次性都把程序加載到內存中,而是在程序運行中需要用到的對應虛擬內存頁裏面的指令和數據時,再將其加載到內存中,這些操作由操作系統來完成。當 CPU 要讀取特定的頁,但卻發現頁的內容卻沒有加載時,就會觸發一個來自 CPU 的缺頁錯誤(Page Fault)。此時操作系統會捕獲這個錯誤,然後找到對應的頁並加載到內存中。通過這種方式,使得我們可以運行哪些遠大於實例物理內存的程序,但相對的執行效率也會有所下降。

在這裏插入圖片描述
通過虛擬存儲器、內存交換、內存分頁三個技術的結合。我們最終得到了一個不需要讓程序員考慮實際的物理內存地址、大小和當前分配空間的程序運行環境。這些技術和方式對於程序員和程序的編譯、鏈接過程而言都是透明的,印證了那句著名的話:所有計算機問題都可以通過插入一箇中間層來解決。

頁表管理機制中有兩個很重要的概念:快表和多級頁表。在分頁內存管理中,很重要的兩點是:

  1. 虛擬地址到物理地址的轉換要快。
  2. 解決虛擬地址空間大,頁表也會很大的問題。

快表

塊表:爲了解決虛擬地址到物理地址的轉換速度,操作系統在 頁表方案 基礎之上引入了 快表 來加速虛擬地址到物理地址的轉換。我們可以把塊表理解爲一種特殊的高速緩衝存儲器(Cache),其中的內容是頁表的一部分或者全部內容。作爲頁表的 Cache,它的作用與頁表相似,但是提高了訪問速率。由於採用頁表做地址轉換,讀寫內存數據時 CPU 要訪問兩次主存。有了快表,有時只要訪問一次高速緩衝存儲器,一次主存,這樣可加速查找並提高指令執行速度。

使用快表之後的地址轉換流程是這樣的:

  1. 根據虛擬地址中的頁號查快表;
  2. 如果該頁在快表中,直接從快表中讀取相應的物理地址;
  3. 如果該頁不在快表中,就訪問內存中的頁表,再從頁表中得到物理地址,同時將頁表中的該映射表項添加到快表中;
  4. 當快表填滿後,又要登記新頁時,就按照一定的淘汰策略淘汰掉快表中的一個頁。

看完了之後你會發現快表和我們平時經常在我們開發的系統使用的緩存(比如 Redis)很像,的確是這樣的,操作系統中的很多思想、很多經典的算法,你都可以在我們日常開發使用的各種工具或者框架中找到它們的影子。

多級頁表

多級頁表:引入多級頁表的主要目的是爲了避免把全部頁表一直放在內存中佔用過多空間,特別是那些根本就不需要的頁表就不需要保留在內存中。多級頁表屬於時間換空間的典型場景,具體可以查看下面這篇文章:https://www.polarxiong.com/archives/多級頁表如何節約內存.html。

爲了提高內存的空間性能,提出了多級頁表的概念;但是提到空間性能是以浪費時間性能爲基礎的,因此爲了補充損失的時間性能,提出了快表(即 TLB)的概念。不論是快表還是多級頁表實際上都利用到了程序的局部性原理,局部性原理在後面的虛擬內存這部分會介紹到。

共同點 :

  • 分頁機制和分段機制都是爲了提高內存利用率,較少內存碎片。
  • 頁和段都是離散存儲的,所以兩者都是離散分配內存的方式。但是,每個頁和段中的內存是連續的。

區別 :

  • 頁的大小是固定的,由操作系統決定;而段的大小不固定,取決於我們當前運行的程序。
  • 分頁僅僅是爲了滿足操作系統內存管理的需求,而段是邏輯信息的單位,在程序中可以體現爲代碼段,數據段,能夠更好滿足用戶的需要。

爲了存儲 64 位操作系統中 128 TiB 虛擬內存的映射數據,Linux 在 2.6.10 中引入了四層的頁表輔助虛擬地址的轉換,在 4.11 中引入了五層的頁表結構,在未來還可能會引入更多層的頁表結構以支持 64 位的虛擬地址。

在這裏插入圖片描述

在如上圖所示的四層頁表結構中,操作系統會使用最低的 12 位作爲頁面的偏移量,剩下的 36 位會分四組分別表示當前層級在上一層中的索引,所有的虛擬地址都可以用上述的多層頁表查找到對應的物理地址。

因爲操作系統的虛擬地址空間大小都是一定的,整片虛擬地址空間被均勻分成了 N 個大小相同的內存頁,所以內存頁的大小最終會決定每個進程中頁表項的層級結構和具體數量,虛擬頁的大小越小,單個進程中的頁表項和虛擬頁也就越多。

因爲目前的虛擬頁大小爲 4096 字節,所以虛擬地址末尾的 12 位可以表示虛擬頁中的地址,如果虛擬頁的大小降到了 512 字節,那麼原本的四層頁表結構或者五層頁表結構會變成五層或者六層,這不僅會增加內存訪問的額外開銷,還會增加每個進程中頁表項佔用的內存大小。

基於頁表的虛實地址轉換原理

同任何緩存設計一樣,虛擬存儲器系統必須有某種方法來判定一個虛擬頁是否存放在物理主存的某個地方。如果存在,系統還必須確定這個虛擬頁存放在哪個物理頁中。如果物理主存不命中,系統必須判斷這個虛擬頁存放在磁盤的哪個位置中,並在物理主存中選擇一個犧牲頁,然後將目標虛擬頁從磁盤拷貝到物理主存中,替換掉犧牲頁。這些功能是由許多軟硬件聯合提供,包括操作系統軟件,MMU(存儲器管理單元)地址翻譯硬件和一個存放在物理主存中的叫做頁表(Page Table)的數據結構,頁表將虛擬頁映射到物理頁。頁表的本質就是一個頁表條目(Page Table Entry,PTE)數組。

CPU 通過虛擬地址(Virtual Address,VA)來訪問存儲空間,這個虛擬地址在被送到存儲器之前需要先轉換成適當的物理地址。將一個虛擬地址轉換爲物理地址的任務叫做地址翻譯(Address Translation)。就像異常處理一樣,地址翻譯需要 CPU 硬件和操作系統之間的緊密合作。比如:Linux 操作系統的交換空間(Swap Space)。如果當 CPU 尋址時發現虛擬地址找不到對應的物理地址,那麼就會觸發一個異常並掛起尋址錯誤的進程。在這個過程中,對其他進程沒有任何影響。

虛擬地址與物理地址之間的轉換主要有 CPU 芯片上內嵌的存儲器管理單元(Memory Management Unit,MMU)完成,它是一個專用的硬件,利用存放在主存中的查詢表(地址映射表)來動態翻譯虛擬地址,該表的內容由操作系統管理。

在這裏插入圖片描述
由於頁的大小爲 2 的整數次冪,所以頁的起點都落在低位字段爲 0 的地址上,可以把虛擬地址分爲兩個字段,高位字段位虛擬頁號,低位字段爲虛頁內地址。在頁表中,對應每一個虛頁號都有一個條目,格式爲 (虛頁號,實頁號,控制字)。
在這裏插入圖片描述
實頁號即爲實頁地址,被作爲物理地址的高字段,而物理地址的低字段則同爲虛擬地址的低字段(虛頁內地址)。拼接成爲了主存物理地址之後,就可以據此訪問主存儲器數據了。
在這裏插入圖片描述
通常的,頁面中還包括有裝入位、修改位、替換控制位以及其他保護位組成的控制字。e.g.

  • 裝入位爲 1:表示該條目對應的虛頁以及輔存調入主存;
  • 裝入位爲 0:表示對應的虛頁尚未裝入主存,如果此時 CPU 訪問該頁就會觸發頁面失效中斷,啓動 I/O 子系統,根據外頁表項目中查找到的輔存地址,進行輔存到主存的頁面交換;
  • 修改位:表示主存頁面的內容是否被修改過,從主存交換到輔存時是否要寫回輔存。
  • 替換位:表示需要替換的頁。

應用 TLB 快表提升虛實地址轉換速度

當頁表已經存放在主存中,那麼當 CPU 訪問(虛擬)存儲器時,首先要查詢頁面得到物理主存地址之後再訪問主存完成存取。顯然,地址轉換機制讓 CPU 多了一次訪問主存的操作,相當於訪問速度下降一半。而且當發生頁面失效時,還要進行主存-輔助的頁面交換,那麼 CPU 訪問主存的次數就更多了。爲了解決這個問題,在一些影響訪問速度的關鍵地方引入了硬件的支持。例如:採用按內容查找的相聯存儲器並行查找。此外,還進一步提出了 “快表” 的概念。把頁表中最活躍的部分存放在快速存儲器中組成快表,是減少 CPU 訪問時間開銷的一種方法。

快表由硬件(門電路和觸發器)組成,屬於 MMU 的部件之一,通常稱爲轉換旁路緩衝器(Translation lookaside buffer,TLB)。TLB 的本質也是一個 Cache,它比頁表小得多,一般在 16 個條目 ~ 128 個條目之間,快表只是頁表的一個小小的副本。查表時,帶着虛頁好同時差快表和慢表(原頁面),當在快表中找打條目時,則馬上返回主存物理地址到主存地址寄存器,並使慢表查詢作廢。此時,雖然使用了虛擬存儲器但實際上 CPU 訪問主存的速度幾乎沒有下降(CPU 不再需要多次訪問主存)。如果快表不命中,則需要花費一個訪主存時間查慢表,然後再返回主存物理地址到主存地址寄存器,並將此條目送入到快表中,替換到快表的某一行條目。

在這裏插入圖片描述

頁式虛擬存儲器工作的全過程

內頁表:虛擬地址與主存地址的映射。
外頁表:虛擬地址與輔存地址的映射。
虛地址格式:(虛頁號,虛頁內地址)
主存地址格式:(實頁號,實頁內地址)
輔存地址格式:(磁盤機號,磁頭號,柱面號,塊號,塊內地址)

從三種地址格式可見,虛地址-主存地址的轉換是虛實頁號替換,有內頁表完成;虛地址-輔存地址的轉換是虛頁號與 “磁盤機號,磁頭號,柱面號,塊號” 的替換,由外頁表完成。

在這裏插入圖片描述
1、2:虛擬存儲器每次訪問主存時都需要將多用戶虛地址轉換層主存實地址,這個由虛頁號轉換爲實頁號的內部地址轉換由內頁表來完成;
3:當對應內頁表條目的有效位爲 1 時,就按照物理主存地址 np 進行主存儲器訪問。
4:如果對應內存表條目的裝入位爲 0 時,表示該虛頁對應的實頁不再主存中,那麼就觸發一個頁面失效中斷。有中斷處理器到輔存中調用對應的實頁。
5:到輔存中調頁,首先要進行外部地址轉換,查找外頁表,將多用戶虛擬地址轉換爲輔存實頁地址 Nvd。
6:根據輔存實頁地址 Nvd 到輔存中選頁。
7:將選中的輔存實頁經過 I/O 處理機送出到物理主存中。
9:此時還要確定調入的輔存實頁應該放置到主存的什麼位置上,這通過查找實存頁面表來完成。
10:當主存對應的目標地址仍然空閒時,就會找到空頁面。
11、12:但當主存已經裝滿時,就是執行頁面替換操作,由替換算法來決定替換哪一個主存實頁到輔存中。
13:把待替換主存實頁放入 I/O 處理機,待替換主存頁是否被修改了是可以通過頁表替換位知道的,此時如果待替換的主存實頁沒有被修改過,那麼是不需要回寫到輔存的。
14:但如果待替換的主存實頁被修改了,那麼就需要寫回輔存。
7:繼續將目標實頁寫入到物理主存中,完成替換。新頁調入主存時,需要修改相應的頁表條目。
8:如果待替換頁沒能裝入緩存,那麼還要繼續進入中斷,進行出錯處理或其他處理。

缺頁中斷

地址映射過程中,若在頁面中發現所要訪問的頁面不在內存中,則發生缺頁中斷。缺頁中斷 就是要訪問的頁不在主存,需要操作系統將其調入主存後再進行訪問。在這個時候,被內存映射的文件實際上成了一個分頁交換文件。當發生缺頁中斷時,如果當前內存中並沒有空閒的頁面,操作系統就必須在內存選擇一個頁面將其移出內存,以便爲即將調入的頁面讓出空間。用來選擇淘汰哪一頁的規則叫做頁面置換算法,我們可以把頁面置換算法看成是淘汰頁面的規則。

  • OPT 頁面置換算法(最佳頁面置換算法) :理想情況,不可能實現,一般作爲衡量其他置換算法的方法。
  • FIFO 頁面置換算法(先進先出頁面置換算法) : 總是淘汰最先進入內存的頁面,即選擇在內存中駐留時間最久的頁面進行淘汰。
  • LRU 頁面置換算法(最近未使用頁面置換算法) :LRU(Least Currently Used)算法賦予每個頁面一個訪問字段,用來記錄一個頁面自上次被訪問以來所經歷的時間T,當須淘汰一個頁面時,選擇現有頁面中其T值最大的,即最近最久未使用的頁面予以淘汰。
  • LFU 頁面置換算法(最少使用頁面排序算法) : LFU(Least Frequently Used)算法會讓系統維護一個按最近一次訪問時間排序的頁面鏈表,鏈表首節點是最近剛剛使用過的頁面,鏈表尾節點是最久未使用的頁面。訪問內存時,找到相應頁面,並把它移到鏈表之首。缺頁時,置換鏈表尾節點的頁面。也就是說內存內使用越頻繁的頁面,被保留的時間也相對越長。

處理的流程如下:

在這裏插入圖片描述

  • cr2:訪問到線性地址、
  • err_code:異常發生時由控制單元壓入棧中,表示發生異常的原因。
  • regs:發生異常時寄存器的值。

爲什麼 Linux 默認頁大小是 4KB?

Linux 同時支持正常大小的內存頁和大內存頁(Huge Page),絕大多數處理器上的內存頁的默認大小都是 4KB,雖然部分處理器會使用 8KB、16KB 或者 64KB 作爲默認的頁面大小,但是 4KB 的頁面仍然是操作系統默認內存頁配置的主流。除了正常的內存頁大小之外,不同的處理器上也包含不同大小的大頁面,我們在 x86 處理器上就可以使用 2MB 的內存頁。

4KB 的內存頁其實是一個歷史遺留問題,在上個世紀 80 年代確定的 4KB 一直保留到了今天。雖然今天的硬件比過去豐富了很多,但是我們仍然沿用了過去主流的內存頁大小。在今天,4KB 的內存頁大小可能不是最佳的選擇,8KB 或者 16KB 說不定是更好的選擇,但是這是過去在特定場景下做出的權衡。

我們需要關注的是兩個影響內存頁大小的因素:

  • 過小的頁面大小會帶來較大的頁表項增加尋址時 TLB(Translation lookaside buffer)的查找速度和額外開銷;
  • 過大的頁面大小會浪費內存空間,造成內存碎片,降低內存的利用率;

上個世紀在設計內存頁大小時充分考慮了上述的兩個因素,最終選擇了 4KB 的內存頁作爲操作系統最常見的頁大小。

段式管理

  • 段式管理:頁式管理雖然提高了內存利用率,但是頁式管理其中的頁實際並無任何實際意義。段式管理把主存分爲一段段的,每一段的空間又要比一頁的空間小很多 。但是,最重要的是段是有實際意義的,每個段定義了一組邏輯信息,例如,有主程序段 MAIN、子程序段 X、數據段 D 及棧段 S 等。段式管理通過段表對應邏輯地址和物理地址

段式存儲管理是一種把主存按段分配的存儲管理方式,主存-輔存間信息傳送單位是不定長的段。

  • 優點:是段的分界與程序的自然分界是相對應的。例如:過程、子程序、數據表和陣列等待程序的模塊化性質都可以與段對應起來。於是段作爲獨立的邏輯單位可以被其他程序段調用,這樣就形成了段間連接,產生規模較大的程序。這樣的特性使得段易於編譯、管理、修改和保護,也便於多道程序共享。
  • 缺點:是容易在段間留下許多空餘的存儲空間碎片,且不好收集利用。除此之外,段式存儲管理還存在交換性能較低的問題。因爲輔存的訪問速度比主存慢得多,而每一次交換,我們都需要把一大段連續的內存數據寫到硬盤上,導致了當內存交換一個較大的段時,會讓機器顯得卡頓。

在這裏插入圖片描述

段頁式管理

段頁式管理結合了段式管理和頁式管理的優點。簡單來說,段頁式管理機制就是把主存先分成若干頁,每個頁又分成若干段。如此的,段頁式管理機制中段與段之間以及段的內部的都是離散的。顯然,段頁式管理是一種折中的方式,具有廣泛的通用性。

在 Linux 內部的地址的映射過程爲:邏輯地址–>線性地址–>物理地址。物理地址,即:地址總線中傳輸的數字信號,而線性地址和邏輯地址所表示的則是一種轉換規則,線性地址規則如下:
在這裏插入圖片描述

這部分由 MMU 完成,其中涉及到主要的寄存器有 CR0、CR3。機器指令中出現的是邏輯地址,邏輯地址規則如下:

在這裏插入圖片描述

在 Linux 中的邏輯地址等於線性地址。

而段頁式管理的 MMU 硬件電路,它包含兩個部件,一個是分段部件,一個是分頁部件:

  • 分段機制把一個邏輯地址轉換爲線性地址。
  • 分頁機制把一個線性地址轉換爲物理地址。

在這裏插入圖片描述

內存分段的原理:邏輯地址的段寄存器中的值提供段描述符,然後從段描述符中得到段基址和段界限,然後加上邏輯地址的偏移量,就得到了線性地址。

段選擇符:

  • 爲了方便快速檢索段選擇符,處理器提供了 6 個分段寄存器來緩存段選擇符,它們是:CS、SS、DS、ES、FS 和 GS。
  • 段的基地址(Base Address):在線性地址空間中段的起始地址。
  • 段的界限(Limit):在虛擬地址空間中,段內可以使用的最大偏移量。

在這裏插入圖片描述

內存分頁(32 位)原理:是在分段機制之後進行的,它進一步將線性地址轉換爲物理地址:

  • 10 位頁目錄,10 位頁表項, 12 位頁偏移地址。
  • 單頁的大小爲 4KB。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章