MIT6.828 Lab2

  本文参考了很多网上其他大佬的资料,这里记录仅供自己学习使用,欢迎大家一起交流讨论。参考链接:fatsheep9146,一丁点儿mit6.828总结

前言

  本次lab总共完成了三个实验,实现了如下的功能,在part1中实现了页目录表的创建(用链表将每个页的下一个位置连接起来),页表与物理地址的映射,创建了页目录数组(之后使用都是通过这个,将所有的除IO口,物理页0之外的都初始化放入空闲页表),在part2中完成了页表的管理问题,解决了多个虚拟地址映射到相同的物理地址时的情况,在part3中将分配内核地址空间,将一些地址还有堆栈区域分别映射到内核页表上。
  谈到内存分配,第一反应就是C语言里的malloc(),以及高级语言C++/Java里的new关键字。可我们现在要写的是系统内核,还没有malloc()库函数(在《操作系统内核Hack》系列里曾讲过,开发内核时是不能随便引用标准库的),更没有高级语言里的new。就在迷茫的时刻,才会思考问题的本质。我们说内存分配时到底在说什么?其实对于内核来说,内存分配就是“随意”地返回一个地址给调用方使用,只要你保证这个地址不被其他人使用,那就是一次成功的内存分配了。所以,我们一般说的不管是new也好还是malloc也好,内存分配和释放的消耗其实都是内存管理器复杂管理的代价。
  在实验开始时先用git checkout -b lab2 origin/lab2
  它首先创建一个本地分支 lab2,即 基于课程提供的原点/实验室2分支 其次,它会更改实验室目录的内容以反映存储在 Lab2 分支上的文件。Git 允许使用在现有分支之间切换。git checkout -bgit checkout branch-name

  现在,您需要将您在 lab1 分支中所做的更改合并到 lab2 分支中,如下所示:
git merge lab1
合并完成后会包含这些新的文件
Inc/Memlayout.h
Kern/pmap.c
Kern/pmap.h
Kern/kclock.h
kern/kclock.c
  在整个lab2中我们主要完成了以下的工作,内存检查,设置基本的内存并且可以自助扩展;创建了页目录表实现了页目录、页表存储空间的申请并初始化;建立物理内存和虚拟内存的映射关系。

第一部分是物理地址空间

  第一部分是物理内存页管理主要完成boot_alloc()(暂时当做一个页的分配器,维护一个nextfree链表存放下一个空闲虚拟地址)mem_init()中的部分函数(分配一块内存用来存放页的数组),page_init()(初始化页数组,初始化空闲链表),page_alloc()(分配一个物理页)page_free()(释放一个页,存入空闲链表)
  memlayout.h描述了虚拟地址空间的结构,我们需要通过修改pmap.c文件来实现这个结构。memlayout.h和pmap.h文件定义了一个PageInfo结构,利用这个结构可以记录有哪些物理页是空闲的。kclock.c和kclock.h文件中操作的是用电池充电的时钟,以及CMOS RAM设备。在这个设备中记录着PC机拥有的物理内存的数量。在pmap.c中的代码必须读取这个设备中的信息才能弄清楚到底有多少内存。

  在exercise1中需要完成kern/pmap.c中的部分函数该文件中最重要的函数就是mem_init(),首先先是调用i386_detect_memory子函数,检测系统中有多少可用的内存空间。这里有几个比较重要的参数,npages记录整个内存的页数,pages _ basement 记录basement的页数,npages_extmem记录extmem的页数,执行完成后执行kern_pgdir = (pde_t *) 和boot_alloc(PGSIZE); memset(kern_pgdir, 0, PGSIZE);这一步主要是创建最初始的页表目录。
其中kern_pgdir是一个指针,pde_t *kern_pgdir,它是指向操作系统的页目录表的指针,操作系统之后工作在虚拟内存模式下时,就需要这个页目录表进行地址转换。我们为这个页目录表分配的内存大小空间为PGSIZE,即一个页的大小。并且首先把这部分内存清0。

  这里调用了boot_alloc函数,这个函数使我们要首先实现的函数:

  这个函数就像在注释中说的那样,它只是被用来暂时当做页分配器,之后我们使用的真实页分配器是page_alloc()函数。而这个函数的核心思想就是维护一个静态变量nextfree,里面存放着下一个可以使用的空闲内存空间的虚拟地址,所以每次当我们想要分配n个字节的内存时,我们都需要修改这个变量的值。函数使得分配一个页的内存,这个页的内存紧跟着操作系统内核,'end’是链接器自动生成的魔术符号,它指向内核的bss段的末尾:链接器未分配给任何内核代码或全局变量的第一个虚拟地址。

  下一跳指令kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;这个指令也是将UVPT处存放一个页表kern_pgdir,并且通过PADDR将页表的真实物理地址映射起来。
  之后需要我们补充代码是为了分配一块内存,用来存放一个struct pageinfo数组,数组中的每一个pageinfo代表内存中的一页,操作系统通过这个数组追踪所有内存页的使用情况。
接着完成page_init()函数,主要功能包括初始化pages数组,初始化pages_free_list链表,存放着所有的空闲页的信息。
  之后进入check_page_free_list(1)子函数为了检查page_free_list链表的空闲页是否合法,接着再运行check_page_alloc()这是为了检查page_alloc()(分配一个物理页,从物理页所对应的pageinfo结构体中取出一个页),page_free()(将一个页的Pageinfo结构体返回给page_free_list空闲页链表)两个子函数是否能正确运行。

