Linux核心原始碼分析--記憶體管理(一、分頁機制)
Linux系統中分為幾大模組:程序排程、記憶體管理、程序通訊、檔案系統、網路模組;各個模組之間都有一定的聯絡,就像蜘蛛網一樣,所以這也是為什麼Linux核心那麼難理解,因為不知道從哪裡開始著手去學習。很多人會跟著系統上電啟動 BIOS-->bootsect-->setup-->head-->main-->.....來學習,但是最後會發現當你在看main的時候你必須知道其他模組大概工作情況,要不然根本不知道為什麼要這麼做(也許其中的C程式碼和彙編你都能看懂,但真正含義其不知道)。注意下:下面的blog中涉及到的作業系統都是選擇0.11版的Linux系統
前言
所幸的是我最開始入手選擇了記憶體管理,而記憶體管理和其他模組聯絡就相對小一些(沒看完其他模組,但感覺和其他模組聯絡不是很大),只有頁面中斷和程序那兩個模組有些關係。好了,現在開始介紹下記憶體管理模組了(其實也是梳理下我的知識點)。
最開始的地方是在head彙編中,如果看boot中那三個彙編的應該記得(那三個彙編還是比較重要的)。首先是分頁機制,在CR0的第31位(PG位)置1表示開啟分頁機制,順便也介紹下其他幾個控制暫存器:CR1保留,沒用;CR2 用來記錄頁面異常時線性地址(不懂沒關係,後面會介紹);CR3 當前CPU使用的頁目錄表的地址(有此可見系統中不僅僅只有一個頁目錄表,但是在某一時刻有效的頁目錄表只有一個);當然有關頁面操作的前提是CR0的第31位必須開啟,也就是必須是在分頁機制開啟的時候那幾個控制暫存器才有效。
分頁機制最最基礎的就是把記憶體空間以4kb為單位分成多個頁。
系統記憶體總分佈
在Linux系統中全部記憶體分佈情況為:
在setup彙編中已經把系統核心程式碼從0x100000移動到從0地址開始的1MB地址內,再根據main函式中設定記憶體不超過16MB,所以這裡就拿16MB記憶體為例子;整個記憶體分佈情況為:核心程式碼及系統資料使用0~1MB ------ 快取記憶體使用1~4MB ------ 虛擬記憶體4MB~xxx(如果有虛擬記憶體)------ 主記憶體區xxx~16MB;具體的設定在main函式有,可以自己檢視下。如果大於16MB記憶體那麼就會限制只使用低16MB地址記憶體,大於16MB的記憶體將會廢掉;如果想使用大於16MB,那麼就要在main函式中和head彙編中修改下(具體修改就要自己動手了)
分頁機制名詞介紹
頁目錄表:由1024個目錄項組成,每個目錄項是4個位元組組成,目錄項中內容為頁表結構的起始地址前20位(因為頁表結構是2^12,所以低12位可以忽略)和該頁表的屬性組成;
頁表:頁表和頁目錄表和相似,都是由1024個表項組成,每個表項由4個位元組組成,頁表項中存放的內容為物理頁的起始地址的前20位(因為物理頁大小為 2^12,所以低12位可以忽略)和該物理頁面的屬性組成;
物理頁面:一般是在主記憶體區中以4kb的倍數為起始地址,大小為4kb的連續記憶體地址(這裡假設沒有虛擬記憶體);
表項:表項分為頁目錄表項和頁表表項,其中格式都是一樣的。前20位頁框地址,後12位表示對於頁面的屬性;表項結構如下:
若是頁目錄項:頁框地址中的前20位表示的是頁表的物理起始地址中的前20位(這裡有幾個重點的:1、表示的是頁表的實體地址,而不是線性地址,這兩個地址關係後面再分析;2、是起始地址,因為一個頁表是4kb大小,所以一個頁表就有4kb個地址(一個位元組對於一個地址嘛),而起始地址表示偏移量為0的地址;3、前20位,因為分頁機制中頁(不管是頁目錄還是頁表或者物理頁)都是以4kb的倍數為起始地址的,也就是說頁的起始地址的低12位全部為0,2^12 = 4kb);
若是頁表項:頁框地址中的前20位表示的是物理頁的物理起始地址中的前20位;
低12位則用來表示相應的頁的一些屬性:p == 是否存在(1 存在;0不存在 == 缺頁中斷);r/w == 是否可讀可寫(預設都是可讀的,1表示頁面可寫);u/s == 是否是超級使用者(這個到現在還沒有怎麼用到,1表示超級使用者);A == 是否訪問,D == 是否修改(這兩個位一般由硬體來處理);
下面是頁目錄表、頁表、物理頁的巨集觀關係圖:
分頁機制本質
在分析分頁機制本質之前先要了解幾個地址概念:邏輯地址、線性地址、虛擬地址、實體地址; 首先定義一個地址:0xb8000(這是一個特殊的地址,還記得嗎?這是一個顯示卡對映過來的實體地址,需要在螢幕上顯示東西就從這個地址開始往裡面寫入需要顯示的內容和字型屬性了);那麼現在假設(真實模式下) ds = 0xb800 ax = 0x00 ==== ds*16 + ax == 0xb8000 + 0 = 0xb8000 邏輯地址:就是偏移地址,就上面而言是ax中的地址(不管在保護模式下還是真實模式下); 線性地址:就是段地址加上偏移量(也即是邏輯地址)而形成的32位地址;若未開啟分頁機制,那麼線性地址就和實體地址對應的;開啟分頁機制,那麼線性地址則是有頁目錄項號(地址的高10位)+ 頁表項號(地址的12~21共10位)+ 物理頁內偏移量(地址的低12位)共同組成的; 虛擬地址:這個還沒仔細研究,每個程序中都有4G的記憶體地址,但是這不是真實的地址,而是有系統虛擬出來的,所以虛擬地址指定是程序中使用的虛擬地址; 實體地址:這就是最根本的地址,硬體上的地址,cpu地址總線上使用的地址; 分頁機制的本質就是把線性地址轉換成實體地址:下面看地址轉換圖根據轉換圖,分步驟來解釋下轉換情況:假設線性地址為 0x00c0 f0ef (我承認這個地址是提前設計好的,但僅僅只是為了方便計算,並不影響轉換工作) 1、查詢頁目錄項的物理起始地址:已知線性地址 eax = 0x00c0 f0ef,那麼怎麼獲取頁目錄項呢? eax >> 22(將線性地址右移22位)得到頁目錄項號(一定要記住這是頁目錄項號要和頁目錄項起始地址分開;因為線性地址上和頁目錄有關的就只有前20位地址,所以線性地址中最大可以表示的頁面項號為 2^10=1024項;而一個頁面有4kb(4096byte),每一個頁目錄項佔用4個位元組,所以 4kb/4 = 1kb(1024)項,正好對的上。一般頁目錄項號都是從0開始的,然後1、2、3、4.....)這裡得到的頁目錄項號為:0x003,化作頁目錄項的物理起始地址為:0x003 x 4(每一項4個位元組) = 0x00c;那麼再根據CR3中頁目錄表的基地址,則可以查詢到頁目錄項的物理起始地址,假設只有一個頁目錄表,CR0 = 0x000,那頁目錄項起始實體地址則為:0x00c0 0000; 2、根據查詢到的頁目錄項,分析頁表物理基地址:從上一步中獲取到頁目錄項,根據目錄項結構可以知道,只有前20位是頁框地址,後12位是對應頁的屬性設定;頁目錄項 & 0xFFFF F000 (其實就是得到前20位頁框地址)就可以得到頁表物理基地址了; 3、獲取頁表項的物理起始地址:和第一步一樣,獲取到頁表項號,通過線性地址 & 0x 3F F000(其實就是提取中間和頁表有關的10位地址),將會得到0x0000 F000頁表項號(同樣和頁表物理起始地址區分開),那麼頁表項物理起始地址為:0x0000 F000 * 4 ; 4、根據頁表項,分析物理頁的物理起始地址:步驟同2一樣,獲取到前20位頁框地址,就是物理頁的物理起始地址了; 5、最後把上一步得到的物理頁的起始地址加上線性地址中最後12位的頁內偏移值,就可以準確的定位到每個位元組上了;