實驗2正篇——內存管理

   經過這麼久的準備終於進入正題了。本實驗主要介紹的是我們實現的操作系統中的內存管理。實驗將以兩部分內容進行講解——內核與操作系統我們將互用,請根據實際情況理解:

       第一部分是內核的物理內存分配器,內核能夠分配內存並且能夠釋放之。分配的內存單元爲4k,被稱之爲內存頁。 我們需要實現的任務是維護數據結構用於記錄那些內存被使用(分配或者釋放)在此基礎上實現一個內存分配器虛擬內存,內核與用戶軟件映射虛擬地址到物理地址。

   第二部分是內核映像。

   實驗前準備——更新代碼

   Git更新軟件到實驗2

   1.更新最新代碼:git pull

   2.創建分支2git checkout -b lab2 origin/lab2

   更新的主要文件爲:

   inc/memlayout.h--->包含內存管理的數據結構

   kern/pmap.c--->操作系統內存管理代碼

   kern/pmap.h

   kern/clock.c--->讀取COMS寄存器的代碼——該部分代碼被內存檢測代碼替換

   kern/clock.h

   在操作系統的內存管理實現過程中會反覆用到虛擬地址與物理地址的轉換,所以我們首先詳細介紹看一張完整的概念關係圖:


    如上圖所示虛線有三個區域:物理內存管理區(1),虛擬地址與物理地址對應區(2),地址轉換區(3)。先看1區,物理內存可以分解爲若干連續的4KB爲單元的物理頁——分配與釋放的邏輯單元,爲了追蹤與記錄物理頁的使用,我們定義了物理頁信息來抽象物理頁,定義一個數組與物理內存大小一一對應,而每個元素即物理頁信息與每4K的物理頁一一對應;而2區主要是介紹虛擬地址通過地址轉換爲物理頁(實際不用考慮),然後通過地址中的偏移量(L12),轉換爲物理地址,而且虛擬地址可以根據x86的內存管理機制分爲3部分——高10位,中10位,低12位,同時在程序使用過程中可以將虛擬地址分爲兩種——其一爲程序虛擬地址,其二爲內核虛擬地址,對於內核實現過程中會有一個特殊的映射關係——將內核地址與物理地址建立一一對應的關係;最後3區是描述了地址轉換的詳細過程。

    對於如上的理解,爲了方便管理物理內存,我們需要代碼實現兩個部分——物理內存管理與虛擬地址與物理地址對應。針對這兩個部分,我們將以面向對象的方式來描述他們,所以我們抽象了兩個類,其一爲頁管理類,地址轉換類,它們分別描述了物理內存管理與地址轉換過程。

    一)物理內存管理

    本節我將以面向對象的方式來描述內存管理代碼,它的主要實現在kern/pmap.c中實現。內存的基本管理策略是記錄與追蹤內存的分配與使用。首先,我們應該明白內存的基本管理單元爲頁——大小爲4KB,所以我們分配與釋放的基本大小爲1頁,同時爲了共享已經分配的內存頁,需要對內存頁進行計數。

    針對如上描述我們以頁管理器爲對象,用類的方式描述核心的數據結構如下:

    屬性:

    struct PageInfo {//頁管理器的核心數據結構,對每個物理頁的抽象——頁信息

      struct PageInfo *pp_link;//指向下一個沒有使用的物理頁,由此鏈接成鏈表用於記錄沒有使用的物理頁。

      uint16_t pp_ref;//物理頁的引用計數。

    };

   方法:

voidpage_init(void);//頁管理器的初始化,初始化頁鏈表,所有的頁信息添加到頁空閒列表中

輸入:alloc_flags表示是否將分配的物理頁清0.(alloc_flags=1)

輸出:分配的物理頁信息

struct PageInfo *page_alloc(int alloc_flags);//爲了分配一個物理頁,需要將其對應的頁信息從頁空閒表中移除,然後被程序使用。

輸入:需要被釋放的物理頁信息

voidpage_free(struct PageInfo *pp);//將需要釋放的物理頁信息,歸還給頁空閒表

輸入:需要修改的物理頁信息

voidpage_decref(struct PageInfo *pp);//減少頁信息引用,當引用爲0時,釋放頁

輸入:物理頁信息

輸出:物理地址

 physaddr_t page2pa(struct PageInfo *pp);//將物理頁信息映射爲物理地址

輸入:物理地址

輸出:物理頁信息

