LAB2 內核的內存管理

Physical Page Management

JOS內核以頁爲最小粒度管理內存,從而實時記錄哪些內存區域空閒,哪些內存區域佔用,這個信息被記錄在一條結構體PageInfo的鏈表中,鏈表的每個結點對應一個物理頁。

內核開始,會調用mem_init()函數對整個操作系統的內存管理系統進行一些初始化的設置。

mem_init()

首先調用i386_detect_memory()子函數, 檢測現在系統中有多少可用的內存空間。

然後調用boot_alloc()函數得到頁目錄表,kern_pgdir指針指向這個表。頁目錄表佔用一個物理頁的內存。

kern_pgdir = (pde_t *)boot_alloc(PGSIZE)
memset(kern_pgdir, 0, PGSIZE)

boot_alloc()是暫時的頁分配器,後面都用page_alloc()做頁分配器。頁分配器維護一個靜態變量nextfree,指向下一個可使用的空閒內存空間的虛擬地址。它的核心代碼如下:

    result = nextfree;
    nextfree = ROUNDUP(nextfree+n, PGSIZE);
    if((uint32_t)nextfree - KERNBASE > (npages*PGSIZE))
        panic("Out of memory!\n");
    return result;

緊接着, 爲頁目錄表添加第一個表項,也就是頁目錄表所在頁的虛擬地址和物理地址的映射。

kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;

宏定義UVPT是一段虛擬地址的起始地址,PADDR(kern_pgdir)計算了kern_pgdir的真實物理地址。

下一步, 分配一塊內存給PageInfo結構體數組。每個PageInfo結構體存儲着一個物理內存頁的信息。內核通過該數組實時記錄所有內存頁使用情況。

pages = (struct PageInfo *)boot_alloc(npages * sizeof(struct PageInfo));
memset(pages, 0, npages * sizeof(struct PageInfo));

下一步, 調用page_init()函數, 初始化pages數組,初始化pages_free_list鏈表。page_init()核心代碼如下:

size_t i;
page_free_list = NULL;
int num_alloc = ((uint32_t)boot_alloc(0) - KERNBASE) / PGSIZE;
int num_iohole = 96;

for (int i = 0; i < npages; i++)
{
    if (i == 0)
    {
        pages[i].pp_ref = 1;
    }else if (i >= npages_basemem && i < npages_basemem + num_iohole + num_alloc)
    {
        page[i].pp_ref = 1;
    }else {
        pages[i].pp_ref = 0;
        pages[i].pp_link = page_free_list;
        page_free_list = &pages[i];
    }
}

下一步, 調用check_page_free_list(1)子函數,檢查page_free_list()鏈表的所謂空閒頁是否都合法都空閒。
調用check_page_alloc()檢查page_alloc()和page_free()能否正常運行。

page_alloc()

分配一個物理頁,並返回這個物理頁所對應的PgaeInfo結構體。

struct PageInfo *page_alloc(int alloc_flags)
{
    struct PageInfo *res;
    if (page_free_list == NULL)
        return NULL;
    
    res = page_free_list;
    page_free_list = res->pp_link;
    res->pp_link = NULL;

    if (alloc_flags & ALLOC_ZERO)
        memset(page2kva(res), 0, PGSIZE);
    
    return res;
}

page_free()

把所給頁的PageInfo結構體返回給page_free_list空閒頁鏈表。

void page_free(struct PageInfo *pp)
{
    assert(pp->pp_ref == 0);
    assert(pp->pp_link == NULL);

    pp->pp_link = page_free_list;
    page_free_list = pp;
}

Virtual Memory

在x86系CPU中,虛擬地址由segment selector和segment offset組成。虛擬地址通過段地址轉換機構轉換後得到的地址成爲線性地址。線性地址通過分頁地址轉換機構把線性地址進行轉換後得到的真實的RAM地址稱爲物理地址,通過總線將它送到內存芯片上即可尋址。我們平時寫的C程序中指針的值就是虛擬地址中的segment offset。

