kvm-PLE代码分析
Linux源码版本: 5.3.0
相关数据结构
#define KVM_DEFAULT_PLE_GAP 128 // ple_gap
#define KVM_VMX_DEFAULT_PLE_WINDOW 4096 //ple_window
// ple_window的增大系数,每次调用grow_ple_window时,ple_window增大2倍
#define KVM_DEFAULT_PLE_WINDOW_GROW 2
// ple_window的缩小系数
#define KVM_DEFAULT_PLE_WINDOW_SHRINK 0
// ple_window最大不能超过这么大,该值在32bit和64bit机器上取值不同
#define KVM_VMX_DEFAULT_PLE_WINDOW_MAX UINT_MAX
// ple_window和ple_gap的初始化,前提是该vcpu没有禁用ple
if (!kvm_pause_in_guest(vmx->vcpu.kvm)) {
vmcs_write32(PLE_GAP, ple_gap);
vmx->ple_window = ple_window;
vmx->ple_window_dirty = true;
}
PAUSE Exit的处理
Intel的cpu上,使用的VMM为kvm时,当guest的vcpu变为busy-waiting状态,也就是loop-wait状态,就会在一定情况下触发vmexit.
触发条件: 由于kvm中不会使能"PAUSE exiting"feature,因此单一的PAUSE指令不会导致vmexit,kvm中只使用"PAUSE-loop exiting" feature,即循环(loop-wait)中的PAUSE指令会导致vmexit,具体情境为:当一个循环中的两次PAUSE之间的时间差不超过PLE_gap常量,且该循环中某次PAUSE指令与第一次PAUSE指令的时间差超过了PLE_window,那么就会产生一个vmexit,触发原因field会填为PAUSE指令.
kvm代码中,如果进入了handle_pause()函数,说明已经触发了pause_vmexit.
handle_pause()的大致结构:

