计算机底层知识拾遗(一)理解虚拟内存机制

这个系列会总结计算机,网络相关的一些重要的底层原理。很多底层原理大家上学的时候都学过,但是在学校的时候大部分的同学都是为了应付考试而学习,过几天全忘了。随着工作的时间越久,越体会到这些基础知识的重要性。做技术和练武功一样,当你到了一定的阶段,也会遇到一个瓶颈,突破了你的眼界就会大不同,突破不了,只能困在原地无法成长。我自己深有体会,这些基础知识,底层原理是助你打破瓶颈的灵丹妙药。当理解了一些底层原理之后,会发现现在很多热门技术,原理,常见的设计都是在底层基础上发展而来的。

这篇总结一下单机系统的虚拟内存原理。在聊聊高并发(三十四)Java内存模型那些事(二)理解CPU高速缓存的工作原理 这篇中说了CPU高速缓存的一些原理,虚拟内存也是缓存原理的应用,它是单机系统管理内存的手段,很多设计非常经典,在分布式存储系统中可以看到这些原理和设计的应用。

虚拟内存是单机系统最重要的几个底层原理之一,它由底层硬件和操作系统两者软硬件结合来实现,是硬件异常,硬件地址翻译,主存,磁盘文件和内核的完美交互。它主要提供了3个能力:

1. 给所有进程提供一致的地址空间,每个进程都认为自己是在独占使用单机系统的存储资源

2. 保护每个进程的地址空间不被其他进程破坏,隔离了进程的地址访问

3. 根据缓存原理,上层存储是下层存储的缓存,虚拟内存把主存作为磁盘的高速缓存,在主存和磁盘之间根据需要来回传送数据,高效地使用了主存

包括几块内容

1. 虚拟地址和物理地址

2. 页表

3. 地址翻译

4. 虚拟内存相关的数据结构

5. 内存映射

虚拟地址和物理地址

对于每个进程来说,它使用到的都是虚拟地址,每个进程都看到一样的虚拟地址空间,对于32位计算机系统来说,它的虚拟地址空间是 0 - 2^32,也就是0 - 4G。对于64位的计算机系统来说,理论的虚拟地址空间是 0 - 2^64,远高于目前常见的物理内存空间。虚拟地址空间不需要和物理地址空间一样大小。

Linux内核把虚拟地址空间分为两部分: 用户进程空间和内核进程空间,两者的比例一般是3:1,比如4G的虚拟地址空间,3G用户用户进程,1G用于内核进程。

下图是一个典型的Linux进程的虚拟地址空间分布

在说CPU高速缓存的时候说过CPU只直接和寄存器和高速缓存打交道,CPU在执行进程的指令时要取一个实际的物理地址的值的时候主要有几步:

1. 把进程指令使用的虚拟地址通过MMU转换成物理地址

2. 把物理地址映射到高速缓存的缓存行

3. 如果高速缓存命中就返回

4. 如果不命中,就产生一个缓存缺失中断,从主存相应的物理地址取值,并加载到高速缓存中。CPU从中断中恢复,继续执行中断前的指令

所以高速缓存是和物理地址相映射的,进程指令中使用到的是虚拟地址。

操作系统内存管理

在缓存原理中,数据都是按块来进行逻辑划分的,一次换入/换出的数据都是以块为最小单位,这样提高了数据处理的性能。同样的原理应用到具体的内存管理时,使用了页(page)来表示块,虚拟地址空间划分为多个固定大小的虚拟页(Virtual Page, VP),物理地址空间划分为多个固定大小的物理页(Physical Page, PP), 通常虚拟页大小等于物理页大小,这样简化了虚拟页和物理页的映射。虚拟页的大小通常在4KB - 2MB之间。在JVM调优的时候有时候会使用2MB的大内存页来提高GC的性能。

要明白一个重要的概念:

1. 对于CPU来说,它的目标存储器是物理内存,使用高速缓存做物理内存的缓存

2. 同样,对于虚拟内存来说,它的目标存储器是磁盘空间,使用物理内存做磁盘的缓存

所以,从缓存原理的角度来理解,在任何时刻,虚拟页的集合都分为3个不相交的子集:

