MIT6.828 LAB2_Part3_内核地址空间


初始化内核地址空间

Exercise 5

Fill in the missing code in mem_init() after the call to check_page().
Your code should now pass the check_kern_pgdir() and check_page_installed_pgdir() checks.

错误

  1. kernel panic at kern/pmap.c:787: assertion failed: check_va2pa(pgdir, KERNBASE + i) == i

    实际卡在以下语句下,

    // check phys mem
     for (i = 0; i < npages * PGSIZE; i += PGSIZE)
         assert(check_va2pa(pgdir, KERNBASE + i) == i);

    发现其以npages * PGSIZE为检查边界,修改KERNBASE以上内存的映射代码为

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

    又出现了以下错误:

  2. kernel panic at kern/pmap.c:804: assertion failed: pgdir[i] & PTE_P.

调试发现,页目录表中的页目录项,960-991索引项都通过assert,错误发生在992项处,即992项无效。(991-960+1)*2^10 = 2^15 = 32768. 刚好等于npages的值。
感觉是npages0xFFFFFFFF-KERNBASE不一致导致的。打印npages =32768, (0xFFFFFFFF-KERNBASE)/PGSIZE 向上取整= 65536页。很明显用npages映射的内存远远没有后一种方法多,所以出现第二种错误很正常,但是第一种错误为什么会出现呢?

继续改为出现第一种错误的写法,通过调试发现,在 i=0时就出错了,证明KERNBASE根本就没有映射到物理地址0处。

根本没有道理啊,第二种能映射成功,证明是没有问题的呀。而且之前的两次boot_map_region都没有问题, 应该是第三次的size= 268435456过大导致boot_map_region()出现了Bug。

最初写法,发现用va+size会出现溢出的情况,比如当前情况,size=268435456=0x10000000, va=0xF0000000, 两者相加直接等于0了。 所以当我们用第二种npage*PGSIZE时能成功初始化一部分映射。
并且,当我们直接给size传入0xFFFFFFFF-KERNBASE即等于0xFFF_FFFF,其与va相加为0xFFFF_FFFF, 就会出现死循环。因为pva从0xF000_0000开始增长,最大只能为0xFFFF_F000,永远不会有大于等于va+size的时候。

static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
    uintptr_t pva = va;
    physaddr_t ppa = pa;
    pte_t *pte;
 
    for (; pva < va+size; pva+=PGSIZE, ppa+=PGSIZE) //循环映射
    {
        pte = pgdir_walk(pgdir, (void *)pva, 1);    //查找页表项
        if (!pte)
        {
            return;
        }
        *pte = PTE_ADDR(ppa) | (perm | PTE_P);        //设置页表项
    }
}

修改后:

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. 要求把pages结构体所在的页面和虚拟地址UPAGES相互映射。这里只要计算出pages结构体的大小,就可以进行映射了。说实话,之前注释有点没看懂。以为要实现虚存对pages指向的物理页的映射。

    boot_map_region(kern_pgdir, 
                     UPAGES, 
                     ROUNDUP((sizeof(struct PageInfo)*npages), PGSIZE),
                     PADDR(pages),
                     PTE_U );
  2. 映射内核栈KSTACKTOP

    boot_map_region(kern_pgdir, 
                     KSTACKTOP-KSTKSIZE, 
                     KSTKSIZE,
                     PADDR(bootstack),
                     PTE_W );
  3. 映射内核基址KSTACKTOP

    uint32_t kern_size = ROUNDUP((0xFFFFFFFF-KERNBASE), PGSIZE);
    cprintf("size: %d   pages:%d\n", kern_size, kern_size/PGSIZE);
    boot_map_region(kern_pgdir, 
                 (uintptr_t) KSTACKTOP, 
                 kern_size,
                 (physaddr_t)0, 
                 PTE_W );

1.1.4. Questions

  1. 我们已将内核和用户环境放在同一地址空间中。 为什么用户程序无法读取或写入内核的内存? 哪些特定机制保护内核内存?

A:页表项中有读写保护位,以及PTE_U区分内核和用户,MMU应该会实现这种保护。

  1. 此操作系统可以支持的最大物理内存量是多少? 为什么?

A:4GB,因为32位地址线,可以寻址4GB大小。

  1. 如果我们实际拥有最大的物理内存量,那么管理内存的空间开销是多少? 这个开销是如何分解的?

A:4GB/PASIZE = 2^32/2^12 = 2^20页, 每个页表项4B,

  • 页表占用内存2^20x4B = 4M,
  • page directory = 4K,
  • Pages结构体2^20*(4+2) = 6M

地址空间布局替代方案

我们在JOS中使用的地址空间布局不是唯一的。操作系统可以将内核映射到低线性地址,同时将线性地址空间的上部留给用户进程。但是x86内核一般不采用这种方法,因为x86的向后兼容模式之一,称为虚拟8086模式,在处理器中“硬连线”使用线性地址空间的底部。因此如果内核映射到那里,就无法可以使用。

Challenge感觉都好难啊,占坑!!

总结

整个lab2都在填充mem_init()这个函数,也就是建立一个二级页表,可分为以下几个步骤。

  1. 内存检查

读取基本内存大小、扩展内存大小,并根据PGSIZE求出有多少个page,同时打印可用内存大小。

  1. 创建页目录表

kernel.ld链接文件中,声明了end在bss段的未使用位置的最前端,实现了boot_alloc()在bss段未使用的最前端进行页目录、页表存储空间的申请。

.bss : {
        PROVIDE(edata = .);
        *(.bss)
        PROVIDE(end = .);
        BYTE(0)
    }

第一次进入boot_alloc函数会执行以下代码。

    if (!nextfree) {
        extern char end[];
        nextfree = ROUNDUP((char *) end, PGSIZE);
    }
  1. 创建物理页表记录的数据结构,并对其初始化

记录各个页面的引用计数,将物理内存的分页用链表串接起来,同时记录目前空闲可用的页。

  1. 建立虚拟内存与物理内存映射关系,即初始化各个页表项

首先要实现通过页目录查到对应的页表,判断页表是否存在,是否需要申请空间存储新的页表。以及页表项的初始化,页的插入,页的查询等等操作。

页表的地址空间也是使用boot_alloc()函数分配,即都是紧跟在bss段后面。 分别映射UPAGESKSTACKTOPKERNBASE三个区域。

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

转载:转载请注明原文链接 - MIT6.828 LAB2_Part3_内核地址空间


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