struct PageInfo* pa2page(physaddr_t pa);//將物理地址映射爲物理頁信息

輸入:物理頁信息

輸出:內核虛擬地址

void* page2kva(struct PageInfo *pp);//將物理頁信息轉換爲內核虛擬地址

實例化:

     extern struct PageInfo *pages;//頁信息數組指針,用於建立與物理頁一一對應的關係,在mem_init()中初始化,分配npages長度的數組。

     struct PageInfo *page_free_list;//頁空閒列表指針,用於指向執行未使用的物理頁信息。

    二)虛擬內存

    根據x86的內存管理機制——分段與分頁,我們需要對頁目錄與頁表進行操作,爲此,我們也以面向對象的方式來描述之。分頁機制主要操作的是頁目錄與頁表——根據x8632位分頁模式,可知頁目錄其實爲4K大小的4K對齊的數組,每個數組元素爲32位。因爲頁目錄項是指向頁表基地址,所以操作的主要對象爲頁目錄,其中穿插了頁表。

    針對如上描述我們以地址轉換過程爲對象,用類的方式描述核心的數據結構如下:

   屬性:

   typedef uint32_t pde_t;//定義頁目錄項。頁表項可以分爲兩部分——映射的物理地址,對應的訪問權限

   pde_t *kern_pgdir;//以頁目錄項的數組,表示內核初始化的頁目錄

   方法:

輸入:pgdir爲操作的頁目錄

  pp爲映射到物理頁信息

  va爲需要映射的虛擬地址

  perm爲映射的權限

輸出:操作狀態——0爲成功,負數爲失敗的原因

intpage_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm);//實現將給的虛擬地址vaperm的權限映射到物理頁信息pp,映射表是建立在pgdir的頁目錄中。

輸入:pgdir爲操作的頁目錄

      va爲需要釋放的虛擬地址

voidpage_remove(pde_t *pgdir, void *va);//釋放虛擬地址va對應的頁表項。

輸入:pgdir爲操作的頁目錄

  va爲用於查詢的虛擬地址

  pte_store爲查找到的頁表項

輸出:查找到的物理頁信息

struct PageInfo *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store);//通過虛擬地址va查找所在的對應的物理頁信息以及頁表。

輸入:pgdir爲操作的頁目錄

      va爲需要釋放的虛擬地址

voidtlb_invalidate(pde_t *pgdir, void *va);//使虛擬地址va對應的TLB(傳輸後備緩衝器)表無效,當修改了va所對應的頁表項被修改之後,需要做此操作。

輸入:pgdir爲操作的頁目錄

  va爲用於查詢的虛擬地址

  create爲是否創建新的頁表項

輸出:查詢到的頁表項

pte_t *pgdir_walk(pde_t *pgdir, const void *va, int create);//通過虛擬地址va查找所在的頁表項。

三)地址映射關係

     根據如上描述,我們還需要對地址映射的關係做一些總結,方便我們理解與代碼實現。地址與代碼實現表,這樣做可以很好的用於地址區分與代碼理解,在inc/types.h中定義:

C類型

地址類型

T*

虛擬地址

uintptr_t

虛擬地址

physaddr_t

物理地址

    物理地址與內核虛擬地址的對應關係如下代碼所示,在kern/pmap.h中定義,基於如下關係——內核虛擬地址-內核基地址=物理地址:

<span style="font-size:12px;">#define PADDR(kva) _paddr(__FILE__, __LINE__, kva)

static inline physaddr_t
_paddr(const char *file, int line, void *kva)
{
	if ((uint32_t)kva < KERNBASE)
		_panic(file, line, "PADDR called with invalid kva %08lx", kva);
	return (physaddr_t)kva - KERNBASE;
}

/* This macro takes a physical address and returns the corresponding kernel
 * virtual address.  It panics if you pass an invalid physical address. */
#define KADDR(pa) _kaddr(__FILE__, __LINE__, pa)

