linux tag: v6.8-rc1
split lock 作为 bus lock 中的一种,常常会影响系统性能。Intel CPU 提供了一种机制可以对其进行检测,在其发生的时候发出警告或 SIGBUS,以提醒开发者优化。
1. bus lock
1.1 bus lock 介绍
SDM vol3, 9.1.2 Bus Locking
Intel 64 and IA-32 processors provide a LOCK# signal that is asserted automatically during certain critical memory operations to lock the system bus or equivalent link. Assertion of this signal is called a bus lock. While this output signal is asserted, requests from other processors or bus agents for control of the bus are blocked.
Intel 64 和 IA-32 处理器提供 LOCK#
信号,该信号在某些关键内存操作期间自动断言,以锁定系统总线或等效链路。此信号的断言称为总线锁 (bus lock) 。给总线加锁后,来自其他处理器或总线代理的用于控制总线的请求会被阻止。程序中可以在指令之前使用 LOCK
前缀来产生 LOCK#
信号。比如:
c
asm volatile ("lock addl $1, %0\n\t"
: "=m" (*ptr_temp));
对于大多数 x86 处理器中,比如 P6 和更新的处理器系列,如果加锁访问的内存被缓存在处理器内部,则 LOCK#
信号通常不被断言 (即不会触发 bus lock),而只会锁处理器的缓存。
以下两种情形时,处理器会产生 bus lock。
- split lock: 加锁访问多个 cache line。
- UC lock: 加锁访问的 memory type 是 WB 以外的类型, 包括 UC, WC, WP 和 WT 等。由于其中最常见的是访问未缓存地址的内存 (Strong Uncacheable) ,因此叫 UC lock。
几种 memory type 的介绍如下: (SDM vol3, 12.3 METHODS OF CACHING AVAILABLE)
- Strong Uncacheable (UC ) ---System memory locations are not cached. All reads and writes appear on the system bus and are executed in program order without reordering. This type of cache-control is useful for memory-mapped I/O devices.
- Uncacheable (UC-) --- Has same characteristics as the strong uncacheable (UC) memory type, except that this memory type can be overridden by programming the MTRRs for the WC memory type.
- Write Combining (WC) --- System memory locations are not cached (as with uncacheable memory) and coherency is not enforced by the processor's bus coherency protocol.
- Write-through (WT) --- Writes and reads to and from system memory are cached. All writes are written to a cache line (when possible) and through to system memory.
- Write-back (WB ) --- Writes and reads to and from system memory are cached. writes are performed entirely in the cache, when possible.
- Write protected (WP) --- Reads come from cache lines when possible, and read misses cause cache fills.
加锁访问未缓存的内存时,会产生 bus lock 很好理解,因为要独占内存总线,确保内存访问的原子性。而访问缓存中跨 cache line 的数据为什么要锁总线,可以参考文章 深入剖析 split locks,i++ 可能导致的灾难-深入剖析是什么意思。主要原因就是 split lock 会退化成 bus lock。
Intel 为了优化总线锁导致的性能问题,在 P6 后的处理器上,引入了缓存锁 (cache locking) 机制:通过缓存一致性协议保证多个 CPU core 访问跨 cache line 的内存地址的多次访问的原子性与一致性,而不需要锁内存总线。 由于缓存一致性协议的粒度是一个 cache line,当原子操作的数据跨 cache line 时,依赖缓存锁机制无法保证数据一致性,会退化为总线锁来保证一致性,这种情况就是 split lock。
1.2 bus lock disable
由于 bus lock 常常会影响系统性能,Intel 处理器提供两种特性以支持关闭 bus lock。这两个分别是 split-lock disable 和 UC-lock disable。
可通过枚举 IA32_CORE_CAPABILITIES
(MSR index CFH) 查看当前处理器是否支持对应的特性。
然后通过配置 MSR_MEMORY_CTRL
(MSR index 33H) 中 bit28, 29 可分别开启/关闭相应特性。
- 开启 split-lock disable 后,split lock 会产生异常
#AC
(alignment check exception) ,而不会触发 bus lock。 - 开启 UC-lock disable 后,UC lock 会产生异常
#GP
(general-protection exception) 。- 未来采用新架构的 Intel 至强处理器 Sierra Forest microarchitecture 或 Grand Ridge microarchitecture, 将通过
CPUID. (EAX=07H, ECX=2): EDX[bit 6]
枚举是否支持 UC-lock disable,并使用#AC
。 - 而且
IA32_CORE_CAPABILITIES[4]
和CPUID. (EAX=07H, ECX=2): EDX[bit 6]
不会同时为 1,即处理器只采用其中一种方式枚举该特性。
- 未来采用新架构的 Intel 至强处理器 Sierra Forest microarchitecture 或 Grand Ridge microarchitecture, 将通过
1.3 #AC
的补充
除了作为 split-lock disable 的异常信号之外, #AC
的传统用法是作为用户空间的对齐检查异常 (check alignment of data),当配置为 CR0.AM = 1, EFLAGS.AC = 1, and CPL = 3
时可生效。因此一共有两类 #AC
:
- legacy alignment check
#AC
- split lock
#AC
Interrupt 17---Alignment Check Exception (#AC
)
2. split lock detection
如前面所说,split lock 是 bus lock 中的一种。借助 split-lock disable 特性中的异常信号 #AC
,可以检测 split lock。此时 bus lock 还未发生。如果在 #AC
handler 中关闭 split-lock disable,则结束异常处理后返回异常点重新执行时,才将发生 bus lock。
对于用户态的 bus lock,可以通过 #DB
进行检测。具体来说,CPL > 0 时触发的 bus lock,可以通过置上 IA32_DEBUGCTL.BLD[bit 2]
开启特性 OS bus-lock detection ,以在 bus lock 发生时产生对应的异常信号 #DB
, 即 bus-lock detection debug exception (见 SDM vol3, 18.3.1.6 OS Bus-Lock Detection)。
2.1 参数 split_lock_detect
split lock detection (SLD) 和 bus lock detection 相关的 patch 早已由 Fenghua 先后提交到了社区并合入 linux。根据 kernel parameter split_lock_detect
指定的值,设置不同的检测等级,然后在 #AC
和 #DB
处理函数中分别实现 split lock detection / bus lock detection 的不同行为。
c
// Documentation/admin-guide/kernel-parameters.txt
// Documentation/arch/x86/buslock.rst
Software handling
=================
The kernel #AC and #DB handlers handle bus lock based on the kernel
parameter "split_lock_detect". Here is a summary of different options:
+------------------+----------------------------+-----------------------+
|split_lock_detect=|#AC for split lock |#DB for bus lock |
+------------------+----------------------------+-----------------------+
|off |Do nothing |Do nothing |
+------------------+----------------------------+-----------------------+
|warn |Kernel OOPs |Warn once per task and |
|(default) |Warn once per task, add a |and continues to run. |
| |delay, add synchronization | |
| |to prevent more than one | |
| |core from executing a | |
| |split lock in parallel. | |
| |sysctl split_lock_mitigate | |
| |can be used to avoid the | |
| |delay and synchronization | |
| |When both features are | |
| |supported, warn in #AC | |
+------------------+----------------------------+-----------------------+
|fatal |Kernel OOPs |Send SIGBUS to user. |
| |Send SIGBUS to user | |
| |When both features are | |
| |supported, fatal in #AC | |
+------------------+----------------------------+-----------------------+
|ratelimit:N |Do nothing |Limit bus lock rate to |
|(0 < N <= 1000) | |N bus locks per second |
| | |system wide and warn on|
| | |bus locks. |
+------------------+----------------------------+-----------------------+
对于 SLD,有三个等级。
split_lock_detect=off
:关闭 split lock 检测。split_lock_detect=warn
:- 若 split lock 被 kernel 触发,kernel OOPS。
- 若被 user space 触发,对每个任务仅触发一次 warn,然后关闭 split-lock disable,允许 split lock,但会将系统延迟增大。以引起开发者注意,提醒他们优化会产生 split lock 的程序。使用 kernel parameter
split_lock_mitigate=0
可强行消除 SLD 制造的延迟。
split_lock_detect=fatal
:- 若 split lock 被 kernel 触发,kernel OOPS。
- 若被 user space 触发,对产生 split lock 的用户程序发送 SIGBUG,并生成 fatal 信息。
2.2 exc_alignment_check()
借助 DEFINE_IDTENTRY_ERRORCODE
设置 #Ac
异常处理函数 exc_alignment_check
宏定义 DEFINE_IDTENTRY_ERRORCODE(func)
主要新建 IDT 表项的入口函数 func
,然后调用 __##func
c
#define DEFINE_IDTENTRY_ERRORCODE(func) \
static __always_inline void __##func(struct pt_regs *regs, \
unsigned long error_code); \
\
__visible noinstr void func(struct pt_regs *regs, \
unsigned long error_code) \
{ \
irqentry_state_t state = irqentry_enter(regs); \
\
instrumentation_begin(); \
__##func (regs, error_code); \
instrumentation_end(); \
irqentry_exit(regs, state); \
} \
\
static __always_inline void __##func(struct pt_regs *regs, \
unsigned long error_code)
定义 #Ac
异常处理函数。
c
// 定义中断向量 `X86_TRAP_AC` 对应的处理函数为 `exc_alignment_check`
DECLARE_IDTENTRY_ERRORCODE(X86_TRAP_AC, exc_alignment_check);
// 定义 exc_alignment_check() 和 __exc_alignment_check()
DEFINE_IDTENTRY_ERRORCODE(exc_alignment_check)
{
char *str = "alignment check";
if (notify_die(DIE_TRAP, str, regs, error_code, X86_TRAP_AC, SIGBUS) == NOTIFY_STOP)
return;
// #AC 发生在 kernal space, 表示检测到 split lock,则 kernal OOPS
if (!user_mode(regs))
die("Split lock detected\n", regs, error_code);
// 执行到此处,表示 #AC 发生在 user space
local_irq_enable();
// 若已经处理 user split lock, 直接 out
// 若没有处理,发送 SIGBUG 到触发 #AC 的程序
if (handle_user_split_lock(regs, error_code))
goto out;
do_trap(X86_TRAP_AC, SIGBUS, "alignment check", regs,
error_code, BUS_ADRALN, NULL);
out:
local_irq_disable();
}
// true 表示触发 #AC 的是 userspace 的 split lock,`split_lock_detect` 配置为 warn, 已执行处理逻辑
bool handle_user_split_lock(struct pt_regs *regs, long error_code)
{
// 如果 #AC (1) 用于数据对齐检查,或者,(2) 用于 SLD 且 split_lock_detect==fatal,
// 直接返回 false
if ((regs->flags & X86_EFLAGS_AC) || sld_state == sld_fatal)
return false;
// 表示 split_lock_detect==warn,执行 SLD 中 warn 的逻辑
split_lock_warn(regs->ip);
return true;
}
2.3 split_lock_warn()
split lock 检测等级为 warn 的处理逻辑,主要包括一些警告信息的打印,和通过休眠一段时间当前线程来增加延迟,然后关闭 SLD,当 split lock 发生后再重新开启。在关闭 SLD 的时间段内,使用锁来防止其他 core 也关闭 SLD。
c
static void split_lock_warn(unsigned long ip)
{
struct delayed_work *work;
int cpu;
// 对每个 task, 只警告一次 split lock
if (!current->reported_split_lock)
pr_warn_ratelimited("#AC: %s/%d took a split_lock trap at address: 0x%lx\n",
current->comm, current->pid, ip);
current->reported_split_lock = 1;
// 默认 sysctl_sld_mitigate 为 1, 开始增加延迟的流程
if (sysctl_sld_mitigate) {
/*
* misery factor #1:
* sleep 10ms before trying to execute split lock.
*/
// 执行 split lock 之前,先休眠 10 ms
if (msleep_interruptible(10) > 0)
return;
/*
* Misery factor #2:
* only allow one buslocked disabled core at a time.
*/
// 同一时间只允许一个 core 上完成 split lock,即对关闭 SLD 的这段时间加锁
if (down_interruptible(&buslock_sem) == -EINTR)
return;
// 设置执行完 split lock 的工作:主要是再次开启 SLD,buslock_sem + 1 以释放锁。
work = &sl_reenable_unlock;
// 若要求 SLD 不要产生额外的延迟
} else {
// 设置执行完 split lock 的工作:主要是再次开启 SLD
work = &sl_reenable;
}
cpu = get_cpu();
// 设置延迟调度的任务。
schedule_delayed_work_on(cpu, work, 2);
/* Disable split lock detection on this CPU to make progress */
// 关闭 SLD, 使得 split lock 能顺利完成。
sld_update_msr(false);
put_cpu();
}
sl_reenable_unlock
和 sl_reenable
两者功能相近,只是前者多了释放锁的操作而已。
c
static void __split_lock_reenable_unlock(struct work_struct *work)
{
sld_update_msr(true);
up(&buslock_sem);
}
static DECLARE_DELAYED_WORK(sl_reenable_unlock, __split_lock_reenable_unlock);
static void __split_lock_reenable(struct work_struct *work)
{
sld_update_msr(true);
}
static DECLARE_DELAYED_WORK(sl_reenable, __split_lock_reenable);
// 开启/关闭 SLD 的功能函数,主要是置上/清除 MSR_MEMORY_CTRL[29]
// (这里的 MSR_TEST_CTRL 就是 MSR_MEMORY_CTRL)
static void sld_update_msr(bool on)
{
u64 test_ctrl_val = msr_test_ctrl_cache;
if (on)
test_ctrl_val |= MSR_TEST_CTRL_SPLIT_LOCK_DETECT;
wrmsrl(MSR_TEST_CTRL, test_ctrl_val);
}
3. split lock detection 虚拟化
SLD 虚拟化为 guest 中的 split lock 的检测提供处理逻辑。patch 分为两大部分,由两个 patchset 发到社区。
- basic split lock
#AC
handling - virtualization support of split lock detection
3.1 basic split lock #AC
handling
第一部分的 patch 提供了对 guest 中 legacy alignment check #AC
的支持,而将 guest 中触发的 split lock #AC
简单看作 host 的 user space #AC
,全部交由 KVM 处理。这部分已被合进 linux。[patch 0/3] x86/kvm: Basic split lock #AC handling - Thomas Gleixner
AC_VECTOR
当 guest 中 #AC
触发 VM-exit 之后,进入 handle_exception_nmi
选择对应异常的处理路径。首先由 vmx_guest_inject_ac
判断是否将 #AC
注入 guest, 若否,则直接由 KVM 处理,调用 handle_guest_split_lock
;若是,则在 guest 中通过 #AC
的处理函数 exc_alignment_check
处理。
c
static int (*kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {
[EXIT_REASON_EXCEPTION_NMI] = handle_exception_nmi
}
// arch/x86/kvm/vmx/vmx.c
static int handle_exception_nmi(struct kvm_vcpu *vcpu)
case AC_VECTOR:
// 判断是否注入 #AC 到 guest
if (vmx_guest_inject_ac(vcpu)) {
// 注入 #AC
kvm_queue_exception_e(vcpu, AC_VECTOR, error_code);
return 1;
}
/*
* Handle split lock. Depending on detection mode this will
* either warn and disable split lock detection for this
* task or force SIGBUS on it.
*/
// 由 KVM 处理 #AC
if (handle_guest_split_lock(kvm_rip_read(vcpu)))
return 1;
fallthrough;
vmx_guest_inject_ac()
为了只将 legacy alignment check #AC
注入 guest,需判断异常的产生源。
- 当 host 不支持 SLD,那么
#AC
肯定是对齐检查产生的,因为 guest 无法配置MSR_MEMORY_CTRL[29]
来开启/关闭 split-lock disable。直接返回 false。 - 当 host 支持 SLD,若为 legacy alignment check
#AC
,返回 true。
c
/*
* If the host has split lock detection disabled, then #AC is
* unconditionally injected into the guest, which is the pre split lock
* detection behaviour.
*
* If the host has split lock detection enabled then #AC is
* only injected into the guest when:
* - Guest CPL == 3 (user mode)
* - Guest has #AC detection enabled in CR0
* - Guest EFLAGS has AC bit set
*/
bool vmx_guest_inject_ac(struct kvm_vcpu *vcpu)
{
// host 不支持 split lock detection, 直接将 #AC 注入 guest
if (!boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT))
return true;
// legacy alignment check, 返回 true, `#AC` 注入 guest
return vmx_get_cpl(vcpu) == 3 && kvm_is_cr0_bit_set(vcpu, X86_CR0_AM) &&
(kvm_get_rflags(vcpu) & X86_EFLAGS_AC);
}
handle_guest_split_lock()
KVM 处理 guest split lock #AC
流程。
c
bool handle_guest_split_lock(unsigned long ip)
{
// 若 host 中设置的 split_lock_detect==warn, 则进入 warn 的处理逻辑 split_lock_warn
if (sld_state == sld_warn) {
split_lock_warn(ip);
return true;
}
// 否则,发送 SIGBUS 给 guest,这将导致 VM 被 killed
pr_warn_once("#AC: %s/%d %s split_lock trap at address: 0x%lx\n",
current->comm, current->pid,
sld_state == sld_fatal ? "fatal" : "bogus", ip);
current->thread.error_code = 0;
current->thread.trap_nr = X86_TRAP_AC;
force_sig_fault(SIGBUS, BUS_ADRALN, NULL);
return false;
}
3.2 virtualization of split lock detection
tag: v5.9-rc1
第二部分的 patch 采用 PV 的方式,提供了对 guest 中 split lock #AC
的支持。可以在 guest 中配置 split_lock_detect
,设置检测等级。可惜还没有被社区接收,最后一个版本 v10 是基于 v5.9-rc1。[PATCH v10 0/9] KVM: Add virtualization support of split lock detection - Xiaoyao Li
SLD 比较特殊,开启/关闭 SLD 的寄存器 MSR_MEMORY_CTRL
的控制范围是整个 core。当支持超线程 (SMT) 时,一个 core 上有两个线程,即 pCPU1, pCPU2。如果 vCPU (假设对应 pCPU1) 关闭 SLD 会使得 pCPU2 上运行的任务也受到影响。
该寄存器 per-core scope 的特点使得 SLD 虚拟化有很多额外的限制,得考虑 host 是否开启超线程,即 smt/nosmt,以及 host SLD 的配置。
- 当 host SLD 为 off, guest 不支持 SLD。
- 当 host SLD 为 warn,
- host 为 nosmt,则 guest 支持 SLD,可设为 off/warn/fatal。
- host 为 smt, 则 guest 不支持 SLD。
- 当 host SLD 为 fatal, guest 支持 SLD, 但仅可设为 off/fatal。
暴露 SLD feature bit 给 guest
在代码实现中,首先提供了两个 KVM feature bit 给 guest, 用于表示 KVM 是否支持 SLD,是否仅支持设为 fatal。guest 可通过 CPUID.40000001H:EAX[15]
, CPUID.40000001H:EDX[1]
枚举。
bash
#define KVM_FEATURE_SPLIT_LOCK_DETECT 15 # 置位表示 KVM 支持 SLD
#define KVM_HINTS_SLD_FATAL 1 # 置位表示 KVM 仅支持 guest 将 SLD 设为 fatal, 反之则不限制
另外有两个 feature flag,分别表示当前系统是否开启 SLD,以及等级是否为 fatal。
bash
#define X86_FEATURE_SPLIT_LOCK_DETECT (11*32+ 6) /* #AC for split lock */
#define X86_FEATURE_SLD_FATAL (11*32+ 7) /* split lock detection in fatal mode */
当 userspace VMM 通过 ioctl KVM_GET_SUPPORTED_CPUID
获取 KVM 支持的 CPUID,以便接下来配置 guest CPUID 时,配置 SLD 相关的 KVM feature bit。
c
kvm_dev_ioctl
default:
kvm_arch_dev_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
case KVM_GET_SUPPORTED_CPUID:
case KVM_GET_EMULATED_CPUID:
kvm_dev_ioctl_get_cpuid
get_cpuid_func
do_cpuid_func
__do_cpuid_func
static inline int __do_cpuid_func(struct kvm_cpuid_array *array, u32 function)
{
case KVM_CPUID_FEATURES:
// ...
// 若 KVM 支持 SLD,暴露 KVM_FEATURE_SPLIT_LOCK_DETECT 给 guest
if (kvm_split_lock_detect_supported())
entry->eax |= (1 << KVM_FEATURE_SPLIT_LOCK_DETECT);
// 若 host 为 SLD fatal, 则 KVM 仅支持 guest 为 SLD fatal。
// 暴露 KVM_HINTS_SLD_FATAL 给 guest
if (boot_cpu_has(X86_FEATURE_SLD_FATAL))
entry->edx |= (1 << KVM_HINTS_SLD_FATAL);
break;
}
// KVM 支持 SLD,如前所述,需满足:
// (1) host SLD fatal, 或
// (2) host SLD warn 且 nosmt
static inline bool kvm_split_lock_detect_supported(void)
{
return boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT) &&
(boot_cpu_has(X86_FEATURE_SLD_FATAL) ||
!cpu_smt_possible());
}
split_lock_setup()
配置 SLD 的函数会调用两次,第一次是 kernel 启动过程中的配置流程之一,第二次仅会发生在 guest 中。
c
setup_arch
early_cpu_init
early_identify_cpu
cpu_set_core_cap_bits
split_lock_setup(false); // kernel 配置 SLD
x86_init.hyper.guest_late_init(); // 仅发生在 guest 中
==> kvm_guest_init
// 根据 KVM feature bit,调用 `split_lock_setup` 设置 guest SLD
if (kvm_para_has_feature(KVM_FEATURE_SPLIT_LOCK_DETECT))
split_lock_setup(kvm_para_has_hint(KVM_HINTS_SLD_FATAL));
// 若 KVM 仅支持 SLD fatal, 即 KVM_HINTS_SLD_FATAL,guest 无法配置 SLD 等级,直接为 fatal。
// 同时配置 feature flag `X86_FEATURE_SLD_FATAL`
void __init split_lock_setup(bool fatal)
{
if (fatal) {
state = sld_fatal;
pr_info("forced on, sending SIGBUS on user-space split_locks\n");
goto set_cap;
}
// ...
set_cap:
setup_force_cpu_cap(X86_FEATURE_SPLIT_LOCK_DETECT);
if (state == sld_fatal)
setup_force_cpu_cap(X86_FEATURE_SLD_FATAL);
}
guest_inject_ac()
注: 旧版 kernel 使用的 guest_inject_ac
,对应新版 kernel 的 vmx_guest_inject_ac
。
当 guest #AC
触发 VM-exit 之后,KVM 对异常注入回 guest 的判断增加了 guest SLD 的检测。当 host SLD 开启时,满足以下条件时注入回 guest:
- legacy alignment check
#AC
. - guest 开启了 SLD,可能为 SLD
#AC
.(新增)
c
/*
* If the host has split lock detection disabled, then #AC is
* unconditionally injected into the guest, which is the pre split lock
* detection behaviour.
*
* If the host has split lock detection enabled then #AC is
* injected into the guest when:
* 1) guest has alignment check enabled;
* or 2) guest has split lock detection enabled;
*/
static inline bool guest_inject_ac(struct kvm_vcpu *vcpu)
{
if (!boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT))
return true;
/*
* A split lock access must be an unaligned access, so we should check
* guest_cpu_alignent_check_enabled() fisrt.
*/
return guest_alignment_check_enabled(vcpu) || guest_sld_on(to_vmx(vcpu));
}
两个判断条件。
c
static inline bool guest_alignment_check_enabled(struct kvm_vcpu *vcpu)
{
return vmx_get_cpl(vcpu) == 3 && kvm_read_cr0_bits(vcpu, X86_CR0_AM) &&
(kvm_get_rflags(vcpu) & X86_EFLAGS_AC);
}
// KVM 检查 guest 是否开启 SLD,可通过记录的寄存器 bit29 的值判断。
#define MSR_TEST_CTRL_SPLIT_LOCK_DETECT_BIT 29
#define MSR_TEST_CTRL_SPLIT_LOCK_DETECT BIT(MSR_TEST_CTRL_SPLIT_LOCK_DETECT_BIT)
static inline bool guest_sld_on(struct vcpu_vmx *vmx)
{
return vmx->msr_test_ctrl & MSR_TEST_CTRL_SPLIT_LOCK_DETECT;
}
KVM 模拟 MSR_MEMORY_CTRL
guest 开启 SLD 时必然涉及 MSR_MEMORY_CTRL[29]
的读写,访问时触发 VM-exit,然后由 KVM 模拟行为。
在 KVM 更新寄存器的值时,guest 的 MSR_MEMORY_CTRL[29]
并不定会在所有情况下都写入真实的 MSR 中,例如 guest SLD fatal 就不会更新真实 MSR。但都会记录在 vcpu_vmx->msr_test_ctrl
中。
c
static int vmx_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
switch (msr_index) {
case MSR_TEST_CTRL:
vmx->msr_test_ctrl = data;
// 更新 MSR_MEMORY_CTRL,并不一定写入真实 MSR
vmx_update_guest_sld(vmx);
break;
}
static int vmx_get_msr(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
{
switch (msr_info->index) {
case MSR_TEST_CTRL:
msr_info->data = vmx->msr_test_ctrl;
break;
}
另外,为了让 vCPU 切换到其他 pCPU 上时,或者同一 pCPU 上 vCPU 和其他任务切换时,不改变 host 的 split lock 设置,需要将真实 MSR_MEMORY_CTRL
恢复回 host 配置的状态。
c
struct vcpu_vmx {
bool guest_has_sld; // KVM 记录 guest 是否开启 SLD
bool host_sld_on; // KVM 记录 host 是否开启 SLD
u64 msr_test_ctrl; // KVM 记录 guest 视角的 MSR_MEMORY_CTRL
}
当 userspace VMM 通过 ioctl KVM_SET_CPUID
配置 vCPU 支持的 feature 时,检查 guest 是否开启 SLD,并记录到 vcpu_vmx->guest_has_sld
。
c
kvm_vcpu_ioctl
default:
kvm_arch_vcpu_ioctl
case KVM_SET_CPUID:
kvm_vcpu_ioctl_set_cpuid
kvm_vcpu_ioctl_set_cpuid2
kvm_vcpu_after_set_cpuid
kvm_x86_ops.vcpu_after_set_cpuid
==> vmx_vcpu_after_set_cpuid
// arch/x86/kvm/vmx/vmx.c
static void vmx_vcpu_after_set_cpuid(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
vmx->guest_has_sld = false;
// 若 KVM 支持 SLD
if (kvm_split_lock_detect_supported()) {
best = kvm_find_cpuid_entry(vcpu, KVM_CPUID_FEATURES, 0);
// 查看 guest 是否开启 SLD
if (best && (best->eax & 1 << KVM_FEATURE_SPLIT_LOCK_DETECT))
vmx->guest_has_sld = true;
}
}
guest 和 host 相互切换时,相应地修改 MSR_MEMORY_CTRL
。
c
// guest 切换到 host, 恢复 host MSR_MEMORY_CTRL[29]
vmx_prepare_switch_to_host
if (static_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT) && vmx->guest_has_sld)
split_lock_restore_host(vmx->host_sld_on);
// host 切换到 guest, 记录 host MSR_MEMORY_CTRL[29],恢复 guest MSR_MEMORY_CTRL[29]
vmx_prepare_switch_to_guest
if (static_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT) && vmx->guest_has_sld)
vmx->host_sld_on = split_lock_set_guest(guest_sld_on(vmx));
4. patches
split lock detection:
Bare metal:
[PATCH v9 00/17] x86/split_lock: Enable split lock detection - Fenghua Yu
KVM:
[patch 0/3] x86/kvm: Basic split lock #AC handling - Thomas Gleixner
[PATCH v10 0/9] KVM: Add virtualization support of split lock detection - Xiaoyao Li
QEMU:
[Qemu-devel] [PATCH v2] target/i386: define a new MSR based feature word
或:[PATCH v2] target/i386: define a new MSR based feature word - FEAT_CORE_CAPABILITY - Xiaoyao Li
bus lock detection:
Bare metal:
[PATCH v6 0/3] x86/bus_lock: Enable bus lock detection - Fenghua Yu
[PATCH 0/4] x86/bus_lock: Set rate limit for bus lock - Fenghua Yu
5. 如何测试
检测 split lock
bash
### 在现有的cpu上检测 split lock
### 其中 event=0xf4, umask=0x10 对应 PMU 事件sq_misc.split_lock。
perf stat -e cpu/event=0xf4,umask=0x10/ -a -I 1000
本文作者:文七安
本文链接:从 bus lock 到 split lock detection - 掘金 (juejin.cn)
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!