* [PATCH RFC 0/2] Fix KVM guest scheduling accounting issue related to stealtime
@ 2026-08-24 1:26 Dongli Zhang
2026-08-24 1:26 ` [PATCH RFC 1/2] KVM: x86: Update stealtime before clearing preempted state Dongli Zhang
2026-08-24 1:26 ` [PATCH RFC 2/2] sched/core: Defer preempted remote vCPU task clock updates Dongli Zhang
0 siblings, 2 replies; 4+ messages in thread
From: Dongli Zhang @ 2026-08-24 1:26 UTC (permalink / raw)
To: linux-kernel, kvm
Cc: seanjc, pbonzini, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, dwmw2, joe.jin
This RFC fixes a KVM guest scheduling accounting issue.
The Linux scheduler may allow one CPU to perform accounting for another CPU
through update_rq_clock_task(). This works well on baremetal.
However, in a KVM guest, stealtime may not be accounted for and deducted
from runtime correctly.
Suppose vCPU A performs accounting for vCPU B, while vCPU B is preempted
and stalled by the KVM host for 10 seconds. Unfortunately, the KVM host
does not update stealtime until vCPU B is about to re-enter the guest. As a
result, vCPU A cannot observe the increase in vCPU B's stealtime.
Consequently, the guest kernel incorrectly considers a task running on
vCPU B during the stall to have exclusively used the vCPU for an extended
period. The task may therefore incur an additional scheduling penalty.
Later, when stealtime is updated, it may exceed the elapsed runtime delta,
preventing all of the stealtime from being deducted.
To fix this issue, update KVM stealtime before clearing the vCPU's
preempted state. In the guest scheduler, use vcpu_is_preempted() to help
defer clock_task updates performed by a remote CPU while the owning vCPU is
reported as preempted.
To reproduce, first configure KVM host pCPU 10 so that a vCPU thread pinned to
it can be stalled.
hv# echo -1 | sudo tee /proc/sys/kernel/sched_rt_runtime_us
hv# echo 0 | sudo tee /sys/kernel/debug/sched/fair_server/cpu10/runtime
hv# cat /sys/kernel/debug/sched/fair_server/cpu10/runtime
0
hv# cat /sys/kernel/debug/sched/fair_server/cpu10/period
1000000000
Here is the QEMU command line. The guest kernel has below configs.
CONFIG_PARAVIRT=y
CONFIG_PARAVIRT_SPINLOCKS=y
CONFIG_PARAVIRT_TIME_ACCOUNTING=y
CONFIG_PARAVIRT_CLOCK=y
qemu-system-x86_64 \
-machine q35,kernel_irqchip=split \
-accel kvm -cpu host \
-smp 4 -m 8G \
-hda boot.qcow2 \
-monitor stdio -vnc :8 \
-net nic -net user,hostfwd=tcp::5028-:22 \
-kernel mainline-linux/arch/x86_64/boot/bzImage \
-append "root=/dev/sda1 init=/sbin/init text loglevel=7 console=ttyS0"
Pin vCPU 2 to pCPU 10.
(qemu) info cpus
* CPU #0: thread_id=12525 model=host
CPU #1: thread_id=12526 model=host
CPU #2: thread_id=12528 model=host
CPU #3: thread_id=12529 model=host
hv$ sudo taskset -pc 10 12528
Run runtime_stall_detector_v2.py in the guest VM. It creates five threads
pinned to vCPU 2. Each thread periodically reads the clocksource and
detects sudden forward jumps in time, indicating that the thread was
previously stalled. The source code is appended to the end of this cover
letter email.
[root@vm ~]# ./runtime_stall_detector_v2.py 2 5
started 5 tasks on guest cpu 2
worker pids: 441 442 443 444 445
trigger host-side vCPU starvation now
worker=3 pid=443 pinned_cpu=2
worker=1 pid=441 pinned_cpu=2
worker=2 pid=442 pinned_cpu=2
worker=4 pid=444 pinned_cpu=2
worker=5 pid=445 pinned_cpu=2
Now run the following command on the KVM host to preempt and stall threads
on pCPU 10. As a result, vCPU 2 and the five threads pinned to it will also
be stalled for 10 seconds.
hv$ sudo timeout 10s taskset -c 10 chrt -f 90 bash -c 'while :; do :; done'
Ideally, we expect each of the five threads to observe a 10-second forward
jump. However, one thread running on vCPU 2 during the stall will incur an
additional penalty.
[root@vm ~]# ./runtime_stall_detector_v2.py 2 5
... ...
1787476745.895051479: worker=5 pid=445 gap=9.997750 sec
1787476745.895614147: worker=1 pid=441 gap=10.004634 sec
1787476745.898544550: worker=2 pid=442 gap=10.005462 sec
1787476745.901545286: worker=4 pid=444 gap=10.006361 sec
1787476785.617044926: worker=3 pid=443 gap=49.718478 sec --> additional penalty!
The issue is no longer reproducible when the patchset is applied to both
the KVM guest and host.
1787507053.410693884: worker=4 pid=436 gap=9.995703 sec
1787507053.411839247: worker=5 pid=437 gap=10.004244 sec
1787507053.413943768: worker=2 pid=434 gap=10.002146 sec
1787507053.414848804: worker=1 pid=433 gap=10.005150 sec
1787507053.419051886: worker=3 pid=435 gap=10.005138 sec
Dongli Zhang (2):
KVM: x86: Update stealtime before clearing preempted state
sched/core: Defer preempted remote vCPU task clock updates
arch/x86/kvm/x86.c | 58 ++++++++++++++++++++++++++---------------------
kernel/sched/core.c | 24 ++++++++++++++++++++
kernel/sched/sched.h | 1 +
3 files changed, 57 insertions(+), 26 deletions(-)
base-commit: 818bebeb63dd6bf5f4e07e145f6cdbace520a34c
Thank you very much!
Dongli Zhang
----------------------
[root@vm ~]# cat runtime_stall_detector_v2.py
#!/usr/bin/env python3
import multiprocessing as mp
import os
import signal
import subprocess
import sys
import time
def worker(cpu, index, done):
os.sched_setaffinity(0, {cpu})
pid = os.getpid()
print(f"worker={index} pid={pid} pinned_cpu={cpu}", flush=True)
prev = time.monotonic_ns()
while True:
now = time.monotonic_ns()
gap = (now - prev) / 1e9
if gap > 0.5:
print(f"{time.time():.9f}: worker={index} pid={pid} gap={gap:.6f} sec", flush=True)
if gap > 30:
done.set()
return
prev = now
def kill_old_detectors():
subprocess.run(["pkill", "-f", "runtime_stall_detector.py"], check=False)
def main():
cpu = int(sys.argv[1]) if len(sys.argv) > 1 else 2
nr = int(sys.argv[2]) if len(sys.argv) > 2 else 5
kill_old_detectors()
done = mp.Event()
procs = [mp.Process(target=worker, args=(cpu, i, done)) for i in range(1, nr + 1)]
for proc in procs:
proc.start()
print(f"started {nr} tasks on guest cpu {cpu}", flush=True)
print("worker pids:", " ".join(str(proc.pid) for proc in procs), flush=True)
print("trigger host-side vCPU starvation now", flush=True)
try:
while not done.wait(1):
pass
except KeyboardInterrupt:
pass
finally:
for proc in procs:
if proc.is_alive():
os.kill(proc.pid, signal.SIGTERM)
for proc in procs:
proc.join()
if __name__ == "__main__":
main()
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH RFC 1/2] KVM: x86: Update stealtime before clearing preempted state
2026-08-24 1:26 [PATCH RFC 0/2] Fix KVM guest scheduling accounting issue related to stealtime Dongli Zhang
@ 2026-08-24 1:26 ` Dongli Zhang
2026-08-24 1:26 ` [PATCH RFC 2/2] sched/core: Defer preempted remote vCPU task clock updates Dongli Zhang
1 sibling, 0 replies; 4+ messages in thread
From: Dongli Zhang @ 2026-08-24 1:26 UTC (permalink / raw)
To: linux-kernel, kvm
Cc: seanjc, pbonzini, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, dwmw2, joe.jin
The guest Linux scheduler may rely on KVM stealtime to determine whether
elapsed time should be deducted from task runtime.
However, when vCPU A reads vCPU B's stealtime for scheduler accounting,
the value may not be up to date. KVM updates stealtime only when the vCPU
is about to enter the guest.
Update stealtime before clearing the preempted state, so a remote vCPU that
observes vcpu_is_preempted() as false also observes the new stealtime.
Otherwise, reading a vCPU's stealtime from another vCPU is not reliable.
The remote vCPU should wait until vcpu_is_preempted() returns false for the
target vCPU.
Signed-off-by: Dongli Zhang <dongli.zhang@oracle.com>
---
arch/x86/kvm/x86.c | 58 +++++++++++++++++++++++++---------------------
1 file changed, 32 insertions(+), 26 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 69469bbdc84a..525a1448195e 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3751,6 +3751,35 @@ static void record_steal_time(struct kvm_vcpu *vcpu)
}
st = (struct kvm_steal_time __user *)ghc->hva;
+
+ if (!user_access_begin(st, sizeof(*st)))
+ return;
+
+ unsafe_get_user(version, &st->version, out);
+ if (version & 1)
+ version += 1; /* first time write, random junk */
+
+ version += 1;
+ unsafe_put_user(version, &st->version, out);
+
+ /* Pairs with the guest side virt_rmb() in kvm_steal_clock(). */
+ smp_wmb();
+
+ unsafe_get_user(steal, &st->steal, out);
+ steal += current->sched_info.run_delay -
+ vcpu->arch.st.last_steal;
+ vcpu->arch.st.last_steal = current->sched_info.run_delay;
+ unsafe_put_user(steal, &st->steal, out);
+
+ version += 1;
+ unsafe_put_user(version, &st->version, out);
+
+ /*
+ * Publish the stealtime before making the vCPU look runnable to
+ * the guest.
+ */
+ smp_wmb();
+
/*
* Doing a TLB flush here, on the guest's behalf, can avoid
* expensive IPIs.
@@ -3759,9 +3788,6 @@ static void record_steal_time(struct kvm_vcpu *vcpu)
u8 st_preempted = 0;
int err = -EFAULT;
- if (!user_access_begin(st, sizeof(*st)))
- return;
-
asm volatile("1: xchgb %0, %2\n"
"xor %1, %1\n"
"2:\n"
@@ -3781,37 +3807,17 @@ static void record_steal_time(struct kvm_vcpu *vcpu)
if (st_preempted & KVM_VCPU_FLUSH_TLB)
kvm_vcpu_flush_tlb_guest(vcpu);
- if (!user_access_begin(st, sizeof(*st)))
- goto dirty;
} else {
- if (!user_access_begin(st, sizeof(*st)))
- return;
-
unsafe_put_user(0, &st->preempted, out);
vcpu->arch.st.preempted = 0;
+ user_access_end();
}
- unsafe_get_user(version, &st->version, out);
- if (version & 1)
- version += 1; /* first time write, random junk */
-
- version += 1;
- unsafe_put_user(version, &st->version, out);
-
- smp_wmb();
-
- unsafe_get_user(steal, &st->steal, out);
- steal += current->sched_info.run_delay -
- vcpu->arch.st.last_steal;
- vcpu->arch.st.last_steal = current->sched_info.run_delay;
- unsafe_put_user(steal, &st->steal, out);
-
- version += 1;
- unsafe_put_user(version, &st->version, out);
+ mark_page_dirty_in_slot(vcpu->kvm, ghc->memslot, gpa_to_gfn(ghc->gpa));
+ return;
out:
user_access_end();
- dirty:
mark_page_dirty_in_slot(vcpu->kvm, ghc->memslot, gpa_to_gfn(ghc->gpa));
}
--
2.43.5
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH RFC 2/2] sched/core: Defer preempted remote vCPU task clock updates
2026-08-24 1:26 [PATCH RFC 0/2] Fix KVM guest scheduling accounting issue related to stealtime Dongli Zhang
2026-08-24 1:26 ` [PATCH RFC 1/2] KVM: x86: Update stealtime before clearing preempted state Dongli Zhang
@ 2026-08-24 1:26 ` Dongli Zhang
2026-09-21 15:59 ` Sean Christopherson
1 sibling, 1 reply; 4+ messages in thread
From: Dongli Zhang @ 2026-08-24 1:26 UTC (permalink / raw)
To: linux-kernel, kvm
Cc: seanjc, pbonzini, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, dwmw2, joe.jin
A remote update of a runqueue can advance rq->clock while the owner
vCPU is still preempted by the host. KVM publishes the matching stealtime
when the vCPU is about to re-enter the guest, so the remote CPU can
otherwise charge the stolen interval to rq->clock_task.
Defer clock_task updates made by a remote CPU while the owner vCPU is
reported preempted. Fold the deferred delta back into the next update
that can proceed so IRQ and steal accounting process it together.
This requires the hypervisor to publish up-to-date stealtime before
clearing the preempted data.
Assisted-by: Codex:GPT-5.5
Signed-off-by: Dongli Zhang <dongli.zhang@oracle.com>
---
kernel/sched/core.c | 24 ++++++++++++++++++++++++
kernel/sched/sched.h | 1 +
2 files changed, 25 insertions(+)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index f78275192036..56aa439182c7 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -816,6 +816,30 @@ static void update_rq_clock_task(struct rq *rq, s64 delta)
*/
s64 __maybe_unused steal = 0, irq_delta = 0;
+#ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING
+ if (static_key_false((¶virt_steal_rq_enabled))) {
+ int rq_cpu = cpu_of(rq);
+
+ /*
+ * A remote CPU can update this rq before the owner vCPU
+ * has re-entered the guest and refreshed its stealtime
+ * state. Do not charge that elapsed time to the current
+ * task until stealtime can be sampled after the vCPU is
+ * no longer preempted.
+ */
+ if (rq_cpu != raw_smp_processor_id() &&
+ vcpu_is_preempted(rq_cpu)) {
+ rq->deferred_clock_task += delta;
+ return;
+ }
+
+ if (rq->deferred_clock_task) {
+ delta += rq->deferred_clock_task;
+ rq->deferred_clock_task = 0;
+ }
+ }
+#endif
+
#ifdef CONFIG_IRQ_TIME_ACCOUNTING
if (irqtime_enabled()) {
irq_delta = irq_time_read(cpu_of(rq)) - rq->prev_irq_time;
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index e656c7059bf8..4fd00228afef 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -1325,6 +1325,7 @@ struct rq {
#endif
#ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING
u64 prev_steal_time_rq;
+ u64 deferred_clock_task;
#endif
/* calc_load related fields */
--
2.43.5
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH RFC 2/2] sched/core: Defer preempted remote vCPU task clock updates
2026-08-24 1:26 ` [PATCH RFC 2/2] sched/core: Defer preempted remote vCPU task clock updates Dongli Zhang
@ 2026-09-21 15:59 ` Sean Christopherson
0 siblings, 0 replies; 4+ messages in thread
From: Sean Christopherson @ 2026-09-21 15:59 UTC (permalink / raw)
To: Dongli Zhang
Cc: linux-kernel, kvm, pbonzini, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, dwmw2, joe.jin
On Sun, Aug 23, 2026, Dongli Zhang wrote:
> A remote update of a runqueue can advance rq->clock while the owner
> vCPU is still preempted by the host. KVM publishes the matching stealtime
> when the vCPU is about to re-enter the guest, so the remote CPU can
> otherwise charge the stolen interval to rq->clock_task.
>
> Defer clock_task updates made by a remote CPU while the owner vCPU is
> reported preempted. Fold the deferred delta back into the next update
> that can proceed so IRQ and steal accounting process it together.
>
> This requires the hypervisor to publish up-to-date stealtime before
> clearing the preempted data.
What happens if the hypervisor doesn't do that? Because it's infeasible to
guarantee this will never run on an older version of KVM.
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-21 15:59 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-24 1:26 [PATCH RFC 0/2] Fix KVM guest scheduling accounting issue related to stealtime Dongli Zhang
2026-08-24 1:26 ` [PATCH RFC 1/2] KVM: x86: Update stealtime before clearing preempted state Dongli Zhang
2026-08-24 1:26 ` [PATCH RFC 2/2] sched/core: Defer preempted remote vCPU task clock updates Dongli Zhang
2026-09-21 15:59 ` Sean Christopherson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®