stratovirt是开源在openeuler社区的轻量级虚拟化平台,具备轻量低噪、强安全性的行业竞争力。 stratovirt进程运行在用户态,在虚拟机启动之前,stratovirt会完成启动之前的准备工作,包括虚拟机内存的初始化、cpu寄存器初始化、设备初始化等,启动,cpu寄存器初始化和虚拟机在运行过程中vcpu陷出事件的处理,都是由stratovirt的vcpu管理模块cpu完成。如下是stratovirt中vcpu管理模块的组成,以及其在stratovirt中的位置。
stratovirt├── acpi├── address_space├── boot_loader├── cargo.lock├── cargo.toml├── cpu│ ├── cargo.toml│ └── src│ ├── aarch64│ │ ├── caps.rs│ │ ├── core_regs.rs│ │ └── mod.rs│ ├── lib.rs│ └── x86_64│ ├── caps.rs│ ├── cpuid.rs│ └── mod.rs├── devices├── hypervisor├── machine├── machine_manager├── migration├── migration_derive├── ozone├── pci├── src│ └── main.rs├── sysbus├── util├── vfio└── virtio
stratovirt vcpu模块的整体设计
stratovirt的虚拟化解决方案也是一套软硬结合的硬件辅助虚拟化解决方案,它的运作依赖于硬件辅助虚拟化的能力(如vt-x或kunpeng-v)。vcpu模块的实现也是紧密依赖于这一套硬件辅助虚拟化的解决方案的。 对于物理机的cpu而言,硬件辅助虚拟化为cpu增加了一种新的模式:non-root模式,在该模式下,cpu执行的并不是物理机的指令,而是虚拟机的指令。这种指令执行方式消除了大部分性能开销,非常高效。但是特权指令(如i/o指令)不能通过这种方式执行,还是会强制将cpu退出到普通模式(即root模式)下交给内核kvm模块和用户态stratovirt去处理,处理完再重新回到non-root模式下执行下一条指令。 而stratovirt中的vcpu模块主要围绕着kvm模块中对vcpu的模拟来实现,为了支持kvm模块中对cpu的模拟,cpu子系统主要负责处理退出到普通模式的事件,以及根据在guestos内核开始运行前对vcpu寄存器等虚拟硬件状态的初始化。整个vcpu模块的设计模型如下图所示:
stratovirt通过第三方库kvm_ioctls来完成和kvm模块的交互,通过匹配vcpu_fd.run()函数的返回值来处理退出到root模式的事件,该函数的返回值是一个名为vcpuexit的枚举,定义了退出到root模式的事件类型,包括i/o的下发、系统关机事件、系统异常事件等,根据事件的类型vcpu将对不同的事件作出各自的处理。以上的整个过程都被包含在一个独立的vcpu线程中,用户可以自己通过对vcpu线程进行绑核等方式让虚拟机的vcpu获取物理机cpu近似百分之百的性能。 同时,对vcpu寄存器虚拟硬件状态信息的初始化则是和stratovirt的另一个模块bootloader相互结合,在bootloader中实现了一种根据linux启动协议快速引导启动linux内核镜像的方法,在这套启动流程中,bootloader将主动完成传统bios对一些硬件信息的获取,将对应的硬件表保存在虚拟机内存中,同时将提供一定的寄存器设置信息,这些寄存器设置信息将传输给vcpu模块,通过设置vcpu结构中的寄存器值,让虚拟机cpu跳过实模式直接进入保护模式运行,这样linux内核就能直接从保护模式的入口开始运行,这种方式让stratovirt的启动流程变得轻量快速。 在整个vcpu模块中,因为涉及到内核的kvm模块,少不了与c语言代码做交互。作为系统编程语言,rust对ffi有非常完善的支持,让vcpu中和kvm模块交互的部分高效且安全。
vcpu线程模型同步
vcpu模块还有一大职责就是管理vcpu的生命周期,包括new(创建),realize(使能),run(运行),pause(暂停),resume(恢复),destroy(销毁)。new和realize的过程就是结构体创建和寄存器初始化的流程,run的过程即是实现kvm中vcpu运作和vcpu_exit退出事件处理的流程。 另外的三种生命周期的实现则涉及到对线程同步的精密控制,例如在虚拟机destroy的过程中,一般只有某一个vcpu接收到vcpu_exit中的shutdown事件,该vcpu线程需要把该事件传递到所有的vcpu线程,同步所有vcpu线程的状态,完成虚拟机的优雅关机。在这种场景下,我们就需要考虑在rust中如何实现在多线程中进行状态同步。
rust中通过条件变量来实现同步
rust多线程编程中,有一类用于同步的机制叫做屏障(barrier),用于让多线程来同步一些流程开始的位置,它相当于一个闸口,使用wait方法,将该线程放进临界区并阻塞住,只有每个barrier都到达wait方法调用的点,闸口才会打开,所有的线程同步往下运行。 而在比较复杂的同步场景中,rust还提供了另一个同步机制条件变量(condition variable)来支持更复杂的同步场景,它和屏障的功能类似,但是它并不阻塞全部进程,而是在满足指定的条件之前阻塞某个得到互斥锁的进程。也就是说,通过条件变量,我们可以在达到某种条件之前阻塞某个线程,这个特性可以让我们很好得对线程进行同步。 为了支持各种场景的同步控制,条件变量还提供了三个方法:
notify_one(): 用来通知一次阻塞线程,如果有复数个线程被阻塞住,notify_one会被一个阻塞的线程所消耗,不会传递到别的阻塞线程去。
notify_all(): 用来通知所有的阻塞线程。
wait_timeout(): 将当前线程置入临界区阻塞住并等待通知,可以设定一个timeout来设置阻塞的最大时间,以免造成永久的阻塞导致程序卡死。
需要注意的一点是条件变量需要和锁一起使用,而在程序运行中,每个条件变量每次只能和一个互斥体(被mutex等锁包裹都可称为互斥体)进行使用。
vcpu生命周期控制和线程同步
在cpu数据结构初始化时,创建一个互斥的生命周期枚举(cpulifecyclestate)和一个条件变量。
pub fn new( vcpu_fd: arc, id: u8, arch_cpu: arc, vm: arc, ) -> self { cpu { id, fd: vcpu_fd, arch_cpu, state: arc::created), condvar::new())), work_queue: arc::new(0), condvar::new())), task: arc::new(none)), tid: arc::new(none)), vm: arc::downgrade(&vm), } }以destory生命周期为例,在x86_64架构下,当某个vcpu线程接收到vcpuexit::shutdown事件后,会将该线程的cpulifecyclestate修改为stopped,并调用保存在cpu数据结构中一个指向上层结构的虚拟机destroy方法,该方法能遍历一个保存着所有cpu数据结构的数组,执行数组中每一个cpu的destory()方法,该函数的实现如下:fn destory(&self) -> result { let (cpu_state, cvar) = &*self.state; if *cpu_state.lock().unwrap() == cpulifecyclestate::running { *cpu_state.lock().unwrap() = cpulifecyclestate::stopping; } else { *cpu_state.lock().unwrap() = cpulifecyclestate::stopped; } /* 省略具体的关机逻辑 */ let mut cpu_state = cpu_state.lock().unwrap(); cpu_state = cvar .wait_timeout(cpu_state, duration::from_millis(32)) .unwrap() .0; if *cpu_state == cpulifecyclestate::stopped { *cpu_state = cpulifecyclestate::nothing; ok(()) } else { err(errorkind::destroyvcpu(format!(vcpu still in {:?} state, *cpu_state)).into()) }}作为cpu的成员方法,destory函数能获取到每个cpu数据结构的互斥状态和条件变量,此时将除触发vcpu外所有的cpu数据的互斥状态解锁,并将状态从运行时的running修改为vcpu关机时的stopping。这里要注意一点,此时所有cpu的destroy函数都是在触发关机事件的vcpu进程中进行的,而不是在每个vcpu各自的进程中进行。 紧接着进入stopping状态后,destroy函数会执行每个vcpu各自的关机逻辑,包括触发vcpu,这部分主要还是与kvm模块进行交互,进行一些退出状态的变更等。在执行完vcpu的关机逻辑后,条件变量会进入到wait_timeout的等待状态,它的参数为每个vcpu的cpulifecyclestate生命周期状态枚举和等待超时时间,也就是说在该生命周期枚举状态变化前,该线程都会进入阻塞状态。 此时除触发vcpu外的vcpu线程中,cpulifecyclestate都已经进入了stopping状态,在所有vcpu线程中,vcpu的指令模拟函数kvm_vcpu_exec()都运行在一个循环中,对于每次循环的入口,都会执行ready_for_running()函数进入是否继续模拟的判断,在该函数中会对每个vcpu对应的cpulifecyclestate进行监控,当发现cpulifecyclestate已经变成stopping时,vcpu将会退出循环,不继续进行vcpu的模拟,退出模拟的循环后,将会修改cpulifecyclestate为stopped:// the vcpu thread is about to exit, marking the state of the cpu state as stopped.let (cpu_state, _) = &*self.thread_cpu.state;*cpu_state.lock().unwrap() = cpulifecyclestate::stopped;修改vcpu线程中互斥的生命周期状态枚举后,将会触发阻塞线程中对应的wait_timeout()函数,同时,该vcpu线程的生命周期结束。而对于阻塞线程,当其余vcpu线程的状态都已经变成stopped后,阻塞解除,此时,所有的vcpu线程都已经状态都已经同步到了stopped,线程状态同步成功。 用类似思路也可以实现pause(暂停)和resume(恢复)的生命周期控制。
原文标题:stratovirt vcpu管理rust线程同步的实现
文章出处:【微信公众号:openeuler】欢迎添加关注!文章转载请注明出处。
Intel计划推出一款新的酷睿i9-9990XE 采取拍卖的方式销售
基于CBI传输结构的USB硬盘接口应用设计
TDA9801各引脚功能的电压参数资料
百度以AI赋能企业工作全场景,引领企业智能化转型
蒸馏也能Step-by-Step:新方法让小模型也能媲美2000倍体量大模型
StratoVirt中vCPU管理模块的组成及位置
百度DeepWay技术研发中心具备高速公路自动驾驶的各项功能
丰田牵手比亚迪和宁德时代 加速纯电动汽车研发
加速AI走深向实 携手昇腾共推产业新发展
iphone15是Type-C吗
晶澳科技获EuPD智利及墨西哥市场“2022顶级光伏品牌”
开关电源变压器参数_开关电源变压器结构组成
智能魔镜将为我们打造一个舒适智能的家居生活
工业物联网与消费物联网的区别
SiC MOSFET的桥式结构及栅极驱动电路
Wifi 6E 路由器可能会用于 5G 蜂窝网络?
四门版思域Type R发布!中置三出排气强烈的征服欲望!
离线语音蓝牙设计应用案例详解
ARM收购AMD新论 处理器巨头欲掀并购潮
后端程序员必备:分布式事务基础篇