1. 未分配的页,即没有任何数据和这些虚拟页关联,不占用任何磁盘空间

2. 缓存的页,即已经分配了的虚拟页,并且已经缓存在具体的物理页中

3. 未缓存的页,即已经为磁盘文件分配了虚拟页,但是还没有缓存到具体的物理页中

虚拟内存系统和高速缓存系统一样,需要判断一个虚拟页面是否缓存在DRAM(主存)中,如果命中,就直接找到对应的物理页。如果不命中,操作系统需要知道这个虚拟页对应磁盘的哪个位置,然后根据相应的替换策略从DRAM中选择一个牺牲的物理页,把虚拟页从磁盘中加载到DRAM物理主存中

虚拟内存的这种缓存管理机制是通过操作系统内核,MMU(内存管理单元)中的地址翻译硬件和每个进程存放在主存中的页表(page table)数据结构来实现的。

页表

页表(page table)是存放在主存中的,每个进程维护一个单独的页表。它是一种管理虚拟内存页和物理内存页映射和缓存状态的数据结构。它逻辑上是由页表条目(Page Table Entry, PTE)为基本元素构成的数组。

1. 数组的索引号对应着虚拟页号

2. 数组的值对应着物理页号

3. 数组的值可以留出几位来表示有效位,权限控制位。有效位为1的时候表示虚拟页已经缓存。有效位为0,数组值为null时,表示未分配。有效位为0,数组值不为null,表示已经分配了虚拟页,但是还未缓存到具体的物理页中。权限控制位有可读,可写,是否需要root权限

聊聊高并发(三十四)Java内存模型那些事(二)理解CPU高速缓存的工作原理 这篇中解释了缓存相联度的概念,DRAM缓存是全相联的,即只有一组,任意的缓存行可以缓存任意的内容,有一个比较判断的过程,即任意的虚拟页可以对应任意的物理页。

DARM缓存的命中称为页命中,不命中称为缺页。举个例子来说,

1. CPU要访问的一个虚拟地址在虚拟页3上(VP3),通过地址翻译硬件从页表的3号页表条目中取出内容,发现有效位0,即没有缓存,就产生一个缺页异常

2. 缺页异常调用内核的缺页异常处理程序,它会根据替换算法选择一个DRAM中的牺牲页,比如PP3。PP3中已经缓存了VP4对应的磁盘文件的内容,如果VP4的内容有改动,就刷新到磁盘中去。然后把VP3对应的磁盘文件内容加载到PP3中。然后更新页表条目,把PTE3指向PP3,并修改PTE4,不再指向PP3.

3. 缺页异常处理程序返回后重新启动缺页异常前的指令,这时候虚拟地址对应的内容已经缓存在主存中了,页命中也可以让地址翻译硬件正常处理了

磁盘和主存之间传送页的活动叫做交换(swapping)或者页面调度(页面调入,页面调出)。现代操作系统都采用按需调度的策略,即不命中发生时才调入页面。操作系统都会在主存中分配一块交换区(swap)来作缓冲区,加速页面调度。

由于页的交换会引起磁盘流量,所以具有好的局部性的程序可以大大减少磁盘流量,提高性能。而如果局部性不好产生大量缺页,从而导致不断地在磁盘和主存交换页,这种现象叫缓存颠簸。可以用Unix的函数getrusage来统计缺页的次数

现代操作系统都采用多级页表的方式来压缩页表的大小。举个例子,

1. 对于32位的机器来说,支持4G的虚拟内存大小,如果每个页表是4KB大小,那么采用一级页表的话,需要10^6(1MB)个页表条目PTE。32位机器的页表条目是4个字节,那么页表需要4MB大小的空间。

2. 假设使用4MB大小的页,那么只需要1KB的页表项。假设每个4MB大小的页又分为4KB大小的子页,那么每个4MB大小的页需要1KB的页表项来指向子页。也就是说可以分为两级页表,第一级页表项只需要1KB的页表项,每个一级页表项又指向一个1KB的二级页表项,二级页表项则指向实际的物理页。

页表项加载是按需加载的,没有分配的虚拟页不需要建立页表项, 所以可以一开始只建立一级页表项,而二级页表项按需创建,这样大大压缩了页表的空间。