第二部分是虚拟内存

  这里主要完成了一些函数pgdir_walk()(返回给定页目录表指针中指向线性va页表项的指针,如果不存在可以分配一个页表)boot_map_region()(将虚拟地址中的一段空间映射到物理空间,这主要用于静态映射)page_insert()(负责建立物理地址pp与虚拟地址va的映射关系),page_lookup()(返回映射到虚拟地址va的物理页的指针)page_remove()(把虚拟地址va和物理地址的映射关系删除)
  在x86体系中,一个虚拟地址(Virtual Address)是由两部分组成,一个是段选择子(segment selector),另一个是段内偏移(segment offset)。一个线性地址(Linear Address)指的是通过段地址转换机构把虚拟地址进行转换之后得到的地址。一个物理地址(Physical Addresses)是分页地址转换机构把线性地址进行转换之后得到的真实的内存地址,这个地址将会最终送到你的内存芯片的地址总线上。根据上一个lab的知识可以知道一旦进入保护模式,我们就不能直接使用线性地址或者物理地址了。所有代码中的地址引用都是虚拟地址的形式,然后被MMU(内存管理单元)系统所转换,所以C语言中的指针其实都是虚拟地址。

  所以在我们操作的时候需要注意JOS内核有时需要读取或者修改内存,但是这时有可能他只知道这个要被修改的内存的物理地址。举个例子,当我们想要加入一个新的页表项时,我们需要分配一块物理内存来存放页目录项,然后初始化这块内存。然而,内核它是不能绕过虚拟地址转换这一步的,因而它也不能直接加载或者存储物理地址。那么我们如何把物理地址转换为虚拟地址,我们可以采用KADDR(pa)指令来获取。其中pa指的是物理地址。
  同样的,如果想通过虚拟地址的值求得物理地址的值,我们可以采用PADDR(va)指令。

  多个不同的虚拟地址被同时映射到相同的物理页上面。这时我们需要记录一下每一个物理页上存在着多少不同的虚拟地址来引用它,这个值存放在这个物理页的PageInfo结构体的pp_ref成员变量中。当这个值变为0时,这个物理页才可以被释放。
  之后编写的是管理页表的程序包括插入和删除线性地址到物理地址的映射关系,以及创建页表等操作。

  先完成pgdir_walk函数,这个函数的主要功能是根据给定的页目录表指针,返回va所对应的页表项指针。
在这里插入图片描述
  这里通过页目录表索引页目录项,查找页目录项中的二级页表是否存在,不存在就看create标志位是否为true如果为true就创建一个新的页表,不为true就返回NULL,最后返回页表项的虚拟地址。

  接着需要完成boot_map_region函数,主要负责把把虚拟地址空间范围[va, va+size)映射到物理空间[pa, pa+size)的映射关系加入到页表pgdir中。这个函数主要的目的是为了设置虚拟地址UTOP之上的地址范围,这一部分的地址映射是静态的,在操作系统的运行过程中不会改变,所以这个页的PageInfo结构体中的pp_ref域的值不会发生改变。
  实现思路就是根据size的大小创建对应大小的页表。
  最后再完成page_insert()主要功能是把物理内存页pp与虚拟地址va建立映射关系。思路是通过pgdir_walk查看va对应的页表项,如果va已经被映射则删除映射,再将va和pp之间的映射关系加入到页表项中。
  之后完成page_lookup函数(返回映射到虚拟地址“va”的页面。如果pte_store不为零,则将该页的pte地址存储在其中,可用于验证系统调用参数的页面权限,但不应被大多数调用者使用。),功能是返回虚拟地址va映射的物理页pageinfo结构体的指针,如果pte_store参数不为0则把物理页的页表项地址存放在pte_store中。
  最后完成page_remove函数,这个功能是删除虚拟地址va和物理页的映射关系pp_ref值要减一, 如果pp_ref减为0,要把这个页回收, 这个页对应的页表项应该被置0。

第三部分是内核地址空间

  在这一部分就是要完善mem_init()函数,把操作系统的一些地址范围映射到新页目录kern_pgdir上,其次映射内核的堆栈区域,最后映射整个操作系统内核我们完成后整个操作系统支持的物理内存有2GB。
  JOS把32位线性地址虚拟空间划分成两个部分。其中用户环境(进程运行环境)通常占据低地址的那部分,叫用户地址空间。而操作系统内核总是占据高地址的部分,叫内核地址空间。这两个部分的分界线是定义在memlayout.h文件中的一个宏 ULIM。JOS为内核保留了接近256MB的虚拟地址空间。这就可以理解了,为什么在实验1中要给操作系统设计一个高地址的地址空间。如果不这样做,用户环境的地址空间就不够了。由于内核和用户进程只能访问各自的地址空间,所以我们必须在x86页表中使用访问权限位(Permission Bits)来使用户进程的代码只能访问用户地址空间,而不是内核地址空间。否则用户代码中的一些错误可能会覆写内核中的数据,最终导致内核的崩溃。处在用户地址空间中的代码不能访问高于ULIM的地址空间,但是内核可以读写这部分空间。而内核和用户对于地址范围[UTOP, ULIM]有着相同的访问权限,那就是可以读取但是不可以写入。这一个部分的地址空间通常被用于把一些只读的内核数据结构暴露给用户地址空间的代码。在UTOP之下的地址范围是给用户进程使用的,用户进程可以访问,修改这部分地址空间的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值