天天看點

保護模式彙編系列之三 - 段頁式記憶體管理(一)

這是本系列第三篇了,我們這次來談談x86的段頁式記憶體管理。這篇文章的定位是闡述分段分頁的來曆和要解決的問題。需要闡述細節的地方,我會貼出相關的文檔和代碼。

首先,如果我這個标題讓你覺得段頁式是一種方式而且密不可分的話,那我先說聲抱歉了。其實分段和分頁沒什麼必然聯系。隻不過intel從8086開始,其制造的cpu就以段位址+偏移位址的方式來通路記憶體。後來要相容以前的cpu,intel不得不一直保留着這個傳統。分段可以說是intel的cpu一直保持着的一種機制,而分頁隻是保護模式下的一種記憶體管理政策。不過想開啟分頁機制,cpu就必須工作在保護模式,而工作在保護模式時候可以不開啟分頁。

關于保護模式的段機制我們在系列一裡面已經談過不少,而且我們也談過“繞過”分段的平坦模式。那麼,我們下文的重點就是談談在設定平坦模式的環境之後,進行記憶體分頁管理的問題了。光說不練是假把式,這次我們就貼上來一些代碼具體感受一下吧。

首先是設定全局段描述符表。我們給出全局段描述符表和全局描述符表寄存器的結構體定義:

保護模式彙編系列之三 - 段頁式記憶體管理(一)

注意結構體定義後面的那個} attribute((packed)) 很重要,這是gcc的擴充,用來設定該結構體不進行位元組對齊。什麼?你不知道什麼是位元組對齊?那麼你先去谷歌一下再回來接着看吧。

好了,為了友善和intel的文檔比對,我們貼出相關的定義參照着看吧。

保護模式彙編系列之三 - 段頁式記憶體管理(一)

我們再貼出gdtr的定義:

保護模式彙編系列之三 - 段頁式記憶體管理(一)

這樣對比着結構體的定義很清楚吧?需要注意的是我們把段描述符表的部分以二進制位來表示的設定資訊合并到了相應的位元組裡,這裡按照位域去定義不是不可以,但是太過于臃腫了,而且等我貼出設定一個段描述符的函數時,你就覺得其實這樣做更清晰。

我們給出全局描述符表的定義以及設定一項描述符的函數實作:

保護模式彙編系列之三 - 段頁式記憶體管理(一)
保護模式彙編系列之三 - 段頁式記憶體管理(一)

怎麼樣?幾張圖檔對比着看很容易就了解了吧?那麼具體的初始化函數呢?别急,接下來就是:

保護模式彙編系列之三 - 段頁式記憶體管理(一)

這裡唯一麻煩的就是需要對照着intel文檔的說明,去為每一個段描述符計算權限位的數值了。不過細心的你肯定發現了最後有一個加載全局描述附表的函數,這個函數用彙編來實作了。代碼如下:

保護模式彙編系列之三 - 段頁式記憶體管理(一)

因為對具體寄存器的操作超過了c語言的能力範圍,與其内聯彙編還不如直接用用彙編實作簡單(我們采用的彙編編譯器是nasm)。

我想這個彙編函數中唯一需要解釋的就是jmp跳轉那一句了,首先0x08是我們跳轉目标段的段選擇子(這個不陌生吧?),其對應段描述符第2項。後面的跳轉目标标号可能會讓你詫異,因為它就是下一行代碼。這是為何?當然有深意了,第一,intel不允許直接修改段寄存器cs的值,我們隻好這樣通過這種方式更新cs段寄存器;第二,x86以後cpu所增加的指令流水線和高速緩存可能會在新的全局描述符表加載之後依舊保持之前的緩存,那麼修改gdtr之後最安全的做法就是立即清空流水線和更新高速緩存。說的這麼牛逼的樣子,其實隻要一句jmp跳轉就能強迫cpu自動更新了,很簡單吧?

到這裡段描述符表的建立就告一段落了,其實我們完全可以直接計算出這些段具體的表示數值然後寫死進去,但是出于學習的目的,我們還是寫了這些函數進行處理。當然了,我們沒有談及一些具體的描述符細節問題,因為intel文檔的描述都很詳細。

接下來我們來聊分頁吧。我們先給出cpu在保護模式下分頁未開啟和分頁開啟的不同狀态時,mmu元件處理位址的流程。

如果沒有開啟分頁:

邏輯位址->段機制處理->線性位址=實體位址

如果開啟分頁:

邏輯位址->段機制處理->線性位址->頁機制處理->實體位址

因為我們采用了平坦模式,是以給出的通路位址實際上已經是線性位址了(段基址為0),那麼剩下的問題就是所謂的頁機制處理了。

時間關系,頁機制的細節我們下次再說。如果你已經迫不及待想知道了,那就先去谷歌看看吧。我們下期再見~

繼續閱讀