Virtualization
虚拟内存
虚拟化:每个进程都认为,自己有自己的地址空间,多个进程的地址空间是分开的。
实际上,CPU 拿到这些虚拟的地址,会让它内部的 MMU 把虚拟地址翻译成物理地址,也就是内存中的地址。内存里,每个地址可以存放一个字节。
分段
每个 segment对应地址空间里的logical entity, 比如stack/data。每个segment都可以:
- 单独放在内存的某个地方
- 拥有自己的protection bits
- grow and shrink
MMU为每个进程保持一个segment table.
在虚拟地址中,top bits表示segment的序号,low bits表示segment内的offset。
虚拟地址0x0240对应的物理地址是多少?
这里有4个segment,所以用2位来表示segment序号。该地址二进制为00000000001001000000,前两位00是段号,即segment 0;后12位是偏移量。
因此它对应的物理地址是0x2000+0x240=0x2240
分段的缺点
会出现外部内存碎片。因为每个segment必须是整块出现的,如果一个segment过大,而内存中可能有多个小空间(碎片),却没有一个能放得下该segment的空间。
外部内存碎片是 OS 可见的。
分页
为了解决外部内存碎片的问题,分页方法把每个进程的地址空间切割成相同大小的page,放入内存中。内存中的每个可以容纳page 的单位,叫做 page frame, 页帧。
页表
每个进程拥有一个页表。 里面记录着该进程的pages分别存放在哪个页帧。
一个页表有多大?
Linux中,一个page大小为4KB。假设系统为32位,那么一个进程的address space由32位表示,由于每一个address space可以保存一个byte,那么一个进程的address space大小为2^32=4GB.
4GB/4KB=2^20,即一个进程可以拥有2^20个page. 假设page table里面一项的数据大小为4KB,那么一个page table的大小为2^20*4KB=4MB.
page table 存储在内存中,MMU负责做翻译。
分页的映射
在32位系统中,一个逻辑地址由20位虚拟页号+12位页内偏移量组成。
当某段数据不足一页大小,还是会被分成一个page,因此page内部存在内存碎片。
主存作为虚拟内存的 cache
CPU交给 MMU 一个虚拟地址,MMU 去 page table里面,根据虚拟页号去找对应的物理页内存的基地址。找到了,则拿页内偏移量加上基地址,得到最终物理地址。
如果没有对应的物理地址,则是一个 cache miss, 即page fault exception。
此时,kernel 里的 page fault handler首先判断DRAM是否已满,如果满了,会从 DRAM 里面驱逐一个page。随后OS把所需要的page从磁盘中加载到DRAM里。
当DRAM满时,这一过程也是swap out和swap in,即从DRAM选择其它进程没有在用的page,把它放到磁盘里去,而从磁盘里加载需要用的page到DRAM中。
当运行一个进程时,不需要把它address space里面所有的 page加载到DRAM中,而是用到哪些page,就加载哪些。
页表的缺点是:太大了,100个进程需要400M内存。