MIT6.828 LAB2_Part2_虚拟内存


Part2:Virtual Memory

首先要熟悉x86的保护模式的内存管理体系,也就是分段和页转换。

Exercise 2

如果您还不熟悉分段与分页,请查看“Intel 80386参考手册”的第5章和第6章。 仔细阅读有关页面转换和基于页面的保护的部分(5.2和6.4)。 我们建议您浏览有关细分的部分; 虽然JOS使用分页硬件进行虚拟内存和保护,但在x86上无法禁用分段转换和基于段的保护,因此您需要对它有基本的了解。

Intel 80386 Reference Programmer's ManualTable of Contents

感觉x86的分段式特别复杂,配置起来很繁琐。 分页式相对来说就很清晰明了了,I like it.

虚拟、线性和物理内存

在x86术语中,虚拟地址由段选择器和段内偏移组成。 线性地址是段转换之后但在页转换之前获得的。 物理地址是在段和页面转换之后得到的,以及最终经过总线到达RAM的内容。

exercise 3

第一个窗口执行make qemu-gdb, 第二个执行make gdb. 然后第二个窗口执行cctrl+c终止程序。键入查看内存指令x/16xw 0xf0100000:

(gdb) x/16xw 0xf0100000
0xf0100000 <_start+4026531828>:    0x1badb002    0x00000000    0xe4524ffe    0x7205c766
0xf0100010 <entry+4>:    0x34000004    0x2000b812    0x220f0011    0xc0200fd8
0xf0100020 <entry+20>:    0x0100010d    0xc0220f80    0x10002fb8    0xbde0fff0
0xf0100030 <relocated+1>:    0x00000000    0x112000bc    0x0056e8f0    0xfeeb0000

第二终端再按c,保持程序的运行。 第一终端按ctrl+a c 出现qemu终端。

(qemu) xp/16xw 0x100000
0000000000100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0000000000100010: 0x34000004 0x2000b812 0x220f0011 0xc0200fd8
0000000000100020: 0x0100010d 0xc0220f80 0x10002fb8 0xbde0fff0
0000000000100030: 0x00000000 0x112000bc 0x0056e8f0 0xfeeb0000

两个地址上的内容一致,故可以证明0xf0100000虚拟地址映射到了0x100000物理地址上。

一旦进入保护模式,所有的指针都会被解释成虚拟地址并由MMU翻译。

JOS内核通常需要将地址转换为不透明值或整数,而不需要对它们解引用,例如在物理内存分配器中。 有时这些是虚拟地址,有时它们是物理地址。 为了帮助规范代码,JOS源代码区分了两种情况:类型uintptr_t表示不透明的虚拟地址,physaddr_t表示物理地址。 这两种类型实际上只是32位整数(uint32_t)的同义词,因此编译器不会阻止您将一种类型分配给另一种类型! 由于它们是整数类型(不是指针),如果您尝试对它们解引用,编译器就会给出警告或错误。

JOS内核可以通过首先将其转换为指针类型来解引用uintptr_t。 相反,内核不能明智地解引用物理地址,因为MMU会转换所有内存引用。 如果将physaddr_t转换为指针并解引用它,您可以加载并存储到结果地址(硬件会将其解释为虚拟地址),但您可能无法获得预期的内存位置。

Questions

Assuming that the following JOS kernel code is correct, what type should variable x have, uintptr_t or physaddr_t?
mystery_t x;
char* value = return_a_pointer();
*value = 10;
x = (mystery_t) value;

答: 是uintprt_t

第三句对*value进行了赋值,所以value肯定是一个虚拟地址,因为直接解引用物理地址是没有意义的。那么x肯定也是虚拟地址,要不然将虚拟地址赋值给一个物理地址也没有意义的。

引用计数

使用page_alloc时要小心。 它返回的页面的引用计数始终为0,因此只要您对返回的页面执行某些操作(例如将其插入页面表),pp_ref就应该递增。 有时这是由其他函数处理的(例如,page_insert),有时调用page_alloc的函数必须直接执行。

页表管理

现在,您将编写一组用于管理页表的例程:插入和删除线性到物理映射,以及在需要时创建页表页。

首先还复习一下MMU的知识。MMU (一)

Execise 4

In the file kern/pmap.c, you must implement code for the following functions.
  • pgdir_walk()
  • boot_map_region()
  • page_lookup()
  • page_remove()
  • page_insert()

    check_page(), called from mem_init(), tests your page table management routines. You should make sure it reports success before proceeding.

需要复习一下LAB1的PART3部分,其处通过一个手写的页表,实现了4M的虚拟内存向物理内存的映射,理解页目录表,页目录项,页表,页表项的异同。

名词说明
页目录表存放各个页目录项的表,页目录常驻内存,页目录表的物理地址存在寄存器CR3中
页目录项存放各个二级页表起始物理地址
页表存放页表项
页表项页表项的高20位存放各页的对应的物理地址的高20位

