lab2 实验报告
思考题
Thinking 2.1
C程序中指针变量存储的地址是虚拟地址。
MIPS汇编程序使用的也是虚拟地址。
CPU只会发出虚拟地址,然后完成虚拟地址到物理地址的转换,最后根据物理地址访存。
Thinking 2.2
![image-20220414103231391](https://s2.loli.net/2022/04/29/Ip8nAhc3lJKZTN4.png)
宏本身就具有可重用性,将一段代码封装成一条语句。
程序运行时存在着大量重复的链表操作,使用宏来实现链表可以减少大量的重复代码。
插入操作:
单向链表的插入很简单,仅两行代码。双向链表的插入由于需要额外判断next是否指向NULL,多了两行代码。循环链表与双向链表实现复杂度相同,需要额外判断next是否指向了头指针。
插入头节点:三者性能相似。
插入尾节点:单向链表和双向链表实现较复杂,需要遍历整个链表。
删除操作:
双线链表和循环链表的删除操作类似,需要额外判断NULL或HEAD。单向链表删除需要通过循环才能找到上一个节点的位置,性能最差。
删除头节点:三者性能相似。
删除尾节点:循环链表性能最好,单向链表和双向链表需要遍历,性能较差。
Thinking 2.3
答案为C。通过阅读使用时的代码,可知Page_list为指针,pp_link为结构体中确实存在的内容,用于存放实现双向链表的指针。
Thinking 2.4
使用命令行grep -r boot_pgdir_walk
和grep -r boot_map_segment
来查找两个函数的使用情况,结果如下:
![image-20220419081418609](https://s2.loli.net/2022/04/29/EfwsN7Sgijy3IWc.png)
在pmap.c中使用/boot_pgdir_walk
和/boot_map_segment
来寻找函数出现的位置。
boot_pgdir_walk
在boot_map_segment
中被调用,用于根据虚拟地址寻找二级页表项。
boot_map_segment
在mips_vm_init
中被调用,用于给UPAGES和UENVS映射物理地址空间。
Thinking 2.5
A1:操作系统会为每一个进程分配一个页表,每个页表都有自己的虚拟地址空间,不同的虚拟地址映射到不同的物理地址。
对于两个不同的进程,一般情况下同一虚拟地址会映射到的不同的物理地址,如果没有ASID来区分当前是哪个进程,可能造成映射错误。
A2:通过阅读,可知ASID一共有6位,即共有64种不同的取值可能。因此R3000中可容纳最多64个地址空间。
Thinking 2.6
A1:在tlb_invalidate中调用tlb_out
A2:tlb_invalidate用于清除TLB中虚拟地址对应的表项。一般用于在页表内容改变后更新TLB
A3:tlb_out
LEAF(tlb_out)
//1: j 1b
nop
mfc0 k1,CP0_ENTRYHI #将CP0_ENTRYHI的值存入k1
mtc0 a0,CP0_ENTRYHI #将a0得值存入CP0_ENTRYHI
nop
tlbp #根据CP0_ENTRYHI中的值查找TLB中与之对应的表项并将表项的索引存入Index寄存器。
// insert tlbp or tlbwi
nop
nop
nop
nop
mfc0 k0,CP0_INDEX #将CP0_INDEX的值存入k0
bltz k0,NOFOUND #k0的值小于0则跳转到NOFOUND处。(当tlbp没有找到匹配项时Index最高位会被置1,即小于0)
nop
mtc0 zero,CP0_ENTRYHI #CP0_ENTRYHI置0
mtc0 zero,CP0_ENTRYLO0 # CP0_ENTRYLO0置0
nop
tlbwi #以Index寄存器的值为索引,将EntryHi与EntryLo写入TLB中(相当于置0)
// insert tlbp or tlbwi
NOFOUND:
mtc0 k1,CP0_ENTRYHI #将k1的值存回CP0_ENTRYHI(相当于是恢复现场)
j ra #函数返回
nop
END(tlb_out)
Thinking 2.7
三级页表页目录的基地址:PD1Base = PTBase >> 9 + PTBase
映射到页目录自身的页目录项(自映射):PD1Base + PD1Base >> 27
Thinking 2.8
X86体系结构:
- X86为段页式的内存管理,mips为页式管理
- X86的地址映射机制分为两部分
- 段映射机制,将逻辑地址映射到线性地址
- 页映射机制,将线性地址映射到物理地址
实验难点展示
除了以上难点,有一个点卡了我很久,就是我在实验时混淆了内存管理page和页表。
-
page
- page是用于管理物理地址的,一个page对应一个物理页面,且对应关系是已知的,page[x]管理物理内存\([x*BY2PG, (x+1)*BY2PG)\)。
- page的数量由内存大小决定(不涉及内存扩展的情况下)。
- page所占的地址空间为\(0x80400000 \sim freemem-1\),其位于kseg0段,因此可以由虚拟地址直接获得物理地址。
-
页表
- 页表是因为虚拟内存的需要而创建的,其所占的空间为\(0x7fc00000 \sim 0x80000000\),为kuseg段,需要使用MMU才能进行访存。
体会与感想
关于课上考试
两次课上exam我几乎都是对照着课下的代码来写的课上代码,虽然都理解了各个函数,但是距离随意使用还有着不小的距离。此外,OS课上实验对于每个细节都有着很高的要求,多亏了课下的实验代码我才能减少很多的bug,例如调用页表项时需要考虑有效位。
感想
本次实验主要实现了对于OS内存的管理,我虽然完成了实验,但是对于实验仍旧有很多的疑问,这些疑问主要在内存管理的更高层一点的实现方式上。在lab2中完成了基础的对物理内存和虚拟内存的管理,但是没有涉及到对相关函数的使用以及用户线程的内存管理,希望通过lab3的学习,能够将其搞懂。
此外,本次实验中我总是需要反复查函数源码才能明确需要的是虚拟地址还是物理地址,对我们所写的微型OS的内存空间还是没有一个非常清晰的认识,仍需深入学习体会。
指导书反馈
pgdir_walk()
函数中应该对pp_ref进行加一操作。
残留难点
-
为什么我们在内存管理的代码中可以直接使用int(这涉及到了OS的内存),而不需要进行相应的内存管理设置,例如:分配页面之类的。这是一个类似先有鸡还是先有蛋的问题。
相似的问题还有:在C程序中,有时给函数一个虚拟地址,函数可以直接通过其修改内存,没涉及到我们所写的函数,那我们所做的意义何在?例如:
bzero()
函数。 -
bzero()
和bcopy()
两个函数的使用。通过阅读bzero()
和bcopy()
的函数定义、调用代码以及指导书内容,可知bzero用于清空page指针对应的页面,存在如下调用:bzero(page2kva(tmp),BY2PG)
,其中tmp是一个即将要被分配出去的页面所对应的page指针。page2kva和page2pa的实现如下:
static inline u_long page2kva(struct Page *pp) { return KADDR(page2pa(pp)); } static inline u_long page2pa(struct Page *pp) { return page2ppn(pp) << PGSHIFT; }
也就是说,所谓的使用
bzero()
清除page* tmp
所对应的页面仅仅限于tmp对应的页面位于kseg0段,不适用于全地址空间。那么,究竟如何实现通过
page*
对全地址段的内容进行访问?简化一下,如何清除page*
对应页面的内容 -
什么时候我们的程序中需要用到物理地址,什么时候用虚拟地址?(本问题也是源于上一个问题,没想明白到底如何进行全地址段的访存)