其中,grow_ple_window()是为了让"没有禁用PLE的guest"调整PLE_window
/*
* Indicate a busy-waiting vcpu in spinlock. We do not enable the PAUSE
* exiting, so only get here on cpu with PAUSE-Loop-Exiting.
*/
static int handle_pause(struct kvm_vcpu *vcpu)
{
if (!kvm_pause_in_guest(vcpu->kvm)) // 1. 如果该vm没有禁用PLE,则增大PLE_window的值
grow_ple_window(vcpu);
/*
* Intel sdm vol3 ch-25.1.3 says: The "PAUSE-loop exiting"
* VM-execution control is ignored if CPL > 0. OTOH, KVM
* never set PAUSE_EXITING and just set PLE if supported,
* so the vcpu must be CPL=0 if it gets a PAUSE exit.
*/
kvm_vcpu_on_spin(vcpu, true); // 2. 找一个之前被抢占,目前又可以运行的vcpu,继续运行spin-loop
// 结束当前vcpu的spin状态
/*
* 3. 如果guest在debug状态,则产生了单步中断,(vcpu_enter_guest)返回0,exit到userspace继续处理
* 如果guest不在debug状态,则(vcpu_enter_guest)返回1,无需exit到userspace处理
*/
return kvm_skip_emulated_instruction(vcpu);
}
/*
* 返回值为:当前guest是否禁用PLE feature
* 禁用:返回true
* 没有禁用: 返回false
*/
static inline bool kvm_pause_in_guest(struct kvm *kvm)
{
return kvm->arch.pause_in_guest;
}
/* 增加PLE_window的值,new_ple_window *= 2 */
static void grow_ple_window(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
int old = vmx->ple_window;
vmx->ple_window = __grow_ple_window(old, ple_window,
ple_window_grow,
ple_window_max);
if (vmx->ple_window != old)
vmx->ple_window_dirty = true;
trace_kvm_ple_window_grow(vcpu->vcpu_id, vmx->ple_window, old);
}
kvm_pause_in_guest()
分析过程
不知道这个kvm_pause_in_guest()是什么意思,但在handle_pause()中可以看到的是,每次发生PAUSE vmexit,都会检查kvm_pause_in_guest()的返回值,如果返回值为false,则要增大PLE_window的值。
分析1:
思考一下什么条件下需要增大PLE_window的值呢?
只有在kvm觉得这个guest提前exit了的时候,才需要增大PLE_window,因为再多等一下就可以等到那个锁了。
结合kvm_pause_in_guest()函数的名字,猜测该函数返回的是PAUSE vmexit前等待的那个锁是否还没有打开,如果没打开,返回true,如果打开了,就返回false.
分析2:
kvm development mail list中对kvm_pause_in_guest()返回的arch.pause_in_guest的说明是:"Allow to disable pause loop exit/pause filtering on a per VM basis. If some VMs have dedicated host CPUs, they won't be negatively affected due to needlessly intercepted PAUSE instructions.", 大意为,允许在特定guest上禁用PLE(intel)/PF(amd). 如果有些guest拥有绑定的host cpu,则不会由于不必要地拦截PAUSE指令而对它们产生负面影响。
什么意思呢?假设有guestA和guestB,guestA有2个固定vcpu,绑定在host的cpu0,cpu1上,guestB有2个vcpu,不固定host cpu。
当在guestB上的vcpu0上发生spin-loop时,需要vcpu1上的lock,但是vcpu1由于调度原因去做其他事情了,该lock无法处理,guestB只能拦截PAUSE指令,exit到host.
当在guestA上的vcpu0上发生spin-loop时,需要vcpu1上的lock,因为vcpu1固定属于guestA,不会被调度去做其他事情,相比与guestB,lock的平均解锁时间肯定小于guestB,所以就没必要exit到host,spin-wait就行.
结论
综上所述,kvm_pause_in_guest()返回的是该guest是否禁用了PLE,如果禁用了就返回true,否则false.
该结论的代码支持:
// arch/x86/kvm/x86.c
int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
struct kvm_enable_cap *cap)
{
...
case KVM_CAP_X86_DISABLE_EXITS:
...
if (cap->args[0] & KVM_X86_DISABLE_EXITS_PAUSE) // 如果禁用该vm中的pause exit
kvm->arch.pause_in_guest = true; // 该bool值就为true
...
}
kvm_vcpu_on_spin()
首先查找了mail list中该函数的相关内容,发现了KVM: introduce kvm_vcpu_on_spin,"Introduce kvm_vcpu_on_spin, to be used by VMX/SVM to yield processing once the cpu detects pause-based looping.",直接说明了kvm_vcpu_on_spin()函数的用意,"一旦cpu检测到pause-loop,就会进行相关操作。"
该函数主要将当前vcpu中的剩余spin-loop的剩余任务切换到新的vcpu中执行
// virt/kvm/kvm_main.c
void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode)
{
...
// 将刚刚pause vmexit的vcpu设置为in-spin-loop状态
kvm_vcpu_set_in_spin_loop(me, true);
// 将当前未运行,但状态为可运行的vcpu的优先级提高,因为这样的vcpu之前被抢占了,
// 被抢占之后又在__vcpu_run()中运行了调度函数,所以我们要提高它的优先级。
// 希望这些vcpu中包含我们需要的锁,从最后一个被提升优先级的vcpu开始循环试图切换
for (pass = 0; pass < 2 && !yielded && try; pass++) {
....
}
// 将当前vcpu设置为非spin-loop状态
kvm_vcpu_set_in_spin_loop(me, false);
// 确保当前vcpu在下一次spin-loop时不被选为exit的vcpu
// 因为只有大家轮流执行spin-loop,性能才能平均且 高
kvm_vcpu_set_dy_eligible(me, false);
}
kvm_skip_emulated_instruction()
该函数主要获取当前vcpu的RFLAGS寄存器内容,赋值给当前guest的相应数据结构。同时检查是否需要产生单步中断.
使Guest的RIP跳过一个指令.
// arch/x86/kvm/x86.c
int kvm_skip_emulated_instruction(struct kvm_vcpu *vcpu)
{
unsigned long rflags = kvm_x86_ops->get_rflags(vcpu);
int r = EMULATE_DONE;
// 更新rip值,确保guest interruptibiliy state的最后2bit为0,即STI和MOV SS为0,即不接收中断
kvm_x86_ops->skip_emulated_instruction(vcpu);
/*
* rflags is the old, "raw" value of the flags. The new value has
* not been saved yet.
*
* This is correct even for TF set by the guest, because "the
* processor will not generate this exception after the instruction
* that sets the TF flag".
*/
if (unlikely(rflags & X86_EFLAGS_TF)) //如果guest处于debug状态,就会产生单步中断,那么就会将r置为1
kvm_vcpu_do_singlestep(vcpu, &r);
return r == EMULATE_DONE; // 如果产生单步中断,则需要exit到VMM中处理
}
static void skip_emulated_instruction(struct kvm_vcpu *vcpu)
{
// 获取RIP寄存器应该跳跃的值,然后将新的RIP值更新到之前vmexit的vcpu寄存器中
unsigned long rip;
rip = kvm_rip_read(vcpu);
rip += vmcs_read32(VM_EXIT_INSTRUCTION_LEN);
kvm_rip_write(vcpu, rip);
/* skipping an emulated instruction also counts */
vmx_set_interrupt_shadow(vcpu, 0);
}
/* 该函数的本意为:
* 如果在vmexit期间,该vm的GUEST_INTERRUPTIBILITY_INFO发生变化,那么就将变化写入vmcs.
* 但在以上skip_emulated_instruction()中,调用了vmx_set_interrupt_shadow(vcpu, 0); mask为0时,
* vmx_set_interrupt_shadow的只是在确定GUEST_INTERRUPTIBILITY_INFO的最后2bit,
* 即GUEST_INTR_STATE_STI和GUEST_INTR_STATE_MOV_SS是否一直为0,而这2个bit为调试使用的状态
*/
void vmx_set_interrupt_shadow(struct kvm_vcpu *vcpu, int mask)
{
u32 interruptibility_old = vmcs_read32(GUEST_INTERRUPTIBILITY_INFO);
u32 interruptibility = interruptibility_old;
interruptibility &= ~(GUEST_INTR_STATE_STI | GUEST_INTR_STATE_MOV_SS);
if (mask & KVM_X86_SHADOW_INT_MOV_SS)
interruptibility |= GUEST_INTR_STATE_MOV_SS;
else if (mask & KVM_X86_SHADOW_INT_STI)
interruptibility |= GUEST_INTR_STATE_STI;
if ((interruptibility != interruptibility_old))
vmcs_write32(GUEST_INTERRUPTIBILITY_INFO, interruptibility);
}
/* 单步中断的赋值操作 */
static void kvm_vcpu_do_singlestep(struct kvm_vcpu *vcpu, int *r)
{
struct kvm_run *kvm_run = vcpu->run;
if (vcpu->guest_debug & KVM_GUESTDBG_SINGLESTEP) {
kvm_run->debug.arch.dr6 = DR6_BS | DR6_FIXED_1 | DR6_RTM;
kvm_run->debug.arch.pc = vcpu->arch.singlestep_rip;
kvm_run->debug.arch.exception = DB_VECTOR;
kvm_run->exit_reason = KVM_EXIT_DEBUG;
*r = EMULATE_USER_EXIT;
} else {
kvm_queue_exception_p(vcpu, DB_VECTOR, DR6_BS);
}
}
kvm-PLE代码分析的更多相关文章
- 几篇QEMU/KVM代码分析文章
QEMU/KVM结合起来分析的几篇文章,代码跟最新的版本有些差异,但大体逻辑一样,写得通俗易懂.我把链接放这里主要是为自己需要查看时调转过去方便,感谢作者的付出! QEMU Source Code S ...
- 关于Linux虚拟化技术KVM的科普 科普二(KVM虚拟机代码揭秘)
代码分析文章<KVM虚拟机代码揭秘--QEMU代码结构分析>.<KVM虚拟机代码揭秘--中断虚拟化>.<KVM虚拟机代码揭秘--设备IO虚拟化>.<KVM虚拟 ...
- IO-Polling的代码分析
在前一篇文章<IO-Polling实现分析与性能评測>中提到了IO-Polling与中断的原理差别,并通过两种模式下NVMe SSD的性能測试对两者进行了对照. 这篇文章将深入到IO-Po ...
- qemu-kvm 代码分析
qemu-kvm 代码分析 虚拟机组成 实际上面所说计算机组成比较笼统,实际处理器,存储器,输入与设备种类繁多, - i440FX host PCI bridge and PIIX3 PC ...
- OpenStack 虚拟机冷/热迁移的实现原理与代码分析
目录 文章目录 目录 前文列表 冷迁移代码分析(基于 Newton) Nova 冷迁移实现原理 热迁移代码分析 Nova 热迁移实现原理 向 libvirtd 发出 Live Migration 指令 ...
- Android代码分析工具lint学习
1 lint简介 1.1 概述 lint是随Android SDK自带的一个静态代码分析工具.它用来对Android工程的源文件进行检查,找出在正确性.安全.性能.可使用性.可访问性及国际化等方面可能 ...
- pmd静态代码分析
在正式进入测试之前,进行一定的静态代码分析及code review对代码质量及系统提高是有帮助的,以上为数据证明 Pmd 它是一个基于静态规则集的Java源码分析器,它可以识别出潜在的如下问题:– 可 ...
- [Asp.net 5] DependencyInjection项目代码分析-目录
微软DI文章系列如下所示: [Asp.net 5] DependencyInjection项目代码分析 [Asp.net 5] DependencyInjection项目代码分析2-Autofac [ ...
- [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(5)(IEnumerable<>补充)
Asp.net 5的依赖注入注入系列可以参考链接: [Asp.net 5] DependencyInjection项目代码分析-目录 我们在之前讲微软的实现时,对于OpenIEnumerableSer ...
- 完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)
构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...
随机推荐
- AJAX传值中文乱码
AJAX传值时采用的是UTF-8编码格式,客户端中文字符传输到服务器端时,如果服务器编码格式或者所采用的MVC框架的编码格式不是UTF-8,则很可能会出现中文乱码.解决办法如下: 客户端用js函数en ...
- 华三交换机NTP配置
clock protocol ntp ntp-service enable ntp-service unicast-server x.x.x.x clock timezone beijing add ...
- linux-Navicat连接linux远程数据
linux-Navicat连接linux远程数据 (一)登陆数据库 (二)创建用户用于远程连接 GRANT ALL PRIVILEGES ON *.* TO '账号'@'%' IDENTIFIED B ...
- git从安装到多账户操作一套搞定(一)入门使用
作者:良知犹存 转载授权以及围观:欢迎添加微信:Allen-Iverson-me-LYN 总述 GIT是当今热门代码管理技术,但是如此火的系统,竟然是大神林纳斯花了两周用C写出来的一个分布式版 ...
- JVM垃圾回收之三色标记
三色标记法是一种垃圾回收法,它可以让JVM不发生或仅短时间发生STW(Stop The World),从而达到清除JVM内存垃圾的目的.JVM中的CMS.G1垃圾回收器所使用垃圾回收算法即为三色标记法 ...
- E - E(最短路解决源点到多点,多点到源点的和(有向图))
问从1号点到各个点的距离+各个点到1号点之间的距离和的最小值 详解键连接https://www.cnblogs.com/csx-zzh/p/13411588.html In the age of te ...
- FZU1894 志愿者选拔
Problem Description 世博会马上就要开幕了,福州大学组织了一次志愿者选拔活动.参加志愿者选拔的同学们排队接受面试官们的面试.参加面试的同学们按照先来先面试并且先结束的原则接受面试官们 ...
- Docker之Dockerfile文件
Dockerfile是一堆指令,每一条指令构建一层,因此每一条指令的内容就是描述该层应当如何构建,在docker build的时候,按照该指令进行操作,最终生成我们期望的镜像文件 Dockerfile ...
- Java中多线程启动,为什么调用的是start方法,而不是run方法?
前言 大年初二,大家新年快乐,我又开始码字了.写这篇文章,源于在家和基友交流的时候,基友问到了,我猛然发现还真是这么回事,多线程启动调用的都是start,那么为什么没人掉用run呢?于是打开我的ide ...
- oslab oranges 一个操作系统的实现 实验五 让操作系统走进保护模式
实验目的: • 如何从软盘读取并加载一个Loader程序到操作 系统,然后转交系统控制权 • 对应章节:第四章 实验内容: 1. 向软盘镜像文件写入一个你指定的文件,手 工读取在磁盘中的信息 2. 在 ...