Page 67 - 《软件学报》2025年第9期
P. 67

3978                                                       软件学报  2025  年第  36  卷第  9  期


                 删除或修改映射和降低权限, 由于影子页表中仍然保留的是原来映射或者权限的表项, 虚拟机就会访问错误的内
                 存地址, 从而造成虚拟机崩溃. 然而, 在任何会导致虚拟机崩溃的操作发生时, 客户机操作系统总是会刷新                               TLB  以
                 保证  TLB  和页表的一致性.

                                             表 3 客户机操作系统页表修改行为分析

                     修改分类             页表行为            是否需要刷新TLB                   页表不一致后果
                                      新增映射                 否                产生由未映射导致的虚拟机退出
                     映射修改
                                    删除/修改映射                是              错误, 虚拟机直接访问无权限内存区域
                                      提升权限                 否              产生由无权限访问导致的虚拟机退出
                     权限位修改
                                      降低权限                 是              错误, 虚拟机直接访问无权限内存区域

                    因此, 懒惰影子页表摒弃了传统写保护的同步方式, 而是通过拦截虚拟机进行页表修改后的绑定操作进行页
                 表同步. 这样一来, 无需执行       TLB  刷新的页表更新操作      (即新增映射和提升权限) 总是会在后续的虚拟机退出中重
                 新进行页表同步, 而需要执行         TLB  刷新的页表更新操作       (即删除/修改映射和降低权限) 就可以在执行              TLB  刷新
                 产生的虚拟机退出处理程序中进行页表同步, 保证了虚拟机执行的安全性. 由此, 该方法可以将传统页表同步产生
                 的两次虚拟机退出减少至一次虚拟机退出.
                    客户机操作系统对进程页表进行修改后, 总是需要将行为同步到影子页表中以保证地址转换的正确性. 而在
                 这之外, MMU   也会主动对页表进行修改, 例如置位访问位或者脏位. 该修改场景下的页表同步方向与客户机操作
                 系统修改场景下的页表同步方向相反, 因为              MMU  将直接读写影子页表, 软件需要将硬件对于影子页表的修改同
                 步到进程页表以保证客户机操作系统的稳定性. 传统影子页表已经具有较为成熟的解决方案对访问位和脏位进行
                 同步. 当客户机操作系统开始监控访问位时, hypervisor 将被监控页面的影子页表项的存在位                       (present bit) 标记为
                 0, 这样客户机任何对于该页面的访问都将触发一次                 VM-exit 进行访问位同步. 当客户机操作系统开始监控脏位
                 时, hypervisor 将被监控页面的影子页表项的写权限位           (writable bit) 标记为  0, 这样客户机任何对于该页面的写都
                 将触发一次    VM-exit 进行脏位同步. 这类从影子页表向进程页表的同步过程通常并不会被频繁触发, 并且信息位
                 的不一致后果通常不会导致严重的虚拟机崩溃. 因此对于该类同步我们仍然沿用传统的同步方式, 而懒惰影子页
                 表将针对更加关键的进程页表向影子页表同步过程进行优化.

                 3.2   页表同步行为推迟
                    在传统影子页表模型下, 进程页表和影子页表之间不存在不一致的时刻, 在虚拟机准备修改进程页表之前, 就
                 已经发生写错误异常从而同步更新了影子页表. 即使使用页表同步机制绑定, 二者之间的同步也是同时更新并且
                 一一映射的. 在页表同步的实现中, hypervisor 需要软件遍历进程页表, 影子页表和第                  2  级页表进行同步, 该操作会
                 导致高额的软件开销. 特别是相比于传统架构, RISC-V              的虚拟机退出更加轻量级, 而执行高开销的页表同步操作
                 就更成为了性能瓶颈. 在传统方法下, 每次页表更新操作都一定会对应一次该高开销的多维页表遍历进行页表
                 同步.
                    页面访问具有局部性, 通常         80%  的时间会聚集在访问       20%  的页面上, 而分析操作系统对于页表的修改可以
                 发现, 在一些页表修改的场景并不存在该性质              (比如系统在监控页面时发生的周期性地全局读写访问位和脏位, 进
                 程  fork  时发生的全局写时复制). 同时, 如果虚拟机并不访问发生修改的页表项, 该不一致性实际上并不会导致任
                 何访存错误和虚拟机崩溃. 因此懒惰影子页表将该高开销的同步操作延迟到页面发生访问时进行. 具体而言, 当拦
                 截到  TLB  刷新时, 懒惰影子页表只将对应的影子页表项无效化, 该操作不需要遍历进程页表和第                           2  级映射页表,
                 更加轻量级.
                    当虚拟机使用这些无效化的页表项进行地址翻译时, 会触发一次虚拟机退出, 在此刻进行页表同步操作. 由
                 此, 懒惰影子页表将真正的页表同步操作从修改时刻推迟到了使用时刻, 该时刻已经是页表同步操作的最晚时刻,
                 在修改时刻和使用时刻之间, 由于该页表项并不会被访问, 所以也不存在安全问题. 这样做的好处在于如果对于某
                 一个进程页表项存在频繁的更新操作, 而对于该表项映射的内存访问并不频繁的话, 这些更新操作可以完全被批
   62   63   64   65   66   67   68   69   70   71   72