关于存储管理,缺页中断以及页表,TLB以及倒序页表的知识整理

      早期的操作系统没有地址空间(如经典macos,windows3.1等),所有的进程都可以公用全部的物理内存,并且以物理地址访问内存,这样的操作会导致很多问题,例如程序中的代码访问的地址必须加上该程序的进程在内存中的偏移量进行访问,以及最致命的物理内存远远小于程序需要的存储容量,另外,程序在运行过程中还会额外申请更大的内存空间来使用。

      对于“程序中的代码访问的地址必须加上该程序的进程在内存中的偏移量进行访问”这样的问题,早期可以使用基址寄存器和界限寄存器来解决,机制寄存器中保存进程的基地址,每次进行地址访问之前将地址加上基地址后获得实际的物理内存地址进行访问。

      而对于程序在运行过程中还需更大的内存的问题,则使用了地址空间来解决,地址空间是一个逻辑的物理内存,和进程是对CPU的抽象类似,地址空间是对物理地址的抽象,地址空间往往叫做虚拟地址空间,因为地址空间不是实际的物理内存,虚拟地址空间设定每个应用程序可以使用4G的物理内存,可执行程序中访问的地址都是虚拟地址空间中的地址。可执行程序的进程的虚拟地址空间主要分为这样四个区间(数据段,代码段,堆,桟),每个进程的地址空间有可以按照固定的大小切成多个页面,一般这个大小为4K,所以,对于4G地址空间来说,页面的总数为4G/4K=1M,即每个进程有一M的页面,这个过程叫做”分页“,另外有一点需要注意,大部分操作系统提供的虚拟地址空间,并不完全提供给进程使用,而是需要将一半或者4分之1提供给操作系统内核部分(当进程调用了操作系统的内核功能,例如分配内存,读取磁盘,等待socket数据等时,内核也需要内存来运行自己的代码),即虚拟地址空间需要分为用户态和内核态两部分。。

      再次强调,虚拟地址空间是虚拟的,不是实际的物理内存,只是告诉进程它可以访问4G的内存,并且是独占访问的,实际上不不能完全兑现。

       对于进程来说,程序并不是一开始就用得到4G那么大的内存,而是只用可执行文件中的代码和数据,代码在虚拟地址空间中在最底部的代码段,并且是只读的,数据也是在可执行文件中(编译时写入的),在虚拟地址空间中代码段的上面,另外,每个进程一开始都有一个主线程,该主线程会在虚拟地址空间中默认分配1M的空间,上面这些就是一个进程开始运行时需要的内存,好像离4G差太远,如果此时将该进程的地址空间中的每个段中的每个页面统统加载到物理内存中,明显是不合适的,就不说物理内存可能没有那么大,主要是此时进程只用到了3个段中的为数不多的页面,完全不需要将虚拟地址空间的所有页面都加载到内存。

       因此,实际进程运行的时候,只是将虚拟地址空间中的部分页面加载到了物理内存中,并不是进程虚拟地址空间的所有页面都在内存上进程才能运行。

       当多进程同时运行的时候,实际的物理内存中会包含多个进程的虚拟地址空间的内存页面,那么进程数一多,势必会发生有的进程需要将新的虚拟地址空间中的页面加载到物理内存,却发现物理内存已经满了的情况,这种情况下,OS就会出手,将物理内存中的某一个不常使用的页面换出内存,将空出来的物理内存加载该进程新的页面,这就是常说的交换页面。

       但是对于底层硬件来说,尤其是CPU,是不认识虚拟地址空间的地址的,他只认物理地址,而可执行程序中访问的地址都是基于虚拟地址空间的虚拟地址,那么在实际运行的时候,CPU中有一个电路单元MMU,会负责进行虚拟地址和物理地址的转换,从而向地址总线上发送正确的物理内存地址。

       那么MMU基于什么来进行虚拟地址和物理地址的翻译的呢,答案是页表,页表是保存在内存中的,前面说过,对于4G的地址空间,4K的页面,那么一个进程的页面有1M个单元,同时物理内存也按照4K的页面大小划分成多个页面,每个页面有一个页面号,页表的每个单元保存了该页面对应的实际物理内存上的页面号,当然,这个是有前提的,就是这个虚拟地址所在的页面实际已经被加载到内存上了,那么内存的页表上就会找到这个虚拟地址的页面号对应的物理内存的页面号,反之,就会发出缺页错误,由内核将该虚拟地址的页面从可执行程序或者交换空间加载到内存中,然后更新这个页面映射关系,将虚拟页面号和物理页面号关联在一起。

       那么有一个情况需要考虑了,在内存中保存页面后,每次CPU执行进程的代码遇到访问地址的时候,都需要将该虚拟内存地址,这个过程是需要页表的支持的,但是MMU并不知道页表在内存上面的哪一个位置。因此,实际的映射过程是这样的,MMU内部有一个硬件页面映射缓存,叫做TLB(translation lookaside buffer),MMU拿到一个虚拟地址后,首先用取模运算获取到该虚拟地址的页面号,然后在TLB中找有没有给虚拟页面的页面号的记录,有的话就将记录中的物理内存页面的页面号拼接上虚拟内存地址中的页面内地址(低12位)组成实际的物理地址送到地址总线上,没有的话,CPU就会调用系统内核,由内核判断该进程的页表上有没有该虚拟地址的页面的对应关系,有的话就直接将该对应关系写如TLB或者替换TLB中某条不常用的条目,然后重新运行访问该虚拟地址的指令。

      对于64位系统来说,如果还以4K为页面大小的话,那么每个页表的大小就非常可怕了,可能会直接超过内存的大小。此时的做法是针对实际的物理内存页面做页表,每个实际的物理页面有一个页表项目,所有的进程都共享同一张页表,每一个页表保存的是物理页面号和进程号,虚拟页面号的关系,进程访问内存时,如果MMU没有在TLB中找到映射单元的话,则由内核根据虚拟地址的虚拟页面号加上进程号找页表中的映射项目获取实际的物理页面号。

http://blog.csdn.net/u010953266/article/details/42774117

http://blog.csdn.net/zdy0_2004/article/details/54956787

猜你喜欢

转载自blog.csdn.net/challeng_everything/article/details/79569066