使用k级页表项的地址翻译如下:

Core i7采用4级页表的结构

地址翻译

地址翻译就是把N个元素的虚拟地址空间(VAS)映射到M个元素的物理地址空间(PAS)的过程。下表是地址翻译时用到的符号

下面看一下CPU如何把一个虚拟地址翻译到对应的物理地址。

1. CPU有一个专门的页表基地址寄存器(page table base register, PTBR)指向当前页表的基地址,从而可以快速定位到该进程的页表

2. n位的虚拟地址划分为p位的虚拟地址偏移量VPO和(n - p)位的虚拟页号VPN

3. 物理地址同样划分为p位的物理地址偏移量PPO和(m - p)位的物理页号PPN

4. 由于虚拟页大小和物理页大小相同,所以VPO = PPO

页面的命中完全由硬件完成,缺页则由硬件和内核共同完成,已经在上面举例说明了。

为了提高地址翻译的效率,地址翻译硬件还引入了一个硬件设备来缓存页表条目PTE,叫做翻译后备缓冲区TLB(translation lookaside buffer)。它是一个小的,虚拟寻址的缓存,每一行都保存一个由单个PTE组成的块。TLB也遵循缓存的设计原理,分为组,行,块的结构。一个虚拟地址映射到TLB的缓存结构如下:

而TLB的命中和不命中的流程如下:

Core i7处理器的地址翻译硬件结构如下

总结一下地址翻译的过程:

1. CPU拿到一个虚拟地址

2. 地址翻译硬件要把这个虚拟地址翻译成一个物理地址,从而可以再根据高速缓存的映射关系,把这个物理地址对应的值找到

3. 地址翻译硬件利用页表数据结构,TLB硬件缓存等技术,目的只是把一个虚拟地址映射到一个物理地址。要记住DRAM缓存是全相联的,所以一个虚拟地址和一个物理地址是动态关联的,不能直接根据虚拟地址推导出物理地址,必须根据DRAM从磁盘把数据缓存到DRAM时存到页表时存的实际物理页才能得到实际的物理地址,用物理页PPN + VPO就能算出实际的物理地址 (VPO = PPO,所以直接用VPO即可)。 PPN的值是存在页表条目PTE中的。地址翻译做了一堆工作,就是为了找到物理页PPN,然后根据VPO页面偏移量,就能定位到实际的物理地址。

4. 得到实际物理地址后,根据高速缓存的原理,把一个物理地址映射到高速缓存具体的组,行,块中,找到实际存储的数据。

Linux虚拟内存机制

Linux把虚拟内存划分成区域area的集合,每个存在的虚拟页面都属于一个area。一个area包含了连续的多个页。Linux通过area相关的数据结构来灵活地管理虚拟内存。

1. 内核为每个进程维护了一个单独的任务结构 task_struct

2. task_struct的mm指针指向了mm_struct,该结构描述了虚拟内存的运行状态

3. mm_struct的pgd指针指向该进程的一级页表的基地址。mmap指针指向了vm_area_struct链表

4. vm_area_struct是描述area结构的一个链表,链表节点的几个重要属性如下:vm_start表示area的开始位置,vm_end表示area的结束位置,vm_prot描述了area内的页的读写权限,vm_flags描述该area内的页面是与其他进程共享还是进程私有, vm_next指向下一个area节点

在Linux系统中,当MMU翻译一个虚拟地址发生缺页异常时,跳转到内核的缺页异常处理程序。

1. Linux的缺页异常处理程序会先检查一个虚拟地址是哪个area内的地址。只需要比较所有area结构的vm_start和vm_end就可以知道。area都是一个连续的块。如果这个虚拟地址不属于任何一个area,将发生一个段错误,终止进程

2. 要访问的目标地址是否有相应的读写权限,如果没有,将触发一个保护异常,终止进程

3. 选择一个牺牲页,如果牺牲页被修改过,那么把它交换出去。从磁盘加载虚拟页内容到物理页,更新页表

内存映射机制

