JOS(2) 分頁機制的建立

一、x86分段機制概述

分段機制可以說是從系統啟動開始就自動執行的內存管理機制。x86提供了6個16位段寄存器:CS、DS、ES、SS、FS和GS。其中CS是代碼段、DS是數據段、SS是堆棧段。在分頁機制開啟前,通過分段機制獲得的線性地址將直接映射/對應到相應的物理內存。實模式下,對應計算物理地址的方式是:
** 物理地址 = 線性地址 = 段寄存器 << 4 + 偏移地址**
這種計算方式對于實模式下還是很便捷的,因為實模式下最高的尋址空間為1MB(0x0010 0000),但在保護模式下,地址空間是32位,無法將32位的地址存入16位的段寄存器,這種情況下,通過使用描述符表來解決分段尋址問題,描述符表中保存了各個段的基地址
描述符表分為全局描述符表和局部描述符表。全局描述符表在全局中唯一,而局部描述符表則可有一個或多個。為了快速方位全局描述符表,x86提供了一個48位的全局描述符表寄存器GDTR,如下圖所示。其中32位線性地址用于保存全局描述符表的入口地址,16位寄存器用于保存全局描述符表的長度。當計算器啟動后,GDTR的32位線性地址部分將被設置為0,而16位表長度部分將會被設置為0xFFFF。在保護模式初始化過程中必須給GDTR加載新值。

GDTR

由于系統提供了描述符表,所以16位段寄存器只需要保存所對應的段在描述符表中偏移的位置就可以很快的查找到該段的基地址,并在基地址的基礎上加上偏移量,就可以獲得線性地址。段寄存器中保存的數據格式如下圖所示。可以看到,段選擇符中只有13位用于記錄索引,故全局描述符表最多只有8192項,但由于第0項只能保存為0,所以在全局描述符表中最多保存8191項。


段選擇符結構

二、x86分頁機制概述

分頁機制是x86內存管理機制的第二部分,用于將線性地址轉換為物理地址。同時在分頁機制下,提供了更多的保護功能。當啟動分頁機制后,處理器會自動完成上述的地址轉換過程,我們需要做的,就是要為處理器分頁機制提供頁表的設置(在第三部分中會詳細的闡述)。這里僅闡述如果通過線性地址找到對應的頁表,更詳細的分頁機制描述可以閱讀CSAPP第九章。


頁表

這里需要注意的是,通過一級、二級頁表可以獲得頁表的基地址。由于系統對物理內存地址空間進行分頁時,要求4K對齊,所以物理內存中的每一頁內存的基地址低12位是無用的,故系統利用低12位保存訪問權限。

三、JOS內存機制建立過程

這一部分將詳細的闡述JOS啟動后內存機制建立的過程。主要涉及到的文件有<inc/mmu.h>、<inc/memlayout.h>、<kern/entry.S>、<kern/pmap.c>、<kern/pmap.h>、<kern/entrypgdir.c>
在上一篇文章中,bootloader在初始化的過程我們已經設置好了全局描述符表,而在這里我們主要闡述的是JOS分頁內存機制的建立。JOS內核在接管控制后,將開啟分頁機制,有如下代碼。

movl    $(RELOC(entry_pgdir)),  %eax  
movl    %eax, %cr3                        # cr3寄存器用于保存一級頁表的基地址
movl    %cr0, %eax
orl  $(CR0_PE|CR0_PG|CR0_WP), %eax     # 開啟分頁機制
movl    %eax, %cr0

在更進一步的敘述之前,我們需要搞清幾個問題。邏輯地址、線性地址和物理地址以及虛擬內存。在分頁機制下,虛擬內存機制會一直伴隨。對于32位系統,每個程序將會獲得4GB的虛擬內存空間,而對于計算機來說,可能并沒有如此大的物理內存來裝填,所以虛擬內存一般是分配在磁盤上,在需要的時候加載到物理內存中。同時,對虛擬內存空間來說,可以劃分為代碼段、數據段、堆棧段等等部分,對于每個段的訪問,都是通過基地址+偏移量來訪問的。基地址是通過段寄存器的值在描述符表中的偏移來獲得,而偏移量就是我們所說的邏輯地址;并且,線性地址 = 基地址+偏移地址(邏輯地址);物理地址則是通過線性地址來尋找,通過頁表的方式來獲取。
JOS的內核地址變換過程如下:JOS內核開始執行后,執行在0x0010 0000處,此后由于開啟分頁機制,虛擬內存機制也隨之一同開啟,此后JOS內核將會在虛擬內存空間中的KERNBASE(0xF000 0000)處執行,更重要的是,此后的JOS代碼執行將全部使用虛擬內存地址
由于分頁機制需要使用頁表,在完整的分頁機制建立之前,JOS使用了一個“人工手寫”的映射關系,將虛擬內存空間中[KERNBASE, KERNBASE+4MB) 、 [0, 4MB)的地址一同映射到物理內存[0, 4MB)處。上述代碼中entry_pgdir就指向了手寫頁表的基地址。
在上述過程完成后,內核將跳轉至mem_init()(/kern/pmap.c)處執行,主要的執行代碼在/kern/pmap.c文件下,該文件主要完成虛擬地址到物理地址之間的轉換,即頁表的建立,為此后的系統內存分配提供了保證。

