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代码分析的更多相关文章

  1. 几篇QEMU/KVM代码分析文章

    QEMU/KVM结合起来分析的几篇文章,代码跟最新的版本有些差异,但大体逻辑一样,写得通俗易懂.我把链接放这里主要是为自己需要查看时调转过去方便,感谢作者的付出! QEMU Source Code S ...

  2. 关于Linux虚拟化技术KVM的科普 科普二(KVM虚拟机代码揭秘)

    代码分析文章<KVM虚拟机代码揭秘--QEMU代码结构分析>.<KVM虚拟机代码揭秘--中断虚拟化>.<KVM虚拟机代码揭秘--设备IO虚拟化>.<KVM虚拟 ...

  3. IO-Polling的代码分析

    在前一篇文章<IO-Polling实现分析与性能评測>中提到了IO-Polling与中断的原理差别,并通过两种模式下NVMe SSD的性能測试对两者进行了对照. 这篇文章将深入到IO-Po ...

  4. qemu-kvm 代码分析

    qemu-kvm 代码分析 虚拟机组成 实际上面所说计算机组成比较笼统,实际处理器,存储器,输入与设备种类繁多,     -   i440FX host PCI bridge and PIIX3 PC ...

  5. OpenStack 虚拟机冷/热迁移的实现原理与代码分析

    目录 文章目录 目录 前文列表 冷迁移代码分析(基于 Newton) Nova 冷迁移实现原理 热迁移代码分析 Nova 热迁移实现原理 向 libvirtd 发出 Live Migration 指令 ...

  6. Android代码分析工具lint学习

    1 lint简介 1.1 概述 lint是随Android SDK自带的一个静态代码分析工具.它用来对Android工程的源文件进行检查,找出在正确性.安全.性能.可使用性.可访问性及国际化等方面可能 ...

  7. pmd静态代码分析

    在正式进入测试之前,进行一定的静态代码分析及code review对代码质量及系统提高是有帮助的,以上为数据证明 Pmd 它是一个基于静态规则集的Java源码分析器,它可以识别出潜在的如下问题:– 可 ...

  8. [Asp.net 5] DependencyInjection项目代码分析-目录

    微软DI文章系列如下所示: [Asp.net 5] DependencyInjection项目代码分析 [Asp.net 5] DependencyInjection项目代码分析2-Autofac [ ...

  9. [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(5)(IEnumerable<>补充)

    Asp.net 5的依赖注入注入系列可以参考链接: [Asp.net 5] DependencyInjection项目代码分析-目录 我们在之前讲微软的实现时,对于OpenIEnumerableSer ...

  10. 完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)

    构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...

随机推荐

  1. Docker -- 日志

    docker 的两总日志 引擎日志 容器日志 引擎日志 简介: Docker 引擎日志就是 dockerd 运行时的日志 在CentOS 7系统中,Docker 引擎日志一般是交给 systemd来管 ...

  2. jackson学习之九:springboot整合(配置文件)

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 系列文章汇总 jackson学习之一:基本信息 jac ...

  3. SQL(replace)替换字段中指定的字符

    语法:update 表名 set 字段名=REPLACE(字段名,'修改前的字符','修改后的字符') 例 Product商品表中Name 名字字段中描述中将'AAA' 修改成 'BBB' SQL语句 ...

  4. 【uva 1349】Optimal Bus Route Design(图论--网络流 二分图的最小权完美匹配)

    题意:有一个N个点的有向带权图,要求找若干个有向圈,使得每个点恰好属于一个圈.请输出满足以上条件的最小权和. 解法:有向圈?也就是每个点有唯一的后继.这是一个可逆命题,同样地,只要每个点都有唯一的后继 ...

  5. 2020-2021 ICPC, NERC, Southern and Volga Russian Regional Contest (Online Mirror, ICPC Rules) D. Firecrackers (贪心,二分)

    题意:有个长度为\(n\)的监狱,犯人在位置\(a\),cop在位置\(b\),你每次可以向左或者向右移动一个单位,或者选择不动并在原地放一个爆竹\(i\),爆竹\(i\)在\(s[i]\)秒后爆炸, ...

  6. python实现通过指定浏览器免费观看vip视频

    程序是先通过一个解析视频的网站,然后我们提取其接口,然后实现观看vip视频的目的 所以说免费观看视频python程序很容易,但是下载视频就有些许麻烦了,下载视频请见我另一篇博客:python+fidd ...

  7. hdu5501 The Highest Mark

    Problem Description The SDOI in 2045 is far from what it was been 30 years ago. Each competition has ...

  8. Filebeat 日志收集

    Filebeat 介绍 Filebeat 安装 # 上传代码包 [root@redis03 ~]# rz filebeat-6.6.0-x86_64.rpm # 安装 [root@redis03 ~] ...

  9. LINUX - pthread_mutex_lock

    原文链接:https://www.cnblogs.com/fengbohello/p/7571722.html 互斥的概念 在多线程编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性. 每个对 ...

  10. VS2010的单元测试(二)

    四.附加测试属性 附加测试属性,在默认生成的测试代码是使被注释掉的,取消注释就可以使用. 例如,要在执行测试前,输出测试开始时间,在执行测试后,输出测试结束时间.代码如下: [ClassInitial ...