在JOS內核源代碼中, KADDR(pa)提供了物理地址->虛擬地址的轉換,PADDR(va)提供了虛擬地址->物理地址的轉換。

PageInfo結構體中的整數型變量pp_ref記錄了該物理頁被多少個不同虛擬地址引用。pp_ref值等於該頁被頁表項中位於虛擬地址UTOP之下的虛擬頁所映射的次數。當物理頁的pp_ref爲0時可以被釋放。一般在page_alloc()後給pp_ref加一。

JOS虛擬內存空間概覽:

/*
 * Virtual memory map:                                Permissions
 *                                                    kernel/user
 *
 *    4 Gig -------->  +------------------------------+
 *                     |                              | RW/--
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     :              .               :
 *                     :              .               :
 *                     :              .               :
 *                     |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| RW/--
 *                     |                              | RW/--
 *                     |   Remapped Physical Memory   | RW/--
 *                     |                              | RW/--
 *    KERNBASE, ---->  +------------------------------+ 0xf0000000      --+
 *    KSTACKTOP        |     CPU0's Kernel Stack      | RW/--  KSTKSIZE   |
 *                     | - - - - - - - - - - - - - - -|                   |
 *                     |      Invalid Memory (*)      | --/--  KSTKGAP    |
 *                     +------------------------------+                   |
 *                     |     CPU1's Kernel Stack      | RW/--  KSTKSIZE   |
 *                     | - - - - - - - - - - - - - - -|                 PTSIZE
 *                     |      Invalid Memory (*)      | --/--  KSTKGAP    |
 *                     +------------------------------+                   |
 *                     :              .               :                   |
 *                     :              .               :                   |
 *    MMIOLIM ------>  +------------------------------+ 0xefc00000      --+
 *                     |       Memory-mapped I/O      | RW/--  PTSIZE
 * ULIM, MMIOBASE -->  +------------------------------+ 0xef800000
 *                     |  Cur. Page Table (User R-)   | R-/R-  PTSIZE
 *    UVPT      ---->  +------------------------------+ 0xef400000
 *                     |          RO PAGES            | R-/R-  PTSIZE
 *    UPAGES    ---->  +------------------------------+ 0xef000000
 *                     |           RO ENVS            | R-/R-  PTSIZE
 * UTOP,UENVS ------>  +------------------------------+ 0xeec00000
 * UXSTACKTOP -/       |     User Exception Stack     | RW/RW  PGSIZE
 *                     +------------------------------+ 0xeebff000
 *                     |       Empty Memory (*)       | --/--  PGSIZE
 *    USTACKTOP  --->  +------------------------------+ 0xeebfe000
 *                     |      Normal User Stack       | RW/RW  PGSIZE
 *                     +------------------------------+ 0xeebfd000
 *                     |                              |
 *                     |                              |
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     .                              .
 *                     .                              .
 *                     .                              .
 *                     |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
 *                     |     Program Data & Heap      |
 *    UTEXT -------->  +------------------------------+ 0x00800000
 *    PFTEMP ------->  |       Empty Memory (*)       |        PTSIZE
 *                     |                              |
 *    UTEMP -------->  +------------------------------+ 0x00400000      --+
 *                     |       Empty Memory (*)       |                   |
 *                     | - - - - - - - - - - - - - - -|                   |
 *                     |  User STAB Data (optional)   |                 PTSIZE
 *    USTABDATA ---->  +------------------------------+ 0x00200000        |
 *                     |       Empty Memory (*)       |                   |
 *    0 ------------>  +------------------------------+                 --+
 *
 * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped.
 *     "Empty Memory" is normally unmapped, but user programs may map pages
 *     there if desired.  JOS user programs map pages temporarily at UTEMP.
 */

Kernel Space Address

下面說的地址默認指的是虛擬內存地址。

