StratoVirt vCPU管理Rust线程同步的实现
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模块的整体设计
kvm_ioctls
来完成和KVM模块的交互,通过匹配 vcpu_fd.run()
函数的返回值来处理退出到ROOT模式的事件,该函数的返回值是一个名为 VcpuExit
的枚举,定义了退出到ROOT模式的事件类型,包括I/O的下发、系统关机事件、系统异常事件等,根据事件的类型vCPU将对不同的事件作出各自的处理。以上的整个过程都被包含在一个独立的vCPU线程中,用户可以自己通过对vCPU线程进行绑核等方式让虚拟机的vCPU获取物理机CPU近似百分之百的性能。 vCPU线程模型同步
VCPU_EXIT
退出事件处理的流程。 VCPU_EXIT
中的 SHUTDOWN
事件,该vCPU线程需要把该事件传递到所有的vCPU线程,同步所有vCPU线程的状态,完成虚拟机的优雅关机。在这种场景下,我们就需要考虑在Rust中如何实现在多线程中进行状态同步。 Rust中通过条件变量来实现同步
-
notify_one(): 用来通知一次阻塞线程,如果有复数个线程被阻塞住, notify_one
会被一个阻塞的线程所消耗,不会传递到别的阻塞线程去。 -
notify_all(): 用来通知所有的阻塞线程。 -
wait_timeout(): 将当前线程置入临界区阻塞住并等待通知,可以设定一个 timeout
来设置阻塞的最大时间,以免造成永久的阻塞导致程序卡死。
vCPU生命周期控制和线程同步
CPU
数据结构初始化时,创建一个互斥的生命周期枚举( CpuLifecycleState
)和一个条件变量。 pub fn new(
vcpu_fd: Arc<VcpuFd>,
id: u8,
arch_cpu: Arc<Mutex<ArchCPU>>,
vm: Arc<Mutex<dyn MachineInterface + Send + Sync>>,
) -> Self {
CPU {
id,
fd: vcpu_fd,
arch_cpu,
state: Arc::new((Mutex::new(CpuLifecycleState::Created), Condvar::new())),
work_queue: Arc::new((Mutex::new(0), Condvar::new())),
task: Arc::new(Mutex::new(None)),
tid: Arc::new(Mutex::new(None)),
vm: Arc::downgrade(&vm),
}
}
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
生命周期状态枚举和等待超时时间,也就是说在该生命周期枚举状态变化前,该线程都会进入阻塞状态。 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;
wait_timeout()
函数,同时,该vCPU线程的生命周期结束。而对于阻塞线程,当其余vCPU线程的状态都已经变成 Stopped
后,阻塞解除,此时,所有的vCPU线程都已经状态都已经同步到了 Stopped
,线程状态同步成功。 关注我们
StratoVirt 当前已经在 openEuler 社区开源。后续我们将开展一系列主题分享,如果您对 StratoVirt 的使用和实现感兴趣,欢迎您围观和加入。
项目地址:https://gitee.com/openeuler/stratovirt
项目wiki:https://gitee.com/openeuler/stratovirt/wikis
您也可以订阅邮件列表:https://mailweb.openeuler.org/postorius/lists/virt.openeuler.org/
如有疑问,也欢迎提交 issue:https://gitee.com/openeuler/stratovirt/issues
入群
如果您对虚拟化技术感兴趣,可以进入 openEuler StratoVirt 主页查看相关资源,点击阅读原文进入项目主页,包括安装指导、虚拟机配置、代码仓库、学习资料等。也欢迎加入 Virt SIG 技术交流群,讨论 StratoVirt、KVM、QEMU 和 Libvirt 等相关虚拟化技术。感兴趣的同学可以添加如下微信小助手,回复 StratoVirt 入群。
本文分享自微信公众号 - openEuler(openEulercommunity)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
在有道 | 同宇:一个正在老去的程序员
——“你想用什么词语来形容自己?” ——“一个正在老去的程序员。” 这次的访谈,我们请到了有道技术团队服务端开发工程师同宇。 在抖音上,他是“有道无霸哥”里的“老大”,和作死小弟们斗智斗勇,用标志性的笑容和坚硬的铁拳把整个短视频推向高潮。而在这次访谈里,他则是以一个31岁的程序员身份,平和而深刻地与我们分享他的人生思考。 关于35岁,关于内卷,关于压力,关于后浪,关于未来,关于热爱。 以下是访谈摘录: Q:你喜欢程序员的工作吗? A:说不上来。至少来讲这是一个比较纯粹的工作。 对感官刺激越纯粹的东西,越容易让人投入,比如文字往往会比绘画更引人投入,绘画比电影更引人投入。一件事对感官的刺激越多,越容易让人理解,也就越不容易让人投入进去。 Q:所以你写代码的时候会很投入? A:是的,写代码对我而言其实也是宣泄压力的一种方式,是一种“体力活”。我比较喜欢简单直接的事,如果既要靠关系,又要靠人格魅力,还有其他因素,那加在一起这个方程我不会算。 你问我喜欢做技术吗,我觉得我喜欢,但是让我死记硬背一些无聊的东西,我可记不住。 同宇的 todolist Q:有压力特别大的时候吗? A:有一段时间特别...
- 下一篇
面试官:Java 线程如何启动的?
摘要:Java 的线程创建和启动非常简单,但如果问一个线程是怎么启动起来的往往并不清楚,甚至不知道为什么启动时是调用start(),而不是调用run()方法呢? 本文分享自华为云社区《Thread.start() ,工作这么久,还不知道它是怎么让线程跑起来的!》,作者:小傅哥。 面试官:我考你个题,看看你进大厂的几率大不。嗯... Java 线程如何启动的? 谢飞机:如何启动的?start 启动的! 面试官:还有吗? 谢飞机:嗯…,没了! 面试官:嗯,可能会与不会这一个题并不会让你代码有多牛、有多好,但是你的技术栈深度和广度,决定你的编程职业生涯是否有一条康庄大道。还是要多努力! 一、线程启动分析 new Thread(() -> { // todo }).start(); 咳咳,Java 的线程创建和启动非常简单,但如果问一个线程是怎么启动起来的往往并不清楚,甚至不知道为什么启动时是调用start(),而不是调用run()方法呢? 那么,为了让大家有一个更直观的认知,我们先站在上帝视角。把这段 Java 的线程代码,到 JDK 方法使用,以及 JVM 的相应处理过程,展示给...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS关闭SELinux安全模块
- CentOS8编译安装MySQL8.0.19
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Docker安装Oracle12C,快速搭建Oracle学习环境