[tip: sched/urgent] sched/core: Account I/O waits independently of delayed dequeue
From: tip-bot2 for Christian Loehle
Date: Fri Nov 06 2026 - 04:54:02 EST
The following commit has been merged into the sched/urgent branch of tip:
Commit-ID: 0965587d00b84f0a3b0fc94930d4a5f8ff011eda
Gitweb: https://git.kernel.org/tip/0965587d00b84f0a3b0fc94930d4a5f8ff011eda
Author: Christian Loehle <christian.loehle@xxxxxxx>
AuthorDate: Thu, 15 Oct 2026 12:37:54 +01:00
Committer: Ingo Molnar <mingo@xxxxxxxxxx>
CommitterDate: Fri, 06 Nov 2026 10:01:52 +01:00
sched/core: Account I/O waits independently of delayed dequeue
Starting blkio delay accounting and incrementing rq->nr_iowait in
__block_task() ties both to removal from the runqueue. With EEVDF delayed
dequeue, this misses the initial part of an I/O sleep, or the entire
sleep if the task wakes before it is dequeued. Such tasks are also
missing from procs_blocked while their dequeue is delayed. Final dequeue
can run in another task's context, causing delayacct_blkio_start() to
timestamp the wrong task.
Start both forms of accounting in try_to_block_task(), when the current
task commits to blocking, before delayed dequeue or proxy execution can
retain it on the runqueue. End both when clearing is_blocked, including
queued wakeups and the proxy execution paths that let the current task
continue. Checking is_blocked excludes wakeups that precede blocking.
Save the CPU charged at the start of the wait. Delayed tasks can migrate
while still queued, and proxy execution can also move blocked tasks.
Decrement the saved CPU's counter on unblock, regardless of the task's
current placement, and remove the old dequeue-based accounting.
CPU iowait continues to classify idle time using the counter on the
charged CPU; it does not accumulate the full per-task blocked duration.
Closes: https://lore.kernel.org/r/20261014081731.1068794-1-zhanxusheng@xxxxxxxxxx
Fixes: 152e11f6df29 ("sched/fair: Implement delayed dequeue")
Reported-by: Zhan Xusheng <zhanxusheng@xxxxxxxxxx>
Signed-off-by: Christian Loehle <christian.loehle@xxxxxxx>
Signed-off-by: Peter Zijlstra (Intel) <peterz@xxxxxxxxxxxxx>
Signed-off-by: Ingo Molnar <mingo@xxxxxxxxxx>
Reviewed-by: Zhan Xusheng <zhanxusheng@xxxxxxxxxx>
Reviewed-by: Kayra Cizmeci <kayracizmeci@xxxxxxxxx>
Link: https://patch.msgid.link/fd8578a1-e60b-4993-ab51-2b5d63abcf77@xxxxxxx
---
include/linux/sched.h | 2 ++
kernel/sched/core.c | 33 ++++++++++++++++++++-------------
kernel/sched/sched.h | 5 -----
3 files changed, 22 insertions(+), 18 deletions(-)
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 1c93da7..e4f6323 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -881,6 +881,8 @@ struct task_struct {
*/
int recent_used_cpu;
int wake_cpu;
+ /* CPU charged while is_blocked && in_iowait. */
+ int iowait_cpu;
int prio;
int static_prio;
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 04b62c1..1a088d4 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -636,7 +636,7 @@ int task_llc(const struct task_struct *p)
*
* p->is_blocked <- { 0, 1 }:
*
- * is set by try_to_block_task() and cleared by ttwu_do_wakeup() and tracks
+ * is set by try_to_block_task() and cleared by unblock_task() and tracks
* if the task is blocked. Traditionally this would mirror p->on_rq, however
* due things like DELAY_DEQUEUE and PROXY_EXEC, this can diverge.
*
@@ -3773,12 +3773,22 @@ ttwu_stat(struct task_struct *p, int cpu, int wake_flags)
__schedstat_inc(p->stats.nr_wakeups_sync);
}
+static inline void unblock_task(struct task_struct *p)
+{
+ if (p->is_blocked && p->in_iowait) {
+ delayacct_blkio_end(p);
+ atomic_dec(&cpu_rq(p->iowait_cpu)->nr_iowait);
+ }
+
+ p->is_blocked = 0;
+}
+
/*
* Mark the task runnable.
*/
static inline void ttwu_do_wakeup(struct task_struct *p)
{
- p->is_blocked = 0;
+ unblock_task(p);
WRITE_ONCE(p->__state, TASK_RUNNING);
trace_sched_wakeup(p);
}
@@ -3889,10 +3899,6 @@ ttwu_do_activate(struct rq *rq, struct task_struct *p, int wake_flags,
en_flags |= ENQUEUE_RQ_SELECTED;
if (wake_flags & WF_MIGRATED)
en_flags |= ENQUEUE_MIGRATED;
- else if (p->in_iowait) {
- delayacct_blkio_end(p);
- atomic_dec(&task_rq(p)->nr_iowait);
- }
activate_task(rq, p, en_flags);
wakeup_preempt(rq, p, wake_flags);
@@ -4465,11 +4471,6 @@ int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
cpu = select_task_rq(p, p->wake_cpu, &wake_flags);
if (task_cpu(p) != cpu) {
- if (p->in_iowait) {
- delayacct_blkio_end(p);
- atomic_dec(&task_rq(p)->nr_iowait);
- }
-
wake_flags |= WF_MIGRATED;
psi_ttwu_dequeue(p);
set_task_cpu(p, cpu);
@@ -6813,6 +6814,12 @@ static bool try_to_block_task(struct rq *rq, struct task_struct *p,
p->is_blocked = 1;
+ if (p->in_iowait) {
+ p->iowait_cpu = cpu_of(rq);
+ atomic_inc(&rq->nr_iowait);
+ delayacct_blkio_start();
+ }
+
/*
* We check should_block after signal_pending because we
* will want to wake the task in that case. But if
@@ -7020,7 +7027,7 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
if (!mutex) {
clear_task_blocked_on(p, mutex);
if (task_current(rq, p)) {
- p->is_blocked = 0;
+ unblock_task(p);
return p;
}
goto deactivate;
@@ -7056,7 +7063,7 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
*/
__clear_task_blocked_on(p, NULL);
if (task_current(rq, p)) {
- p->is_blocked = 0;
+ unblock_task(p);
return p;
}
goto deactivate;
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 6c7c563..1d76950 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -3124,11 +3124,6 @@ static inline void __block_task(struct rq *rq, struct task_struct *p)
if (p->sched_contributes_to_load)
rq->nr_uninterruptible++;
- if (p->in_iowait) {
- atomic_inc(&rq->nr_iowait);
- delayacct_blkio_start();
- }
-
ASSERT_EXCLUSIVE_WRITER(p->on_rq);
/*