目录
一、引入
二、物理内存和外设空间的交互
三、解决页表过大问题
一、引入
我们在往期的博客中有讲解过进程地址空间:【Linux】进程地址空间
但是在上述博客中我们只是对进程地址空间的左边部分详细进行了讲解,下面我们就来谈谈右边的部分:
我们以32位平台为例:虚拟地址空间中的每一个地址依次为 [ 0 , 2^32 − 1 ]即 0x00000000 - 0xFFFFFFFF,每一个单位地址有1字节的空间,总共也就是我们常说的4GB虚拟内存空间
但是我们来思考一个问题假如极端情况下:每个单位虚拟地址空间都对应着物理内存中的一个空间,那页表中岂不是有2^32个对应映射?假设页表中每个虚拟地址单位都会至少占有4字节的映射关系,那页表的大小岂不是成了12GB?这显然是不合理的
下面我们来一步步深入,看看是如何解决这个问题的:
二、物理内存和外设空间的交互
数据要从磁盘中进入到CPU中就必须意味着物理内存要和外设空间的数据进行交互(我们将这个过程成为IO),具体交互的设计我们在之前的博客中说到过:【Linux】文件系统,这里不再赘述
既然在磁盘中是以4KB(可以就行调整)为单位存储的,所以在Linux下为了提高IO效率,每次内存和外设交互都以块为交互单位,所以不管我们具体需要多少大小的数据,都是以4KB为大小进行交换的
这就注定了物理内存中是要对应每次IO的大小为单位来存储数据的;即便物理内存中寻址是以字节为单位,但并不代表按一个个字节为单位来存储;我们将内存中存储数据一个个单位叫做页框,将磁盘中存储数据一个个单位叫做页帧:
OS为了管理这些物理内存中的页,会构建一个结构体数组,该数组内中的每个结构体中属性非常少,主要是为了记录该页是否有被使用:
struct page
{
int status;
//属性非常少
};
struct page mem[1048576];//使用数组来管理(1048576是一块4GB内存所需的数组大小)
那我们会难免有一个疑问:为什么要每次都要以4KB块大小来加载数据,仅仅是因为文件系统的存在吗?
💡并不是仅仅由文件系统所决定的,其中真正在于局部性原理,当系统一次将4KB的空间加载到内存中,即便我们要使用的资源没有这么多,但是在代码向下运行时,要访问的数据很可能在上次访问数据空间的附近,如此一来该空间很可能已经被OS加载到内存中了,这时就不需要再IO,提高了系统的运行效率
所以一次多加载进来的数据被称为:数据的预加载
三、解决页表过大问题
为了解决页面会占据很大空间的问题,设计者在设计页表时并不是将进程地址空间的32位的数据直接映射到页表上面的,而是将32位比特位的数据从前往后划分为10/10/12位来建立映射的:
先用进程地址空间的前10位比特位找到页目录中对于映射的二级页表位置,再在对应的二级页表中找到进程地址空间的11-20位比特位对应的物理空间地址(该物理地址一定指向某一个物理页框的起始地址),最后将对应的物理地址加上进程地址空间的最后12位比特位就可以找到指定的字节位置进行访问了(基地址+偏移量):
现在我们来计算一下即便所有的虚拟地址都有对应的映射地址,页表最多会占据多少空间呢?
💡前20位比特位最多生成2^20种组合,假设每个页表会占据4字节的空间,最多也就是2^20*4=4MB的空间,而且在进程运行时并不会加载所有的数据到内存中,而是加载所需要的数据,如此一来页表占据的总空间大小要比4MB小很多
下面来解释一下页框的大小为什么是4KB:
我们可以看到进程地址空间最后12位比特位是用来表示偏移量的,也就是最多可以指向起始位置后2^12字节的空间,也就是4KB的大小,所以无论是OS对于进程地址空间的设计、还是文件系统的设计都是有精密的联系的,我们并不能将其拆开看~
四、缺页中断
缺页中断(Page Fault)指的是一个进程试图访问的页面(页)在当前的物理内存中不存在,需要从磁盘或其他外部存储器中加载到内存中的操作。这种情况通常发生在虚拟内存系统中,当一个进程需要访问的页面不在主存中时,就会发生缺页中断
当发生缺页中断时,操作系统会进行一系列的处理:
中断处理程序:操作系统会捕获缺页中断,并调用特定的中断处理程序。
页面调度:操作系统会根据特定的算法决定从磁盘或其他外部存储器中选择哪些页面加载到内存中。
I/O操作:如果所需页面在磁盘或其他外部存储器上,操作系统会发起相应的I/O操作,将页面加载到主存中。
页面映射:加载完成后,操作系统会更新页表,将页面映射到合适的虚拟地址空间。
重启进程:一旦所需页面已加载到内存中,操作系统会重新执行被中断的指令。
缺页中断是虚拟内存系统中的一种常见情况,通过将主存和辅存(如磁盘)结合使用,可以扩展可用的地址空间和提高系统性能