Hypervisor
虚拟化
intel书里的定义,
虚拟化定义 一台物理机构建多个虚拟机
hypervisor就是物理机和虚拟机之间的中间层,允许多个操作系统和应用共享硬件
和emulator的区别
虚拟化,物理环境与虚拟环境体系结构一致 通构
isa理解成指令集架构
指令集可以直接转交给物理环境
模拟器就可以不一样
模拟器主要目的是仿真
虚拟化的两种类型
1型效率比较高,hypervisor是专用软件,就用来实现虚拟化
2型比如linux的kvm机制
这里用1型
支撑资源对象层次
vm包括整个机器的地址空间
vm中几个执行流就有几个vcpu
在vcpu之间调度实现虚拟机并发
vmem根据虚拟机平台规格来模仿的
vDevice有些虚拟设备在现实中不存在,模拟,存在的就映射
vutilities是把剩下的归为一大类,中断虚拟化或者总线发现设备
vCPU和CPU关系
虚拟cpu,通过多任务
vcpu直接绑定单个cpu,受限cpu数量
内存
虚拟机认为自己有一块或几块物理内存设备,且内存连续
实际都是不连续的,间隔零散的状态
Hypervisor基于页表扩展等机制,维护两者之间的关系
设备
跟cpu类似
比如网卡,设备可以当做一个网关或网桥
虚拟设备把数据发到网关和网桥上
还有一种由于插槽有限,有一种网卡只需要一个插槽,
但里面包含多个核,可以当做多个物理网卡
h_1_0建立最简化的Hypervisor
跟宏内核类似
这里虚拟化环境要启动一个Guest OS内核
特权级H扩展
没有设置之前,只有3个特权级
M最高,用来运行sbi
低一点S特权级,super啥啥内核一般在这一级
最低user,放应用
开虚拟化后
从S级做一个分层扩展,叫HS,虚拟化S
既有S特权级的性质,又能转向hypervisor虚拟化的特性
可以受控地通过vm_entry进入与host并列的guest环境
这里有虚拟化S特权级VS,和VU
一般情况,虚拟化应用在VU只会与VS进行切换
只有超出了虚拟化范围容纳范围,才会进入HS
体系结构H扩展后的寄存器
之前只有s打头的寄存器
加了hypervisor后会新加一组h打头的寄存器
hsxxx寄存器,面向guest域进行路径控制
对它进行某些特殊设置以后,会创造一种条件,在特定情况下会切换到guest运行虚拟机的内核或应用
同样两组,guest里的s寄存器vsxxx跟原本的s寄存器有对应关系
还有一组寄存器在vu里,很少实现user级别的控制,很少用到
准备条件
第一个是否能启用虚拟化扩展
ISA寄存器在misa第七位代表Hypervisor启用禁止,m态寄存器,比s高
如果支持这个是1,不然是0
opensbi会打印支持哪些扩展mafdch最后一个h代表支持
写入0可以禁用这个h扩展
定制sbi,m态能操作,s态不行
做一个路径控制
在s态,切换到用户态指令是sret
虚拟化环境,同样通过sret来切换到vs态
通过hstatus第七位的spv来决定进入是vs还是u
决定在进入s特权级之前上一级是谁
如果上一次从虚拟化vs过来的话,spv是1
spvp的supervisor是决定虚拟化hs有没有对vs的操作权限,现在希望有操作权限,所以是1
前面设置好后,后面
类似于宏内核进入到第一个用户应用一样
把具体入口和状态设置好
主要设置guest里的sstatus,初始状态设置成s特权级
切过去从s态开始运行
第二个设置guest里的sepc,内核启动入口,RIscv64启动入口就是0x8020_0000所以设置成这个就可以
sret后会从sepc的启动入口开始运行
真正的切换
封装了一个run_guest函数
首先把hypervisor里有可能破坏的寄存器保存起来
如果之前有guest上下文,或者伪造的上下文,加载到虚拟机的寄存器组里
退出
上面过程反过来
退出原因
第一种原因,执行了guest里无法承担的操作
典型是调用了sbi-call
guest里没有m模式特权级
第二种原因
故意制造的缺页异常
应用触发第一阶段缺页异常,这时候从GVA->GPA,由于没有真正的物理页帧,则会触发进一步的缺页
跟U用户态应用缺页进入host kernel的缺页类似
简化实验模型
因为只有一个虚拟机,没有虚拟机管理的对象,被忽略了
只有一个全局的虚拟机
也没有启动多任务,只启动了一个虚拟机任务
目标和需要解决的问题
两种虚拟方式
模拟方式
跟u_3_0一样,给pflash准备一个文件
针对每个虚拟机都可以设置个文件
里面存pflash内容
上面访问,hypervisor就返回文件里内容
透传模式
直接把qemu里的pflash设备给映射进虚拟机
行成一对绑定关系
第一种方式,如果多虚拟机,每个都可以提供一个不同的文件
第二种方式,效率高,如果绑给了一个就不能给其他虚拟机了
两种模式都涉及到两阶段映射问题
guest域host的地址空间关系
guest是虚拟机所在的执行环境
host是hypervisor所在的执行环境
hypervisor在下面支撑guest虚拟机运行
需要3个地址空间
hypervisor需要管理所有物理地址空间,简称Hpa
给guest虚拟一个guest的物理地址空间,gpa
guest有了gpa后,hypervisor工作完成,后续guest会根据自己的gpa建立自己的虚拟地址空间gva
如果gva缺页,会找gpa补全,如果gpa已有,可以在小循环内完成
如果gpa也没有,一般也是个假象,一般没有,就继续找hpa要映射
体系结构riscv64对guest地址映射支持
两阶段映射
vsatp第一阶段页表翻译,v代表virtual s代表super
第二阶段hgatp,h代表host,g代表guest
现在需要映射范围小,可以支撑,没有处理扩展
虚拟化情况下,对三个区域采用不同处理方式
mmio设备模拟透传
sbi加载区域,直接映射,少量lazy映射
本来这段区域用来加载sbi
但这里没有加载
因为guest里没有m态
guest里用不到sbi所以null
上面区域都是lazy映射方式
什么时候缺页再填充
第二个实验
只有一个vm,忽略对象
hypervisor第一部创建和初始化两个资源,地址空间和vcpu
地址空间初始化
vcpu需要设置初始寄存器值
流程
前三步完成了地址空间的工作主要看axmm和axfs
然后设置vcpu的启动入口,就是启动后第一行指令从哪里开始
设置成guest内核里的入口,就是那个啥0x80xxxxx
还有设置页表,是guest里的的页表
vcpu准备代码
一般体系结构相关,所以会做个hal层,但这里直接就进体系结构相关的init了
创建对象,设置entry设置ept页表
set_entry,进入虚拟机前,设置的spec会被进一步写入sepc寄存器 sepc是发生异常的时候,保存的断点
当通过run_guest进入虚拟机的时候,这个断点会被恢复出来作为指令地址
一旦进入guest,就会从这个地址开始执行
set_ept_root设置二级页表根地址
hgatp
俩值,前面的值是模式,8代表sv39
后面是根页表的页根,
直接通过指令csrw写进去就可以了
然后刷一下快表
页表才能真正生效,不然cache里的内容是旧的
host与guest环境之间切换的框架
hypervisor是起点,做准备工作
然后它执行run_guest特殊函数
完成切换
里面要做的工作是把Hypervisor里可能被破坏的寄存器的值保存起来
然后把已经准备好的或者退出的guest上下文载入寄存器组
到此准备好了虚拟机进入条件
然后通过sret,从host环境切换到guest环境
之后开始运行guest os内核
如果guestos要执行特权指令,或者要访问的资源超出了虚拟化能容纳的范围
会发生退出vm-exit
hypervisor提前准备好了退出的入口
退出入口里的工作跟上面的工作相反
把当前虚拟机涉及的寄存器存起来
恢复host的寄存器组
于是恢复到hypervisor发生断点进入前的状态
可以继续执行hypervisor的逻辑
处理好异常后,继续调用上面的过程
环境切换框架的实现
无论首次进入guest,还是将来进入guest,通过同一个入口vcpu_run
vcpu_run在关中断情况下调用具体体系结构的vcpu_run
就是那个_run_guest进入guest
vmexit_handler处理体系结构相关exit_reason,回去后如果第一个ok没法处理,进入err处理
具体的5步骤
保存host状态,以备从guest恢复
设置sepc等寄存器,指示guest从断点处继续的位置 首次就是入口地址那个0x8020
上面是状态
第四步是恢复guest的寄存器状态
最后是sret,之前设置过hstatus指示上一步是从guest进入的(假装的),所以执行sret会回到guest
第三个跟guest退出相关,
stvec寄存器值临时设成了_guest_exit
这样guest发生虚拟机退出,就是从这个入口找的
走_guest_exit
guest_exit如何实现
跟run_guest是同一个文件
跟run_guest就是个逆过程
需要注意
先恢复hyp_ra保存的ra值恢复到ra
然后ret的时候会把ra的值作为指令寄存器的值
就是会从ra指向的值,做一个函数返回
这个值是run_guest的返回值
再看右边函数
从_run_guest(regs)是为了从host进入guest
保存寄存器的时候其中要保存的一项就是ra项
当时把ra存到了hyp_ra
这个值是这个函数的返回地址
进入到guest之后,发生了虚拟机退出
退出的时候恢复这个hyp_ra
正好就是那个函数的返回地址
因而会执行到下一步vmexit_handler()
所以_run_guest会发生特权级切换,进到guest虚拟机运行,这边就停住了,block?
而guest退出的时候刚好能落回来
模拟pflash的两种方式
第一种没有映射,触发嵌套的缺页异常
回到Hypervisor
如果发现是pflash的,直接跟qemu的pflash地址做一个关联
采用map_linear线性映射
完成了一个绑定
另一种在Hypervisor同样截获之后,采用读文件的方式读取后备文件
使用map_alloc申请一个page frame
从文件里把对应的文件内容给填充进去
因为已经完成了两阶段映射
异常返回以后,内核再次访问这个地址就能访问到真正的页帧
也在h_2_0实验里面
这边注释掉了,放开,并注释掉左边,就能看到
简单对比宏内核和Hypervisor
宏内核是SU切换
guest回到Hypervisor没有sys-call方式
都是异常或中断的方式
都要做地址空间的隔离
两阶段地址映射补充说明
Hypervisor也启用了分页
也有va->pa的过程
在hypervisor内部va通过走satp指向的页表从虚拟地址映射到物理地址
另一条路Hypervisor起一个guest运行操作系统内核
guest里也分页
访问guest里的gva地址的时候
首先做第一阶段映射,通过vsatp,这个寄存器指向根页表
完成从gva到gpa的映射
gpa本身是虚拟的,guest当它是真实pa
如果gpa已经有Hypervisor准备好了
就完成了没有第二阶段
很多情况下gpa是没有的
会发生第二阶段地址映射,通过hgatp
这一步是从gpa到hpa,hpa就是hypervisor的pa
hgatp和satp指向的根页表,是两个不同的页表
两个实验
如果虚拟机里想支持中断,时钟中断
虚拟机外设的支持
pflash本来需要封装成外设,这里就是封装这个外设
第二个实验尝试启动宏内核
要加载用户应用的img
通过透传pflash的方式加载应用
实验目标
配合的Hypervisor内核是第一周做的第六个实验
引入了抢占式调度,依赖时钟中断
里面会多出来很多timer相关的日志
timer irq emulation是在Hypervisor发现guest因为时钟中断退出后再将时钟中断注入回去
具体机制和实现
受限在axhal注册时钟中断
axtask会向runqueue传递定时事件
会传给特定调度器task_tick
每次触发后会检查下内部状态
内外条件都具备,会导致任务队列重排
内部俩组件
一个axruntime
会去注册定时器响应函数
更新定时器关键update_timer
核心代码最后一行,通过axhal设置了一个一次性的定时器
前面计算下一次触发需要什么时间
具体实现最后调用的sbi_call
sbi服务提供了设置定时器
u_6_0面临的问题
具体怎么在虚拟机里运行
两个办法,能让虚拟机内核仍然响应时钟中断
高级方法,riscv有个aia机制,高级中断架构,可以把特定中断,委托到guest环境下
需要平台支持,实现比较复杂
简单方法,中断注入
时钟中断设置和注入的实现
受限响应虚拟机发出的SBI-Call如果调用的是setTimer
要代替它去调用set_timer
每次guest触发中断都会通过sbi-call调用set_timer
guest没有m态
退出虚拟机
Hypervisor替它调用
触发时钟中断,不管在guest还是hypervisor都会被中断运行
然后退进Hypervisor
禁用时钟中断
然后给注入回去
因为虚拟机被中断打断之后,不会进入虚拟机的中断入口表stvec
只会退出
而我们需要guest虚拟机能感受到时钟中断,触发响应时钟中断的内部函数
可以设置hvip注入时钟中断
这个stvec可以加个v嗯?
时钟中断设置和注入的实现
第一步响应虚拟机发出的SBI-Call
在Hypervisor就能截获到原因
第一步把pending bit给清零,就是hvip时钟中断对应bit
因为这时候还没来时钟中断
把host时钟中断的bit给打开
然后等时钟到期
虚拟机又被打断
退回到Hypervisor
再检查原因,发现是S级的定时中断导致的
设置hvip,显然是时钟到期了
把这个事通知到guest里去
完成了时钟中断的注入
把sie,host的时钟中断给禁掉
不能在这个过程里再次触发时钟中断
等到什么时候虚拟机再次settimer的时候,也就是左边,再给打开
h.4.0实验目标
前面的功能串一串
配合的guest内核是m_1_1
如何从hypervisor加载宏内核image
宏内核启动后,如何获得应用的image
让Hypervisor给虚拟机模拟出来一个pflash
模式是透传模式
把下面qemu的pflash给透传上来
建立对虚拟设备管理的支持
第一层是虚拟机简称vm
为了方便管理外设,增加group一层
理解成一种容器,便于管理
然后是代表虚拟机设备对象vmdev
riscv通过mmio访问虚拟机
核心资源就是一个地址区域
提供方法只有handle_mmio()响应访问
本来还应该分read和write
具体到实现
现要注册设备
建立一个group
把设备加进来
更优雅应该从配置注册进来
一开始没设备,访问的时候会触发嵌套式页面异常
查看是不是在物理之下,还是之内
然后找目标设备
调用handle_mmio
多vcpu支持
每个vcpu对应一个task
可以直接复用task里的调度机制
有时一个虚拟机里会有多个cpu
一个虚拟机有多vcpu但也是一个地址空间
因为调度器看到的是task
同一个vm下的task切换,不会切换根页表
其他的会
对应于task扩展,需要添加额外资源
这里是vcpu资源和关联虚拟机资源
其实可以省掉,axvmref,因为vcpu里有一个引用
使用宏内核的搞法类似定义
最后调用宏
hypervisor的配置
虚拟机的配置
比如vmware
通过一个图形化工具,一部分是配置软件,可以配置cpu,内存,和哪些设备
还有个os镜像,或者根文件系统

Generated 2025-05-05 04:17:35 +0000
25bcfca-dirty 2025-05-04 14:47:56 +0000