错误

  1. kernel panic at kern/pmap.c:841: assertion failed: page_insert(kern_pgdir, pp1, 0x0, PTE_W) < 0
    这里的行号与源代码不太一致,不知道为什么,应该是死在了828行的assert上,通过反汇编找到代码位置,f01016fe,gdb进去。最后发现自己被坑了,在page_insert()函数中,我写得return E_NO_MEM, 而E_NO_MEM是一个大于1的枚举值,也就是如果发生错误,返回值是非负数,所以会一直卡在assert上。

     // Fill this function in
     pte_t *pte = pgdir_walk(pgdir, va, 1);
     
     if (!pte) {
         return -E_NO_MEM;
     }
  2. `kernel panic at kern/pmap.c:837: assertion failed: PTE_ADDR(kern_pgdir[0]) == page2pa(pp0)
    , 出现这个错误,应该是insert不成功导致的,但是始终看不insert哪里错了。最后发现是pdgir_walk()`函数里就出现了错误。
    原来当页表不存在时,创建页表给错了其物理地址部分

    *pde = PTE_ADDR(*pte) | PTE_P | PTE_W | PTE_U;  // 设置页目录项
    应该改为
    *pde = PADDR(pte) | PTE_P | PTE_W | PTE_U;      //设置页目录项

最终代码

pgdir_walk

pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
    // Fill this function in
    uint32_t pdx = PDX(va);   // 页目录项索引
    uint32_t ptx = PTX(va);   // 页表项索引
    pte_t   *pde;             // 页目录项指针
    pte_t   *pte;             // 页表项指针
    struct PageInfo *pp;
    
    pde = &pgdir[pdx];        //获取页目录项
    
    if (*pde & PTE_P) {
        // 二级页表有效
        // PTE_ADDR得到物理地址,KADDR转为虚拟地址
        pte = (KADDR(PTE_ADDR(*pde)));
    }
    else {

        // 二级页表不存在,
        if (!create) {
            return NULL;
        }
        // 获取一页的内存,创建一个新的页表,来存放页表项
        if(!(pp = page_alloc(ALLOC_ZERO))) {  
            return NULL;
        }
        pte = (pte_t *)page2kva(pp);
        pp->pp_ref++; 
        *pde = PADDR(pte) | (PTE_P | PTE_W | PTE_U);  // 设置页目录项
    }
     // 返回页表项的虚拟地址
    return &pte[ptx];                              
}

1.5.2. boot_map_region

static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
    size_t pgs = size / PGSIZE;    
    if (size % PGSIZE != 0) {
        pgs++;
    }                            //计算总共有多少页
    for (int i = 0; i < pgs; i++) {
        pte_t *pte = pgdir_walk(pgdir, (void *)va, 1);//获取va对应的PTE的地址
        if (pte == NULL) {
            panic("boot_map_region(): out of memory\n");
        }
        *pte = pa | PTE_P | perm; //修改va对应的PTE的值
        pa += PGSIZE;             //更新pa和va,进行下一轮循环
        va += PGSIZE;
    }
}

1.5.3. page_insert

int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{
    // Fill this function in
    pte_t *pte = pgdir_walk(pgdir, va, 1);
    
    if (!pte) {

        return -E_NO_MEM;
    }
    
    if (*pte & PTE_P) {
        if (PTE_ADDR(*pte) == page2pa(pp)) {

            // 插入的是同一个页面,只需要修改权限等即可
            pp->pp_ref--;
        }
        else {

            page_remove(pgdir, va);
        }
        
    }
    
    pp->pp_ref++;
    *pte = page2pa(pp)| perm | PTE_P;
    
    return 0;
}

1.5.4. page_lookup

struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
    // Fill this function in
    pte_t *pte = pgdir_walk(pgdir, va, 0);
    if (!pte) {

        return NULL;
    }
    if (pte_store) {
        *pte_store = pte;  // 通过指针的指针返回指针给调用者
    }
    
    // 难道不用考虑页表项是否存在
    
    if (*pte & PTE_P) {

        return (pa2page(PTE_ADDR(*pte)));
    }
    
    return NULL;
    
    //return pa2page(PTE_ADDR(*pte));
}

1.5.5. page_remove

void
page_remove(pde_t *pgdir, void *va)
{
    // Fill this function in
    // 二级指针有点晕
    pte_t *pte;
    pte_t **pte_store = &pte;
    
    struct PageInfo *pi = page_lookup(pgdir, va, pte_store);
    if (!pi) {
        return ;
    }
    
    page_decref(pi);     // 减引用
    
    **pte_store = 0;     // 取消映射
    tlb_invalidate(pgdir, va);
}

声明:一丁点儿|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - MIT6.828 LAB2_Part2_虚拟内存


勿在浮沙筑高台,每天进步一丁点儿!