XV6 0x12
视频结尾论文部分
VM FOR APPLICATION
原语
论文论述的(primitive)原语
及在linux
上的实现
- trap 中断处理函数 ——> sigaction
- prrt1 减少访问权限 ——> mprotect
- portN 节省TLB刷新 ——> mprotect
- unport增加访问权限 ——> mprotect
- dirty脏页
- map2 ——> mmap
unix/linux today
mmap,unmap
将文件映射到虚拟内存,允许用指针来操作文件mprotect
,修改页权限sigaction
,定义信号处理程序
USER-LEVEL TRAPS
用户可以设置中断处理程序。中断产生时,跳转到内核,内核发现用户设置了中断处理程序,跳转到中断处理程序,处理完后返回内核,然后内核恢复进程。
应用
calculate
我们可以将昂贵的函数计算值存储在内存中,若我们想控制计算值内存空间的大小,我们可以通过mmap
和munmap
保持计算值的内存大小。当计算值存储产生page fault时,我们可以选择一个牺牲页,将旧值替换为新值。
garbage collect
A copying garbage collector:
from space:原来的状态,包含垃圾,从root 遍历图,指针指向能遍历到的节点。
- forward(转发)
to space:新的状态,将指针指向的节点复制过来。
Backer’s: real time <— incrementally
USE VM
通过map2将未扫描的页面映射到垃圾回收器,实现对未扫描的页面访问。
当页面访问时触发中断,在中断处理程序进行检查,将要访问的对象复制到to space。
- //TODO
其他特性
- 5 levels pagetable:页表层数增加
- ASIO :异步I/O操作
- 刷新TLB
- KPTI:将内核页表与用户空间页表隔离
OS Organization
BIG ABSTRACTION
PORTABILITY 可移植性
HIDE COMPLEXITY 隐藏底层的复杂性
RSESOURCE MANAGEMENT 资源管理
WHY NOT MONOLITHIC
- BIG -> complex -> bugs ->
security
- GENERAL -> purpose ->
slow
Design hard
(add module or remove module)
- BIG -> complex -> bugs ->
MICRO KERNELS (微内核)
- IDEA:TINY KERNEL
(inter-process communication)IPC
进程间通信TASKS
WHY
- small ——> security
- small ——> verifiable ——> sel4
- small ——> fast
- small ——> flexibility
- user level ——> modular、customize
CHALLENGES
- minimum syscall API fork、exec
- rest of O/S
- fast IPC
L4 kernel
- small kernel
- 直接在L4微内核将linux内核作为
用户任务
运行。- 用户任务通过定制的库将系统调用通过
IPC
发送给linux kernel - 任务调度由 l4 kernel负责,linux无法调度。所以这里linux和task的关系更像
cs模型
。
- 用户任务通过定制的库将系统调用通过
- 7 syscall
- support task、address space、thread、IPC
- 如出现中断等:task通过IPC发送给中断处理程序(如缺页异常),中断处理用户程序将页映射到该task。
- fast ipc
- synchronous、unbuffered
- 想象当两个任务进行
pipe通信
,当一方send(a,&addr1)且同时一方recv(b, &addr2)时,我们能获得消息的源地址和目的地址。可以直接在两任务间进行IPC传输而不用进入L4内核(避免上下文切换、pipe缓冲区的复制、TLB刷新等操作)- 当消息可以被寄存器放下时,可以通过寄存器传输
registers —— zero copy
- 当消息很大时,我们甚至可以直接传输
页 PAGE
RPC
call() —— send + recv
- 即我们的内核等基础服务像服务器那样监听任务传来的IPC通信讯息
- 当消息可以被寄存器放下时,可以通过寄存器传输
- dual space mistake(双空间错误)
- linux将内核线程和用户进程映射到同一个页表,减少系统调用等开支。
- 即在L4 kernel,内核和用户进程是不共用页表的,但是论文作者想实现linux相似功能(即用户的虚拟地址对内核是可见的),为每个任务分配对应的linux内核副本。但是由于这样导致任务翻倍,效果反而大打折扣。
virtual machines
虚拟机分类
- 软件模拟:VMware、qemu
- 硬件辅助:Microsoft Hyper-V、KVM
pure virtual machines software: so slow
VMM实现
HOST: VMM
USER: guest Linux、guest windows
TRAP-AND-EMULATE
VMM在
监督者模式
下运行,用以执行特权指令,客户机(linux)做任何有特权的事时,都会进入trap,然后VMM模拟特权指令。监视器为每个guest user维护一个virtual state,当执行特权指令时(如stvec、sepc),VMM
模拟
这些指令的操作,确保它们在虚拟机内部正确执行,同时保护物理硬件不受虚拟机直接访问的威胁。客户机视角来看,VMM是不存在的,她们直接与硬件进行交互。
satp寄存器
:当客户机(guest)尝试访问内存时,它会使用虚拟地址(GVA - Guest Virtual Address)。
虚拟机监视器(VMM)需要将这个虚拟地址转换为物理地址(GPA - Guest Physical Address),以便在物理硬件上访问实际的内存位置。
查看期望的客户机虚拟地址:客户机尝试访问的虚拟地址被记录下来。
VMM映射:VMM使用其内部的数据结构(如影子页表)来查找GPA对应的物理地址。
影子页表:
(shadow pagetable)影子页表
是VMM维护的一个数据结构,它将GVA映射到GPA,然后将GPA映射到实际的物理地址。设置satp:一旦VMM找到物理地址,它将这个物理地址加载到
satp
寄存器中
Device
- EMULATION:将设备地址设置为不可访问,当客户机访问时,进入
trap
- 客户机可以根据访问速度判度她们处在虚拟机中
- VIRTUAL DEVICE:设备和虚拟硬件是
解隅
的(减少trap),其设计和实现与物理硬件的接口相匹配 - PASS-THRU:硬件支持虚拟机。
- EMULATION:将设备地址设置为不可访问,当客户机访问时,进入
HARDWARE VMS-VT-s
- 让客户机直接通过硬件执行特权指令
- 虚拟机:”NON-ROOT”
- VMM: “ROOT”
- 通过硬件支持特权指令的检测
- 通过VMM指令开启启动关闭客户机
- EPT:VMM通过设置EPT指向一个页表来控制客户机可访问的物理内存
Dune(VT-X support)
进程通过Dune设置EPT实现独立的页表
获得user mode和 super mode
- 沙箱支持,被检测的程序在用户状态下运行,检测程序在监督者模式下运行,使得可以追踪系统调用等情况
- 垃圾回收,可以对扫描过的节点检测PTE 的Dirty位来判断时候需要重新扫描。
kernels and high level language
C
优点
可以精确控制内存分配和释放
易于理解的代码(指linux之父能从c看到汇编(人肉编译器))
直接访问内存(通过指针)
低依赖,指通过几条汇编指令设置寄存器后即可跳转到C代码
缺点
- 缓冲区溢出
- 使用已经释放的内存
- 线程共享的内存释放问题
高级语言
- 优点
- 类型安全
- 垃圾回收机制
- 对并发的支持
- 支持更好的抽象,模块化代码
- 缺点
- 低性能
- 边界检查,类型转换等操作消耗性能
- 垃圾回收
- 不适用于内核编程
- 不能直接访问内存
- 难以集成汇编,如c和cpp提供了内联汇编的功能,也可以在链接时链接到汇编程序‘’
- 编程语言本身支持的并发与内核需要的并发并不一致
- 低性能
meltdown
KASLR
:内核地址随机化预测执行:根据分支预测的结果来提前执行指令的过程。
如果预测正确,则能显著提升性能;如果预测失败,
则所有推测执行的结果会被抛弃,并且流水线会被清空。
影子寄存器
暂存推测执行的结果,直到分支预测的结果被确认。
- 漏洞原因:
intel cpu
在执行命令时会先加载内存的值,当加载退出时才会检测标志
caches结构:
core
L1: va | data | perms TLB:
va -> pa
L2、L3: pa | data
RAM
在多核处理器系统中,L2缓存通常是每个核心私有的,而L3缓存是多个核心共享的。如果L2和L3缓存使用虚拟地址,那么在处理缓存一致性问题时会变得复杂,因为不同的核心可能有不同的虚拟地址映射到相同的物理地址。使用物理地址可以简化这个问题,因为物理地址在所有核心之间是一致的。
缓存刷新:FLUSH + RELOAD —-x
- clFlush x 刷新缓冲
- f()
- a = rdtsc 获取加载时间
- junk = x
- b = rdtsc 再次获取时间
- b - a = c 若c时间短则说明使用了x内存。
漏洞原理:逐步获取内核每一位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24char buf[8192];
//FLUSH + RELOAD
clflush buf[0];
clfush bf[4096];
//消耗cpu时间的指令,延长`加载`退出时间
r1 = kernel addr;
r2 = r1 & 1;
r2 = r2 * 4096; //r2为0或4098
r3 = buf[r2]; //将buf[r2]加载到缓存中
< handle the page fault from "r2 = *r1" > //不恢复缓存
//计算加载时间,通过时长判断是否在缓存
// the reload of flush+reload
a = rdtsc;
r0 = buf[0];
b = rdtsc;
r1 = buf[4096];
c = rdtsc
if b - a < c - b //buf[0]在缓存中
//0 & 1 = 0, 0 * 4096 = 0,则该bit为0
- 解决方案:
- 像xv6将内核和用户空间反别使用虚拟地址空间
- 在读取指令时获取指令权限
- 通过操作系统修复
RCU(读远多于写)
multiple reader,only one writer
- 维持write锁和read锁(一个变量n,-1表示写,0表示没有进程使用,>=1表示有一个或多个读者),通过
Compare and Swap(CAS)
实现原子操作- 如果有多个 读写者,则陷入竞争
- 如果是多核机器,需要保持
缓存一致性
(标记其他cpu的缓存无效),导致O(n^2)的消耗。
- 维持write锁和read锁(一个变量n,-1表示写,0表示没有进程使用,>=1表示有一个或多个读者),通过
IDEA
- 更新数据分配一个副本,再将指针指向副本(commit 原子的, 需要在编写时添加屏障(读写者都需要)),同时不要释放原来数据,因为可能有读者正在访问
延迟冻结
- 读者不能进行上下文切换
- 将释放延迟到每个cpu至少切换过一次上下文。