学习资料:
https://pdos.csail.mit.edu/6.828/2020/schedule.html
https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/
https://th0ar.gitbooks.io/xv6-chinese/content/
https://www.bilibili.com/video/BV19k4y1C7kA
参考资料
https://blog.csdn.net/qq_53111905/article/details/120275916
地址空间(Address Spaces)
每个用户程序都被装进一个盒子里,这样它们就不会彼此影响了。类似的,我们也想让它们与内核操作系统相互独立,这样如果某个应用程序无意或者故意做了一些坏事,也不会影响到操作系统。
- 为了防止cat和shell 混乱
我们给包括内核在内的所有程序专属的地址空间。所以,当我们运行cat时,它的地址空间从0到某个地址结束。当我们运行Shell时,它的地址也从0开始到某个地址结束。内核的地址空间也从0开始到某个地址结束。
如果cat程序想要向地址1000写入数据,那么cat只会向它自己的地址1000,而不是Shell的地址1000写入数据。所以,基本上来说,每个程序都运行在自己的地址空间,并且这些地址空间彼此之间相互独立。在这种不同地址空间的概念中,cat程序甚至都不具备引用属于Shell的内存地址的能力。这是我们想要达成的终极目标,因为这种方式为我们提供了强隔离性,cat现在不能引用任何不属于自己的内存。
初步实现地址转换(理想化)
在一个物理内存上,创建不同的地址空间
最常见的方法,同时也是非常灵活的一种方法就是使用页表(Page Tables)。页表是在硬件中通过处理器和内存管理单元(Memory Management Unit)实现
- 对于任何一条带有地址的指令,其中的地址应该认为是虚拟内存地址而不是物理地址。假设寄存器a0中是地址0x1000,那么这是一个虚拟内存地址。虚拟内存地址会被转到内存管理单元(MMU,Memory
Management Unit) - 内存管理单元会将虚拟地址翻译成物理地址。之后这个物理地址会被用来索引物理内存,并从物理内存加载,或者向物理内存存储数据。
- 从CPU的角度来说,一旦MMU打开了,它执行的每条指令中的地址都是虚拟内存地址。为了能够完成虚拟内存地址到物理内存地址的翻译,MMU会去查看一个表单,表单中,一边是虚拟内存地址,另一边是物理内存地址。举个例子,虚拟内存地址0x1000对应了一个我随口说的物理内存地址0xFFF0。这样的表单可以非常灵活。
- 通常来说,内存地址对应关系的表单也保存在内存中。所以CPU中需要有一些寄存器用来存放表单在物理内存中的地址。现在,在内存的某个位置保存了地址关系表单,我们假设这个位置的物理内存地址是0x10。那么在RISC-V上一个叫做SATP的寄存器会保存地址0x10。
这样,CPU就可以告诉MMU,可以从哪找到将虚拟内存地址翻译成物理内存地址的表单。 - 每个应用程序都有自己独立的表单,并且这个表单定义了应用程序的地址空间。所以当操作系统将CPU从一个应用程序切换到另一个应用程序时,同时也需要切换SATP寄存器中的内容,从而指向新的进程保存在物理内存中的地址对应表单。
地址转换表是以page为粒度(不是地址)
首先对于虚拟内存地址,我们将它划分为两个部分,index和offset,index用来查找page,offset对应的是一个page中的哪个字节。
当MMU在做地址翻译的时候,通过读取虚拟内存地址中的index可以知道物理内存中的page号,这个page号对应了物理内存中的4096个字节。之后虚拟内存地址中的offset指向了page中的4096个字节中的某一个,假设offset是12,那么page中的第12个字节被使用了。将offset加上page的起始地址,就可以得到物理内存地址。
page table实际是一个三级的结构(节省空间)
我们之前提到的虚拟内存地址中的27bit的index,实际上是由3个9bit的数字组成(L2,L1,L0)。
- 前9个bit被用来索引最高级的page directory(注:通常page directory是用来索引page table或者其他page directory物理地址的表单,但是在课程中,page table,page directory,
page directory table区分并不明显,可以都认为是有相同结构的地址对应表单)。 - 一个directory是4096Bytes,就跟page的大小是一样的。Directory中的一个条目被称为PTE(Page Table Entry)是64bits,就像寄存器的大小一样,也就是8Bytes。所以一个Directory page有512个条目。
注: 2^9(索引大小) * 8 Bytes(寄存器大小) = 4096 Bytes(页大小) - 所以实际上,SATP寄存器会指向最高一级的page directory的物理内存地址,之后我们用虚拟内存中index的高9bit用来索引最高一级的page
directory,这样我们就能得到一个PPN,也就是物理page号。这个PPN指向了中间级的page directory。 - 当我们在使用中间级的page directory时,我们通过虚拟内存地址中的L1部分完成索引。接下来会走到最低级的page directory,我们通过虚拟内存地址中的L0部分完成索引。在最低级的page directory中,我们可以得到对应于虚拟内存地址的物理内存地址。
页表缓存(Translation Lookaside Buffer)
当处理器从内存加载或者存储数据时,基本上都要做3次内存查找,第一次在最高级的page directory,第二次在中间级的page directory,最后一次在最低级的page directory。所以对于一个虚拟内存地址的寻址,需要读三次内存,这里代价有点高**。所以实际中,几乎所有的处理器都会对于最近使用过的虚拟地址的翻译结果有缓存。这个缓存被称为:Translation Lookside Buffer(通常翻译成页表缓存)**。
你会经常看到它的缩写TLB。基本上来说,这就是Page Table Entry的缓存,也就是PTE的缓存。
当处理器第一次查找一个虚拟地址时,硬件通过3级page table得到最终的PPN,TLB会保存虚拟地址到物理地址的映射关系。这样下一次当你访问同一个虚拟地址时,处理器可以查看TLB,TLB会直接返回物理地址,而不需要通过page table得到结果。
当kernel创建了一个进程,针对这个进程的page table也会从Free memory中分配出来。内核会为用户进程的page table分配几个page,并填入PTE。在某个时间点,当内核运行了这个进程,内核会将进程的根page table的地址加载到SATP中。从那个时间点开始,处理器会使用内核为那个进程构建的虚拟地址空间。