虚拟内存的目标存储器是磁盘,所以虚拟内存区域是和磁盘中的文件对应的。初始化虚拟内存区域的内容时,会把虚拟内存区域和一个磁盘文件对象对应起来,这个过程叫内存映射(memory mapping)。虚拟内存可以映射的磁盘文件对象包括两种:

1. 一个普通的磁盘文件,文件中的内容被分成页大小的块。因为按需进行页面调度,只有真正需要读取这些虚拟页时,才会交换到主存

2. 一个匿名文件,匿名文件是内核创建的,内容全是二进制0,它相当于一个占位符,不会产生实际的磁盘流量。映射到匿名文件中的页叫做请求二进制零的页(demand zero page)

一旦一个虚拟页面被初始化了,它就在一个由内核维护的专门的交换区(swap area)之间换来换去。

由于内存映射机制,所以一个磁盘文件对象可以被多个进程共享访问,也可以被多个进程对象私有访问。如果是共享访问,那么一个进程对这个对象的修改会显示到其他进程。如果是私有访问,内核会采用写时拷贝copy on write的方式,如果一个进程要修改一个私有的写时拷贝的对象,会产生一个保护故障,内核会拷贝这个私有对象,写进程会在新的私有对象上修改,其他进程仍指向原来的私有对象。

理解了内存映射机制就可以理解几个重要的函数:

1. fork函数会创建带有独立虚拟地址空间的新进程,内核会为新进程创建各种数据结构,分配一个唯一的PID,把当前进程的mm_struct, area结构和页表都复制给新进程。两个进程的共享同样的区域,但是这些区域都被标记为私有的写时拷贝。如果新建的进程对虚拟页做修改,那么会触发写时拷贝,为新的进程维护私有的虚拟地址空间。

2. mmap函数可以创建新的虚拟内存area,并把磁盘对象映射到新建的area。

mmap是一种高效的操作文件的方式,直接把一个文件映射到内存,通过修改内存就相当于修改了磁盘文件,减少了普通文件操作的一次拷贝操作。普通文件操作时会先把文件内容从磁盘复制到内核空间管理的一块虚拟内存区域area,然后内核再把内容复制到用户空间管理的虚拟内存area。 mmap相当于创建了一个内核空间和用户空间共享的area,文件的内容只需要在这个area对应的物理内存和磁盘文件之间交换即可。mmap一般是在非堆空间来创建area

参考资料:

《深入理解计算机系统》

时间: 2024-11-08 18:10:13

计算机底层知识拾遗(一)理解虚拟内存机制的相关文章

计算机底层知识拾遗(三)理解磁盘的机制

磁盘是一种重要的存储器,位于主存结构的下方,是永久存储的介质.在计算机底层知识拾遗(一)理解虚拟内存机制 这篇中说了虚拟内存是面向磁盘的,理解磁盘的工作原理对理解计算机的很多概念有很大的帮助.尤其是在数据库和分布式存储领域,要经常和磁盘打交道. 磁盘这块主要有几个部分的概念: 1. 磁盘的基本结构和工作原理 2. 如何在虚拟内存机制下与内存高效地交换数据 3. 磁盘如何保证数据存储的可靠性及故障恢复 磁盘的基本结构和工作原理 从单个磁盘来说,由一个个的同心圆组成,一个同心圆就是一个磁道,每个磁道

计算机底层知识拾遗(二)深入理解进程和线程

关于进程和线程,大家总是说的一句话是"进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元".这句话理论上没问题,我们来看看什么是所谓的"资源"呢. 什么是计算机资源 经典的冯诺依曼结构把计算机系统抽象成 CPU + 存储器 + IO,那么计算机资源无非就两种: 1. 计算资源 2. 存储资源 CPU是计算单元,单纯从CPU的角度来说它是一个黑盒,它只对输入的指令和数据进行计算,然后输出结果,它不负责管理计算哪些"指令和数据". 换句话

计算机底层知识拾遗(四)理解文件系统

