美文网首页
JOS(2) 分页机制的建立

JOS(2) 分页机制的建立

作者: zfzhang | 来源:发表于2017-05-31 23:22 被阅读0次

    一、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的分页内存建立的过程的核心代码就是叙述完成。如果能够绕过这个弯,还是相对容易理解的。

    相关文章

      网友评论

          本文标题:JOS(2) 分页机制的建立

          本文链接:https://www.haomeiwen.com/subject/ordyxxtx.html