Windows/Linux核心地址空間管理的異同?

相信很多人都知道Windows頁表自對映一說,也曉得Linux核心的一一線性對映。然而很多人也僅僅就是知道而已,記住一個結論比理解一個原因要簡單得多。

上週末,有人極具挑釁態度的問我能否分別用一句話描述它們,我承認我不是佈道者,也難以說出”道可道,非常道“的玄語,但我十分贊同老子的觀點,能說出來的道就不是大道,雖難以說出,但卻可以解釋,說到解釋,那就是越詳細越好了,冗長並不總是貶義詞。在這樣的心理慰藉下,才會有以下的文字,雖已深更,卻不無聊...

本文基於32位Intel體系結構討論!怕打字跟不上思維,有些地方只好缺失嚴謹性,比如在應該寫下”1G記憶體或者2G記憶體(後者處在2G/2G模式下)“的時候,我會直接寫”1G記憶體“,但是並不總是這樣。

1.虛擬地址空間概述

現代作業系統上,實體記憶體不再對程式可見。也就是說,程式指令本身以及其訪問的任何資料都處在虛擬地址空間,機器通過一個叫做MMU的機構將其對映為真實的實體記憶體頁面。

程式直接訪問的地址為虛擬地址,訪問地址(也包含取指等)會觸發MMU工作,MMU自動將訪問的地址對映到真實的實體地址,如果沒有分配物理頁面,將會觸發缺頁異常,系統捕獲該異常,之後默默地分配一個頁面,重新發起由於沒有分配頁面而失敗的訪問,所有這一切都是自動且默默地發生的,對應用程式是完全透明的。頁面排程這個機制完美地迎合了程式訪問的區域性性原則。

虛擬地址填充整個32位地址空間,為了管理的高效性,很多的系統將這32位的地址空間拆分成了兩個部分,即使用者空間和核心空間。但是記住,這個拆分並不是必須的!所謂的核心空間和使用者空間在Intel體系上表現為特權環0和特權環3。根本上的意義是,一個任務有一個滿32位的地址空間,如果某個系統將程序32位的地址空間拆成了兩個部分,那麼則說明該任務程序本身包含核心特權環0的部分,如果沒有拆分,那麼就說明該任務程序沒有核心部分。Remenber,一個滿32位的地址空間使用一套MMU頁表!

如果一個程序沒有核心部分,當系統中斷,系統異常,或者該程序本身呼叫系統呼叫的時候,怎麼辦呢?不要被現有的Linux,Windows的實現迷惑了,再次宣告,拆分地址空間並不是必須的!如果在沒有拆分地址空間的情況下出現上述情況,很簡單,切換MMU頁表即可,也就是說,系統單獨維護一個滿32位的核心地址空間為所有的滿32位地址空間的程序服務!

說了這麼多,該來點例項了,我們熟知的Linux,Windows系統,可以支援3G/1G模式,意即滿32位的程序地址空間中,使用者態佔3G,核心態佔1G;可以是2G/2G模式,解釋同上,這些都是拆分地址空間的情況,這些情況在進入核心態的時候叫做陷入核心,因為即使進入了核心態,還處在同一個地址空間中,並不切換CR3暫存器。還有一種模式是4G/4G模式,即不拆分地址空間的情況,核心單獨佔有一個4G的地址空間,所有的使用者程序獨享自己的4G地址空間,這種模式下,在進入核心態的時候,叫做切換到核心,因為需要切換CR3暫存器(切換MMU頁表),所以進入了不同的地址空間!

說到這裡,應該知道為何文件上說4G/4G模式雖然解放了核心地址空間,使其可以容納更多的管理機構,然而會付出一點小的代價了吧,所謂的代價就是切換CR3以及所有因此而引發的副作用!

2.Windows地址空間

一直我都以為,一個好的開始會帶來令人愉快的結果,一個不好的開始會讓人很累!確實是這樣。很多人想理解Windows頁表自對映,然後去google,去百度,得到的結果幾乎都是在解釋以下這個巨集定義:

#define MiGetVirtualAddressMappedByPte(PTE) ((PVOID)((ULONG)(PTE) << 10))

於是很多人都在糾結於那個魔術字10,畫了N個圖,但是基本都是在抄襲Dave Probert很久前寫的一篇文章《Windows Kernel Internals II Processes, Threads, VirtualMemory》。關鍵是最終還是沒有講明白。本來是一個很簡單的事情,被卻無端複雜化了。我覺得就是沒有找到一個好的開始。什麼是好的開始呢?

我認為我找到了,那就是WIndows程序虛擬地址空間的佈局!如果這個佈局設計的原則你搞明白了,那些巨集你自己也能寫出來了!不管怎麼說,在看圖之前,還是要先說一下Windows地址空間設計的原則,那就是:每個程序擁有自己單獨的滿32位地址空間!不管是3G/1G模式,還是2G/2G模式,還是4G/4G模式,每個程序都是獨立的虛擬地址空間,這也是現代作業系統的設計原則,並非Windows獨創。在這些單獨的地址空間中,所有程序擁有相同的對映規則,比如虛擬地址XX不管在程序A還是在程序B,對映的都是自己的程序控制塊PCB...如下圖所示:

其實知道了這個,悟性好的同學可能已經知道自對映的設計細節了,但是我還是繼續下去吧,以免讓人家說我虎頭蛇尾。

頁表自對映,一個神奇的對映方式,為什麼呢?它可不僅僅是為了節省4K的記憶體空間,雖然它確實可以節省4K的記憶體空間。最要緊的是,頁表自對映機制提供了一套核心空間直接訪問任意頁面的高效方式。在講頁表自對映前,我先說一下WIndows的線性對映機制,即“頁表項虛擬地址和程序地址空間虛地址頁面的線性對映關係”,簡稱頁表的線性對映。(注意和下一節中要講的Linux的核心虛擬地址和實體地址的線性對映相區分)

Windows頁表的線性對映理解起來很簡單。Windows的所有頁表處在地址空間的固定部分且按照虛擬地址連續分佈,那麼所有的頁表項的虛擬地址也是連續分佈,從最開始處,連續的頁表項負責連續的虛擬地址的對映,如下圖所示:

注意,直到現在,我都沒有提到頁目錄,因為頁目錄純粹是為了多級頁表而引入的,Windows只是藉助了頁目錄的概念,無形中用將頁表對映到虛擬地址空間而取消了頁目錄帶來的4K開銷。Windows只是在地址空間的固定位置開始連續對映所有的頁表,這些頁表當中存在一個頁表的頁表,即頁目錄,頁目錄就這樣湮沒在頁表中了。且往下看!

有了這個基礎做依託,後面的自對映以及神奇的巨集就是一個自然而然的結果了。為何這麼說呢?分別來說。

本篇文章來源於 Linux公社網站(www.linuxidc.com) 原文連結:

相關問題答案