段,页,段页
是为实现虚拟内存而产生的技术。直接使用物理内存弊端:地址空间不隔离,内存使用效率低。
段
段:就是按照二进制文件的格式,在内存给进程分段(包括堆栈、数据段、代码段)。通过段寄存器中的段表来进行虚拟地址和物理地址的转换。
段实现的虚拟地址 = 段号+offset
物理地址:被分为很多个有编号的段,每个进程的虚拟地址都有段号,这样可以实现虚实地址之间的转换。其实所谓的地址映射,就是虚实地址转换之间的那个公式。
经过上面的理解我们知道了,实现段机制的器件其实就是段寄存器(段表)
不足:内存碎片(程序结束后内存被分割为一小块一小块),解决方法是SWAP分区(将散开的进程读出来再粘贴到另一个进程边上,使空闲的内存是连贯的,但这样效率很低)
页
页:就是将物理内存固定大小地分为n个页(linux中是4KB一页)。通过MMU的页表来进行虚实地址转换,这里就不是用寄存器来实现了。页表记录着页号、页号对应的物理地址,比较创新的是还记录着权限。基本功能已经有了,就是物理地址和页号怎么对应上。
页实现的虚拟地址 = 页号 + 页内偏移
物理地址:被分为很多个有编号的页,每个进程的虚拟地址都是用页号和偏移来记录,通过MMU来进行虚实转换。
经过上面的理解我们知道了,实现页机制的器件其实就是MMU。
优点:为什么说MMU可以让原本1G SDRAM的物理内存,实现能够实际使用4G SDRAM空间?甚至给每个程序都安排上4G SDRAM的内容空间?为什么每一个进程的内存空间是独立的?
就是因为物理SDRAM用页来管理,有些不用的页,可以暂时先存到硬盘上,硬盘的空间就非常大了动不动就是几百个G,所以可以做到虚拟出很大的内存空间给到程序。因为每一个进程都有自己单独的页表,一个进程根本没法知道或者操作另一个进程的页表,对应的也就不知道另一个进程的地址(虚拟地址对应的物理地址)在哪里,自然就是独立的。
缺点:页表本身占用的内存太多了,每一个进程有一个页表,那么理论上32位的机器4G的物理内存,每一页是4K的话,总共会有4G/4K = 2^10 * 2*10 个页表,如果一个页表要4MB,那光是记录页表就要占用800M的内存。
解决:套娃,用多级页表。
采用多级页表机制的虚拟地址 = 一级页号 + 二级页号 + 页内偏移。这样MMU可以通过页号1来找到页号2,通过页号2+页内偏移来找到对应的物理地址从而实现虚实转换。那为啥这样能够降低页表占用内存的大小呢?其实就行把文件折叠了一样,如果一级页表里面有的二级页表没有被分配,那这些二级页表实际上就是没有分配内存的;如果有些二级页表很久没有被使用,那这些二级页表也可以暂时存到硬盘里,也不占用空间。假如一个一级页表是4MB的话,那就相当于你可以用1个4MB的内存大小去管理很多个占4MB的二级页表,然而实际只占用了4MB。照着这个思路,可以造出很多级的页表,进行极致的页表空间压缩。
缺点2:多级页表是省了空间,但是从虚拟地址转成物理地址也多了很多步骤,要一级一级地查找最终才能找到对应的物理地址。
解决2:建立快表TLB,实际就是把一些最近使用的进程它虚拟地址对应的最底层页号和偏移,存到一个一级表中(快表),这样要地址转换时,先去快表找,找不到再去查多级页表。如此就显著提高了虚实地址转换速度,而且也节省了空间。
段页式
段页式:其实就是把物理地址分为一大段一大段的,在段的内部,再进行分页。
段页式机制下虚拟地址 = 段号 + (段内)页号 + 页内偏移
这样就结合了段(契合二进制文件堆栈,代码段,数据段分层,便于程序分配地址)和页(多级页表节省空间)两个特点。
不足:就是实现起来比较复杂。但是段页式是现在内存(SDRAM)管理的主流。需要注意的是,Linux是使用页机制的。
但是linux中的页机制也一定程度上有段页结合的影子,下面是段页式的虚实地址转换流程:
linux中每一个进程虚拟地址中的段号都是对应的0~4GB的地址空间,每一个进程段空间范围都一样就意味着虚拟地址中的段号没有划分地址的作用,实际上linux中的虚拟地址是 "虚拟地址 = 段号+页号+页内偏移 ",系统有规定某些段号只有内核程序能使用,应用层程序只能使用另一些段号。从而实现了访问权限的机制。