JOS內存空間分布

上圖所示的是JOS的虛擬內存空間分布,JOS內核空間位于KERNBASE之上。在/kern/pmap.c中存在以下幾個全局變量需要注意:

  • kern_pgdir:內核一級頁表指針/基地址
  • pages : 頁表信息指針/基地址,將物理內存劃分成頁后,每一個頁i對應一個pages[i]
  • page_free_list : 物理頁表空閑鏈表

3.1 頁表初始化

頁表初始化在函數page_init()執行,將已經被占用的物理內存頁對應的pages做標記,未被占用的物理內存頁則加入空閑頁鏈表,該鏈表可以近似的看做一個棧,當申請空閑物理頁時,在該鏈表的尾部取出一個頁信息節點;當釋放一個物理頁后,將此物理頁對應的頁信息節點放入鏈表尾部。該部分的代碼如下。

    // 這種實現方式將鏈表鏈接起來,近似的看作一個棧(stack),每次的分配從棧頂開始
    /*    +--------------+
     *    +    page n    +    <--- page_free_list
     *    +--------------+
     *            |                     |
     *            |                     |
     *            v                     v
     *    +--------------+         increasing
     *    +    page n-1  +              
     *    +--------------+
     *            |
     *            |
     *            v
     *           ...
     *    +--------------+
     *    +    page 0    +
     *    +--------------+
     *            |
     *            |
     *            v
     *          NULL         // if page_free_list == NULL, [Out of memory]
     */
    page_free_list = NULL;

    for(i = 0; i < npages; i++) {
        if(i == 0) {     
            // Page0 : Marked Used
            pages[i].pp_ref  = 1;
            pages[i].pp_link = NULL;
        }
        else if(i >= 1 && i < npages_basemem) {
            // Base memory : Marked Free
            pages[i].pp_ref  = 0;
            pages[i].pp_link = page_free_list;
            page_free_list   = &pages[i];
        }
        else if(i >= IOPHYSMEM / PGSIZE && i < EXTPHYSMEM / PGSIZE) {
            // IO Hole : Marked Used
            pages[i].pp_ref  = 1;
            pages[i].pp_link = NULL;
        } 
        else if(i >= EXTPHYSMEM / PGSIZE && i < ((int)(boot_alloc(0) - KERNBASE)) / PGSIZE) {
            // 此前已經分配的內存設置為已分配, 此部分屬于0-4MB區間內, 屬于初始化過程中已經提前建立的分頁映射
            pages[i].pp_ref  = 1;
            pages[i].pp_link = NULL;
        }
        else {
            // Extend Memory : Marked Free
            pages[i].pp_ref  = 0;
            pages[i].pp_link = page_free_list;
            page_free_list   = &pages[i];
        }
    }

3.2 內存映射

UVPT、UPAGES分別是kern_pgdir、pages的映射,指向了同一物理內存空間,但內核對UVPT與UPAGES設置了權限,用戶(user)只能讀取,不可修改。UVPT、UPAGES的位置在JOS內存空間分布的圖中指明。下面的代碼完成了映射的過程。

// Code 1:
kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;
// Code 2:
int i, perm = PTE_U | PTE_P, nsize = ROUNDUP(npages * sizeof(struct PageInfo), PGSIZE);
for(int i = 0; i < nsize; i += PGSIZE)
    page_insert(kern_pgdir, pa2page(PADDR(pages) + i), (void*)(UPAGES + i), perm);

下圖是UVPT映射過程。可以看到,UVPT對應的二級頁表的基地址實際上指向的是kern_pgdir的物理地址,從而UVPT處存放了一級頁表的只讀副本(內核只讀、用戶只讀)。UPAGES的映射方式與UVPT類似,這里不再贅述。


UVPT映射

至此,JOS的分頁內存建立的過程的核心代碼就是敘述完成。如果能夠繞過這個彎,還是相對容易理解的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1 內存尋址 1.1 物理地址、虛擬地址以及線性地址 物理地址: 物理內存的內存單元地址 虛擬地址: 程序員看到的...
    瘋狂小王子閱讀 2,946評論 3 21
  • Introduction 該 lab 主要需要編寫操作系統的內存管理部分。內存管理分為兩個部分: 內核的物理內存分...
    找不到工作閱讀 12,359評論 0 12
  • 簡介 在 lab4 中我們將實現多個同時運行的用戶進程之間的搶占式多任務處理。在 part A 中,我們需要給 J...
    找不到工作閱讀 6,911評論 0 7
  • 母親,如果你是雨,我便是虹,如果你是花,我便是果,如果你是貝,我便是珍珠,母親,你是我心中的暖陽,你是我生命里最偉...
    英榮擺渡ly閱讀 246評論 2 5
  • 月下欄桿枕上眠,柔波淙水對影憐。 案前新墨無人濡,素箋難載故人言。
    一尾漣漪閱讀 171評論 0 3