內核將它管理的虛擬地址空間劃分爲內核空間(高位)和用戶空間(低位)兩個區域,在JOS實驗中,分界線是inc/memlayout.h中的宏定義ULIM。

/*
 * 高位
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     |                              |  \
 *                     |                              |   |
 * User can't R/W      |                              |   |
 *                     |                              |   |
 *                     |                              |   |
 *   UTOP --------->   --------------------------------    >  Kernel Zone
 *                     |                              |   |
 * User only Read      |                              |   |
 *                     |                              |   |
 *                     |                              |  /
 *   ULIM --------->   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     |                              |  \
 *                     |                              |   |
 * User can R/W        |                              |    >  User Zone
 *                     |                              |   |
 *                     |                              |  /
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * 
 * 低位
 */

mem_init()函數中,前面我們已經把頁目錄表建起來了,也把kern_pgdir的頁面映射作爲一個頁目錄表項加到表裏了,接下來還要添加這幾項到表中:

  • PageInfo結構體數組到線性地址UPAGES,大小爲一個PTSIZE
  • 堆棧區域,由bootstack變量標記的物理地址範圍映射給內核的堆棧,內核堆棧的虛擬地址範圍是[KSTACKTOP-PTSIZE,KSTACKTOP]。其中[KSTACKTOP-KSTKSIZE,KSTACKTOP]段加入到表中。
  • 整個操作系統內核,虛擬地址範圍[KERNBASE, 2^32],物理地址[0, 2^32-KERNBASE]。

對應代碼實現爲:

boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U);
boot_map_region(kern_pgdir, KSTACKTOP - KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W)
boot_map_region(kern_pgdir, KERNBASE, 0xffffffff - KERNBASE, 0, PTE_W);

Questions

  1. What entries (rows) in the page directory have been filled in at this point? What addresses do they map and where do they point? In other words, fill out this table as much as possible:
Entry Base Virtual Address Points to (logically)
1023 ? Page table for top 4MB of phys memory
1022 ? ?
. ? ?
4 0x01600000 3C0-3FF kernel
3 0x01200000 3BF bootstack
2 0x00800000 3BC pages數組
1 0x00400000 3BD kern_pgdir
0 0x00000000 [see next question]
  1. We have placed the kernel and user environment in the same address space. Why will user programs not be able to read or write the kernel's memory? What specific mechanisms protect the kernel memory?

user program無權修改內核區內存,否則會破壞內核,操作系統崩潰。

常見操作系統藉助段機制和分頁機制這兩個部件實現對內核地址的保護,分頁機制把頁表項中的Supervisor/User位設置爲0,那麼user program就無法訪問這個內存頁。

  1. What is the maximum amount of physical memory that this operating system can support? Why?

JOS用一段4096KB的內存空間來存放頁目錄表,每個PageInfo結構體大小爲8B,所以一共可以存放512K個PageInfo結構體,對應512個內存頁,每個頁大小4096B,對應着2GB的物理內存。

  1. How much space overhead is there for managing memory, if we actually had the maximum amount of physical memory? How is this overhead broken down?

PageInfo結構體數組佔用4096KB的內存空間,頁目錄表4KB,當前頁表4096KB,總開銷6MB+4KB。

  1. Revisit the page table setup in kern/entry.S and kern/entrypgdir.c. Immediately after we turn on paging, EIP is still a low number (a little over 1MB). At what point do we transition to running at an EIP above KERNBASE? What makes it possible for us to continue executing at a low EIP between when we enable paging and when we begin running at an EIP above KERNBASE? Why is this transition necessary?

kern/entry.S文件中有一個指令jmp *%eax,它會將EIP的值設置爲寄存器eax中的值,這個值大於KERNBASE。

在entry_pgdir頁表中,有把虛擬地址空間[0,4MB)映射到物理地址空間[0,4MB)上,當訪問位於[0,4MB)區間內時,可將其轉換爲物理地址。

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