static inline void*
_kaddr(const char *file, int line, physaddr_t pa)
{
	if (PGNUM(pa) >= npages)
		_panic(file, line, "KADDR called with invalid pa %08lx", pa);
	return (void *)(pa + KERNBASE);//
}</span>

    建立如上關係是在函數mem_init()中實現:

    boot_map_region(kern_pgdir,(uintptr_t)KERNBASE,npages* PGSIZE ,0,PTE_W);

  四)內核映像——地址空間

    內核爲了對內存進行有效的管理,需要對整個地址空間(4G)進行合理規劃,這樣纔能有效的進行數據保護以及程序的進程切換。爲此必需在整個地址空間創建內核映像來描述之,詳細請參考inc/memlayout.h


    將上圖從左往右看——定義宏表示對應的地址,中間部分爲對應的地址區域,右邊部分爲對應地址與權限。從下往上看,我們先將整個地址空間(進程空間)分爲兩部分一部分爲內核空間與用戶空間,內核空間代碼爲操作系統所使用的地址空間,而用戶空間爲用戶進程所使用的地址空間。針對如上空間分佈知道,我們在每個進程運行都要創建如上的地址空間,而對於內核運行時需要創建內核的地址空間。

  (一)內核空間

   如上圖所示,內核空間與地址空間的分割點爲ULIM,對於ULIM只上的地址空間用戶進程是沒有權限訪問的。通過簡單的計算內核空間用了264M的地址——內核直接映射區爲256M4M內核堆棧區(每個處理器單獨堆棧),4Mio映射區。對於該區域,我們只需要詳細介紹內核直接映射區。

    內核直接映射區——爲了簡化系統實現,操作系統使用最高的256M地址空間作爲內核直接訪問的地址區域,該區域爲內核代碼所直接引用的區域——以宏KERNBASE指向的地址爲界。而內核直接將該區映射到了最低256M——[KERNBASE,4G)--->[0,256M).所以對內存的物理地址訪問就只需要將內核虛擬地址減去KERNBASE即可。當然對於高於256M的地址空間,我們需要做二次映射或者其他方式來訪問,目前我們沒有涉及,所以不做多的介紹。如下我們將更詳細的介紹這部分空間的使用這樣方便我們進一步理解操作系統的細節,我們先看看如下圖:

    如上圖爲在函數mem_init()下建立的內核空間:從下往上,最下的低1M爲物理內存的低1M留給8088bios映像空間;在其上的爲內核執行空間——又可以分爲代碼段與數據段,其中堆棧在data區的4K空間(詳細的描述,請參考代碼kernel.ldentry.S);後續爲兩給配置的地址空間段——內核頁目錄,頁信息數組,其他數據空間。

   (二)用戶空間

    如內核映像所示,在UTOP之下的部分爲用戶空間,在此區域又可以詳細分爲3個部分介紹:用戶進程空間,用戶臨時空間,用戶異常堆棧區。這裏介紹這些有些提前了,但是爲了介紹的完整性就只好如此了。

   A)用戶進程空間

   根據應用程序的鏈接腳本user.ld得到運行時的映像,如下:

    如上所示調試信息的stab分區從USTABDATA0x200000)開始的2M空間,第二部分爲用戶程序空間UTEXT(0x800000)之上的空間,第三部分爲用戶堆棧區域——爲USTACKTOP之下的部分。

A)用戶臨時空間——位於UTEMPUTEXT4M空間,被內核使用,作爲臨時拷貝數據用。

B)用戶異常堆棧區——位於UXSTACKTOP之下4K空間,被內核使用,設置爲用戶進程的異常堆棧使用。

  (三)公共訪問空間

    在[UTOP,ULIM)地址區間,內核與用戶進程都只有讀的權限。該區域也分爲3部分——從上到下爲4M的內核分配的頁表區域,4M的頁信息數組的映射區域,4M的用戶程序映射區域。

    一葉說:根據如上分析,可以看出實驗2的部分與操作系統實現的內存管理功能,是使用頁管理類與地址轉換類創建內核映像的過程。從整個內核映像可以看出,內核執行空間爲所有進程所共享,爲用戶進程提供硬件控制的接口。由內核映像可以看出,有3個堆棧空間用於進程運行時使用,它們會在不同的場景中進行切換,保證代碼有序運行。從代碼運行的角度來看,整個進程運行空間分爲兩段——一段爲獨立運行的部分,一段爲內核運行的共享部分。而實現的內存管理的代碼爲內存的訪問提供了最基本的操作,保證內核對內存有效的控制。當然內存在使用過程中會出現碎片——內部碎片與外部碎片,需要其他的複雜的算法來保證。爲了更有效的使用內存空間,還需要進一步的對內存空間進行有效的規劃與使用,這些算法在後續有一些介紹。同樣,我們也需要進一步開發更有效的內存管理機制。本實驗的介紹只是基礎,起了“拋磚引玉”的作用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章