操作系统的很多核心组件都是相互关联的,比如虚拟内存管理,物理内存管理,文件系统,缓存系统,IO,设备管理等等,都要放在一起来看才能从整体上理解各个模块到底是如何交互和工作的.这个系列的目的也就是从整体上来理解计算机底层硬件和操作系统的一些重要的组件是如何工作的,从而来指导应用层的开发.这篇讲讲文件系统的重要概念,为后面的IO系统做铺垫. 文件系统主要有三类 1. 位于磁盘的文件系统,在物理磁盘上存储文件,比如NTFS, FAT, ext3, ext4 2. 虚拟文件系统,在内核中生成,没有物理的

计算机底层知识拾遗(六)理解页缓存page cache和地址空间address_space

在这篇计算机底层知识拾遗(五)理解块IO层 中讲了块缓存buffer cache块缓存,这篇说说页缓存page cache以及相关的地址空间address_space的要点. 在Linux 2.4内核中块缓存buffer cache和页缓存page cache是并存的,表现的现象是同一份文件的数据,可能即出现在buffer cache中,又出现在页缓存中,这样就造成了物理内存的浪费.Linux 2.6内核对两个cache进行了合并,统一使用页缓存在做缓存,只有极少数的情况下才使用到buffer

计算机底层知识拾遗(十)理解进程调度【转】

转自:http://www.cnblogs.com/zfyouxi/p/4504042.html 这篇说说内核的进程调度机制,进程调度是内核的一个重要工作,由调度器完毕. 进程状态 内核调度器调度的实体(KSE, kernal schedule entry)是进程和线程.内核必须知道全部进程和线程的状态,比方把时间片给一个堵塞的进程是没有意义的.从内核的角度来看,进程的状态有3种: 1. 执行,表示正在执行的进程 2. 等待,没有执行,可是等待时间片执行的进程 3. 睡眠,也就是堵塞,包含可中断

计算机底层知识拾遗(七)页缓存数据同步和页回收机制

这篇说说Linux的页缓存数据同步和页回收机制.数据同步和页回收是两个独立的概念,数据同步处理的是内存/缓存的数据和后备设备的数据一致问题,页回收处理的是在内存空间不足时如何回收已分配的物理内存页,来获得足够空间分配干净页,支持优先级更高的工作.数据同步在任意时刻都有可能触发,页回收则是在物理内存使用达到一定阀值的时候触发. 数据同步就是把物理内存和页缓存中的脏页写回到后备设备的文件中去.有两种方式可以调用数据同步 1. 周期性的调用,主要是pdflush机制 2. 强制调用,比如调用sync,

计算机底层知识

1 这篇万字长文,一下子把计算机底层知识说明白了 https://mp.weixin.qq.com/s?__biz=Mzg2NTA4OTUwOQ==&mid=2247486427&idx=3&sn=f03bc1a695a9e2f837673f3012d65c6c&chksm=ce5e2951f929a047413a06b85c26d898ca9126f1369147e35a763542d435134584987a4e45b6&mpshare=1&scene=

计算机基本知识拾遗(七)页面缓存数据的同步和恢复机制页

本讲座Linux页面缓存数据的同步和恢复机制页.数据同步和恢复是两个独立的页面概念.数据同步处理是存储器/数据一致性问题缓存数据和备份设备.页面回收是如何回收分配的内存空间不足的物理内存页.为了获得足够的空间来分配一个干净的页面,支持更高优先级的工作.步在随意时刻都有可能触发,页回收则是在物理内存使用达到一定阀值的时候触发. 数据同步就是把物理内存和页缓存中的脏页写回到后备设备的文件里去.有两种方式能够调用数据同步 1. 周期性的调用,主要是pdflush机制 2. 强制调用,比方调用sync,

计算机基础知识对编程的重要性

码农和架构师的差别在哪里 从题目看文章,大家的第一想法是本篇文章是一篇讲述计算机基础知识和编程之间的关系,但是为什么开篇是这样的呢?当然这样的开篇并没有多大的意义,唯一的目的就是吸引读者而已. 纵观计算机专业的学生在毕业之后大多数会走向编写程序的道路,当然也会有部分同学有自己更感兴趣的事情,从而在毕业之后选择其他的职业.在这里根据我个人在毕业之后的编写程序的道路,写下一点总结,希望能和大家交流,有什么不对的地方,希望大家能给我指正,因为我个人觉得"三人行必有我师"是一句很有道理的话,每