PA3.2

十年热恋 提交于 2020-01-11 01:50:04

写在前面的话

如果您对该系列感兴趣的话,推荐您先看一下南京大学的计算机组成原理实验(也就是PA)的讲义,然后再来看这篇文章可能有更多地收获。如果您是要完成该作业的学生,我推荐你先看讲义,或者好好听老师的讲课,然后自己独立完成这个作业,但是如果你没有听懂,或者你无论如何也无法理解讲义上面的字,又或者说对讲义上面的某点知识某个问题不了解而又觉得太简单不好意思问老师,那么您可能会从这篇文章里面获得一些你需要的信息。本篇文章将会包括笔者自己做PA的所有经过,希望你并不将该文章当成抄袭的根源,而是成为你思考的源泉。
现在已经到了PA3的最后阶段,经过了PA0的搭建环境、PA1简单的功能函数实现、PA2的机器指令的模拟实现,现在我们已经基本上拥有进行更高级操作的环境,所以这个阶段更关注的是系统层面的实现。
我们已经实现了最困难的部分,也就是PA3.1运行仙剑奇侠传的部分,现在要做的是在分页机制上面运行仙剑奇侠传,让我们的NEMU系统同时支持多个程序运行

PA系列传送门

PA0:https://blog.csdn.net/qq_41983842/article/details/88921427
PA1.1:https://blog.csdn.net/qq_41983842/article/details/88934779
PA1.2:https://blog.csdn.net/qq_41983842/article/details/89714479
PA1.3:https://blog.csdn.net/qq_41983842/article/details/89714689
PA2.1:https://blog.csdn.net/qq_41983842/article/details/95232055
PA2.2&2.3:https://blog.csdn.net/qq_41983842/article/details/101164495
PA3.1:https://blog.csdn.net/qq_41983842/article/details/103094859
PA3.2:https://blog.csdn.net/qq_41983842/article/details/103843093
PA4:待补充

目录

思考题

  1. i386 不是一个 32 位的处理器吗, 为什么表项中的基地址信息只有 20 位, 而不是 32 位?

    因为还有低12位的页内偏移量。加载一起是32位。

  2. 手册上提到表项(包括 CR3)中的基地址都是物理地址, 物理地址是必须的吗? 能否使用虚拟地址?

    物理地址是必须的,就是靠着CR3这个寄存器的来找到页目录的基地址,如果它里面放的是虚拟地址,那么怎么找到物理地址呢?只会不断把虚拟地址转化成虚拟地址,然后就死循环了。

  3. 为什么不采用一级页表? 或者说采用一级页表会有什么缺点?

    一级页表会让页表变得很庞大,页表本来就是用来快速找到我们要访问的单元的,这回找页表都这么麻烦,所以采用一级页表是不可行的,应设置多级页表。

  4. 当程序对空指针解引用的时候, 计算机内部具体都做了些什么?

    我觉得可能空指针指向的是一个不能找到物理地址的虚拟地址,并不是里面什么都没有。所以指针解引用的时候进行虚拟地址到物理地址的转换,如果不能转换到物理地址就是空指针。或者说空指针指向一个用户没有权限访问的地方。

  5. 描述 _map() 所做的事情

    首先取页目录基地址,然后根据传入的虚拟地址和基地址得到页目录项,判断是否需要新的页表,如果需要,就用palloc_f函数申请一个新的页表,并且把这个页表的地址和其他的标志位相结合存到一个页目录项里面,并且根据传入的虚拟地址就得到了一个页表项,最后把物理地址跟这些标志位放到一个页表项里面,然后写入。

  6. 注释拷贝内核映射的代码,解释为什么会发生这个错误
    在这里插入图片描述
    pdepresent位为0。页错误,原因就是因为没有把内核映射拷贝过来,所以没有对应的页表来存放内核区的虚拟地址,所以就会出现这种缺页情况。

实验内容

在NEMU中实现分页机制

打开PTE
在这里插入图片描述
在寄存器结构体里面添加CR0CR3寄存器。其实已经在mmu.h定义好了,只要添加就行了。但是要注意要添加头文件使其包含mmu.h
在这里插入图片描述
对 CR0 寄存器初始化为 0x60000011
在这里插入图片描述
之后就是修改vaddr_readvaddr_write照着讲义上面的框架进行修改

uint32_t vaddr_read(vaddr_t addr, int len) {
    if(cpu.cr0.paging) {
        if (data cross the page boundary) {
            /* this is a special case, you can handle it later. */
            assert(0);
        }
        else {
            paddr_t paddr = page_translate(addr);
            return paddr_read(paddr, len);
        }
    }
    else
        return paddr_read(paddr, len);
}

