【CPU】Hypervisor 中 LHP / LWP 问题
在虚拟化技术中,LHP (Lock Holder Preemption)和LWP (Lock Waiter Preemption)是导致虚拟机性能急剧下降的两个核心痛点。
这两个问题主要源于Guest OS(客户操作系统)和Hypervisor(宿主管理程序)之间的“信息不对称”。
以下是详细的深度解析:
1. 核心背景:自旋锁 (Spinlock) 的假设
为了理解 LHP/LWP,首先需要理解操作系统内核中常见的自旋锁 (Spinlock)。
机制:当一个线程(或 vCPU)想要访问共享资源时,如果发现锁被别人拿了,它不会进入睡眠(Sleep),而是会在原地“转圈圈”(空循环检查),直到锁被释放。
物理机上的假设:自旋锁是基于一个假设设计的——持有锁的人正在另一个 CPU 上干活,很快就会释放锁。因此,与其切换上下文进入睡眠(开销大),不如原地等一会(开销小)。
然而,在虚拟化环境中,Hypervisor 可能会随时打断(抢占)任何一个 vCPU 的运行。这破坏了上述假设。
2. LHP: 锁持有者被抢占 (Lock Holder Preemption)
这是最严重的问题,通常被称为“性能杀手”。
场景描述:
vCPU-A获取了一个自旋锁(Lock X),开始处理临界区代码。
Hypervisor突然介入(可能是时间片到了,或者有更高优先级的 PVM 任务),强制暂停了vCPU-A,把它踢出了物理 CPU(pCPU)。
此时,vCPU-B(运行在另一个物理核上)也想要获取 Lock X。
vCPU-B发现锁被占用了,于是开始自旋(Spinning)。
后果:
由于 vCPU-A 处于“休眠”状态,它根本无法运行代码来释放锁。
vCPU-B不知道 vCPU-A 被暂停了,它以为 vCPU-A 还在干活,所以 vCPU-B 会一直空转,直到它的时间片用完。
资源浪费:vCPU-B 占用的物理 CPU 处于 100% 满载状态,但没有做任何有意义的工作(都在空转)。如果有 vCPU-C、vCPU-D 也要这个锁,它们也会全部卡死。
比喻:
你(vCPU-B)在厕所门口排队。里面的人(vCPU-A)进去锁上门后,突然被灭霸(Hypervisor)打了个响指时间停止了。你在门口一直敲门(自旋),以为他马上就好,结果他在里面“冻结”了整整一个小时。你这一个小时的时间完全被浪费了。
3. LWP: 锁等待者被抢占 (Lock Waiter Preemption)
这是一个关于“延迟”的问题。
场景描述:
vCPU-A持有锁。
vCPU-B正在排队等待锁(自旋中)。
Hypervisor认为vCPU-B耗尽了时间片,或者为了负载均衡,把vCPU-B抢占(暂停)了。
随后,vCPU-A释放了锁。
后果:
此时锁是空闲的,但vCPU-B已经被挂起了(Sleeping/Preempted)。
直到 Hypervisor 再次调度vCPU-B运行之前,这个锁可能一直闲置,或者被别人插队。
当vCPU-B终于醒来拿到锁时,已经过去很久了。
影响:增加了获取锁的延迟(Latency)。在实时系统(RT)中,这会导致任务无法在规定时间内完成。
比喻:
叫号机叫到了你的号(锁空闲了),但你刚才等太久睡着了(vCPU-B 被抢占)。柜台空着没人办业务(资源闲置),直到管理员把你叫醒。
4. 其他解决方案
如果不采用独占核方案,通常用以下技术解决 LHP:
PLE (Pause Loop Exiting): 硬件辅助(Intel VT-x / ARM 架构支持)。当 CPU 检测到 vCPU 在死循环(自旋)时,会自动触发“VM Exit”退出到 Hypervisor。Hypervisor 意识到:“这哥们在傻等”,于是主动挂起它,去调度那个持有锁的 vCPU。
PV Spinlocks (半虚拟化自旋锁): 修改 Guest OS 内核。当 Guest 发现等锁时间太长,主动发指令给 Hypervisor:“我卡住了,请把我挂起(Yield),让持有锁的兄弟先跑。”
Gang Scheduling (群组调度): Hypervisor 保证属于同一个 VM 的所有 vCPU同时被调度上线,或同时被下线。这样就不会出现“持有者下线,等待者上线”的尴尬局面。
总结
在虚拟化场景中:
LHP是导致系统卡顿、CPU 飙高但无产出的元凶。
LWP是导致实时性变差、响应变慢的原因。
物理隔离是最简单粗暴但也最有效的根治手段。