美文网首页LeetCode 算法题 BAT 字节跳动 拼多多 美团 京东大厂面试题集锦linux
一切皆是映射:浅谈操作系统内核的缺页异常(Page Fault)

一切皆是映射:浅谈操作系统内核的缺页异常(Page Fault)

作者: 光剑书架上的书 | 来源:发表于2021-03-25 23:53 被阅读0次

    浅谈操作系统内核的缺页异常(Page Fault)

    缺页中断就是要访问的页不在主存,需要操作系统将其调入主存后再进行访问。在这个时候,被内存映射的文件实际上成了一个分页交换文件。

    页缺失(英语:Page fault,又名硬错误、硬中断、分页错误、寻页缺失、缺页中断、页故障等),指的是当软件试图访问已映射在虚拟地址空间中,但是并未被加载在物理内存中的一个分页时,由中央处理器的内存管理单元所发出的中断。

    简单讲,就是:内核接住了这个异常,并处理了这个异常(Page Fault Handler)。

    通常情况下,用于处理此中断的程序是操作系统的一部分。如果操作系统判断此次访问是有效的,那么操作系统会尝试将相关的分页从硬盘上的虚拟内存文件中调入内存。而如果访问是不被允许的,那么操作系统通常会结束相关的进程。

    虽然其名为“页缺失”错误,但实际上这并不一定是一种错误。而且这一机制对于利用虚拟内存来增加程序可用内存空间的操作系统(比如Microsoft Windows和各种类Unix系统)中都是常见且有必要的。

    术语约定

    VA:Virtual Address 虚拟地址
    PA:Physical Address 物理地址
    MMU:Memory Manage Unit 内存管理单元
    TLB:Translation Lookaside Buffer 旁路快表缓存/地址变换高速缓存
    PTE:Page Table Entry 分页表项

    概述

    CPU通过地址总线可以访问连接在地址总线上的所有外设,包括物理内存、IO设备等等,但从CPU发出的访问地址并非是这些外设在地址总线上的物理地址,而是一个虚拟地址,由MMU将虚拟地址转换成物理地址再从地址总线上发出,MMU上的这种虚拟地址和物理地址的转换关系是需要创建的,并且MMU还可以设置这个物理页是否可以进行写操作,当没有创建一个虚拟地址到物理地址的映射,或者创建了这样的映射,但那个物理页不可写的时候,MMU将会通知CPU产生了一个缺页异常。

    只有程序运行时用到了才去内存中寻找虚拟地址对应的页帧,找不到才可能进行分配,这就是内存的惰性(延时)分配机制。

    对于一个运行中的进程来说,不是所有的虚拟地址在物理内存中都有对应的页。虚拟地址空间根据固定大小一般是4KB进行划分,物理内存可以设置不同的页面大小,通常物理页大小和虚拟页大小是一样的,都是 4KB。

    CPU如何获取内存中的数据?

    CPU并不直接和物理内存打交道,而是把地址转换的活外包给了MMU,MMU是一种硬件电路,其速度很快,主要工作是进行内存管理,地址转换只是它承接的业务之一。

    一起看看MMU是如何搞定地址转换的。

    一切皆是映射。(光剑)

    MMU和Page Table

    每个进程都会有自己的页表Page Table,页表存储了进程中虚拟地址到物理地址的映射关系,所以就相当于一张地图,MMU收到CPU的虚拟地址之后开始查询页表,确定是否存在映射以及读写权限是否正常,如图:

    对于4GB的虚拟地址且大小为4KB页,一级页表将有2^20个表项,页表占有连续内存并且存储空间大,多级页表可以有效降低页表的存储空间以及内存连续性要求,但是多级页表同时也带来了查询效率问题。

    我们以2级页表为例,MMU要先进行两次页表查询确定物理地址,在确认了权限等问题后,MMU再将这个物理地址发送到总线,内存收到之后开始读取对应地址的数据并返回。

    MMU在2级页表的情况下进行了2次检索和1次读写,那么当页表变为N级时,就变成了N次检索+1次读写。

    可见,页表级数越多查询的步骤越多,对于CPU来说等待时间越长,效率越低,这个问题还需要优化才行。

    本段小结 敲黑板 划重点
    1.页表存在于进程的内存之中,MMU收到虚拟地址之后查询Page Table来获取物理地址。
    2.单级页表对连续内存要求高,于是引入了多级页表,但是多级页表也是一把双刃剑,在减少连续存储要求且减少存储空间的同时降低了查询效率。

    CPU觉得MMU干活虽然卖力气,但是效率有点低。有没有提升效率的办法呢?

    计算机科学中的所有问题,都可以通过添加一个中间层来解决。

    我们知道 CPU 用的数据经常是一小搓,但是每次MMU都还要重复之前的步骤来检索,害,就知道埋头干活了,也得讲究方式方法呀!

    找到瓶颈之后,MMU引入了新武器,江湖人称快表的TLB(其实,就是缓存),别看TLB容量小,但是正式上岗之后干活还真是不含糊。

    当CPU给MMU传新虚拟地址之后,MMU先去问TLB那边有没有,如果有就直接拿到物理地址发到总线给内存,齐活。

    TLB容量比较小,难免发生Cache Miss,这时候MMU还有保底的老武器页表 Page Table,在页表中找到之后MMU除了把地址发到总线传给内存,还把这条映射关系给到TLB,让它记录一下刷新缓存。

    TLB容量不满的时候就直接把新记录存储了,当满了的时候就开启了淘汰大法把旧记录清除掉,来保存新记录,彷佛完美解决了问题。

    TLB的容量毕竟有限,为此必须依靠Page Table一起完成TLB Miss情况的查询,并且更新到TLB建立新映射关系。

    缺页异常Page Fault大揭秘

    设想,CPU给MMU的虚拟地址,在TLB和 Page Table都没有找到对应的物理页帧,该怎么办呢?

    没错,这就是缺页异常Page Fault,它是一个由硬件中断触发的可以由软件逻辑纠正的错误。

    假如目标内存页在物理内存中没有对应的页帧或者存在但无对应权限,CPU 就无法获取数据,这种情况下CPU就会报告一个缺页错误。

    由于CPU没有数据就无法进行计算,CPU罢工了用户进程也就出现了缺页中断,进程会从用户态切换到内核态,并将缺页中断交给内核的 Page Fault Handler 处理。

    缺页异常并不可怕,只要CPU要的虚拟地址经过MMU的一番寻址之后没有找到或者找到后无权限,就会出现缺页异常,因此触发异常后的处理流程将是重点内容。

    缺页错误的分类处理

    缺页中断会交给PageFaultHandler处理,其根据缺页中断的不同类型会进行不同的处理:

    • Hard Page Fault 也被称为Major Page Fault,翻译为硬缺页错误/主要缺页错误,这时物理内存中没有对应的页帧,需要CPU打开磁盘设备读取到物理内存中,再让MMU建立VA和PA的映射。
    • Soft Page Fault 也被称为Minor Page Fault,翻译为软缺页错误/次要缺页错误,这时物理内存中是存在对应页帧的,只不过可能是其他进程调入的,发出缺页异常的进程不知道而已,此时MMU只需要建立映射即可,无需从磁盘读取写入内存,一般出现在多进程共享内存区域。
    • Invalid Page Fault 翻译为无效缺页错误,比如进程访问的内存地址越界访问,又比如对空指针解引用内核就会报segment fault错误中断进程直接挂掉。

    Buffered IO/mmap/Direct IO 与 Page Cache

    page cache用于缓存文件的页数据,buffer cache用于缓存块设备(如磁盘)的块数据。页是逻辑上的概念,因此page cache是与文件系统同级的;块是物理上的概念,因此buffer cache是与块设备驱动程序同级的。

    page cache与buffer cache的共同目的都是加速数据I/O:写数据时首先写到缓存,将写入的页标记为dirty,然后向外部存储flush,也就是缓存写机制中的write-back(另一种是write-through,Linux未采用);读数据时首先读取缓存,如果未命中,再去外部存储读取,并且将读取来的数据也加入缓存。操作系统总是积极地将所有空闲内存都用作page cache和buffer cache,当内存不够用时也会用LRU等算法淘汰缓存页。

    page cache中的每个文件都是一棵基数树(radix tree,本质上是多叉搜索树),树的每个节点都是一个页。根据文件内的偏移量就可以快速定位到所在的页,如下图所示。

    IO 会产生page cache ,具体过程参考下图:

    首先往用户缓冲区 buffer(这是 Userspace Page) 写入数据,然后 buffer 中的数据拷贝到内核缓冲区(这是 Pagecache Page),如果内核缓冲区中还没有这个 Page,就会发生 Page Fault 会去分配一个 Page,拷贝结束后该 Pagecache Page 是一个 Dirty Page(脏页),然后该 Dirty Page 中的内容会同步到磁盘,同步到磁盘后,该 PageCache Page 变为 Clean Page 并且继续存在系统中。

    番外篇 : Kafka对page cache的利用

    Kafka为什么不自己管理缓存,而非要用page cache?原因有如下三点:

    • JVM中一切皆对象,数据的对象存储会带来所谓object overhead,浪费空间;
    • 如果由JVM来管理缓存,会受到GC的影响,并且过大的堆也会拖累GC的效率,降低吞吐量;
    • 一旦程序崩溃,自己管理的缓存数据会全部丢失。

    Kafka三大件(broker、producer、consumer)与page cache的关系可以用下面的简图来表示。

    producer生产消息时,会使用 pwrite() 系统调用(对应到Java NIO中是FileChannel.write() API),按偏移量写入数据,并且都会先写入page cache里。consumer消费消息时,会使用sendfile()系统调用(对应FileChannel.transferTo() API),零拷贝地将数据从page cache传输到broker的Socket buffer,再通过网络传输。

    参考资料

    https://blog.csdn.net/sinat_22338935/article/details/114320664
    https://www.jianshu.com/p/92f33aa0ff52

    相关文章

      网友评论

        本文标题:一切皆是映射:浅谈操作系统内核的缺页异常(Page Fault)

        本文链接:https://www.haomeiwen.com/subject/zeudhltx.html