虚拟内存是什么?
虚拟内存(virtual memory,VM)是一种内存管理技术。它是操作系统提供的一种对主存的抽象。
虚拟内存可以做什么?
- 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。
- 它为每个进程提供了一致的地址空间,从而简化了内存管理。从程序员的观点看,它为程序员提供了无限线性内存的视图从而不必担心实际有限的内存及其管理过程。VM简化了链接、加载、代码和数据共享及应用程序的内存分配。
- 位置无关代码 数据/代码可以位于物理内存中的任何位置,这不会以任何方式影响其功能。
- 代码共享 一般而言,每个进程都有自己的私有代码、数据、堆及栈区域,不和其他进程共享。这种情况下,操作系统创建页表,将相应的虚拟页映射到不连续的物理页面。不过有时还是要进程共享代码和数据。在这种情况下,只需要代码的一个副本(例如典型的python库或是c中的printf等)。不同的虚拟地址可以映射到物理内存中的相同位置,这消除了在内存中多个副本。
- 它保护了每个进程的地址空间不被其他进程破坏。防止有意或无意地尝试访问某些其他进程(或OS)内存。
- 文件可以直接映射到进程的内存空间,以便快速访问。只有在需要数据时才会发生I/O。
- 存在高级垃圾收集算法,其使用关于对页面的读取和写入访问的信息(虚拟存储器硬件的特征)以使其更有效。
分页
分段允许进程的物理地址空间是非连续的。分页是提供这种优势的另一种内存管理方案。然而,分页避免了外部碎片和紧缩,而分段不可以。不仅如此,分页还避免了将不同大小的内存块匹配到交换空间的问题。在只分段的情况下,CPU认为进程需要的地址等于物理地址,而进程需要的地址是由编译器编译出来的,他本身是连续的,所以物理地址也必须要连续才可以。
在保护模式中段寄存器中的内容已经是选择子了 ,但选择子最终就是为了找到段基址,其内存访问的核心仍是"段基址:段内偏移地址",这2个地址相加后之后得到的是绝对地址,此地址在分段机制下被CPU认为是物理地址。
但是如果CPU打开了分页机制,段部件输出的线性地址称之为虚拟地址。此虚拟地址对应的物理地址需要在页表中查找,这项工作是由页部件完成的。
分页通过 CPU 的 MMU完成,每个处理器都有自己的页表集合。MMU 通过当前的分页表完成虚拟地址到物理地址的转换。 在 x86 下 MMU 通过两级分页表(也可以开启三级)完成地址转换,这两级分别是页目录(Page Directory)和页表(Page Table)。Intel x86 CPU将页表目录的指针存储在特殊寄存器CR3中. 该寄存器指向一个包含1024个32位值的数组, 称为页目录. 每个数组元素称为页目录项, 它指定了页表在物理内存中的基地址, 还通过状态位指示该页表当前是否存在于内存中. 从页表中可以获得实际的物理地址。 下面我们来看下虚拟地址的组成: 由图可知, 虚拟地址的前10位用来定位页目录项, 中间10位用来定位页表项, 最后12位得到具体物理地址的偏移.
到了这里, 我们来总结下具体的步骤: 1. CPU查询CR3寄存器以找到页表目录的基地址 2. 操作系统根据所请求的虚拟地址的前10位(如图3), 来定位页目录项, 从而在内存中找到相应的页表. 3. 页表根据中间的10位定位该页相应的物理内存首地址 4. 根据虚拟地址的后12位得到具体的物理地址相对于首地址的偏移量. 5. 最后得到的物理地址即包含我们要请求的数据
页大小
页大小是由硬件来决定的,通常由处理器的结构决定。可用的页面大小取决于指令集架构,处理器类型和操作(寻址)模式。某些指令集体系结构可以支持多种页面大小。操作系统从架构支持的大小中选择一个或多个大小。
架构 | 页面大小 |
---|---|
arm64 | 4K,2M和1G(如果使用CONFIG_ARM64_64K_PAGES = y构建自己的内核,则为64K和512M) |
I386 | 4K和4M(PAE模式下2M) |
IA64 | 4K,8K,64K,256K,1M,4M,16M,256M |
PPC64 | 4K和16M |
常见页大小为4KB,如Linux和Windows在用户空间都使用4KB的页。0-4095B处于第0页,4096-8191是第1页,依次类推。32位下,最低的12位是存放页偏移,所以可能的偏移一共有2^12次方,内存中基本的存储单位是1B,所以对应一个帧表的大小是4KB,页表也是4KB。
描述一个物理页的单位叫页帧(page frame)。页帧的尺寸大小和硬件和Linux系统配置有关。对Intel来说,Linux采用4KB页帧大小作为标准内存分配单元。 1. 开启PAE的情况下,为2MB。 1. 如果开启了大页,那么页帧大小为4MB。
页帧之所以选择4KB大小,原因:
- 分页系统造成的缺页异常,要么是页存在但process不被允许访问,要么是页不存在。如果不存在,内存分配器需要找一个free 4KB页帧给process。(这样一来管理虚拟地址空间的分页系统的4KB单位与页帧大小一致,处理起来也就省事。此外,ULK这里的说法是高度抽象的。)
- 尽管4K和4M都是磁盘块尺寸的倍数,但内存和磁盘间往往传递小块更为的高效。(这是考虑到页的换入换出机制,磁盘IO比内存寻址慢的多,由于进程地址空间是虚拟的,因此CPU和操作系统必须记住哪个页面属于哪个进程以及存储位置。页越大那么单次执行换页操作就越耗时,查找内存映射位置所需的时间就越多。) 处理器分页通常都是支持4KB的页,一个页设得如此之小,造成的一个问题就是页表条目变多,页表size大,TLB miss也增多了。现在它也支持4MB的大页。可以从一定程度上缓解上述问题。
win32系统中可使用kernel32.dll中的函数获取页大小: 1
2
3
4
5
6
7
8
9
10
11
12
int main(void)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
printf("The page size for this system is %u bytes.\n", si.dwPageSize);
return 0;
}