要注意的一点就是判断数据跨越页的边界的情况。我们知道页的大小是4KB,也就是2120x1000这么大,传进来的addr可能在任何位置,所以要根据他的相对位置与len相加,看看是不是比0x1000大。所以要找到addr所在页的起始位置,因为页开始的位置都是整除4KB的,所以只需要关心addr的后三位加上len是不是比0x1000大,如果比他大,那么就是超越了页的边界。
在这里插入图片描述
在这里插入图片描述
接下来就要写 page_translate() 函数,实现虚拟地址转换成物理地址的功能,讲义上面把要实现的功能说的很清楚:

  • 根据 CR3 寄存器的高二十位也就是page_directory_base得到页目录表基址;
  • 用这个基址和从虚拟地址中隐含的页目录字段项也就是高十位结合计算出所需页目录项地址,此时vaddr要左移两位,因为二级页表的地址是4字节,相当于数组下表;
  • 从内存中读出这个页目录项,并对有效位进行检验;
  • 将取出的 PDE 和虚拟地址的页表字段相组合,得到所需页表项地址(是个物理地址);
  • 从内存中读出这个页表项,并对有效位进行检验;
  • 检验 PDEaccessed 位,如果为 0 则需变为 1,并写回到页目录项所在地址;
  • 检验 PTEaccessed 位如果为 0,或者 PTE 的脏位为 0 且现在正在做写内存操作,而判断写内存操作可以通过在vaddr_write中给page_translate传入一个参数来表明是写内存操作。所以满足这两个条件之一时需要将 accessed 位置为1,然后更新 dirty 位,最后并写回到页表项所在地址;
  • 页级地址转换结束,返回转换结果.

在这里插入图片描述
之后运行了一下,跳出来了指令没有实现,查手册得知这是mov指令0x200x22处的cr2r的操作没有实现,老规矩填表
在这里插入图片描述
在这里插入图片描述
之后在system.c里面完成这两个指令的填写,一个是把数据从其他寄存器搬到cr寄存器,一个是从cr寄存器搬到其他寄存器,注意id_dest->reg的值,0的时候是对cr0寄存器操作,3的时候是对cr3寄存器的操作。如果两个都不是,就说明出错了,直接assert
在这里插入图片描述
跑出来大概是这个情况,因为这个时候还没有改变loader,所以vaddr和物理地址是一样的。
在这里插入图片描述

让用户程序运行在分页机制上

首先将 navy-apps/Makefile.compile 中的链接地址 -Ttext 参数改为 0x8048000 并重新编译,nanos-lite/src/loader.c 中的 DEFAULT_ENTRY 也要作相应的修改。让 Nanos-lite 通过 load_prog() 函数(在 nanos-lite/src/proc.c 中定义)来进行用户程序的加载
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
之后就要把之前的loader中写的东西改一下。

  • 打开待装入的文件后,获取文件大小;
  • 需要循环判断是否已创建足够的页来装入程序;
  • 对于程序需要的每一页,做后面三步:
  • 使用 Nanos-liteMM 提供的 new_page() 函数获取一个空闲物理页
  • 使用映射函数 _map() 将本虚拟空间内当前正在处理的这个页和上一步申请到的空闲物理页建立映射
  • 读一页内容,写到这个物理页上
  • 每一页都处理完毕后,关闭文件,并返回程序入口点地址(虚拟地址)

在这里插入图片描述
运行之后发现触发了vaddr_readassert(0),发现是数据发生了分页。现在实现它。这时候实现的就复杂一些,因为数据在两个不同的页上面,所以要对两个页面分别进行page_translate,之后再把他们的数据合在一起。而writeread不同的地方就在于write要把传入的data分成高位和低位两部分分别写入两个页面中。
在这里插入图片描述
在这里插入图片描述
成功运行dummy
在这里插入图片描述

在分页机制上运行仙剑奇侠传

这部分框架已经实现好了,所以直接跑就行了,但是我一跑出来发现出现game start字样以后就会出发pte.present = 0的这个assert,经过多方排查,发现是3.1里面的syscall.c函数中的sys_brk情况没有进行更新,在3.1中不运行在分页机制上,所以直接调用sys_brk让他等于0就行了,但是这会我们有了mm_brk,在虚拟空间上面运行需要把虚拟空间映射到上面,这个函数框架已经写好了,在sys_brk函数里面直接调用就行了。
在这里插入图片描述
成功运行,但是运行比不在分页机制上面卡多了。
在这里插入图片描述
到此为止,我们的PA3部分已经全部结束了,仙剑奇侠传已经成功运行在分页机制上,我们PA4再见!

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!