1. 程式人生 > >IA-32e架構下的核心初始化記憶體管理

IA-32e架構下的核心初始化記憶體管理

初級記憶體管理單元

關於記憶體的分頁

  • 以往的物理頁是按照4KB進行分配和管理的, 而在Linux之後流行的就是2MB大小的物理頁的分配和管理, 整個實體記憶體管理單元也是2MB物理頁管理的

先獲取基本的實體地址空間資訊

  • 在bootloader程式中, 已經呼叫了BIOS的int 15h中斷將實體記憶體地址的結構體放置到了1MB之下的實體地址0x7e00處, 我們需要將其提取出來
  • 每一條物理空間資訊BIOS載入到記憶體時20B, 因此我們要獲取該資料, 也需要定義一個結構體也佔用20B的實體記憶體大小, 獲取0x7e00地址上的資料, 但是在IA-32e模式下, 我們能夠使用的是線性地址, 不能直接訪問實體地址, 訪問實體地址需要通過我們已經在head.S程式中定義好的頁表進行頁的對映, 在head.S中我們只進行了10MB的對映, 這對於我們目前來說已經足夠了, 不過我們還是要知道實體地址0對應的線性地址時多少, 這樣我們才能進行編碼
  • 實體地址空間的大小為32個, 我們一開始先打印出這些資訊, 輸出實體記憶體哪些是可用的(type == 1), 如果多了髒資料(type > 4)證明這個地方的資料經不明資料汙染了, 也就是說有資料將BIOS寫到0x7e00地址上的資料覆蓋了, 那我們就不要在讀取了, 因為已經是錯誤的了, 沒有意義了, 打印出來我們總共可用的記憶體大小

在分配可用物理頁之前先獲取可用物理頁的頁數

  • 在這個部分, 我們需要對複製BIOS的全部實體地址空間資訊到一個memory_management_struct中, 這個結構體就是我們記憶體管理的核心, 它儲存著所有的記憶體頁的資訊, 包括不可用的
  • 接下來我們計算出可用的實體記憶體頁數, 獲取一個可用的實體記憶體段之後, 我們對其起始地址盡心2MB的物理頁的對齊, 返回的就是對齊後的實體地址, 接著計算出這個實體記憶體段的結束地址end, (end - start) >> page_2m_shift 計算出在這個物理段中有幾個物理頁, 也就一個分隔遊戲, start就是對齊之後的實體地址, 在作業系統中一般有兩個可用的實體地址段, 即為A和B, 他們一般不在一起, 第一個就是從我們的實體地址0開始的實體記憶體段, 顯然我們的bootloader和核心程式碼都在這裡, 這裡肯定是可用的實體記憶體, 其實地址就是0, 對齊後的地址也是0, 因為2MB對齊就是將我們所有的記憶體分成一個一個的2MB, 但是另外一個可用的實體地址段它的start地址就不一定就是在一個矩形的2MB的起始位置了, 也就是說地址不對齊了, 這個時候我們就需要進行實體地址的對齊, 一般來說經過運算後, 我們原來的實體地址start的實體地址會增加一點到一個2MB的起始地址, 雖然這樣會浪費一小段可用的實體地址空間, 但是我們完成了2MB的分隔, 更加方便我們的管理

分配可用實體記憶體頁

需要用到的邏輯上的結構體
  • struct page --> 代表的就是我們說的2MB的物理頁, 但是它本身不是2MB, 而是他管理的實體記憶體時2MB的, 這裡所謂的管理就是通過屬性儲存地址, phyaddr就是管理的物理頁的物理起始地址
  • struct zone --> 區域空間結構體, 它與page聯絡緊密, 它標誌一個可用實體地址段, 就是我們在上面講到了兩個可用的實體地址段, 他代表著一個段, 怎麼代表的呢, 也是通過屬性startaddr和endaddr, startaddr儲存著一個可用實體記憶體段的起始地址, endaddr儲存著一個可用實體記憶體段的結束地址, 我們已經知道另一個一個物理段是很大的, 有多個2MB的物理頁, 因此這裡的endaddr - startadd的值也大於2MB, 所以會有多個page結構體引用著一個zone, 用來在邏輯層面上模擬分配一個頁
  • 上面已經提到過的memory_management_struct(在後面簡稱為mm), 他包含了從BIOS獲取的實體地址資訊, bitsmap(就是一個整數, 和ext3檔案系統一樣一樣的)用來方便索引空閒的物理頁page, 一個page結構體陣列(在邏輯上屬於zone), 一個zone結構體陣列, 核心程式碼結束位置, 自己的結束位置,注意page和zone結構體分配在了核心程式碼之後, zone在page之後
開始分配可用實體記憶體頁(類似與Python中的__new__()魔法方法的功能, 但是與Java中的new關鍵字的功能不同, 這裡只是為page和zone結構體分配了記憶體, 這裡到處了實質: 可用物理頁的分配就是在為結構體建立記憶體空間, 但是不進行初始化)
初始化bitmap
  • mm.bits_size屬性賦值為我們之前計算出來的可用實體記憶體頁的個數, 這樣才能確定bitmap的大小
  • 將bitmap的值置為0, 雖然在這個時候我們的核心程式碼已經在記憶體中了, 我們理應將對應的bitmap中的一個位置位, 但是我們現在的記憶體管理的資料結構體還不完善, 所以我們將這個往後推
初始化page struct結構體陣列
  • mm.pages_size等都記錄下來, page結構體採用的4K物理頁的對齊方式, 反正這些元資料結構體會比較特殊
  • 分配記憶體空間, 將所有的值初始化為0
初始化zone 結構體陣列
  • 分配記憶體空間, 將所有的值初始化為0
第二次初始化(此時資料結構體的記憶體大致已經分配好了, 就是屬性需要進行賦值, 類似於Python中的__init__()方法)
  • 先為zone結構體進行屬性的初始化, 從mm中的bios的實體地址空間資訊中讀取 type == 1的地址, 對齊, 賦值該zone的start, end和上面的一樣計算, 這樣一個物理段就初始化完畢了, 此時在這個物理段zone的基礎上初始上page的屬性(但是這裡的zone和page, 並不是所有的屬性都被初始化了, 有一些需要在函式page_init中進行初始化), 我們知道page都是屬於zone的, 通過迴圈將zone所代表的物理段分成多個2MB大小的物理頁, 當然是使用page結構體的phyaddr和length來表示了, 接著這樣page指向該zone, 表示page的所屬是誰
現在我們也為page和zone的屬性都賦值了, 現在我們就要通過一個page_init函式來初始化核心程式碼所在的記憶體, 還記得上面提到了在初始化bitmap的時候, 我說過的要將這個事情往後推嗎!
  • 在初始化記憶體的時候, page的屬性refcount++, page指向的zone的freepages--, pageusing++, 並且置位bitmap表示已用

  • 記憶體管理需要的東西完成了, 下面就是通過一個函式介面來通過訪問這裡的記憶體管理機制分配到記憶體了

通過alloc_pages函式返回一個struct page陣列核心層和應用層使用

  • 判斷向記憶體中的哪個區域要物理頁
  • 通過bitmap找到指定連續數量的未被使用的位, 通過該位計算得出這個page陣列的首地址, 將連續的page資料返回, 同時標誌bitmap對應的位已用