| 0/0 |
2026/06/09 11:51 |
flow |
assessment-security |
51m
Results: map[DenialOfService:true Explanation:**Bug Analysis and Mechanism**
The warning `WARN_ON_ONCE(task_rq(p) != rq)` in `hrtick_start_fair()` is
triggered due to a critical flaw in how the scheduler handles the migration of
`sched_delayed` tasks when Core Scheduling (`CONFIG_SCHED_CORE`) is enabled.
Here is the step-by-step mechanism of the bug:
1. **Core Scheduling & Delayed Dequeue:** An unprivileged user creates a core
scheduling group using `prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, ...)`. A task
`p` in this group becomes `sched_delayed` (a feature of the EEVDF scheduler
where a task is logically dequeued but kept on the runqueue because it is still
eligible for execution).
2. **Task Migration:** The task `p` is migrated to another CPU (e.g., via
automatic load balancing `migrate_load` or proxy execution). During migration,
`deactivate_task()` calls `dequeue_task(src_rq, p, DEQUEUE_NOCLOCK)`.
3. **Core Tree Desync:** `dequeue_task()` calls `sched_core_dequeue(src_rq, p)`.
However, because `p->se.sched_delayed` is true, `sched_core_dequeue()` returns
early and **fails to remove the task from `src_rq->core_tree`**.
4. **Incomplete State Update:** `dequeue_task_fair()` does not clear the
`sched_delayed` flag because the `DEQUEUE_DELAYED` flag was not passed during
migration.
5. **Destination Enqueue Failure:** `set_task_cpu(p, dst_cpu)` updates the
task's CPU. Then, `activate_task()` calls `enqueue_task(dst_rq, p,
ENQUEUE_MIGRATED)`. `sched_core_enqueue(dst_rq, p)` is called, but again returns
early because `p->se.sched_delayed` is still true. Thus, the task is **not added
to `dst_rq->core_tree`**.
6. **The Inconsistency:** At this point, task `p` is physically enqueued on
`dst_rq->cfs_tasks`, its `task_cpu` is `dst_cpu`, but it is still lingering in
`src_rq->core_tree`.
7. **Triggering the Bug:** When `src_rq` performs core scheduling,
`sched_core_find(src_rq, cookie)` finds `p` in its `core_tree` and selects it as
the next task to run. `src_rq` then calls `set_next_task_fair(src_rq, p, true)`.
8. **The Warning:** `set_next_task_fair()` calls `hrtick_start_fair(src_rq, p)`,
which checks `WARN_ON_ONCE(task_rq(p) != rq)`. Since `task_rq(p)` is `dst_rq`
and `rq` is `src_rq`, the warning is triggered.
**Security Impact**
* **Exploitable:** **Yes.** The desynchronization leads to a severe race
condition and memory corruption. When `src_rq` picks the task and calls
`set_next_task_fair()`, it executes `list_move(&p->se.group_node,
&src_rq->cfs_tasks)`. Because `p->se.group_node` is currently linked in
`dst_rq->cfs_tasks`, this performs a concurrent list modification on `dst_rq`'s
runqueue without holding `dst_rq`'s lock. Corrupting the scheduler's core
runqueue lists (`cfs_tasks`) provides a strong primitive for arbitrary memory
read/write and control-flow hijacking (privilege escalation).
* **Denial of Service:** **Yes.** Even if not successfully exploited for
privilege escalation, the runqueue list corruption will inevitably cause the
kernel to crash (e.g., infinite loops during runqueue traversal, NULL pointer
dereferences, or `BUG()` halts), leading to a complete system denial of service.
* **Accessible From Unprivileged Processes:** **Yes.** The
`prctl(PR_SCHED_CORE)` syscall is available to unprivileged users to group their
own processes. The subsequent migration (load balancing) happens automatically
in the background. No special capabilities (like `CAP_SYS_ADMIN`) are required
to trigger this vulnerability.
* **Accessible From User Namespaces:** **Yes.** Since it is reachable by a
standard unprivileged user, it is also reachable from within user namespaces. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/06/09 11:51 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
static long syz_open_dev(volatile long a0, volatile long a1, volatile long a2)
{
if (a0 == 0xc || a0 == 0xb) {
char buf[128];
sprintf(buf, "/dev/%s/%d:%d", a0 == 0xc ? "char" : "block", (uint8_t)a1, (uint8_t)a2);
return open(buf, O_RDWR, 0);
} else {
unsigned long nb = a1;
char buf[1024];
char* hash;
strncpy(buf, (char*)a0, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
while ((hash = strchr(buf, '#'))) {
*hash = '0' + (char)(nb % 10);
nb /= 10;
}
return open(buf, a2 & ~O_CREAT, 0);
}
}
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// prctl$PR_SCHED_CORE arguments: [
// option: const = 0x3e (8 bytes)
// cmd: intptr = 0x1 (8 bytes)
// pid: pid (resource)
// type: pid_type = 0x2 (8 bytes)
// uaddr: nil
// ]
syscall(__NR_prctl, /*option=*/0x3eul, /*cmd=*/1ul, /*pid=*/0, /*type=PIDTYPE_PGID*/2ul, /*uaddr=*/0ul);
// sendmsg$inet arguments: [
// fd: sock (resource)
// msg: nil
// f: send_flags = 0x0 (8 bytes)
// ]
syscall(__NR_sendmsg, /*fd=*/(intptr_t)-1, /*msg=*/0ul, /*f=*/0ul);
// recvmsg$unix arguments: [
// fd: sock_unix (resource)
// msg: ptr[inout, recv_msghdr_un] {
// recv_msghdr_un {
// addr: nil
// addrlen: len = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// vec: nil
// vlen: len = 0x0 (8 bytes)
// ctrl: nil
// ctrllen: bytesize = 0x0 (8 bytes)
// f: const = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// }
// f: recv_flags = 0x0 (8 bytes)
// ]
*(uint64_t*)0x200000000000 = 0;
*(uint32_t*)0x200000000008 = 0;
*(uint64_t*)0x200000000010 = 0;
*(uint64_t*)0x200000000018 = 0;
*(uint64_t*)0x200000000020 = 0;
*(uint64_t*)0x200000000028 = 0;
*(uint32_t*)0x200000000030 = 0;
syscall(__NR_recvmsg, /*fd=*/(intptr_t)-1, /*msg=*/0x200000000000ul, /*f=*/0ul, 0);
// syz_open_dev$video4linux arguments: [
// dev: nil
// id: intptr = 0xfff (8 bytes)
// flags: open_flags = 0x48c40 (8 bytes)
// ]
// returns fd_video
syz_open_dev(/*dev=*/0, /*id=*/0xfff, /*flags=O_NONBLOCK|O_NOATIME|O_LARGEFILE|O_CREAT|O_APPEND*/0x48c40);
return 0;
}
]
|
| 2/1 |
2026/06/09 11:51 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/19c9d6b2b709747fca296817adff58dddc7206cb]
|
| 3/1 |
2026/06/09 11:51 |
action |
kernel-builder |
24m
Results: map[KernelObj:/app/workdir/cache/build/7dbf2d2525d5ca07d8069c3d1fee4ce089d81284]
|
| 4/1 |
2026/06/09 12:16 |
action |
codesearch-prepare |
18m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/06/09 12:34 |
agent |
expert |
8m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption or elevated privileges.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
------------[ cut here ]------------
task_rq(p) != rq
WARNING: kernel/sched/fair.c:7656 at hrtick_start_fair+0x196/0x1f0 kernel/sched/fair.c:7656, CPU#0: syz.0.20/5965
Modules linked in:
CPU: 0 UID: 0 PID: 5965 Comm: syz.0.20 Not tainted syzkaller #0 PREEMPT_{RT,(full)}
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
RIP: 0010:hrtick_start_fair+0x196/0x1f0 kernel/sched/fair.c:7656
Code: 42 80 3c 20 00 74 08 4c 89 ff e8 85 e3 97 00 4d 39 37 0f 85 0c ff ff ff 48 89 df 5b 41 5c 41 5d 41 5e 41 5f e9 4b 65 fa ff 90 <0f> 0b 90 e9 d1 fe ff ff 44 89 f9 80 e1 07 80 c1 03 38 c1 0f 8c 82
RSP: 0018:ffffc90003dd7a60 EFLAGS: 00010087
RAX: ffff8880b873ba40 RBX: ffff8880b863ba40 RCX: ffffffff8197c7de
RDX: 0000000000000000 RSI: ffff88802eb48000 RDI: ffff8880b863ba40
RBP: dffffc0000000000 R08: ffffffff8fcf0b0f R09: 1ffffffff1f9e161
R10: dffffc0000000000 R11: fffffbfff1f9e162 R12: dffffc0000000000
R13: 1ffff110170c78d6 R14: ffff88802eb48000 R15: ffffffff8dc217d8
FS: 0000000000000000(0000) GS:ffff888125a76000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000555565f2c4e8 CR3: 000000003c1aa000 CR4: 00000000003526f0
Call Trace:
<TASK>
set_next_task_fair+0xa68/0xce0 kernel/sched/fair.c:15058
put_prev_set_next_task kernel/sched/sched.h:2770 [inline]
pick_next_task kernel/sched/core.c:6443 [inline]
__schedule+0x3e03/0x5550 kernel/sched/core.c:7144
do_task_dead+0xc6/0xe0 kernel/sched/core.c:7247
do_exit+0x1b11/0x22c0 kernel/exit.c:1047
do_group_exit+0x21b/0x2d0 kernel/exit.c:1147
__do_sys_exit_group kernel/exit.c:1158 [inline]
__se_sys_exit_group kernel/exit.c:1156 [inline]
__x64_sys_exit_group+0x3f/0x40 kernel/exit.c:1156
x64_sys_call+0x221a/0x2240 arch/x86/include/generated/asm/syscalls_64.h:232
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f092ce9ce59
Code: Unable to access opcode bytes at 0x7f092ce9ce2f.
RSP: 002b:00007ffd3d40c658 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f092ce9ce59
RDX: 0000000000000064 RSI: 0000000000000000 RDI: 0000000000000000
RBP: 00007ffd3d40c6bc R08: 0000000000000000 R09: 00000000000927c0
R10: 0000000000000001 R11: 0000000000000246 R12: 0000000000000003
R13: 00000000000927c0 R14: 000000000001f893 R15: 00007ffd3d40c710
</TASK>
It is reproducible with the followint program.
Keep in mind that it may lack the precise threading, sandboxing, and some arguments of a working reproducer.
But it should give an idea of the involved syscalls.
// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
static long syz_open_dev(volatile long a0, volatile long a1, volatile long a2)
{
if (a0 == 0xc || a0 == 0xb) {
char buf[128];
sprintf(buf, "/dev/%s/%d:%d", a0 == 0xc ? "char" : "block", (uint8_t)a1, (uint8_t)a2);
return open(buf, O_RDWR, 0);
} else {
unsigned long nb = a1;
char buf[1024];
char* hash;
strncpy(buf, (char*)a0, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
while ((hash = strchr(buf, '#'))) {
*hash = '0' + (char)(nb % 10);
nb /= 10;
}
return open(buf, a2 & ~O_CREAT, 0);
}
}
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// prctl$PR_SCHED_CORE arguments: [
// option: const = 0x3e (8 bytes)
// cmd: intptr = 0x1 (8 bytes)
// pid: pid (resource)
// type: pid_type = 0x2 (8 bytes)
// uaddr: nil
// ]
syscall(__NR_prctl, /*option=*/0x3eul, /*cmd=*/1ul, /*pid=*/0, /*type=PIDTYPE_PGID*/2ul, /*uaddr=*/0ul);
// sendmsg$inet arguments: [
// fd: sock (resource)
// msg: nil
// f: send_flags = 0x0 (8 bytes)
// ]
syscall(__NR_sendmsg, /*fd=*/(intptr_t)-1, /*msg=*/0ul, /*f=*/0ul);
// recvmsg$unix arguments: [
// fd: sock_unix (resource)
// msg: ptr[inout, recv_msghdr_un] {
// recv_msghdr_un {
// addr: nil
// addrlen: len = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// vec: nil
// vlen: len = 0x0 (8 bytes)
// ctrl: nil
// ctrllen: bytesize = 0x0 (8 bytes)
// f: const = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// }
// f: recv_flags = 0x0 (8 bytes)
// ]
*(uint64_t*)0x200000000000 = 0;
*(uint32_t*)0x200000000008 = 0;
*(uint64_t*)0x200000000010 = 0;
*(uint64_t*)0x200000000018 = 0;
*(uint64_t*)0x200000000020 = 0;
*(uint64_t*)0x200000000028 = 0;
*(uint32_t*)0x200000000030 = 0;
syscall(__NR_recvmsg, /*fd=*/(intptr_t)-1, /*msg=*/0x200000000000ul, /*f=*/0ul, 0);
// syz_open_dev$video4linux arguments: [
// dev: nil
// id: intptr = 0xfff (8 bytes)
// flags: open_flags = 0x48c40 (8 bytes)
// ]
// returns fd_video
syz_open_dev(/*dev=*/0, /*id=*/0xfff, /*flags=O_NONBLOCK|O_NOATIME|O_LARGEFILE|O_CREAT|O_APPEND*/0x48c40);
return 0;
}
Reply: **Bug Analysis and Mechanism**
The warning `WARN_ON_ONCE(task_rq(p) != rq)` in `hrtick_start_fair()` is triggered due to a critical flaw in how the scheduler handles the migration of `sched_delayed` tasks when Core Scheduling (`CONFIG_SCHED_CORE`) is enabled.
Here is the step-by-step mechanism of the bug:
1. **Core Scheduling & Delayed Dequeue:** An unprivileged user creates a core scheduling group using `prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, ...)`. A task `p` in this group becomes `sched_delayed` (a feature of the EEVDF scheduler where a task is logically dequeued but kept on the runqueue because it is still eligible for execution).
2. **Task Migration:** The task `p` is migrated to another CPU (e.g., via automatic load balancing `migrate_load` or proxy execution). During migration, `deactivate_task()` calls `dequeue_task(src_rq, p, DEQUEUE_NOCLOCK)`.
3. **Core Tree Desync:** `dequeue_task()` calls `sched_core_dequeue(src_rq, p)`. However, because `p->se.sched_delayed` is true, `sched_core_dequeue()` returns early and **fails to remove the task from `src_rq->core_tree`**.
4. **Incomplete State Update:** `dequeue_task_fair()` does not clear the `sched_delayed` flag because the `DEQUEUE_DELAYED` flag was not passed during migration.
5. **Destination Enqueue Failure:** `set_task_cpu(p, dst_cpu)` updates the task's CPU. Then, `activate_task()` calls `enqueue_task(dst_rq, p, ENQUEUE_MIGRATED)`. `sched_core_enqueue(dst_rq, p)` is called, but again returns early because `p->se.sched_delayed` is still true. Thus, the task is **not added to `dst_rq->core_tree`**.
6. **The Inconsistency:** At this point, task `p` is physically enqueued on `dst_rq->cfs_tasks`, its `task_cpu` is `dst_cpu`, but it is still lingering in `src_rq->core_tree`.
7. **Triggering the Bug:** When `src_rq` performs core scheduling, `sched_core_find(src_rq, cookie)` finds `p` in its `core_tree` and selects it as the next task to run. `src_rq` then calls `set_next_task_fair(src_rq, p, true)`.
8. **The Warning:** `set_next_task_fair()` calls `hrtick_start_fair(src_rq, p)`, which checks `WARN_ON_ONCE(task_rq(p) != rq)`. Since `task_rq(p)` is `dst_rq` and `rq` is `src_rq`, the warning is triggered.
**Security Impact**
* **Exploitable:** **Yes.** The desynchronization leads to a severe race condition and memory corruption. When `src_rq` picks the task and calls `set_next_task_fair()`, it executes `list_move(&p->se.group_node, &src_rq->cfs_tasks)`. Because `p->se.group_node` is currently linked in `dst_rq->cfs_tasks`, this performs a concurrent list modification on `dst_rq`'s runqueue without holding `dst_rq`'s lock. Corrupting the scheduler's core runqueue lists (`cfs_tasks`) provides a strong primitive for arbitrary memory read/write and control-flow hijacking (privilege escalation).
* **Denial of Service:** **Yes.** Even if not successfully exploited for privilege escalation, the runqueue list corruption will inevitably cause the kernel to crash (e.g., infinite loops during runqueue traversal, NULL pointer dereferences, or `BUG()` halts), leading to a complete system denial of service.
* **Accessible From Unprivileged Processes:** **Yes.** The `prctl(PR_SCHED_CORE)` syscall is available to unprivileged users to group their own processes. The subsequent migration (load balancing) happens automatically in the background. No special capabilities (like `CAP_SYS_ADMIN`) are required to trigger this vulnerability.
* **Accessible From User Namespaces:** **Yes.** Since it is reachable by a standard unprivileged user, it is also reachable from within user namespaces.
|
| 6/2 |
2026/06/09 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 8587
output: 36
thoughts: 89
Thoughts: Okay, here's my take on that kernel bug report, as if it's my own thought process:
**Diving into the Scheduler's Heart**
Alright, a kernel bug report has landed on my desk. The warning is pretty specific: `kernel/sched/fair.c:7656` at `hrtick_start_fair`. That's pointing directly to the Completely Fair Scheduler (CFS), and specifically the `hrtick_start_fair` function. The key problem appears to be `task_rq(p) != rq`.
My first instinct is to zoom in on that section of `kernel/sched/fair.c`. I need to understand the context of line 7656. What's happening in `hrtick_start_fair`? What are the preconditions for this function's execution, and what does this comparison, `task_rq(p) != rq`, signify? I need to figure out the significance of the "task running on a different run queue than expected." I'll need to see how the task's run queue is determined, how it should relate to the run queue `rq` that this function is working with, and what might be going wrong in the transition. I'll need to trace back from that line to understand the flow control and the data that could lead to this state. My guess is that it has something to do with preemption, migration, or perhaps some sort of race condition in the scheduler's logic. Time to start digging!
|
| 7/2 |
2026/06/09 12:34 |
tool |
read-file |
0m
Args: map[File:kernel/sched/fair.c FirstLine:7640 LineCount:50]
Results: map[Contents:7640: static inline void sched_fair_update_stop_tick(struct rq *rq, struct task_struct *p) {}
7641: #endif
7642:
7643: /**************************************************
7644: * CFS operations on tasks:
7645: */
7646:
7647: #ifdef CONFIG_SCHED_HRTICK
7648: static void hrtick_start_fair(struct rq *rq, struct task_struct *p)
7649: {
7650: struct sched_entity *se = &p->se;
7651: unsigned long scale = 1024;
7652: unsigned long util = 0;
7653: u64 vdelta;
7654: u64 delta;
7655:
7656: WARN_ON_ONCE(task_rq(p) != rq);
7657:
7658: if (rq->cfs.h_nr_queued <= 1)
7659: return;
7660:
7661: /*
7662: * Compute time until virtual deadline
7663: */
7664: vdelta = se->deadline - se->vruntime;
7665: if ((s64)vdelta < 0) {
7666: if (task_current_donor(rq, p))
7667: resched_curr(rq);
7668: return;
7669: }
7670: delta = (se->load.weight * vdelta) / NICE_0_LOAD;
7671:
7672: /*
7673: * Correct for instantaneous load of other classes.
7674: */
7675: util += cpu_util_irq(rq);
7676: if (util && util < 1024) {
7677: scale *= 1024;
7678: scale /= (1024 - util);
7679: }
7680:
7681: hrtick_start(rq, (scale * delta) / 1024);
7682: }
7683:
7684: /*
7685: * Called on enqueue to start the hrtick when h_nr_queued becomes more than 1.
7686: */
7687: static void hrtick_update(struct rq *rq)
7688: {
7689: struct task_struct *donor = rq->donor;
]
|
| 8/2 |
2026/06/09 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 9742
output: 37
thoughts: 338
Thoughts: **Analyzing a Scheduling Warning**
Okay, so I've got a `WARN_ON_ONCE` warning popping up in `hrtick_start_fair`, specifically `WARN_ON_ONCE(task_rq(p) != rq);`. That's interesting, so it's time to dig into the call trace to figure out the path that's leading to this condition. Let's see... the trace starts with `set_next_task_fair`, then through inlines to `put_prev_set_next_task` and then into `pick_next_task`. Following this we have `__schedule` which leads us on the path to `do_task_dead`, then through the usual exit functions: `do_exit`, `do_group_exit`, and finally to the system call interface at `__x64_sys_exit_group`.
Now, the crucial part is understanding what's happening within `set_next_task_fair` at `kernel/sched/fair.c:15058`. That's where the warning originates, so the next logical step is to examine the code there to see why `task_rq(p)` isn't equal to `rq` at this point in the process.
|
| 9/2 |
2026/06/09 12:34 |
tool |
read-file |
0m
Args: map[File:kernel/sched/fair.c FirstLine:15040 LineCount:50]
Results: map[Contents:15040: if (throttled)
15041: task_throttle_setup_work(p);
15042:
15043: se = &p->se;
15044:
15045: if (task_on_rq_queued(p)) {
15046: /*
15047: * Move the next running task to the front of the list, so our
15048: * cfs_tasks list becomes MRU one.
15049: */
15050: list_move(&se->group_node, &rq->cfs_tasks);
15051: }
15052: if (!first)
15053: return;
15054:
15055: WARN_ON_ONCE(se->sched_delayed);
15056:
15057: if (hrtick_enabled_fair(rq))
15058: hrtick_start_fair(rq, p);
15059:
15060: update_misfit_status(p, rq);
15061: sched_fair_update_stop_tick(rq, p);
15062: }
15063:
15064: void init_cfs_rq(struct cfs_rq *cfs_rq)
15065: {
15066: cfs_rq->tasks_timeline = RB_ROOT_CACHED;
15067: cfs_rq->zero_vruntime = (u64)(-(1LL << 20));
15068: raw_spin_lock_init(&cfs_rq->removed.lock);
15069: }
15070:
15071: #ifdef CONFIG_FAIR_GROUP_SCHED
15072: static void task_change_group_fair(struct task_struct *p)
15073: {
15074: /*
15075: * We couldn't detach or attach a forked task which
15076: * hasn't been woken up by wake_up_new_task().
15077: */
15078: if (READ_ONCE(p->__state) == TASK_NEW)
15079: return;
15080:
15081: detach_task_cfs_rq(p);
15082:
15083: /* Tell se's cfs_rq has been changed -- migrated */
15084: p->se.avg.last_update_time = 0;
15085: set_task_rq(p, task_cpu(p));
15086: attach_task_cfs_rq(p);
15087: }
15088:
15089: void free_fair_sched_group(struct task_group *tg)
]
|
| 10/2 |
2026/06/09 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 11191
output: 37
thoughts: 115
Thoughts: Okay, let me break this down.
**Diving into the Heart of the Scheduler**
Alright, I'm looking at this warning: `WARN_ON_ONCE(task_rq(p) != rq);` in `hrtick_start_fair`. My immediate focus is on what `p` and `rq` represent here. So, `p` is the next task about to be scheduled. And `rq` is, of course, the runqueue we're dealing with. The crux of the matter is that `task_rq(p) != rq`. This is the warning condition, meaning the task we're picking to run next, `p`, doesn't belong to the runqueue thatβs currently selecting it. That's a red flag! A task should be selected from its assigned runqueue, not some other runqueue.
Now, hold on... `p` is *the* task being picked. Okay, that helps to clarify things. The warning is specifically alerting us to a situation where a task is being considered on the "wrong" runqueue. I need to trace back how `p` gets passed to `hrtick_start_fair` and to identify where it could be picked up from the wrong runqueue. I better check out `set_next_task_fair` and see how this variable is being populated. I need to get to the root cause of this mis-scheduling.
|
| 11/2 |
2026/06/09 12:34 |
tool |
read-file |
0m
Args: map[File:kernel/sched/fair.c FirstLine:15000 LineCount:50]
Results: map[Contents:15000: attach_task_cfs_rq(p);
15001:
15002: set_task_max_allowed_capacity(p);
15003:
15004: if (task_on_rq_queued(p)) {
15005: /*
15006: * We were most likely switched from sched_rt, so
15007: * kick off the schedule if running, otherwise just see
15008: * if we can still preempt the current task.
15009: */
15010: if (task_current_donor(rq, p))
15011: resched_curr(rq);
15012: else
15013: wakeup_preempt(rq, p, 0);
15014: }
15015: }
15016:
15017: /*
15018: * Account for a task changing its policy or group.
15019: *
15020: * This routine is mostly called to set cfs_rq->curr field when a task
15021: * migrates between groups/classes.
15022: */
15023: static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first)
15024: {
15025: struct sched_entity *se = &p->se;
15026: bool throttled = false;
15027:
15028: for_each_sched_entity(se) {
15029: struct cfs_rq *cfs_rq = cfs_rq_of(se);
15030:
15031: if (IS_ENABLED(CONFIG_FAIR_GROUP_SCHED) &&
15032: first && cfs_rq->curr)
15033: break;
15034:
15035: set_next_entity(cfs_rq, se, first);
15036: /* ensure bandwidth has been allocated on our new cfs_rq */
15037: throttled |= account_cfs_rq_runtime(cfs_rq, 0);
15038: }
15039:
15040: if (throttled)
15041: task_throttle_setup_work(p);
15042:
15043: se = &p->se;
15044:
15045: if (task_on_rq_queued(p)) {
15046: /*
15047: * Move the next running task to the front of the list, so our
15048: * cfs_tasks list becomes MRU one.
15049: */
]
|
| 12/2 |
2026/06/09 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 12421
output: 36
thoughts: 137
Thoughts: **Analyzing the Scheduler's Next Task Selection**
Okay, so I'm looking at this call trace: `set_next_task_fair`, then `put_prev_set_next_task`, which is apparently inlined, and finally, `pick_next_task`, and then `__schedule`. Clearly, `pick_next_task` is the critical point where the scheduler decides what task gets the CPU next.
Given my experience, I'm already mentally flagging that `pick_next_task` in `kernel/sched/core.c` is going to be the central point of interest. I'll need to dive into that function to understand the precise logic the kernel uses to make its task selection. I bet there's a lot of interesting stuff to unpack in there, from scheduling classes to runnable queue algorithms. Let's see how that next task is being chosen.
|
| 13/2 |
2026/06/09 12:34 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:6420 LineCount:50]
Results: map[Contents:6420: task_vruntime_update(rq_i, rq_i->core_pick, !!rq->core->core_forceidle_count);
6421:
6422: rq_i->core_pick->core_occupation = occ;
6423:
6424: if (i == cpu) {
6425: rq_i->core_pick = NULL;
6426: rq_i->core_dl_server = NULL;
6427: continue;
6428: }
6429:
6430: /* Did we break L1TF mitigation requirements? */
6431: WARN_ON_ONCE(!cookie_match(next, rq_i->core_pick));
6432:
6433: if (rq_i->curr == rq_i->core_pick) {
6434: rq_i->core_pick = NULL;
6435: rq_i->core_dl_server = NULL;
6436: continue;
6437: }
6438:
6439: resched_curr(rq_i);
6440: }
6441:
6442: out_set_next:
6443: put_prev_set_next_task(rq, rq->donor, next);
6444: if (rq->core->core_forceidle_count && next == rq->idle)
6445: queue_core_balance(rq);
6446:
6447: return next;
6448: }
6449:
6450: static bool try_steal_cookie(int this, int that)
6451: {
6452: struct rq *dst = cpu_rq(this), *src = cpu_rq(that);
6453: struct task_struct *p;
6454: unsigned long cookie;
6455: bool success = false;
6456:
6457: guard(irq)();
6458: guard(double_rq_lock)(dst, src);
6459:
6460: cookie = dst->core->core_cookie;
6461: if (!cookie)
6462: return false;
6463:
6464: if (dst->curr != dst->idle)
6465: return false;
6466:
6467: p = sched_core_find(src, cookie);
6468: if (!p)
6469: return false;
]
|
| 14/2 |
2026/06/09 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 13479
output: 36
thoughts: 79
Thoughts: Okay, let me mentally reconstruct thisβ¦
**Analyzing the Core Scheduling Issue in the Linux Kernel**
Right, so I'm trying to wrap my head around this issue. First thing's first, I need to pinpoint the relevant code. The function of interest, `pick_next_task`, is likely the heart of the scheduler. I seem to recall it residing in `kernel/sched/core.c`, around line 6443... but wait, the reproducer utilizes `prctl` with `PR_SCHED_CORE`. That's a huge clue! That points directly to core scheduling functionality. Now, I should double check that location... let me see... it looks like `pick_next_task` is in `kernel/sched/core.c` around line 6400. Ok, good to go.
|
| 15/2 |
2026/06/09 12:34 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:6380 LineCount:50]
Results: map[Contents:6380: rq->core->core_forceidle_occupation = occ;
6381: }
6382:
6383: rq->core->core_pick_seq = rq->core->core_task_seq;
6384: next = rq->core_pick;
6385: rq->core_sched_seq = rq->core->core_pick_seq;
6386:
6387: /* Something should have been selected for current CPU */
6388: WARN_ON_ONCE(!next);
6389:
6390: /*
6391: * Reschedule siblings
6392: *
6393: * NOTE: L1TF -- at this point we're no longer running the old task and
6394: * sending an IPI (below) ensures the sibling will no longer be running
6395: * their task. This ensures there is no inter-sibling overlap between
6396: * non-matching user state.
6397: */
6398: for_each_cpu(i, smt_mask) {
6399: rq_i = cpu_rq(i);
6400:
6401: /*
6402: * An online sibling might have gone offline before a task
6403: * could be picked for it, or it might be offline but later
6404: * happen to come online, but its too late and nothing was
6405: * picked for it. That's Ok - it will pick tasks for itself,
6406: * so ignore it.
6407: */
6408: if (!rq_i->core_pick)
6409: continue;
6410:
6411: /*
6412: * Update for new !FI->FI transitions, or if continuing to be in !FI:
6413: * fi_before fi update?
6414: * 0 0 1
6415: * 0 1 1
6416: * 1 0 1
6417: * 1 1 0
6418: */
6419: if (!(fi_before && rq->core->core_forceidle_count))
6420: task_vruntime_update(rq_i, rq_i->core_pick, !!rq->core->core_forceidle_count);
6421:
6422: rq_i->core_pick->core_occupation = occ;
6423:
6424: if (i == cpu) {
6425: rq_i->core_pick = NULL;
6426: rq_i->core_dl_server = NULL;
6427: continue;
6428: }
6429:
]
|
| 16/2 |
2026/06/09 12:35 |
llm |
expert |
0m
Model:
Tokens:
input: 14529
output: 37
thoughts: 15
Thoughts: Okay, here's my attempt at summarizing the thought process, as requested, tailored for an expert audience and in the first person:
**Analyzing the `pick_next_task` Function**
Alright, let's dive into the core logic of the `pick_next_task` function. I want to really understand the flow and any potential bottlenecks. Where does this start? What are the key decision points? I need to quickly identify the variables and conditions that drive task selection. I'm looking for any assumptions being made about the task queue's state or the availability of resources. Are there any implicit dependencies I need to be aware of? Are we prioritizing tasks based on certain criteria, or is it more of a first-come, first-served approach? I'm already mentally running through edge cases and thinking about how this function might handle concurrent requests. Letβs get to it.
|
| 17/2 |
2026/06/09 12:35 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:6200 LineCount:100]
Results: map[Contents:6200: return p;
6201: }
6202:
6203: BUG(); /* The idle class should always have a runnable task. */
6204: }
6205:
6206: extern void task_vruntime_update(struct rq *rq, struct task_struct *p, bool in_fi);
6207:
6208: static void queue_core_balance(struct rq *rq);
6209:
6210: static struct task_struct *
6211: pick_next_task(struct rq *rq, struct rq_flags *rf)
6212: __must_hold(__rq_lockp(rq))
6213: {
6214: struct task_struct *next, *p, *max;
6215: const struct cpumask *smt_mask;
6216: bool fi_before = false;
6217: bool core_clock_updated = (rq == rq->core);
6218: unsigned long cookie;
6219: int i, cpu, occ = 0;
6220: struct rq *rq_i;
6221: bool need_sync;
6222:
6223: if (!sched_core_enabled(rq))
6224: return __pick_next_task(rq, rf);
6225:
6226: cpu = cpu_of(rq);
6227:
6228: /* Stopper task is switching into idle, no need core-wide selection. */
6229: if (cpu_is_offline(cpu)) {
6230: /*
6231: * Reset core_pick so that we don't enter the fastpath when
6232: * coming online. core_pick would already be migrated to
6233: * another cpu during offline.
6234: */
6235: rq->core_pick = NULL;
6236: rq->core_dl_server = NULL;
6237: return __pick_next_task(rq, rf);
6238: }
6239:
6240: /*
6241: * If there were no {en,de}queues since we picked (IOW, the task
6242: * pointers are all still valid), and we haven't scheduled the last
6243: * pick yet, do so now.
6244: *
6245: * rq->core_pick can be NULL if no selection was made for a CPU because
6246: * it was either offline or went offline during a sibling's core-wide
6247: * selection. In this case, do a core-wide selection.
6248: */
6249: if (rq->core->core_pick_seq == rq->core->core_task_seq &&
6250: rq->core->core_pick_seq != rq->core_sched_seq &&
6251: rq->core_pick) {
6252: WRITE_ONCE(rq->core_sched_seq, rq->core->core_pick_seq);
6253:
6254: next = rq->core_pick;
6255: rq->dl_server = rq->core_dl_server;
6256: rq->core_pick = NULL;
6257: rq->core_dl_server = NULL;
6258: goto out_set_next;
6259: }
6260:
6261: prev_balance(rq, rf);
6262:
6263: smt_mask = cpu_smt_mask(cpu);
6264: need_sync = !!rq->core->core_cookie;
6265:
6266: /* reset state */
6267: rq->core->core_cookie = 0UL;
6268: if (rq->core->core_forceidle_count) {
6269: if (!core_clock_updated) {
6270: update_rq_clock(rq->core);
6271: core_clock_updated = true;
6272: }
6273: sched_core_account_forceidle(rq);
6274: /* reset after accounting force idle */
6275: rq->core->core_forceidle_start = 0;
6276: rq->core->core_forceidle_count = 0;
6277: rq->core->core_forceidle_occupation = 0;
6278: need_sync = true;
6279: fi_before = true;
6280: }
6281:
6282: /*
6283: * core->core_task_seq, core->core_pick_seq, rq->core_sched_seq
6284: *
6285: * @task_seq guards the task state ({en,de}queues)
6286: * @pick_seq is the @task_seq we did a selection on
6287: * @sched_seq is the @pick_seq we scheduled
6288: *
6289: * However, preemptions can cause multiple picks on the same task set.
6290: * 'Fix' this by also increasing @task_seq for every pick.
6291: */
6292: rq->core->core_task_seq++;
6293:
6294: /*
6295: * Optimize for common case where this CPU has no cookies
6296: * and there are no cookied tasks running on siblings.
6297: */
6298: if (!need_sync) {
6299: restart_single:
]
|
| 18/2 |
2026/06/09 12:35 |
llm |
expert |
0m
Model:
Tokens:
input: 16300
output: 37
thoughts: 12
Thoughts: Okay, here's my attempt at summarizing the thought process, adopting a first-person perspective as requested, assuming the user is an expert.
**Analyzing `pick_next_task`**
Alright, let's dive back into `pick_next_task`. I need to re-familiarize myself with its logic and the factors that influence its decision-making. Thinking back, it's probably going to be the central point of control in this system. I want to quickly evaluate how it prioritizes tasks, and see if I can pinpoint any bottlenecks or areas ripe for optimization. I need to refresh my memory on any recent updates to the task scheduling, and any considerations for resource allocation. The critical part is understanding what variables and conditions itβs considering. Any interaction with external data sources will be key. Time to get my head back in the game and re-establish my mental model of this part of the system.
|
| 19/2 |
2026/06/09 12:35 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:6300 LineCount:100]
Results: map[Contents:6300: next = pick_task(rq, rf);
6301: if (unlikely(next == RETRY_TASK))
6302: goto restart_single;
6303: if (!next->core_cookie) {
6304: rq->core_pick = NULL;
6305: rq->core_dl_server = NULL;
6306: /*
6307: * For robustness, update the min_vruntime_fi for
6308: * unconstrained picks as well.
6309: */
6310: WARN_ON_ONCE(fi_before);
6311: task_vruntime_update(rq, next, false);
6312: goto out_set_next;
6313: }
6314: }
6315:
6316: /*
6317: * For each thread: do the regular task pick and find the max prio task
6318: * amongst them.
6319: *
6320: * Tie-break prio towards the current CPU
6321: */
6322: restart_multi:
6323: max = NULL;
6324: for_each_cpu_wrap(i, smt_mask, cpu) {
6325: rq_i = cpu_rq(i);
6326:
6327: /*
6328: * Current cpu always has its clock updated on entrance to
6329: * pick_next_task(). If the current cpu is not the core,
6330: * the core may also have been updated above.
6331: */
6332: if (i != cpu && (rq_i != rq->core || !core_clock_updated))
6333: update_rq_clock(rq_i);
6334:
6335: p = pick_task(rq_i, rf);
6336: if (unlikely(p == RETRY_TASK))
6337: goto restart_multi;
6338:
6339: rq_i->core_pick = p;
6340: rq_i->core_dl_server = rq_i->dl_server;
6341:
6342: if (!max || prio_less(max, p, fi_before))
6343: max = p;
6344: }
6345:
6346: cookie = rq->core->core_cookie = max->core_cookie;
6347:
6348: /*
6349: * For each thread: try and find a runnable task that matches @max or
6350: * force idle.
6351: */
6352: for_each_cpu(i, smt_mask) {
6353: rq_i = cpu_rq(i);
6354: p = rq_i->core_pick;
6355:
6356: if (!cookie_equals(p, cookie)) {
6357: p = NULL;
6358: if (cookie)
6359: p = sched_core_find(rq_i, cookie);
6360: if (!p)
6361: p = idle_sched_class.pick_task(rq_i, rf);
6362: }
6363:
6364: rq_i->core_pick = p;
6365: rq_i->core_dl_server = NULL;
6366:
6367: if (p == rq_i->idle) {
6368: if (rq_i->nr_running) {
6369: rq->core->core_forceidle_count++;
6370: if (!fi_before)
6371: rq->core->core_forceidle_seq++;
6372: }
6373: } else {
6374: occ++;
6375: }
6376: }
6377:
6378: if (schedstat_enabled() && rq->core->core_forceidle_count) {
6379: rq->core->core_forceidle_start = rq_clock(rq->core);
6380: rq->core->core_forceidle_occupation = occ;
6381: }
6382:
6383: rq->core->core_pick_seq = rq->core->core_task_seq;
6384: next = rq->core_pick;
6385: rq->core_sched_seq = rq->core->core_pick_seq;
6386:
6387: /* Something should have been selected for current CPU */
6388: WARN_ON_ONCE(!next);
6389:
6390: /*
6391: * Reschedule siblings
6392: *
6393: * NOTE: L1TF -- at this point we're no longer running the old task and
6394: * sending an IPI (below) ensures the sibling will no longer be running
6395: * their task. This ensures there is no inter-sibling overlap between
6396: * non-matching user state.
6397: */
6398: for_each_cpu(i, smt_mask) {
6399: rq_i = cpu_rq(i);
]
|
| 20/2 |
2026/06/09 12:35 |
llm |
expert |
0m
Model:
Tokens:
input: 17996
output: 36
thoughts: 297
Thoughts: **My Analysis of `pick_next_task`'s Core Scheduling Logic**
Okay, so I'm diving into `pick_next_task`, which I understand is the heart of core scheduling. My initial understanding is that it iterates through each CPU within a core, the main scheduling action performed by calling `pick_task(rq_i, rf)` for each CPU. After that, it needs to figure out which task has the highest priority. I'm seeing that it then iterates through CPUs again, comparing the picked task with a "cookie" (which I assume is some kind of identifier). If there's a mismatch, it calls `sched_core_find(rq_i, cookie)`. I'm guessing that's the core's mechanism for resolving conflicts or finding a suitable task. Then it sets `rq_i->core_pick = p`.
Now, the loop over siblings catches my attention. I need to understand this better. It iterates through the sibling CPUs:
```c
for_each_cpu(i, smt_mask) {
rq_i = cpu_rq(i);
if (!rq_i->core_pick)
continue;
// ...
if (i == cpu) {
rq_i->core_pick = NULL;
rq_i->core_dl_server = NULL;
continue;
}
// ...
resched_curr(rq_i);
}
```
If `rq_i->core_pick` isn't set, then it skips it.
If the current CPU is the same as the sibling, it clears the `core_pick` and `core_dl_server` values.
Otherwise, it calls `resched_curr(rq_i)`. So, it's essentially ensuring that the picked tasks are updated across siblings. The `resched_curr` call is the key there, which is what I'll need to focus on next.
Then, there's the `out_set_next:` label. It's calling `put_prev_set_next_task(rq, rq->donor, next)`. Wait, why is it `rq->donor`? That doesn't seem right. Shouldn't that be `rq->curr`? I need to verify that call, to ensure I'm clear on the inputs.
|
| 21/2 |
2026/06/09 12:35 |
tool |
read-file |
0m
Args: map[File:kernel/sched/sched.h FirstLine:2750 LineCount:50]
Results: map[Contents:2750: struct task_struct *prev,
2751: struct task_struct *next)
2752: {
2753: prev->dl_server = NULL;
2754: next->dl_server = rq->dl_server;
2755: rq->dl_server = NULL;
2756: }
2757:
2758: static inline void put_prev_set_next_task(struct rq *rq,
2759: struct task_struct *prev,
2760: struct task_struct *next)
2761: {
2762: WARN_ON_ONCE(rq->donor != prev);
2763:
2764: __put_prev_set_next_dl_server(rq, prev, next);
2765:
2766: if (next == prev)
2767: return;
2768:
2769: prev->sched_class->put_prev_task(rq, prev, next);
2770: next->sched_class->set_next_task(rq, next, true);
2771: }
2772:
2773: /*
2774: * Helper to define a sched_class instance; each one is placed in a separate
2775: * section which is ordered by the linker script:
2776: *
2777: * include/asm-generic/vmlinux.lds.h
2778: *
2779: * *CAREFUL* they are laid out in *REVERSE* order!!!
2780: *
2781: * Also enforce alignment on the instance, not the type, to guarantee layout.
2782: */
2783: #define DEFINE_SCHED_CLASS(name) \
2784: const struct sched_class name##_sched_class \
2785: __aligned(__alignof__(struct sched_class)) \
2786: __section("__" #name "_sched_class")
2787:
2788: /* Defined in include/asm-generic/vmlinux.lds.h */
2789: extern struct sched_class __sched_class_highest[];
2790: extern struct sched_class __sched_class_lowest[];
2791:
2792: extern const struct sched_class stop_sched_class;
2793: extern const struct sched_class dl_sched_class;
2794: extern const struct sched_class rt_sched_class;
2795: extern const struct sched_class fair_sched_class;
2796: extern const struct sched_class idle_sched_class;
2797:
2798: /*
2799: * Iterate only active classes. SCX can take over all fair tasks or be
]
|
| 22/2 |
2026/06/09 12:35 |
llm |
expert |
0m
Model:
Tokens:
input: 19612
output: 36
thoughts: 121
Thoughts: Okay, here's the summary, framed as my thought process:
**Diving Deep into Task Scheduling**
Alright, let's break this down. It looks like `put_prev_set_next_task` is calling `next->sched_class->set_next_task(rq, next, true)`. Standard stuff, setting up the *next* task to run. Hmm, interesting... I see this call in `pick_next_task`, specifically in `kernel/sched/core.c` around line 6443. And it's doing something specific at the `out_set_next` label.
Wait a second⦠`rq->donor`? That's not the usual variable name. That's got me curious. I'm going to have to re-read that section of `kernel/sched/core.c` around line 6443. I need to understand what `rq->donor` is in this context and how it's being used with `put_prev_set_next_task`. There's probably something subtle going on with task donation, maybe related to load balancing or stealing. Time to get to the bottom of this!
|
| 23/2 |
2026/06/09 12:35 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:6435 LineCount:20]
Results: map[Contents:6435: rq_i->core_dl_server = NULL;
6436: continue;
6437: }
6438:
6439: resched_curr(rq_i);
6440: }
6441:
6442: out_set_next:
6443: put_prev_set_next_task(rq, rq->donor, next);
6444: if (rq->core->core_forceidle_count && next == rq->idle)
6445: queue_core_balance(rq);
6446:
6447: return next;
6448: }
6449:
6450: static bool try_steal_cookie(int this, int that)
6451: {
6452: struct rq *dst = cpu_rq(this), *src = cpu_rq(that);
6453: struct task_struct *p;
6454: unsigned long cookie;
]
|
| 24/2 |
2026/06/09 12:35 |
llm |
expert |
0m
Model:
Tokens:
input: 20303
output: 36
thoughts: 182
Thoughts: **Dissecting the `put_prev_set_next_task` Call**
Okay, hold on a second. I need to make sure I'm following this correctly. It looks like `rq->donor` is being passed as `prev` to `put_prev_set_next_task`. That doesn't immediately click. Let me double-check the definition of `put_prev_set_next_task` again, just to be absolutely certain. Ah, yes, `put_prev_set_next_task(rq, prev, next)`. Right. Okay, but... looking at the context in `kernel/sched/core.c:6443`, it's definitely `put_prev_set_next_task(rq, rq->donor, next);`? Something about `rq->donor` in that `prev` position is throwing me off. I need to revisit the `put_prev_set_next_task` implementation within `kernel/sched/core.c` around line 6443 to clarify. Right, yes, that's what it is, `put_prev_set_next_task(rq, prev, next)`. Just to be sure, let me go back and re-examine the `pick_next_task` function; I want to ensure my understanding of the larger context is sound.
|
| 25/2 |
2026/06/09 12:35 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:6440 LineCount:10]
Results: map[Contents:6440: }
6441:
6442: out_set_next:
6443: put_prev_set_next_task(rq, rq->donor, next);
6444: if (rq->core->core_forceidle_count && next == rq->idle)
6445: queue_core_balance(rq);
6446:
6447: return next;
6448: }
6449:
]
|
| 26/2 |
2026/06/09 12:35 |
llm |
expert |
0m
Model:
Tokens:
input: 20953
output: 49
thoughts: 43
Thoughts: Okay, here's my interpretation of that code snippet, delivered as a stream of consciousness, as you requested:
**Deconstructing a Task Placement Decision**
Right, `put_prev_set_next_task(rq, rq->donor, next);`... Yes, that line makes sense. We're setting up the next task in the runqueue. But hold on a second. `rq->donor`? I need to quickly recall what that represents within this context. Let me just check what exactly `rq->donor` is referring to. I need to understand the relationship between `rq` and this "donor" to fully grasp what's happening here with task placement. I'll mentally step back and review that part of the code again.
|
| 27/2 |
2026/06/09 12:35 |
tool |
codesearch-find-references |
0m
Error:
Args: map[ContextFile:kernel/sched/core.c IncludeSnippetLines:0 Name:donor SourceTreePrefix:kernel/sched/]
Results: map[References:<nil> TruncatedOutput:false]
|
| 28/2 |
2026/06/09 12:35 |
llm |
expert |
0m
Model:
Tokens:
input: 21224
output: 25
thoughts: 18
Thoughts: Here's my thought process, summarized as you requested:
**Navigating Kernel Scheduling Code**
Okay, so I need to find something related to "donor" within the kernel's scheduler code. That immediately points me to `kernel/sched/core.c`. My experience tells me that's the central file for scheduling logic. I'm going to fire up my editor/IDE or even `grep` and search directly in that file for the string "donor". I'm anticipating that this search term could be part of a variable name, function name, comment, or even a string literal used within the code. Based on the context, I'll quickly be able to gauge whether the results pertain to what I'm looking for, and will probably have a very good idea of what section of the code I need to start reading. I expect this approach will quickly uncover any relevant code sections. Let's get to it!
|
| 29/2 |
2026/06/09 12:35 |
tool |
grepper |
0m
Args: map[Expression:donor PathPrefix:kernel/sched/]
Results: map[Output:
Full output is too long, showing 500 out of 727 lines.
Use more precise expression if possible.
kernel/sched/core.c=907=static enum hrtimer_restart hrtick(struct hrtimer *timer)
--
kernel/sched/core.c-915- update_rq_clock(rq);
kernel/sched/core.c:916: rq->donor->sched_class->task_tick(rq, rq->donor, 1);
kernel/sched/core.c-917- rq_unlock(rq, &rf);
--
kernel/sched/core.c=2283=void wakeup_preempt(struct rq *rq, struct task_struct *p, int flags)
kernel/sched/core.c-2284-{
kernel/sched/core.c:2285: struct task_struct *donor = rq->donor;
kernel/sched/core.c-2286-
--
kernel/sched/core.c-2299- */
kernel/sched/core.c:2300: if (task_on_rq_queued(donor) && test_tsk_need_resched(rq->curr))
kernel/sched/core.c-2301- rq_clock_skip_update(rq);
--
kernel/sched/core.c=2959=static int affine_move_task(struct rq *rq, struct task_struct *p, struct rq_flags *rf,
--
kernel/sched/core.c-2968- *
kernel/sched/core.c:2969: * We are also done if the task is the current donor, boosting a lock-
kernel/sched/core.c-2970- * holding proxy, (and potentially has been migrated outside its
--
kernel/sched/core.c-2973- if (cpumask_test_cpu(task_cpu(p), &p->cpus_mask) ||
kernel/sched/core.c:2974: (task_current_donor(rq, p) && !task_current(rq, p))) {
kernel/sched/core.c-2975- struct task_struct *push_task = NULL;
--
kernel/sched/core.c=3746=static void zap_balance_callbacks(struct rq *rq);
kernel/sched/core.c-3747-
kernel/sched/core.c:3748:static inline void proxy_reset_donor(struct rq *rq)
kernel/sched/core.c-3749-{
kernel/sched/core.c:3750: WARN_ON_ONCE(rq->donor == rq->curr);
kernel/sched/core.c-3751-
kernel/sched/core.c:3752: put_prev_set_next_task(rq, rq->donor, rq->curr);
kernel/sched/core.c:3753: rq_set_donor(rq, rq->curr);
kernel/sched/core.c-3754- zap_balance_callbacks(rq);
--
kernel/sched/core.c=3765=static inline bool proxy_needs_return(struct rq *rq, struct task_struct *p)
--
kernel/sched/core.c-3789-
kernel/sched/core.c:3790: /* If we're return migrating the rq->donor, switch it out for idle */
kernel/sched/core.c:3791: if (task_current_donor(rq, p))
kernel/sched/core.c:3792: proxy_reset_donor(rq);
kernel/sched/core.c-3793- }
--
kernel/sched/core.c=5669=unsigned long long task_sched_runtime(struct task_struct *p)
--
kernel/sched/core.c-5696- */
kernel/sched/core.c:5697: if (task_current_donor(rq, p) && task_on_rq_queued(p)) {
kernel/sched/core.c-5698- prefetch_curr_exec_start(p);
--
kernel/sched/core.c=5757=void sched_tick(void)
--
kernel/sched/core.c-5760- struct rq *rq = cpu_rq(cpu);
kernel/sched/core.c:5761: /* accounting goes to the donor task */
kernel/sched/core.c:5762: struct task_struct *donor;
kernel/sched/core.c-5763- struct rq_flags rf;
--
kernel/sched/core.c-5772- rq_lock(rq, &rf);
kernel/sched/core.c:5773: donor = rq->donor;
kernel/sched/core.c-5774-
kernel/sched/core.c:5775: psi_account_irqtime(rq, donor, NULL);
kernel/sched/core.c-5776-
--
kernel/sched/core.c-5783-
kernel/sched/core.c:5784: donor->sched_class->task_tick(rq, donor, 0);
kernel/sched/core.c-5785- if (sched_feat(LATENCY_WARN))
--
kernel/sched/core.c-5797-
kernel/sched/core.c:5798: if (donor->flags & PF_WQ_WORKER)
kernel/sched/core.c:5799: wq_worker_tick(donor);
kernel/sched/core.c-5800-
--
kernel/sched/core.c=5844=static void sched_tick_remote(struct work_struct *work)
--
kernel/sched/core.c-5868- */
kernel/sched/core.c:5869: WARN_ON_ONCE(rq->curr != rq->donor);
kernel/sched/core.c-5870- update_rq_clock(rq);
--
kernel/sched/core.c=6096=static void prev_balance(struct rq *rq, struct rq_flags *rf)
kernel/sched/core.c-6097-{
kernel/sched/core.c:6098: const struct sched_class *start_class = rq->donor->sched_class;
kernel/sched/core.c-6099- const struct sched_class *class;
--
kernel/sched/core.c=6119=__pick_next_task(struct rq *rq, struct rq_flags *rf)
--
kernel/sched/core.c-6135- */
kernel/sched/core.c:6136: if (likely(!sched_class_above(rq->donor->sched_class, &fair_sched_class) &&
kernel/sched/core.c-6137- rq->nr_running == rq->cfs.h_nr_queued)) {
--
kernel/sched/core.c-6146-
kernel/sched/core.c:6147: put_prev_set_next_task(rq, rq->donor, p);
kernel/sched/core.c-6148- return p;
--
kernel/sched/core.c-6158- if (p) {
kernel/sched/core.c:6159: put_prev_set_next_task(rq, rq->donor, p);
kernel/sched/core.c-6160- return p;
--
kernel/sched/core.c=6211=pick_next_task(struct rq *rq, struct rq_flags *rf)
--
kernel/sched/core.c-6442-out_set_next:
kernel/sched/core.c:6443: put_prev_set_next_task(rq, rq->donor, next);
kernel/sched/core.c-6444- if (rq->core->core_forceidle_count && next == rq->idle)
--
kernel/sched/core.c=6740=static inline struct task_struct *proxy_resched_idle(struct rq *rq)
kernel/sched/core.c-6741-{
kernel/sched/core.c:6742: put_prev_set_next_task(rq, rq->donor, rq->idle);
kernel/sched/core.c-6743- rq->next_class = &idle_sched_class;
kernel/sched/core.c:6744: rq_set_donor(rq, rq->idle);
kernel/sched/core.c-6745- set_tsk_need_resched(rq->idle);
--
kernel/sched/core.c-6748-
kernel/sched/core.c:6749:static void proxy_deactivate(struct rq *rq, struct task_struct *donor)
kernel/sched/core.c-6750-{
kernel/sched/core.c:6751: unsigned long state = READ_ONCE(donor->__state);
kernel/sched/core.c-6752-
kernel/sched/core.c-6753- WARN_ON_ONCE(state == TASK_RUNNING);
kernel/sched/core.c:6754: WARN_ON_ONCE(donor->blocked_on);
kernel/sched/core.c-6755- /*
kernel/sched/core.c:6756: * Because we got donor from pick_next_task(), it is *crucial*
kernel/sched/core.c-6757- * that we call proxy_resched_idle() before we deactivate it.
kernel/sched/core.c:6758: * As once we deactivate donor, donor->on_rq is set to zero,
kernel/sched/core.c-6759- * which allows ttwu() to immediately try to wake the task on
kernel/sched/core.c:6760: * another rq. So we cannot use *any* references to donor
kernel/sched/core.c:6761: * after that point. So things like cfs_rq->curr or rq->donor
kernel/sched/core.c-6762- * need to be changed from next *before* we deactivate.
--
kernel/sched/core.c-6764- proxy_resched_idle(rq);
kernel/sched/core.c:6765: block_task(rq, donor, state);
kernel/sched/core.c-6766-}
--
kernel/sched/core.c=6808=static void proxy_migrate_task(struct rq *rq, struct rq_flags *rf,
--
kernel/sched/core.c-6816- /*
kernel/sched/core.c:6817: * Since we are migrating a blocked donor, it could be rq->donor,
kernel/sched/core.c-6818- * and we want to make sure there aren't any references from this
kernel/sched/core.c-6819- * rq to it before we drop the lock. This avoids another cpu
kernel/sched/core.c:6820: * jumping in and grabbing the rq lock and referencing rq->donor
kernel/sched/core.c-6821- * or cfs_rq->curr, etc after we have migrated it to another cpu,
--
kernel/sched/core.c-6823- *
kernel/sched/core.c:6824: * So call proxy_resched_idle() to drop the rq->donor references
kernel/sched/core.c-6825- * before we release the lock.
--
kernel/sched/core.c-6839-/*
kernel/sched/core.c:6840: * Find runnable lock owner to proxy for mutex blocked donor
kernel/sched/core.c-6841- *
--
kernel/sched/core.c-6846- * | v
kernel/sched/core.c:6847: * blocked_donor | mutex
kernel/sched/core.c-6848- * | | owner
--
kernel/sched/core.c-6851- *
kernel/sched/core.c:6852: * and set the blocked_donor relation, this latter is used by the mutex
kernel/sched/core.c-6853- * code to find which (blocked) task to hand-off to.
--
kernel/sched/core.c=6865=static struct task_struct *
kernel/sched/core.c:6866:find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
kernel/sched/core.c-6867- __must_hold(__rq_lockp(rq))
--
kernel/sched/core.c-6875- /* Follow blocked_on chain. */
kernel/sched/core.c:6876: for (p = donor; p->is_blocked; p = owner) {
kernel/sched/core.c-6877- /* if its PROXY_WAKING, do return migration or run if current */
--
kernel/sched/core.c-6973- * lock(&wait_lock);
kernel/sched/core.c:6974: * donor(owner) = current->blocked_donor;
kernel/sched/core.c-6975- * unlock(&wait_lock);
--
kernel/sched/core.c-6995- */
kernel/sched/core.c:6996: owner->blocked_donor = p;
kernel/sched/core.c-6997- }
--
kernel/sched/core.c=7009=static struct task_struct *
kernel/sched/core.c:7010:find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
kernel/sched/core.c-7011-{
kernel/sched/core.c-7012- WARN_ONCE(1, "This should never be called in the !SCHED_PROXY_EXEC case\n");
kernel/sched/core.c:7013: return donor;
kernel/sched/core.c-7014-}
--
kernel/sched/core.c=7056=static void __sched notrace __schedule(int sched_mode)
--
kernel/sched/core.c-7146- if (sched_proxy_exec()) {
kernel/sched/core.c:7147: struct task_struct *prev_donor = rq->donor;
kernel/sched/core.c-7148-
kernel/sched/core.c:7149: rq_set_donor(rq, next);
kernel/sched/core.c:7150: next->blocked_donor = NULL;
kernel/sched/core.c-7151- if (unlikely(next->is_blocked)) {
--
kernel/sched/core.c-7161- }
kernel/sched/core.c:7162: if (rq->donor == prev_donor && prev != next) {
kernel/sched/core.c:7163: struct task_struct *donor = rq->donor;
kernel/sched/core.c-7164- /*
--
kernel/sched/core.c-7167- * prev next
kernel/sched/core.c:7168: * donor: B B
kernel/sched/core.c-7169- * curr: A B or C
--
kernel/sched/core.c-7175- */
kernel/sched/core.c:7176: donor->sched_class->put_prev_task(rq, donor, donor);
kernel/sched/core.c:7177: donor->sched_class->set_next_task(rq, donor, true);
kernel/sched/core.c-7178- }
kernel/sched/core.c-7179- } else {
kernel/sched/core.c:7180: rq_set_donor(rq, next);
kernel/sched/core.c-7181- }
--
kernel/sched/core.c=7607=void rt_mutex_post_schedule(void)
--
kernel/sched/core.c-7615- * @p: task to boost
kernel/sched/core.c:7616: * @pi_task: donor task
kernel/sched/core.c-7617- *
--
kernel/sched/core.c=8248=void __init init_idle(struct task_struct *idle, int cpu)
--
kernel/sched/core.c-8288- rq->idle = idle;
kernel/sched/core.c:8289: rq_set_donor(rq, idle);
kernel/sched/core.c-8290- rcu_assign_pointer(rq->curr, idle);
--
kernel/sched/core.c=11178=struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int flags)
--
kernel/sched/core.c-11203- .queued = task_on_rq_queued(p),
kernel/sched/core.c:11204: .running = task_current_donor(rq, p),
kernel/sched/core.c-11205- };
--
kernel/sched/deadline.c=1140=static enum hrtimer_restart dl_server_timer(struct hrtimer *timer, struct sched_dl_entity *dl_se)
--
kernel/sched/deadline.c-1158- */
kernel/sched/deadline.c:1159: rq->donor->sched_class->update_curr(rq);
kernel/sched/deadline.c-1160-
--
kernel/sched/deadline.c=1210=static enum hrtimer_restart dl_task_timer(struct hrtimer *timer)
--
kernel/sched/deadline.c-1285- enqueue_task_dl(rq, p, ENQUEUE_REPLENISH);
kernel/sched/deadline.c:1286: if (dl_task(rq->donor))
kernel/sched/deadline.c-1287- wakeup_preempt_dl(rq, p, 0);
--
kernel/sched/deadline.c=1795=void dl_server_start(struct sched_dl_entity *dl_se)
--
kernel/sched/deadline.c-1806- */
kernel/sched/deadline.c:1807: rq->donor->sched_class->update_curr(rq);
kernel/sched/deadline.c-1808-
--
kernel/sched/deadline.c=2128=static void update_curr_dl(struct rq *rq)
kernel/sched/deadline.c-2129-{
kernel/sched/deadline.c:2130: struct task_struct *donor = rq->donor;
kernel/sched/deadline.c:2131: struct sched_dl_entity *dl_se = &donor->dl;
kernel/sched/deadline.c-2132- s64 delta_exec;
kernel/sched/deadline.c-2133-
kernel/sched/deadline.c:2134: if (!dl_task(donor) || !on_dl_rq(dl_se))
kernel/sched/deadline.c-2135- return;
--
kernel/sched/deadline.c=2575=static void yield_task_dl(struct rq *rq)
--
kernel/sched/deadline.c-2582- */
kernel/sched/deadline.c:2583: rq->donor->dl.dl_yielded = 1;
kernel/sched/deadline.c-2584-
--
kernel/sched/deadline.c=2606=select_task_rq_dl(struct task_struct *p, int cpu, int flags)
kernel/sched/deadline.c-2607-{
kernel/sched/deadline.c:2608: struct task_struct *curr, *donor;
kernel/sched/deadline.c-2609- bool select_rq;
--
kernel/sched/deadline.c-2618- curr = READ_ONCE(rq->curr); /* unlocked access */
kernel/sched/deadline.c:2619: donor = READ_ONCE(rq->donor);
kernel/sched/deadline.c-2620-
--
kernel/sched/deadline.c-2629- */
kernel/sched/deadline.c:2630: select_rq = unlikely(dl_task(donor)) &&
kernel/sched/deadline.c-2631- (curr->nr_cpus_allowed < 2 ||
kernel/sched/deadline.c:2632: !dl_entity_preempt(&p->dl, &donor->dl)) &&
kernel/sched/deadline.c-2633- p->nr_cpus_allowed > 1;
--
kernel/sched/deadline.c=2686=static void check_preempt_equal_dl(struct rq *rq, struct task_struct *p)
--
kernel/sched/deadline.c-2692- if (rq->curr->nr_cpus_allowed == 1 ||
kernel/sched/deadline.c:2693: !cpudl_find(&rq->rd->cpudl, rq->donor, NULL))
kernel/sched/deadline.c-2694- return;
--
kernel/sched/deadline.c=2707=static int balance_dl(struct rq *rq, struct rq_flags *rf)
--
kernel/sched/deadline.c-2709- /*
kernel/sched/deadline.c:2710: * Note, rq->donor may change during rq lock drops,
kernel/sched/deadline.c-2711- * so don't re-use prev across lock drops
kernel/sched/deadline.c-2712- */
kernel/sched/deadline.c:2713: struct task_struct *p = rq->donor;
kernel/sched/deadline.c-2714-
--
kernel/sched/deadline.c=2734=static void wakeup_preempt_dl(struct rq *rq, struct task_struct *p, int flags)
--
kernel/sched/deadline.c-2743-
kernel/sched/deadline.c:2744: if (dl_entity_preempt(&p->dl, &rq->donor->dl)) {
kernel/sched/deadline.c-2745- resched_curr(rq);
--
kernel/sched/deadline.c-2752- */
kernel/sched/deadline.c:2753: if ((p->dl.deadline == rq->donor->dl.deadline) &&
kernel/sched/deadline.c-2754- !test_tsk_need_resched(rq->curr))
--
kernel/sched/deadline.c=2773=static void set_next_task_dl(struct rq *rq, struct task_struct *p, bool first)
--
kernel/sched/deadline.c-2790-
kernel/sched/deadline.c:2791: if (rq->donor->sched_class != &dl_sched_class)
kernel/sched/deadline.c-2792- update_dl_rq_load_avg(rq_clock_pelt(rq), rq, 0);
--
kernel/sched/deadline.c=3135=static int push_dl_task(struct rq *rq)
--
kernel/sched/deadline.c-3150- */
kernel/sched/deadline.c:3151: if (dl_task(rq->donor) &&
kernel/sched/deadline.c:3152: dl_time_before(next_task->dl.deadline, rq->donor->dl.deadline) &&
kernel/sched/deadline.c-3153- rq->curr->nr_cpus_allowed > 1) {
--
kernel/sched/deadline.c=3215=static void pull_dl_task(struct rq *this_rq)
--
kernel/sched/deadline.c-3274- if (dl_time_before(p->dl.deadline,
kernel/sched/deadline.c:3275: src_rq->donor->dl.deadline))
kernel/sched/deadline.c-3276- goto skip;
--
kernel/sched/deadline.c=3309=static void task_woken_dl(struct rq *rq, struct task_struct *p)
--
kernel/sched/deadline.c-3313- p->nr_cpus_allowed > 1 &&
kernel/sched/deadline.c:3314: dl_task(rq->donor) &&
kernel/sched/deadline.c-3315- (rq->curr->nr_cpus_allowed < 2 ||
kernel/sched/deadline.c:3316: !dl_entity_preempt(&p->dl, &rq->donor->dl))) {
kernel/sched/deadline.c-3317- push_dl_tasks(rq);
--
kernel/sched/deadline.c=3561=static void switched_to_dl(struct rq *rq, struct task_struct *p)
--
kernel/sched/deadline.c-3577-
kernel/sched/deadline.c:3578: if (rq->donor != p) {
kernel/sched/deadline.c-3579- if (p->nr_cpus_allowed > 1 && rq->dl.overloaded)
kernel/sched/deadline.c-3580- deadline_queue_push_tasks(rq);
kernel/sched/deadline.c:3581: if (dl_task(rq->donor))
kernel/sched/deadline.c-3582- wakeup_preempt_dl(rq, p, 0);
--
kernel/sched/deadline.c=3590=static u64 get_prio_dl(struct rq *rq, struct task_struct *p)
--
kernel/sched/deadline.c-3594- */
kernel/sched/deadline.c:3595: if (task_current_donor(rq, p))
kernel/sched/deadline.c-3596- update_curr_dl(rq);
--
kernel/sched/deadline.c=3605=static void prio_changed_dl(struct rq *rq, struct task_struct *p, u64 old_deadline)
--
kernel/sched/deadline.c-3615-
kernel/sched/deadline.c:3616: if (task_current_donor(rq, p)) {
kernel/sched/deadline.c-3617- /*
--
kernel/sched/ext.c=2313=static void yield_task_scx(struct rq *rq)
kernel/sched/ext.c-2314-{
kernel/sched/ext.c:2315: struct task_struct *p = rq->donor;
kernel/sched/ext.c-2316- struct scx_sched *sch = scx_task_sched(p);
--
kernel/sched/ext.c=2324=static bool yield_to_task_scx(struct rq *rq, struct task_struct *to)
kernel/sched/ext.c-2325-{
kernel/sched/ext.c:2326: struct task_struct *from = rq->donor;
kernel/sched/ext.c-2327- struct scx_sched *sch = scx_task_sched(from);
--
kernel/sched/ext.c=5310=bool scx_hardlockup(int cpu)
--
kernel/sched/ext.c-5319-
kernel/sched/ext.c:5320:static u32 bypass_lb_cpu(struct scx_sched *sch, s32 donor,
kernel/sched/ext.c-5321- struct cpumask *donee_mask, struct cpumask *resched_mask,
kernel/sched/ext.c:5322: u32 nr_donor_target, u32 nr_donee_target)
kernel/sched/ext.c-5323-{
kernel/sched/ext.c:5324: struct rq *donor_rq = cpu_rq(donor);
kernel/sched/ext.c:5325: struct scx_dispatch_q *donor_dsq = bypass_dsq(sch, donor);
kernel/sched/ext.c-5326- struct task_struct *p, *n;
kernel/sched/ext.c:5327: struct scx_dsq_list_node cursor = INIT_DSQ_LIST_CURSOR(cursor, donor_dsq, 0);
kernel/sched/ext.c:5328: s32 delta = READ_ONCE(donor_dsq->nr) - nr_donor_target;
kernel/sched/ext.c-5329- u32 nr_balanced = 0, min_delta_us;
--
kernel/sched/ext.c-5332- * All we want to guarantee is reasonable forward progress. No reason to
kernel/sched/ext.c:5333: * fine tune. Assuming every task on @donor_dsq runs their full slice,
kernel/sched/ext.c-5334- * consider offloading iff the total queued duration is over the
--
kernel/sched/ext.c-5340-
kernel/sched/ext.c:5341: raw_spin_rq_lock_irq(donor_rq);
kernel/sched/ext.c:5342: raw_spin_lock(&donor_dsq->lock);
kernel/sched/ext.c:5343: list_add(&cursor.node, &donor_dsq->list);
kernel/sched/ext.c-5344-resume:
kernel/sched/ext.c-5345- n = container_of(&cursor, struct task_struct, scx.dsq_list);
kernel/sched/ext.c:5346: n = nldsq_next_task(donor_dsq, n, false);
kernel/sched/ext.c-5347-
--
kernel/sched/ext.c-5351-
kernel/sched/ext.c:5352: n = nldsq_next_task(donor_dsq, n, false);
kernel/sched/ext.c-5353-
kernel/sched/ext.c:5354: if (donor_dsq->nr <= nr_donor_target)
kernel/sched/ext.c-5355- break;
--
kernel/sched/ext.c-5360- /*
kernel/sched/ext.c:5361: * If an earlier pass placed @p on @donor_dsq from a different
kernel/sched/ext.c-5362- * CPU and the donee hasn't consumed it yet, @p is still on the
kernel/sched/ext.c:5363: * previous CPU and task_rq(@p) != @donor_rq. @p can't be moved
kernel/sched/ext.c-5364- * without its rq locked. Skip.
kernel/sched/ext.c-5365- */
kernel/sched/ext.c:5366: if (task_rq(p) != donor_rq)
kernel/sched/ext.c-5367- continue;
--
kernel/sched/ext.c-5384- * and DSQ are already locked. Do an abbreviated dequeue and
kernel/sched/ext.c:5385: * then perform enqueue without unlocking $donor_dsq.
kernel/sched/ext.c-5386- *
kernel/sched/ext.c-5387- * We don't want to drop and reacquire the lock on each
kernel/sched/ext.c:5388: * iteration as @donor_dsq can be very long and potentially
kernel/sched/ext.c-5389- * highly contended. Donee DSQs are less likely to be contended.
--
kernel/sched/ext.c-5392- */
kernel/sched/ext.c:5393: dispatch_dequeue_locked(p, donor_dsq);
kernel/sched/ext.c-5394- dispatch_enqueue(sch, cpu_rq(donee), donee_dsq, p, SCX_ENQ_NESTED);
--
kernel/sched/ext.c-5407- list_move_tail(&cursor.node, &n->scx.dsq_list.node);
kernel/sched/ext.c:5408: raw_spin_unlock(&donor_dsq->lock);
kernel/sched/ext.c:5409: raw_spin_rq_unlock_irq(donor_rq);
kernel/sched/ext.c-5410- cpu_relax();
kernel/sched/ext.c:5411: raw_spin_rq_lock_irq(donor_rq);
kernel/sched/ext.c:5412: raw_spin_lock(&donor_dsq->lock);
kernel/sched/ext.c-5413- goto resume;
--
kernel/sched/ext.c-5417- list_del_init(&cursor.node);
kernel/sched/ext.c:5418: raw_spin_unlock(&donor_dsq->lock);
kernel/sched/ext.c:5419: raw_spin_rq_unlock_irq(donor_rq);
kernel/sched/ext.c-5420-
--
kernel/sched/ext.c=5424=static void bypass_lb_node(struct scx_sched *sch, int node)
--
kernel/sched/ext.c-5429- u32 nr_tasks = 0, nr_cpus = 0, nr_balanced = 0;
kernel/sched/ext.c:5430: u32 nr_target, nr_donor_target;
kernel/sched/ext.c-5431- u32 before_min = U32_MAX, before_max = 0;
--
kernel/sched/ext.c-5449- /*
kernel/sched/ext.c:5450: * We don't want CPUs to have more than $nr_donor_target tasks and
kernel/sched/ext.c-5451- * balancing to fill donee CPUs upto $nr_target. Once targets are
--
kernel/sched/ext.c-5454- nr_target = DIV_ROUND_UP(nr_tasks, nr_cpus);
kernel/sched/ext.c:5455: nr_donor_target = DIV_ROUND_UP(nr_target * SCX_BYPASS_LB_DONOR_PCT, 100);
kernel/sched/ext.c-5456-
--
kernel/sched/ext.c-5469- continue;
kernel/sched/ext.c:5470: if (READ_ONCE(bypass_dsq(sch, cpu)->nr) <= nr_donor_target)
kernel/sched/ext.c-5471- continue;
--
kernel/sched/ext.c-5473- nr_balanced += bypass_lb_cpu(sch, cpu, donee_mask, resched_mask,
kernel/sched/ext.c:5474: nr_donor_target, nr_target);
kernel/sched/ext.c-5475- }
--
kernel/sched/fair.c=1355=static s64 update_se(struct rq *rq, struct sched_entity *se)
--
kernel/sched/fair.c-1365- if (entity_is_task(se)) {
kernel/sched/fair.c:1366: struct task_struct *donor = task_of(se);
kernel/sched/fair.c-1367- struct task_struct *running = rq->curr;
--
kernel/sched/fair.c-1378-
kernel/sched/fair.c:1379: /* cgroup time is always accounted against the donor */
kernel/sched/fair.c:1380: cgroup_account_cputime(donor, delta_exec);
kernel/sched/fair.c-1381- } else {
kernel/sched/fair.c:1382: /* If not task, account the time against donor se */
kernel/sched/fair.c-1383- se->sum_exec_runtime += delta_exec;
--
kernel/sched/fair.c=1977=s64 update_curr_common(struct rq *rq)
kernel/sched/fair.c-1978-{
kernel/sched/fair.c:1979: return update_se(rq, &rq->donor->se);
kernel/sched/fair.c-1980-}
--
kernel/sched/fair.c=1985=static void update_curr(struct cfs_rq *cfs_rq)
--
kernel/sched/fair.c-1988- * Note: cfs_rq->curr corresponds to the task picked to
kernel/sched/fair.c:1989: * run (ie: rq->donor.se) which due to proxy-exec may
kernel/sched/fair.c-1990- * not necessarily be the actual task running
--
kernel/sched/fair.c=2033=static void update_curr_fair(struct rq *rq)
kernel/sched/fair.c-2034-{
kernel/sched/fair.c:2035: update_curr(cfs_rq_of(&rq->donor->se));
kernel/sched/fair.c-2036-}
--
kernel/sched/fair.c=6670=static bool enqueue_throttled_task(struct task_struct *p)
--
kernel/sched/fair.c-6692- * LOCK task_rq_lock
kernel/sched/fair.c:6693: * task_current_donor(p) == true
kernel/sched/fair.c-6694- * task_on_rq_queued(p) == true
--
kernel/sched/fair.c-6711- if (throttled_hierarchy(cfs_rq) &&
kernel/sched/fair.c:6712: !task_current_donor(rq_of(cfs_rq), p)) {
kernel/sched/fair.c-6713- list_add(&p->throttle_node, &cfs_rq->throttled_limbo_list);
--
kernel/sched/fair.c=6845=static bool throttle_cfs_rq(struct cfs_rq *cfs_rq)
--
kernel/sched/fair.c-6898- * If current hierarchy was throttled, add throttle work to the
kernel/sched/fair.c:6899: * current donor. In case of proxy-execution, the execution
kernel/sched/fair.c-6900- * context cannot exit to the userspace while holding a mutex
--
kernel/sched/fair.c-6904- if (curr && curr->on_rq)
kernel/sched/fair.c:6905: task_throttle_setup_work(rq->donor);
kernel/sched/fair.c-6906-
--
kernel/sched/fair.c=7648=static void hrtick_start_fair(struct rq *rq, struct task_struct *p)
--
kernel/sched/fair.c-7665- if ((s64)vdelta < 0) {
kernel/sched/fair.c:7666: if (task_current_donor(rq, p))
kernel/sched/fair.c-7667- resched_curr(rq);
--
kernel/sched/fair.c=7687=static void hrtick_update(struct rq *rq)
kernel/sched/fair.c-7688-{
kernel/sched/fair.c:7689: struct task_struct *donor = rq->donor;
kernel/sched/fair.c-7690-
kernel/sched/fair.c:7691: if (!hrtick_enabled_fair(rq) || donor->sched_class != &fair_sched_class)
]
|
| 30/2 |
2026/06/09 12:35 |
llm |
expert |
0m
Model:
Tokens:
input: 31617
output: 35
thoughts: 885
Thoughts: **Analyzing a Potential Scheduling Anomaly**
Okay, so I'm looking at this `rq->donor` variable and immediately my brain jumps to thinking about proxy execution, particularly with `CONFIG_SCHED_PROXY_EXEC` enabled. It feels like I should check `kernel/sched/core.c` for how `rq->donor` is used. Ah, right, `rq->donor` is the task that's donating its time or priority. I recall seeing it in `pick_next_task` (line 6443 or thereabouts in `core.c`): specifically, `put_prev_set_next_task(rq, rq->donor, next)`. That makes `rq->donor` the previously running task, so I have to check `put_prev_set_next_task` in `sched.h`.
`put_prev_set_next_task` calls `next->sched_class->set_next_task(rq, next, true)`. Now, for the fair scheduler, that translates to `set_next_task_fair`. Right, that means looking at the `set_next_task_fair` function. And in there, there's `hrtick_start_fair`, which makes a WARN_ON_ONCE if `task_rq(p) != rq`. This is where it gets interesting... `p` is `next` in `pick_next_task`, so I'm trying to wrap my head around how `task_rq(next) != rq` could even *happen*.
Back in `pick_next_task`, I see that `next` is set to `rq->core_pick`. Where the heck is *that* set? Looking at the code, it's set in a loop. I see `sched_core_find(rq_i, cookie)` being used. Okay, so that should be returning a task that *should* be runnable on `rq_i`. But could it *potentially* return a task where `task_rq(p) != rq_i`? That seems like the likely spot. Time to look at `sched_core_find`. That's where the problem is most likely hiding.
|
| 31/2 |
2026/06/09 12:36 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:250 LineCount:50]
Results: map[Contents: 250: a_dl = a->dl_server;
251:
252: b_dl = &b->dl;
253: if (b->dl_server)
254: b_dl = b->dl_server;
255:
256: return !dl_time_before(a_dl->deadline, b_dl->deadline);
257: }
258:
259: if (pa == MAX_RT_PRIO + MAX_NICE) /* fair */
260: return cfs_prio_less(a, b, in_fi);
261:
262: #ifdef CONFIG_SCHED_CLASS_EXT
263: if (pa == MAX_RT_PRIO + MAX_NICE + 1) /* ext */
264: return scx_prio_less(a, b, in_fi);
265: #endif
266:
267: return false;
268: }
269:
270: static inline bool __sched_core_less(const struct task_struct *a,
271: const struct task_struct *b)
272: {
273: if (a->core_cookie < b->core_cookie)
274: return true;
275:
276: if (a->core_cookie > b->core_cookie)
277: return false;
278:
279: /* flip prio, so high prio is leftmost */
280: if (prio_less(b, a, !!task_rq(a)->core->core_forceidle_count))
281: return true;
282:
283: return false;
284: }
285:
286: #define __node_2_sc(node) rb_entry((node), struct task_struct, core_node)
287:
288: static inline bool rb_sched_core_less(struct rb_node *a, const struct rb_node *b)
289: {
290: return __sched_core_less(__node_2_sc(a), __node_2_sc(b));
291: }
292:
293: static inline int rb_sched_core_cmp(const void *key, const struct rb_node *node)
294: {
295: const struct task_struct *p = __node_2_sc(node);
296: unsigned long cookie = (unsigned long)key;
297:
298: if (cookie < p->core_cookie)
299: return -1;
]
|
| 32/2 |
2026/06/09 12:36 |
llm |
expert |
0m
Model:
Tokens:
input: 33783
output: 36
thoughts: 11
Thoughts: Here's my thought process, summarized as you requested:
**Finding `sched_core_find`**
Okay, so I need to track down the `sched_core_find` function. I'm likely looking for the core scheduling implementation here. I need to understand its purpose within the scheduler's hierarchy. Is it involved in core affinity, NUMA balancing, or something else entirely? Knowing where it's used and the context of its execution is crucial. I'll probably start with a grep across the kernel source, or if I'm feeling fancy, jump into cscope or LXR to get a jump on things. I'll need to remember to account for potential variations in naming conventions across different kernel versions, so I'll keep an open mind. This is going to be about understanding the details behind the scheduler's logic.
|
| 33/2 |
2026/06/09 12:36 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/sched/core.c Name:sched_core_find]
Results: map[SourceCode: 369: /*
370: * Find left-most (aka, highest priority) and unthrottled task matching @cookie.
371: * If no suitable task is found, NULL will be returned.
372: */
373: static struct task_struct *sched_core_find(struct rq *rq, unsigned long cookie)
374: {
375: struct task_struct *p;
376: struct rb_node *node;
377:
378: node = rb_find_first((void *)cookie, &rq->core_tree, rb_sched_core_cmp);
379: if (!node)
380: return NULL;
381:
382: p = __node_2_sc(node);
383: if (!sched_task_is_throttled(p, rq->cpu))
384: return p;
385:
386: return sched_core_next(p, cookie);
387: }
SourceFile:kernel/sched/core.c]
|
| 34/2 |
2026/06/09 12:36 |
llm |
expert |
0m
Model:
Tokens:
input: 34321
output: 51
thoughts: 59
Thoughts: Okay, let's see, I'm thinking about `sched_core_find`. My understanding is that it's meant to search `rq->core_tree`. Now, a question arises: can a task actually be *present* within `rq->core_tree` and yet its `task_rq(p)` not point back to the same runqueue, `rq`? That seems like an inconsistency, a potential flaw. I need to understand this more deeply.
To investigate this, I need to trace how tasks are added to `rq->core_tree`. I need to review that code path to understand if this apparent discrepancy is actually *possible*. I need to see if there's a race condition, a bug, or some intended behaviour that I'm overlooking. I'm going to follow the logic and look at *exactly* how tasks become part of `rq->core_tree`. This is where I'll find my answer, likely.
|
| 35/2 |
2026/06/09 12:36 |
tool |
codesearch-find-references |
0m
Error:
Args: map[ContextFile:kernel/sched/core.c IncludeSnippetLines:0 Name:core_tree SourceTreePrefix:kernel/sched/]
Results: map[References:<nil> TruncatedOutput:false]
|
| 36/2 |
2026/06/09 12:36 |
llm |
expert |
0m
Model:
Tokens:
input: 34654
output: 27
thoughts: 10
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Running a Quick Diagnostic Check**
Alright, I need to get to the bottom of this. Let's start with the basics. My initial instinct is to use `grep`. It's quick, dirty, and usually tells me what I need to know in a hurry. Specifically, I'm going to look for `core_tree`. That's a good starting point, it's a specific enough term that I expect it might lead me right to the critical area or code responsible. This suggests I need to scan for this term - it could be a variable, file name, or some kind of identifier. Let's see what that uncovers.
|
| 37/2 |
2026/06/09 12:36 |
tool |
grepper |
0m
Args: map[Expression:core_tree PathPrefix:kernel/sched/]
Results: map[Output:kernel/sched/core.c=307=void sched_core_enqueue(struct rq *rq, struct task_struct *p)
--
kernel/sched/core.c-316-
kernel/sched/core.c:317: rb_add(&p->core_node, &rq->core_tree, rb_sched_core_less);
kernel/sched/core.c-318-}
--
kernel/sched/core.c=320=void sched_core_dequeue(struct rq *rq, struct task_struct *p, int flags)
--
kernel/sched/core.c-327- if (sched_core_enqueued(p)) {
kernel/sched/core.c:328: rb_erase(&p->core_node, &rq->core_tree);
kernel/sched/core.c-329- RB_CLEAR_NODE(&p->core_node);
--
kernel/sched/core.c=373=static struct task_struct *sched_core_find(struct rq *rq, unsigned long cookie)
--
kernel/sched/core.c-377-
kernel/sched/core.c:378: node = rb_find_first((void *)cookie, &rq->core_tree, rb_sched_core_cmp);
kernel/sched/core.c-379- if (!node)
--
kernel/sched/core.c=465=static void sched_core_assert_empty(void)
--
kernel/sched/core.c-469- for_each_possible_cpu(cpu)
kernel/sched/core.c:470: WARN_ON_ONCE(!RB_EMPTY_ROOT(&cpu_rq(cpu)->core_tree));
kernel/sched/core.c-471-}
--
kernel/sched/core.c=8912=void __init sched_init(void)
--
kernel/sched/core.c-9048- rq->core_enabled = 0;
kernel/sched/core.c:9049: rq->core_tree = RB_ROOT;
kernel/sched/core.c-9050- rq->core_forceidle_count = 0;
--
kernel/sched/sched.h=1135=struct rq {
--
kernel/sched/sched.h-1350- unsigned int core_sched_seq;
kernel/sched/sched.h:1351: struct rb_root core_tree;
kernel/sched/sched.h-1352-
]
|
| 38/2 |
2026/06/09 12:36 |
llm |
expert |
0m
Model:
Tokens:
input: 35480
output: 53
thoughts: 37
Thoughts: Okay, here's my take on those thoughts, framed from an expert's perspective:
**Diving into Core Tree Management**
Alright, so we're looking at `sched_core_enqueue` and `sched_core_dequeue`. These two functions are the gatekeepers, the traffic controllers for the `core_tree`, which I assume is the data structure representing the logical core hierarchy, maybe even with some cache topology info. My immediate thought is, 'Okay, good, these are the primitives for maintaining consistency.' Now, I need to understand the control flow, so I need to find the call sites for `sched_core_enqueue`. Where is this function *actually* being invoked? That's where the real story lies, the context will give me the insights I need to determine how the core tree is being managed within the scheduler and understand the implications for workload placement and resource allocation. Let's trace those calls.
|
| 39/2 |
2026/06/09 12:36 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:kernel/sched/core.c IncludeSnippetLines:0 Name:sched_core_enqueue SourceTreePrefix:kernel/sched/]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:enqueue_task SourceFile:kernel/sched/core.c SourceLine:2192] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:sched_core_update_cookie SourceFile:kernel/sched/core_sched.c SourceLine:82]] TruncatedOutput:false]
|
| 40/2 |
2026/06/09 12:36 |
llm |
expert |
0m
Model:
Tokens:
input: 35884
output: 36
thoughts: 272
Thoughts: **Analyzing Task Enqueue/Dequeue with `sched_core`**
Okay, let's break this down. I see that `enqueue_task` uses `sched_core_enqueue` when the `sched_core` feature is enabled on the runqueue (`rq`). And, I know that `sched_core_dequeue` is called in `dequeue_task`. This suggests that the `core_tree` likely holds tasks enqueued on the `rq`.
Now, here's where it gets interesting. I'm seeing a potential scenario where `task_rq(p)` is *not* equal to `rq` while the task `p` is enqueued on `rq`. That seems... odd. But, hold on, `task_rq(p)` is actually equivalent to `cpu_rq(task_cpu(p))`. That makes more sense. Still, the core of my question remains. Can `task_cpu(p)` be different from `rq->cpu` while the task is, in fact, enqueued on `rq`? The usual flow is to dequeue the task, update its CPU, and then re-enqueue it.
Ah, but the reproducer is using `prctl$PR_SCHED_CORE`. That throws a wrench in the typical scenario. I need to understand how the core scheduling mechanisms, especially using `PR_SCHED_CORE`, interact with enqueue/dequeue operations. Let's dig into `sched_core_update_cookie` in `kernel/sched/core_sched.c`. That's where the key lies, I bet.
|
| 41/2 |
2026/06/09 12:36 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core_sched.c FirstLine:60 LineCount:50]
Results: map[Contents: 60: struct rq *rq;
61:
62: rq = task_rq_lock(p, &rf);
63:
64: /*
65: * Since creating a cookie implies sched_core_get(), and we cannot set
66: * a cookie until after we've created it, similarly, we cannot destroy
67: * a cookie until after we've removed it, we must have core scheduling
68: * enabled here.
69: */
70: WARN_ON_ONCE((p->core_cookie || cookie) && !sched_core_enabled(rq));
71:
72: if (sched_core_enqueued(p))
73: sched_core_dequeue(rq, p, DEQUEUE_SAVE);
74:
75: old_cookie = p->core_cookie;
76: p->core_cookie = cookie;
77:
78: /*
79: * Consider the cases: !prev_cookie and !cookie.
80: */
81: if (cookie && task_on_rq_queued(p))
82: sched_core_enqueue(rq, p);
83:
84: /*
85: * If task is currently running, it may not be compatible anymore after
86: * the cookie change, so enter the scheduler on its CPU to schedule it
87: * away.
88: *
89: * Note that it is possible that as a result of this cookie change, the
90: * core has now entered/left forced idle state. Defer accounting to the
91: * next scheduling edge, rather than always forcing a reschedule here.
92: */
93: if (task_on_cpu(rq, p))
94: resched_curr(rq);
95:
96: task_rq_unlock(rq, p, &rf);
97:
98: return old_cookie;
99: }
100:
101: static unsigned long sched_core_clone_cookie(struct task_struct *p)
102: {
103: unsigned long cookie, flags;
104:
105: raw_spin_lock_irqsave(&p->pi_lock, flags);
106: cookie = sched_core_get_cookie(p->core_cookie);
107: raw_spin_unlock_irqrestore(&p->pi_lock, flags);
108:
109: return cookie;
]
|
| 42/2 |
2026/06/09 12:36 |
llm |
expert |
0m
Model:
Tokens:
input: 37229
output: 36
thoughts: 782
Thoughts: **Analyzing Potential Race Conditions in Core Scheduling**
Okay, so I'm looking at `sched_core_update_cookie`. It locks the runqueue (`rq`), dequeues from `core_tree`, updates a cookie, then enqueues back. Seems straightforward enough at first glance. But I need to dive deeper here. The crucial question that's nagging at me is: what if `task_rq(p)` isn't equal to `rq`, and *that* happens because `p` is `rq->donor`? That's the edge case.
I need to circle back and carefully review `pick_next_task` again. Specifically, this section:
```c
out_set_next:
put_prev_set_next_task(rq, rq->donor, next);
```
Right, `rq->donor` is the task *donating* its time to another runqueue. So, if the previous task (`prev`) is `rq->donor`, and the next task (`next`) is `rq->core_pick`, then I have to consider: what happens in `put_prev_set_next_task(rq, prev, next)`?
```c
prev->sched_class->put_prev_task(rq, prev, next);
next->sched_class->set_next_task(rq, next, true);
```
My concern is whether `next` (which is `rq->core_pick`) *belongs* to `rq`. Let's step back and look at where `rq->core_pick` comes from, the relevant section of `pick_next_task` in `kernel/sched/core.c`:
```c
for_each_cpu(i, smt_mask) {
rq_i = cpu_rq(i);
p = rq_i->core_pick;
if (!cookie_equals(p, cookie)) {
p = NULL;
if (cookie)
p = sched_core_find(rq_i, cookie);
if (!p)
p = idle_sched_class.pick_task(rq_i, rf);
}
rq_i->core_pick = p;
// ...
```
Okay, `sched_core_find(rq_i, cookie)` finds a task on `rq_i`. So `p` *does* belong to `rq_i`. Therefore, `p` can be assigned to `rq_i->core_pick`. And then `next = rq->core_pick;` where `rq` is the *current* runqueue. Thus, `next` *should* belong to `rq`.
What if `next` came from `sched_core_find(rq, cookie)`? It *should* be on `rq`. What would cause `task_rq(next) != rq`? Could `next` be `rq->idle`? Even if `next` is `rq->idle`, `task_rq(next)` is still `rq`. Could it be a migrated task? No, since `rq` is locked during `pick_next_task`, and tasks can't migrate under lock.
Hmm... Could `next` be a proxy execution donor? That's a possibility, but there are safeguards in place, specifically, the `WARN_ON_ONCE` in `hrtick_start_fair`:
```c
WARN_ON_ONCE(task_rq(p) != rq);
```
Here, `p` is `next`. So, we should be covered. Even if `next` is `rq->donor`, `task_rq(next)` is still `rq`.
However, the question that I have is: what if `next` is a task that's blocked on a mutex, and we are doing proxy execution? Let me check around the `proxy_resched_idle` calls in `pick_next_task` in `kernel/sched/core.c`.
|
| 43/2 |
2026/06/09 12:37 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:6110 LineCount:50]
Results: map[Contents:6110: if (class->balance && class->balance(rq, rf))
6111: break;
6112: }
6113: }
6114:
6115: /*
6116: * Pick up the highest-prio task:
6117: */
6118: static inline struct task_struct *
6119: __pick_next_task(struct rq *rq, struct rq_flags *rf)
6120: __must_hold(__rq_lockp(rq))
6121: {
6122: const struct sched_class *class;
6123: struct task_struct *p;
6124:
6125: rq->dl_server = NULL;
6126:
6127: if (scx_enabled())
6128: goto restart;
6129:
6130: /*
6131: * Optimization: we know that if all tasks are in the fair class we can
6132: * call that function directly, but only if the @prev task wasn't of a
6133: * higher scheduling class, because otherwise those lose the
6134: * opportunity to pull in more work from other CPUs.
6135: */
6136: if (likely(!sched_class_above(rq->donor->sched_class, &fair_sched_class) &&
6137: rq->nr_running == rq->cfs.h_nr_queued)) {
6138:
6139: p = pick_task_fair(rq, rf);
6140: if (unlikely(p == RETRY_TASK))
6141: goto restart;
6142:
6143: /* Assume the next prioritized class is idle_sched_class */
6144: if (!p)
6145: p = pick_task_idle(rq, rf);
6146:
6147: put_prev_set_next_task(rq, rq->donor, p);
6148: return p;
6149: }
6150:
6151: restart:
6152: prev_balance(rq, rf);
6153:
6154: for_each_active_class(class) {
6155: p = class->pick_task(rq, rf);
6156: if (unlikely(p == RETRY_TASK))
6157: goto restart;
6158: if (p) {
6159: put_prev_set_next_task(rq, rq->donor, p);
]
|
| 44/2 |
2026/06/09 12:37 |
llm |
expert |
0m
Model:
Tokens:
input: 39640
output: 36
thoughts: 40
Thoughts: Okay, let's see...
**Diving into the Scheduler**
Right, I'm tracing this execution path and just noticed that `pick_next_task` within `kernel/sched/core.c` calls `pick_task` with `rq` and `rf`. Hmm, let's take a closer look at what `pick_task` is actually doing. My immediate thought is to understand its role in the scheduler's decision-making process. This is the heart of task selection, and I need a clear mental model of how it works in this context. Time to dig in!
|
| 45/2 |
2026/06/09 12:37 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:6050 LineCount:50]
Results: map[Contents:6050: if (irqs_disabled())
6051: print_irqtrace_events(prev);
6052: if (IS_ENABLED(CONFIG_DEBUG_PREEMPT)) {
6053: pr_err("Preemption disabled at:");
6054: print_ip_sym(KERN_ERR, preempt_disable_ip);
6055: }
6056: check_panic_on_warn("scheduling while atomic");
6057:
6058: dump_stack();
6059: add_taint(TAINT_WARN, LOCKDEP_STILL_OK);
6060: }
6061:
6062: /*
6063: * Various schedule()-time debugging checks and statistics:
6064: */
6065: static inline void schedule_debug(struct task_struct *prev, bool preempt)
6066: {
6067: #ifdef CONFIG_SCHED_STACK_END_CHECK
6068: if (task_stack_end_corrupted(prev))
6069: panic("corrupted stack end detected inside scheduler\n");
6070:
6071: if (task_scs_end_corrupted(prev))
6072: panic("corrupted shadow stack detected inside scheduler\n");
6073: #endif
6074:
6075: #ifdef CONFIG_DEBUG_ATOMIC_SLEEP
6076: if (!preempt && READ_ONCE(prev->__state) && prev->non_block_count) {
6077: printk(KERN_ERR "BUG: scheduling in a non-blocking section: %s/%d/%i\n",
6078: prev->comm, prev->pid, prev->non_block_count);
6079: dump_stack();
6080: add_taint(TAINT_WARN, LOCKDEP_STILL_OK);
6081: }
6082: #endif
6083:
6084: if (unlikely(in_atomic_preempt_off())) {
6085: __schedule_bug(prev);
6086: preempt_count_set(PREEMPT_DISABLED);
6087: }
6088: rcu_sleep_check();
6089: WARN_ON_ONCE(ct_state() == CT_STATE_USER);
6090:
6091: profile_hit(SCHED_PROFILING, __builtin_return_address(0));
6092:
6093: schedstat_inc(this_rq()->sched_count);
6094: }
6095:
6096: static void prev_balance(struct rq *rq, struct rq_flags *rf)
6097: {
6098: const struct sched_class *start_class = rq->donor->sched_class;
6099: const struct sched_class *class;
]
|
| 46/2 |
2026/06/09 12:37 |
llm |
expert |
0m
Model:
Tokens:
input: 40633
output: 34
thoughts: 21
Thoughts: Here's my thought process, as if I were analyzing that code:
**Debugging the Task Picker**
Okay, the program's not behaving as expected. The critical function, `pick_task`, isn't immediately visible where I anticipated it to be. This is a common situation, right? Time to start investigating. I need to locate where `pick_task` is defined, or at least used. A quick `grep` will be the most efficient way to achieve this. It's the standard first line of investigation when trying to understand a code base I'm unfamiliar with, or just dealing with some internal logic. Let's see where that takes us.
|
| 47/2 |
2026/06/09 12:37 |
tool |
grepper |
0m
Error:
Args: map[Expression:static inline struct task_struct \* pick_task PathPrefix:kernel/sched/]
Results:
|
| 48/2 |
2026/06/09 12:37 |
llm |
expert |
0m
Model:
Tokens:
input: 40843
output: 28
thoughts: 20
Thoughts: Okay, here's my summary, channeling the thought process:
**Diving into the Scheduler Core**
Right, time to get my hands dirty. I need to understand how this scheduling thing actually works at a fundamental level. Let's start with the heart of it all. I know `pick_task()` is a crucial function β it's where the scheduler actually *chooses* which process to run next. I need to see the logic.
My first move? A quick `grep` command. I'll target the `kernel/sched/core.c` file. That's the logical place to start looking for the core scheduling routines. Let's see what `grep 'pick_task(' kernel/sched/core.c` turns up. This should point me right to the function's definition and, hopefully, reveal some of the surrounding code that calls it. This'll give me the context I need to really dig in and analyze the selection process. I want the whole picture. Let's see what we've got!
|
| 49/2 |
2026/06/09 12:37 |
tool |
grepper |
0m
Args: map[Expression:pick_task\( PathPrefix:kernel/sched/]
Results: map[Output:kernel/sched/core.c=6119=__pick_next_task(struct rq *rq, struct rq_flags *rf)
--
kernel/sched/core.c-6154- for_each_active_class(class) {
kernel/sched/core.c:6155: p = class->pick_task(rq, rf);
kernel/sched/core.c-6156- if (unlikely(p == RETRY_TASK))
--
kernel/sched/core.c=6178=static inline bool cookie_match(struct task_struct *a, struct task_struct *b)
--
kernel/sched/core.c-6189- */
kernel/sched/core.c:6190:static inline struct task_struct *pick_task(struct rq *rq, struct rq_flags *rf)
kernel/sched/core.c-6191-{
--
kernel/sched/core.c-6197- for_each_active_class(class) {
kernel/sched/core.c:6198: p = class->pick_task(rq, rf);
kernel/sched/core.c-6199- if (p)
--
kernel/sched/core.c=6211=pick_next_task(struct rq *rq, struct rq_flags *rf)
--
kernel/sched/core.c-6299-restart_single:
kernel/sched/core.c:6300: next = pick_task(rq, rf);
kernel/sched/core.c-6301- if (unlikely(next == RETRY_TASK))
--
kernel/sched/core.c-6334-
kernel/sched/core.c:6335: p = pick_task(rq_i, rf);
kernel/sched/core.c-6336- if (unlikely(p == RETRY_TASK))
--
kernel/sched/core.c-6360- if (!p)
kernel/sched/core.c:6361: p = idle_sched_class.pick_task(rq_i, rf);
kernel/sched/core.c-6362- }
--
kernel/sched/deadline.c=1584=void dl_server_update(struct sched_dl_entity *dl_se, s64 delta_exec)
--
kernel/sched/deadline.c-1709- * pick_task_dl()
kernel/sched/deadline.c:1710: * p = server_pick_task();
kernel/sched/deadline.c-1711- * if (!p)
--
kernel/sched/deadline.c=2814=static struct task_struct *__pick_task_dl(struct rq *rq, struct rq_flags *rf)
--
kernel/sched/deadline.c-2827- if (dl_server(dl_se)) {
kernel/sched/deadline.c:2828: p = dl_se->server_pick_task(dl_se, rf);
kernel/sched/deadline.c-2829- if (!p) {
--
kernel/sched/ext.c=3362=static struct task_struct *
kernel/sched/ext.c:3363:ext_server_pick_task(struct sched_dl_entity *dl_se, struct rq_flags *rf)
kernel/sched/ext.c-3364-{
--
kernel/sched/fair.c=9946=static struct task_struct *
kernel/sched/fair.c:9947:fair_server_pick_task(struct sched_dl_entity *dl_se, struct rq_flags *rf)
kernel/sched/fair.c-9948- __must_hold(__rq_lockp(dl_se->rq))
]
|
| 50/2 |
2026/06/09 12:37 |
llm |
expert |
0m
Model:
Tokens:
input: 42063
output: 36
thoughts: 53
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience:
**Analyzing Task Selection in the Scheduler**
Right, let's break this down. My mental model is tracing the task selection flow, and it's crystal clear that the `pick_task` function is the core driver here. It iterates through each active scheduling class using `for_each_active_class(class)`. Inside that loop, for *each* active class, we call the class-specific `pick_task` method β `class->pick_task(rq, rf)`. The key part is that *this* is the interface point for scheduler-class specialization.
Specifically, when the active class is `fair_sched_class`, we're invoking `pick_task_fair`. This confirms that we've found our place within the multi-class scheduling framework, and that for the fair scheduler, the selection process defaults to that specific function. It signals that this function is where the details of the Completely Fair Scheduler (CFS) task selection logic reside. Excellent, that's the entry point!
|
| 51/2 |
2026/06/09 12:37 |
tool |
read-file |
0m
Args: map[File:kernel/sched/fair.c FirstLine:9900 LineCount:50]
Results: map[Contents:9900: }
9901:
9902: resched_curr_lazy(rq);
9903: }
9904:
9905: struct task_struct *pick_task_fair(struct rq *rq, struct rq_flags *rf)
9906: __must_hold(__rq_lockp(rq))
9907: {
9908: struct sched_entity *se;
9909: struct cfs_rq *cfs_rq;
9910: struct task_struct *p;
9911: bool throttled;
9912: int new_tasks;
9913:
9914: again:
9915: cfs_rq = &rq->cfs;
9916: if (!cfs_rq->nr_queued)
9917: goto idle;
9918:
9919: throttled = false;
9920:
9921: do {
9922: /* Might not have done put_prev_entity() */
9923: if (cfs_rq->curr && cfs_rq->curr->on_rq)
9924: update_curr(cfs_rq);
9925:
9926: se = pick_next_entity(rq, cfs_rq, true);
9927: if (!se)
9928: goto again;
9929: cfs_rq = group_cfs_rq(se);
9930: } while (cfs_rq);
9931:
9932: p = task_of(se);
9933: if (unlikely(throttled))
9934: task_throttle_setup_work(p);
9935: return p;
9936:
9937: idle:
9938: new_tasks = sched_balance_newidle(rq, rf);
9939: if (new_tasks < 0)
9940: return RETRY_TASK;
9941: if (new_tasks > 0)
9942: goto again;
9943: return NULL;
9944: }
9945:
9946: static struct task_struct *
9947: fair_server_pick_task(struct sched_dl_entity *dl_se, struct rq_flags *rf)
9948: __must_hold(__rq_lockp(dl_se->rq))
9949: {
]
|
| 52/2 |
2026/06/09 12:37 |
llm |
expert |
0m
Model:
Tokens:
input: 43078
output: 36
thoughts: 201
Thoughts: **Analyzing the Task Selection Logic**
Okay, so `pick_task_fair` returns `p`. Let me see, right, it returns `p = task_of(se)`. Hmmm, I need to make sure that the task returned by the function I wrote can be on a different runqueue, meaning `task_rq(p) != rq`. Let's break this down. `task_rq(p)` is equivalent to `cpu_rq(task_cpu(p))`.
Right, if `p` is enqueued on `rq->cfs`, then the scheduling entity (`se`) associated with it should be on that runqueue's `cfs_rq`. Now, I have to be sure about this. When a task is added to a runqueue, the task's CPU (`task_cpu(p)`) *should* be the same as the runqueue's CPU (`rq->cpu`). But, and this is important, what if the task's CPU affinity was changed, and the task is still mistakenly enqueued on the *old* runqueue? I have to account for that.
No, no, that shouldn't happen. `set_task_rq` is called during migration to update the runqueue. Oh wait, what about proxy execution? That could be a source of problems. Looking at `kernel/sched/core.c`, I have to check `proxy_migrate_task`. Okay, let me go check `proxy_migrate_task`.
|
| 53/2 |
2026/06/09 12:37 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:6800 LineCount:50]
Results: map[Contents:6800: * This is because we must respect the CPU affinity of execution
6801: * contexts (owner) but we can ignore affinity for scheduling
6802: * contexts (@p). So we have to move scheduling contexts towards
6803: * potential execution contexts.
6804: *
6805: * Note: The owner can disappear, but simply migrate to @target_cpu
6806: * and leave that CPU to sort things out.
6807: */
6808: static void proxy_migrate_task(struct rq *rq, struct rq_flags *rf,
6809: struct task_struct *p, int target_cpu)
6810: __must_hold(__rq_lockp(rq))
6811: {
6812: struct rq *target_rq = cpu_rq(target_cpu);
6813:
6814: lockdep_assert_rq_held(rq);
6815: WARN_ON(p == rq->curr);
6816: /*
6817: * Since we are migrating a blocked donor, it could be rq->donor,
6818: * and we want to make sure there aren't any references from this
6819: * rq to it before we drop the lock. This avoids another cpu
6820: * jumping in and grabbing the rq lock and referencing rq->donor
6821: * or cfs_rq->curr, etc after we have migrated it to another cpu,
6822: * and before we pick_again in __schedule.
6823: *
6824: * So call proxy_resched_idle() to drop the rq->donor references
6825: * before we release the lock.
6826: */
6827: proxy_resched_idle(rq);
6828:
6829: deactivate_task(rq, p, DEQUEUE_NOCLOCK);
6830: proxy_set_task_cpu(p, target_cpu);
6831:
6832: proxy_release_rq_lock(rq, rf);
6833:
6834: attach_one_task(target_rq, p);
6835:
6836: proxy_reacquire_rq_lock(rq, rf);
6837: }
6838:
6839: /*
6840: * Find runnable lock owner to proxy for mutex blocked donor
6841: *
6842: * Follow the blocked-on relation:
6843: *
6844: * ,-> task
6845: * | | blocked-on
6846: * | v
6847: * blocked_donor | mutex
6848: * | | owner
6849: * | v
]
|
| 54/2 |
2026/06/09 12:37 |
llm |
expert |
0m
Model:
Tokens:
input: 44399
output: 35
thoughts: 577
Thoughts: **Diving Deep into the Scheduler's Core**
Okay, so `proxy_migrate_task` calls `proxy_set_task_cpu` followed by `attach_one_task`. This is all part of the blocked donor migration process, and it got me thinking: what if the `task_rq(p) != rq` condition arises because `p` is a proxy task? Time to re-examine that reproducer.
It starts with `prctl(PR_SCHED_CORE, 1)`, essentially setting up a core scheduling cookie. Then, `sendmsg` and `recvmsg` are calledβpotentially blocking callsβfollowed by an `/dev/video4linux` open. But the key warning we're seeing is in `hrtick_start_fair`: `task_rq(p) != rq`. Let's refresh our minds on the relevant code snippet:
```c
static void hrtick_start_fair(struct rq *rq, struct task_struct *p)
{
// ...
WARN_ON_ONCE(task_rq(p) != rq);
// ...
}
```
This means task `p` is scheduled on `rq`, but its `task_cpu(p)` doesn't match `rq->cpu`. How is that even possible? A task scheduled on an runqueue, but on the *wrong* CPU?
Let's look at `pick_next_task` in `kernel/sched/core.c`. This is where the core scheduler decides what to run:
```c
for_each_cpu(i, smt_mask) {
rq_i = cpu_rq(i);
p = rq_i->core_pick;
if (!cookie_equals(p, cookie)) {
p = NULL;
if (cookie)
p = sched_core_find(rq_i, cookie);
if (!p)
p = idle_sched_class.pick_task(rq_i, rf);
}
rq_i->core_pick = p;
// ...
```
The puzzle is how `sched_core_find(rq_i, cookie)` could return a task `p` where `task_cpu(p)` differs from `rq_i->cpu`. What would cause that task to be returned? `sched_core_find` is searching the `rq_i->core_tree`. Can a task end up in the `rq_i->core_tree` but have a mismatched CPU assignment? Time to go and look at `sched_core_enqueue` and see how tasks get added to that tree.
|
| 55/2 |
2026/06/09 12:37 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:300 LineCount:50]
Results: map[Contents: 300:
301: if (cookie > p->core_cookie)
302: return 1;
303:
304: return 0;
305: }
306:
307: void sched_core_enqueue(struct rq *rq, struct task_struct *p)
308: {
309: if (p->se.sched_delayed)
310: return;
311:
312: rq->core->core_task_seq++;
313:
314: if (!p->core_cookie)
315: return;
316:
317: rb_add(&p->core_node, &rq->core_tree, rb_sched_core_less);
318: }
319:
320: void sched_core_dequeue(struct rq *rq, struct task_struct *p, int flags)
321: {
322: if (p->se.sched_delayed)
323: return;
324:
325: rq->core->core_task_seq++;
326:
327: if (sched_core_enqueued(p)) {
328: rb_erase(&p->core_node, &rq->core_tree);
329: RB_CLEAR_NODE(&p->core_node);
330: }
331:
332: /*
333: * Migrating the last task off the cpu, with the cpu in forced idle
334: * state. Reschedule to create an accounting edge for forced idle,
335: * and re-examine whether the core is still in forced idle state.
336: */
337: if (!(flags & DEQUEUE_SAVE) && rq->nr_running == 1 &&
338: rq->core->core_forceidle_count && rq->curr == rq->idle)
339: resched_curr(rq);
340: }
341:
342: static int sched_task_is_throttled(struct task_struct *p, int cpu)
343: {
344: if (p->sched_class->task_is_throttled)
345: return p->sched_class->task_is_throttled(p, cpu);
346:
347: return 0;
348: }
349:
]
|
| 56/2 |
2026/06/09 12:37 |
llm |
expert |
0m
Model:
Tokens:
input: 46295
output: 36
thoughts: 1253
Thoughts: **Analyzing Task Migration and Proxy Task Selection**
Okay, let's break this down. My initial thought process revolves around understanding how tasks are handled during migration between runqueues (`rq`). The core concern is ensuring data integrity and consistency, especially when dealing with proxy tasks.
If a task gets migrated, the basic assumption is that it's dequeued from the old `rq` and enqueued on the new one. During this process, `set_task_rq` should be called. Then, I remember `sched_core_enqueue` has a check for `p->se.sched_delayed`. If this is true, the task isn't enqueued right away. This raises a question: What if `task_cpu(p)` changes without an explicit dequeue? That would be problematic. But that shouldn't happen during migration.
Now, consider proxy execution tasks. What if the runqueue (`task_rq(p)`) doesn't equal `rq`? Then, let's revisit `proxy_migrate_task`. It seems this function is responsible for the actual migration. It first deactivates the task via `deactivate_task`, which calls `dequeue_task` and then `sched_core_dequeue`. Then, it changes the CPU. Finally, `attach_one_task` enqueues the task again via `enqueue_task`, then `sched_core_enqueue`. This suggests the proxy task is moved to the target `rq`'s `core_tree`.
But what about the donor task's `rq`, which may be blocked on a mutex? In `pick_next_task`, within the scheduler core, a task is chosen. Here's what's going on: the `core_pick` variable of the runqueue is set to a task (`p`). If this is indeed set, `next` is also set to `p`. Then, for each CPU in the SMT mask, the `core_pick` variable is set to null, and `resched_curr(rq_i)` is called. The issue here is, if the picked task, `next`, is a task on `rq`, its CPU should be the same as the runqueue's CPU. Is that true?
The real challenge seems to be if `next` is a proxy task. In `pick_task_fair`, the task is selected through a call to `pick_next_entity`. If the task selected is a proxy task, what happens to `task_cpu(p)`? However, the proxy tasks *are* migrated to the execution runqueue.
Then, there's the more complex scenario. What if the selected task is the *owner* of a mutex, and we are proxying for a blocked donor? Here, `find_proxy_task` comes into play. It migrates the `owner` to the CPU of the requesting `rq` using `proxy_migrate_task`. This will make the CPU of the `owner` task equal to the `rq`'s CPU. The `owner` is then returned.
The question now is, *where* is `find_proxy_task` actually invoked? This is the crucial missing link I need to understand the full picture.
|
| 57/2 |
2026/06/09 12:37 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:6990 LineCount:50]
Results: map[Contents:6990: }
6991: /*
6992: * OK, now we're absolutely sure @owner is on this
6993: * rq, therefore holding @rq->lock is sufficient to
6994: * guarantee its existence, as per ttwu_remote().
6995: */
6996: owner->blocked_donor = p;
6997: }
6998: WARN_ON_ONCE(owner && !owner->on_rq);
6999: return owner;
7000:
7001: deactivate:
7002: proxy_deactivate(rq, p);
7003: return NULL;
7004: migrate_task:
7005: proxy_migrate_task(rq, rf, p, owner_cpu);
7006: return NULL;
7007: }
7008: #else /* SCHED_PROXY_EXEC */
7009: static struct task_struct *
7010: find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
7011: {
7012: WARN_ONCE(1, "This should never be called in the !SCHED_PROXY_EXEC case\n");
7013: return donor;
7014: }
7015: #endif /* SCHED_PROXY_EXEC */
7016:
7017: /*
7018: * __schedule() is the main scheduler function.
7019: *
7020: * The main means of driving the scheduler and thus entering this function are:
7021: *
7022: * 1. Explicit blocking: mutex, semaphore, waitqueue, etc.
7023: *
7024: * 2. TIF_NEED_RESCHED flag is checked on interrupt and userspace return
7025: * paths. For example, see arch/x86/entry_64.S.
7026: *
7027: * To drive preemption between tasks, the scheduler sets the flag in timer
7028: * interrupt handler sched_tick().
7029: *
7030: * 3. Wakeups don't really cause entry into schedule(). They add a
7031: * task to the run-queue and that's it.
7032: *
7033: * Now, if the new task added to the run-queue preempts the current
7034: * task, then the wakeup sets TIF_NEED_RESCHED and schedule() gets
7035: * called on the nearest possible occasion:
7036: *
7037: * - If the kernel is preemptible (CONFIG_PREEMPTION=y):
7038: *
7039: * - in syscall or exception context, at the next outmost
]
|
| 58/2 |
2026/06/09 12:37 |
llm |
expert |
0m
Model:
Tokens:
input: 49046
output: 36
thoughts: 25
Thoughts: Here's my thought process, summarized as you requested:
**Digging into Task Scheduling**
Okay, so `find_proxy_task` is called from `__schedule`... that's interesting. I need to understand the connection there, so my first instinct is to drill down and examine `__schedule` itself. I need to see what's happening within that function to understand how `find_proxy_task` is being invoked and what its role is in the overall scheduling process. This will give me the context I need to properly analyze its functionality. Let's see what `__schedule` reveals.
|
| 59/2 |
2026/06/09 12:37 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:7100 LineCount:50]
Results: map[Contents:7100: * barrier matches a full barrier in the proximity of the membarrier
7101: * system call exit.
7102: */
7103: rq_lock(rq, &rf);
7104: smp_mb__after_spinlock();
7105:
7106: hrtick_schedule_enter(rq);
7107:
7108: /* Promote REQ to ACT */
7109: rq->clock_update_flags <<= 1;
7110: update_rq_clock(rq);
7111: rq->clock_update_flags = RQCF_UPDATED;
7112:
7113: switch_count = &prev->nivcsw;
7114:
7115: /* Task state changes only considers SM_PREEMPT as preemption */
7116: preempt = sched_mode == SM_PREEMPT;
7117:
7118: /*
7119: * We must load prev->state once (task_struct::state is volatile), such
7120: * that we form a control dependency vs deactivate_task() below.
7121: */
7122: prev_state = READ_ONCE(prev->__state);
7123: if (sched_mode == SM_IDLE) {
7124: /* SCX must consult the BPF scheduler to tell if rq is empty */
7125: if (!rq->nr_running && !scx_enabled()) {
7126: next = prev;
7127: rq->next_class = &idle_sched_class;
7128: goto picked;
7129: }
7130: } else if (!preempt && prev_state) {
7131: /*
7132: * We pass task_is_blocked() as the should_block arg
7133: * in order to keep mutex-blocked tasks on the runqueue
7134: * for slection with proxy-exec (without proxy-exec
7135: * task_is_blocked() will always be false).
7136: */
7137: try_to_block_task(rq, prev, &prev_state,
7138: !task_is_blocked(prev));
7139: switch_count = &prev->nvcsw;
7140: }
7141:
7142: pick_again:
7143: assert_balance_callbacks_empty(rq);
7144: next = pick_next_task(rq, &rf);
7145: rq->next_class = next->sched_class;
7146: if (sched_proxy_exec()) {
7147: struct task_struct *prev_donor = rq->donor;
7148:
7149: rq_set_donor(rq, next);
]
|
| 60/2 |
2026/06/09 12:37 |
llm |
expert |
0m
Model:
Tokens:
input: 50011
output: 29
thoughts: 41
Thoughts: Okay, here's my interpretation of those thoughts, presented as a personal thought process, as requested:
**Debugging Discrepancies in Task Scheduling**
Hmm, something doesn't feel right. I was expecting the `find_proxy_task` function to be invoked within the `__schedule` function. That seems to be the logical place, given what I know about the overall scheduling process. But, wait, is that *actually* the case? I need to verify this assumption.
Okay, instead of blindly assuming, let's confirm the call locations for `find_proxy_task`. Time to get into the details; I need to search the kernel source, specifically the `kernel/sched/core.c` file. Hopefully, a direct search for `find_proxy_task` will provide the answer quickly, whether or not `__schedule` is responsible. This should clarify the intended flow of execution. Let's see what's actually going on.
|
| 61/2 |
2026/06/09 12:38 |
tool |
grepper |
0m
Args: map[Expression:find_proxy_task PathPrefix:kernel/sched/]
Results: map[Output:kernel/sched/core.c=6865=static struct task_struct *
kernel/sched/core.c:6866:find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
kernel/sched/core.c-6867- __must_hold(__rq_lockp(rq))
--
kernel/sched/core.c-6948- * the migration a chance to complete. Much like the migrate_task
kernel/sched/core.c:6949: * case we should end up back in find_proxy_task(), this time
kernel/sched/core.c-6950- * hopefully with all relevant tasks already enqueued.
--
kernel/sched/core.c-6970- * lock(&rq->lock);
kernel/sched/core.c:6971: * find_proxy_task()
kernel/sched/core.c-6972- * mutex_unlock()
--
kernel/sched/core.c=7009=static struct task_struct *
kernel/sched/core.c:7010:find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
kernel/sched/core.c-7011-{
--
kernel/sched/core.c=7056=static void __sched notrace __schedule(int sched_mode)
--
kernel/sched/core.c-7151- if (unlikely(next->is_blocked)) {
kernel/sched/core.c:7152: next = find_proxy_task(rq, next, &rf);
kernel/sched/core.c-7153- if (!next) {
]
|
| 62/2 |
2026/06/09 12:38 |
llm |
expert |
0m
Model:
Tokens:
input: 50722
output: 29
thoughts: 1730
Thoughts: **Analyzing Task Selection in the Scheduler**
Okay, let's break this down. We're in `__schedule`, specifically looking at how the next task, `next`, is selected and how the proxy/donor relationships are handled. I'm focusing on the case where a task is potentially blocked and represented by a proxy, because thatβs where the complexity seems to be.
The core logic initially chooses `next` using `pick_next_task`. Importantly, `pick_next_task` *also* implicitly handles setting up the previous task and the next task. It calls `put_prev_set_next_task` which, in turn, calls `put_prev_task` and `set_next_task`, *before* any check for a blocked proxy is performed. That sets up the runqueue and accounting.
The critical point here is the scenario where `next` is marked as blocked (`next->is_blocked`). If so, a follow up call to `find_proxy_task` happens. This is where we attempt to find a proxy task that is *actually* running on behalf of the blocked task (the βdonorβ). The problem arises because `set_next_task` could have already been called on the donor *before* we determine that a proxy is needed. This looks like a race condition and it requires us to manage the relationship between the blocked donor, and proxy tasks effectively.
I was initially confused because I was thinking, "why call `set_next_task` on a donor *before* checking for a proxy?" but the logic seems to be: 1. Pick a task; 2. Configure the task with the runqueue; and 3. Re-evaluate the configuration (the proxy) if the task is blocked. This is a subtle optimization in the scheduler, trying to reduce overhead. It's likely trying to do the "normal" path (no proxy) as efficiently as possible, while still correctly handling the proxy case.
Now, let's consider a potential issue. Could `task_rq(next)` be *different* from the runqueue we're currently on (`rq`) after this sequence? `pick_next_task` is supposed to return a task runnable on `rq`. The thing that seems odd, is the reference to the runqueue's `core_pick` variable.
If `next` is actually a core-picked task (`rq->core_pick`), it gets its pointer set by `sched_core_find`, but that task might be on a *different* CPU's runqueue! If so, this creates a potential inconsistency because `pick_next_task` is called on the runqueue, but `next` isn't necessarily on *that* runqueue. I was concerned that this mismatch is possible.
However, I've just been reviewing the code related to core scheduling. The logic flow involving `sched_core_enqueue` and `sched_core_dequeue` looks correct. A task is added to `rq->core_tree` when enqueued on runqueue `rq`. When it's migrated, it's removed from `src_rq->core_tree` and added to `dst_rq->core_tree`, so consistency is maintained. `sched_core_update_cookie` is also calling `sched_core_enqueue` on `task_rq(p)`. This makes sure the task gets re-enqueued on the right runqueue after modifications.
So, *I believe* the core scheduling infrastructure manages the CPU affinity and placement of tasks correctly. The code is complex, but I believe the runqueue consistency is enforced. I'm confident that the code accounts for the scenario where a task might be selected from a *sibling* runqueue and later be migrated. The scheduler logic seems careful to maintain the necessary invariants to avoid inconsistencies. The critical point is: if we *do* find a proxy after having picked what we think is the next task, we need to correctly update the runqueue's state. It appears the `put_prev_task` and `set_next_task` calls in `pick_next_task` are designed to handle this transition properly, by explicitly putting and setting the "donor" task on the right runqueue if it is displaced by a proxy task.
|
| 63/2 |
2026/06/09 12:38 |
tool |
grepper |
0m
Args: map[Expression:sched_core_enqueue PathPrefix:kernel/sched/]
Results: map[Output:kernel/sched/core.c=293=static inline int rb_sched_core_cmp(const void *key, const struct rb_node *node)
--
kernel/sched/core.c-306-
kernel/sched/core.c:307:void sched_core_enqueue(struct rq *rq, struct task_struct *p)
kernel/sched/core.c-308-{
--
kernel/sched/core.c=320=void sched_core_dequeue(struct rq *rq, struct task_struct *p, int flags)
--
kernel/sched/core.c-326-
kernel/sched/core.c:327: if (sched_core_enqueued(p)) {
kernel/sched/core.c-328- rb_erase(&p->core_node, &rq->core_tree);
--
kernel/sched/core.c=514=void sched_core_put(void)
--
kernel/sched/core.c-530-
kernel/sched/core.c:531:static inline void sched_core_enqueue(struct rq *rq, struct task_struct *p) { }
kernel/sched/core.c-532-static inline void
--
kernel/sched/core.c=2172=void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
--
kernel/sched/core.c-2191- if (sched_core_enabled(rq))
kernel/sched/core.c:2192: sched_core_enqueue(rq, p);
kernel/sched/core.c-2193-}
--
kernel/sched/core_sched.c=55=static unsigned long sched_core_update_cookie(struct task_struct *p,
--
kernel/sched/core_sched.c-71-
kernel/sched/core_sched.c:72: if (sched_core_enqueued(p))
kernel/sched/core_sched.c-73- sched_core_dequeue(rq, p, DEQUEUE_SAVE);
--
kernel/sched/core_sched.c-81- if (cookie && task_on_rq_queued(p))
kernel/sched/core_sched.c:82: sched_core_enqueue(rq, p);
kernel/sched/core_sched.c-83-
--
kernel/sched/sched.h=1537=static inline bool sched_group_cookie_match(struct rq *rq,
--
kernel/sched/sched.h-1553-
kernel/sched/sched.h:1554:static inline bool sched_core_enqueued(struct task_struct *p)
kernel/sched/sched.h-1555-{
--
kernel/sched/sched.h-1558-
kernel/sched/sched.h:1559:extern void sched_core_enqueue(struct rq *rq, struct task_struct *p);
kernel/sched/sched.h-1560-extern void sched_core_dequeue(struct rq *rq, struct task_struct *p, int flags);
]
|
| 64/2 |
2026/06/09 12:38 |
llm |
expert |
0m
Model:
Tokens:
input: 54156
output: 36
thoughts: 1599
Thoughts: **Analyzing a Race Condition in Task Scheduling**
Okay, so I'm digging into a potential race condition and trying to understand why this WARN_ON_ONCE is firing in `hrtick_start_fair`. The core issue seems to be around `task_rq(p)` not matching `rq` when it *should*. I'm starting from the point where `hrtick_start_fair` gets called, which in turn is from `set_next_task_fair` and that's from `put_prev_set_next_task`. `put_prev_set_next_task` is called from `pick_next_task`, which is in `__schedule` β that's my starting point.
First, I'm tracing the likely path to ensure I understand how a task gets dequeued, and whether a race in `sched_core_dequeue` could be the origin. I realize that `sched_core_dequeue` is invoked from `dequeue_task`, and the critical question is whether a task's runqueue can change in a way that *bypasses* the call to `sched_core_dequeue`. I'm verifying this isn't possible by checking the `set_task_rq` path. I saw that `set_task_cpu` modifies the CPU affinity of the task and is typically not called during enqueuing. Checking `proxy_migrate_task` suggests that the task is not enqueued when `set_task_cpu` is called, ensuring that the critical call to `sched_core_dequeue` is made.
Then I considered the donor situation. If a task is the donor (`rq->donor`), I can verify that `task_rq(p)` returns the correct runqueue value, which is important for the tick processing. I'm investigating how proxy tasks might play into this, and then back into the call chain: `pick_next_task`, then `put_prev_set_next_task`, and eventually `set_next_task_fair` and `hrtick_start_fair`. It's clear that the donor relationship *shouldn't* cause this mismatch either. In `pick_next_task`, the donor is updated, and the logic seems sound in the normal case.
The key then is to examine that `__schedule` is called. It gets called from `do_task_dead`, which is what I'm looking at. The current task is exiting (dead). And that's where things get interesting. If the previous task is exiting, it becomes the donor. Inside `pick_next_task`, a `next` task gets picked. The key point is: `task_rq(next)` *should* be `rq`, but the WARN_ON_ONCE suggests it isn't, so I'm trying to work through why not.
I'm considering whether `next` can be picked from a different CPU, but looking at `pick_next_task` again, I can verify that is not the case. `sched_core_find` picks from runqueues on the same CPU, which means that `next` *should* reside on the current runqueue. That means it must be something else, the `task_rq(next)` is returning a *different* runqueue. I've rechecked the logic to see the source of the value, but I need to dive deeper to determine what could influence `task_rq`.
|
| 65/2 |
2026/06/09 12:38 |
tool |
read-file |
0m
Args: map[File:kernel/sched/sched.h FirstLine:3000 LineCount:50]
Results: map[Contents:3000: }
3001: #else /* !CONFIG_NO_HZ_FULL: */
3002: static inline int sched_tick_offload_init(void) { return 0; }
3003: static inline void sched_update_tick_dependency(struct rq *rq) { }
3004: #endif /* !CONFIG_NO_HZ_FULL */
3005:
3006: static inline void add_nr_running(struct rq *rq, unsigned count)
3007: {
3008: unsigned prev_nr = rq->nr_running;
3009:
3010: rq->nr_running = prev_nr + count;
3011: if (trace_sched_update_nr_running_tp_enabled()) {
3012: call_trace_sched_update_nr_running(rq, count);
3013: }
3014:
3015: if (prev_nr < 2 && rq->nr_running >= 2)
3016: set_rd_overloaded(rq->rd, 1);
3017:
3018: sched_update_tick_dependency(rq);
3019: }
3020:
3021: static inline void sub_nr_running(struct rq *rq, unsigned count)
3022: {
3023: rq->nr_running -= count;
3024: if (trace_sched_update_nr_running_tp_enabled()) {
3025: call_trace_sched_update_nr_running(rq, -count);
3026: }
3027:
3028: /* Check if we still need preemption */
3029: sched_update_tick_dependency(rq);
3030: }
3031:
3032: static inline void __block_task(struct rq *rq, struct task_struct *p)
3033: {
3034: if (p->sched_contributes_to_load)
3035: rq->nr_uninterruptible++;
3036:
3037: if (p->in_iowait) {
3038: atomic_inc(&rq->nr_iowait);
3039: delayacct_blkio_start();
3040: }
3041:
3042: ASSERT_EXCLUSIVE_WRITER(p->on_rq);
3043:
3044: /*
3045: * The moment this write goes through, ttwu() can swoop in and migrate
3046: * this task, rendering our rq->__lock ineffective.
3047: *
3048: * __schedule() try_to_wake_up()
3049: * LOCK rq->__lock LOCK p->pi_lock
]
|
| 66/2 |
2026/06/09 12:38 |
llm |
expert |
0m
Model:
Tokens:
input: 57303
output: 34
thoughts: 12
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into Task Runqueues**
Okay, so I need to get my hands on `task_rq(p)`. Straightforward, really. I know it's fundamental for understanding how a task maps to a runqueue in the scheduler. I'm probably looking at diving into the kernel's data structures here. The task pointer `p` is my starting point, which is usually readily available from any context I'm in - whether I have a process ID or have a task as an argument to a function.
Depending on *why* I need the runqueue, I might be looking to analyze things like task migration, fairness, latency, or even CPU affinity. Knowing the runqueue allows me to examine the task's scheduling state, its priority, its wait queues, the other tasks competing for the same CPU, and, perhaps most importantly, the lock contention thatβs happening if I'm looking at scheduler performance or debugging lock issues. So, `task_rq(p)` is the gateway to understanding the task's execution environment within the scheduler. Now, let's see where that takes me...
|
| 67/2 |
2026/06/09 12:39 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:kernel/sched/sched.h Name:task_rq]
Results: map[SourceCode: SourceFile:]
|
| 68/2 |
2026/06/09 12:39 |
llm |
expert |
0m
Model:
Tokens:
input: 57623
output: 28
thoughts: 17
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Diving into the Scheduler: Hunting for `task_rq`**
Alright, time to get to the heart of the matter. The scheduler's where the magic happens, so let's zero in on it. I need to understand something specific about task runtime queues, and the quickest way to do that is a direct code dive. Iβm thinking `task_rq` is the key. Where does this structure get used within the scheduler's code? `kernel/sched/` is the obvious starting point. I could spend ages manually browsing the directories, but let's be efficient. A simple `grep` for `task_rq(` should quickly pinpoint all the relevant occurrences β function calls, structure uses, whatever. This'll get me straight to the relevant lines of code, bypassing the noise and allowing me to understand the critical interactions with the task runtime queue. This is the fastest way to get a solid grasp of how `task_rq` is employed in context. I'm hoping I can find the answer quickly and move on with a solid understanding of how that struct is used.
|
| 69/2 |
2026/06/09 12:39 |
tool |
grepper |
0m
Args: map[Expression:task_rq\( PathPrefix:kernel/sched/]
Results: map[Output:
Full output is too long, showing 500 out of 555 lines.
Use more precise expression if possible.
kernel/sched/core.c=270=static inline bool __sched_core_less(const struct task_struct *a,
--
kernel/sched/core.c-279- /* flip prio, so high prio is leftmost */
kernel/sched/core.c:280: if (prio_less(b, a, !!task_rq(a)->core->core_forceidle_count))
kernel/sched/core.c-281- return true;
--
kernel/sched/core.c=732=struct rq *___task_rq_lock(struct task_struct *p, struct rq_flags *rf)
--
kernel/sched/core.c-738- for (;;) {
kernel/sched/core.c:739: rq = task_rq(p);
kernel/sched/core.c-740- raw_spin_rq_lock(rq);
kernel/sched/core.c:741: if (likely(rq == task_rq(p) && !task_on_rq_migrating(p))) {
kernel/sched/core.c-742- rq_pin_lock(rq, rf);
--
kernel/sched/core.c=755=struct rq *_task_rq_lock(struct task_struct *p, struct rq_flags *rf)
--
kernel/sched/core.c-760- raw_spin_lock_irqsave(&p->pi_lock, rf->flags);
kernel/sched/core.c:761: rq = task_rq(p);
kernel/sched/core.c-762- raw_spin_rq_lock(rq);
--
kernel/sched/core.c-766- * ACQUIRE (rq->lock)
kernel/sched/core.c:767: * [S] ->on_rq = MIGRATING [L] rq = task_rq()
kernel/sched/core.c-768- * WMB (__set_task_cpu()) ACQUIRE (rq->lock);
kernel/sched/core.c:769: * [S] ->cpu = new_cpu [L] task_rq()
kernel/sched/core.c-770- * [L] ->on_rq
--
kernel/sched/core.c-776- * If we observe the new CPU in task_rq_lock(), the address
kernel/sched/core.c:777: * dependency headed by '[L] rq = task_rq()' and the acquire
kernel/sched/core.c-778- * will pair with the WMB to ensure we then also see migrating.
kernel/sched/core.c-779- */
kernel/sched/core.c:780: if (likely(rq == task_rq(p) && !task_on_rq_migrating(p))) {
kernel/sched/core.c-781- rq_pin_lock(rq, rf);
--
kernel/sched/core.c=1524=void set_load_weight(struct task_struct *p, bool update_load)
--
kernel/sched/core.c-1541- if (update_load && p->sched_class->reweight_task)
kernel/sched/core.c:1542: p->sched_class->reweight_task(task_rq(p), p, &lw);
kernel/sched/core.c-1543- else
--
kernel/sched/core.c=2343=unsigned long wait_task_inactive(struct task_struct *p, unsigned int match_state)
--
kernel/sched/core.c-2356- */
kernel/sched/core.c:2357: rq = task_rq(p);
kernel/sched/core.c-2358-
--
kernel/sched/core.c=2611=static int migration_cpu_stop(void *data)
--
kernel/sched/core.c-2647- /*
kernel/sched/core.c:2648: * If task_rq(p) != rq, it cannot be migrated here, because we're
kernel/sched/core.c-2649- * holding rq->lock, if p->on_rq == 0 it cannot get enqueued because
--
kernel/sched/core.c-2651- */
kernel/sched/core.c:2652: if (task_rq(p) == rq) {
kernel/sched/core.c-2653- if (is_migration_disabled(p))
--
kernel/sched/core.c=2725=int push_cpu_stop(void *arg)
--
kernel/sched/core.c-2732-
kernel/sched/core.c:2733: if (task_rq(p) != rq)
kernel/sched/core.c-2734- goto out_unlock;
--
kernel/sched/core.c-2751- // XXX validate p is still the highest prio task
kernel/sched/core.c:2752: if (task_rq(p) == rq) {
kernel/sched/core.c-2753- move_queued_task_locked(rq, lowest_rq, p);
--
kernel/sched/core.c=3341=void set_task_cpu(struct task_struct *p, unsigned int new_cpu)
--
kernel/sched/core.c-3371- WARN_ON_ONCE(debug_locks && !(lockdep_is_held(&p->pi_lock) ||
kernel/sched/core.c:3372: lockdep_is_held(__rq_lockp(task_rq(p)))));
kernel/sched/core.c-3373-#endif
--
kernel/sched/core.c-3384- if (p->sched_class->migrate_task_rq)
kernel/sched/core.c:3385: p->sched_class->migrate_task_rq(p, new_cpu);
kernel/sched/core.c-3386- p->se.nr_migrations++;
--
kernel/sched/core.c=3395=static void __migrate_swap_task(struct task_struct *p, int cpu)
--
kernel/sched/core.c-3400-
kernel/sched/core.c:3401: src_rq = task_rq(p);
kernel/sched/core.c-3402- dst_rq = cpu_rq(cpu);
--
kernel/sched/core.c=3521=EXPORT_SYMBOL_GPL(kick_process);
--
kernel/sched/core.c-3541- * And can assume that any active CPU must be online. Conversely
kernel/sched/core.c:3542: * select_task_rq() below may allow selection of !active CPUs in order
kernel/sched/core.c-3543- * to satisfy the above rules.
--
kernel/sched/core.c=3613=static inline
kernel/sched/core.c:3614:int select_task_rq(struct task_struct *p, int cpu, int *wake_flags)
kernel/sched/core.c-3615-{
--
kernel/sched/core.c-3618- if (p->nr_cpus_allowed > 1 && !is_migration_disabled(p)) {
kernel/sched/core.c:3619: cpu = p->sched_class->select_task_rq(p, cpu, *wake_flags);
kernel/sched/core.c-3620- *wake_flags |= WF_RQ_SELECTED;
--
kernel/sched/core.c=3748=static inline void proxy_reset_donor(struct rq *rq)
--
kernel/sched/core.c-3761- * so that it can be properly woken up on the p->wake_cpu
kernel/sched/core.c:3762: * (or whichever cpu select_task_rq() picks at the bottom of
kernel/sched/core.c-3763- * try_to_wake_up()
--
kernel/sched/core.c=3805=ttwu_do_activate(struct rq *rq, struct task_struct *p, int wake_flags,
--
kernel/sched/core.c-3821- delayacct_blkio_end(p);
kernel/sched/core.c:3822: atomic_dec(&task_rq(p)->nr_iowait);
kernel/sched/core.c-3823- }
--
kernel/sched/core.c-3857- *
kernel/sched/core.c:3858: * By taking task_rq(p)->lock we serialize against schedule(), if @p->on_rq
kernel/sched/core.c-3859- * then schedule() must still happen and p->state can be changed to
--
kernel/sched/core.c=4101=bool ttwu_state_match(struct task_struct *p, unsigned int state, int *success)
--
kernel/sched/core.c-4237- * - p->sched_task_group
kernel/sched/core.c:4238: * in order to do migration, see its use of select_task_rq()/set_task_cpu().
kernel/sched/core.c-4239- *
kernel/sched/core.c:4240: * Tries really hard to only take one task_rq(p)->lock for performance.
kernel/sched/core.c-4241- * Takes rq->lock in:
--
kernel/sched/core.c=4252=int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
--
kernel/sched/core.c-4393-
kernel/sched/core.c:4394: cpu = select_task_rq(p, p->wake_cpu, &wake_flags);
kernel/sched/core.c-4395- if (task_cpu(p) != cpu) {
--
kernel/sched/core.c-4397- delayacct_blkio_end(p);
kernel/sched/core.c:4398: atomic_dec(&task_rq(p)->nr_iowait);
kernel/sched/core.c-4399- }
--
kernel/sched/core.c-4406- * If we were proxy-migrated to cpu, then
kernel/sched/core.c:4407: * select_task_rq() picks cpu instead of wake_cpu
kernel/sched/core.c-4408- * to return to, we won't call set_task_cpu(),
--
kernel/sched/core.c=4942=void wake_up_new_task(struct task_struct *p)
--
kernel/sched/core.c-4958- p->recent_used_cpu = task_cpu(p);
kernel/sched/core.c:4959: __set_task_cpu(p, select_task_rq(p, task_cpu(p), &wake_flags));
kernel/sched/core.c-4960- rq = __task_rq_lock(p, &rf);
--
kernel/sched/core.c=5618=void sched_exec(void)
--
kernel/sched/core.c-5624- scoped_guard (raw_spinlock_irqsave, &p->pi_lock) {
kernel/sched/core.c:5625: dest_cpu = p->sched_class->select_task_rq(p, task_cpu(p), WF_EXEC);
kernel/sched/core.c-5626- if (dest_cpu == smp_processor_id())
--
kernel/sched/core.c=5653=static inline void prefetch_curr_exec_start(struct task_struct *p)
--
kernel/sched/core.c-5657-#else
kernel/sched/core.c:5658: struct sched_entity *curr = task_rq(p)->cfs.curr;
kernel/sched/core.c-5659-#endif
--
kernel/sched/core.c=6168=static inline bool is_task_rq_idle(struct task_struct *t)
kernel/sched/core.c-6169-{
kernel/sched/core.c:6170: return (task_rq(t)->idle == t);
kernel/sched/core.c-6171-}
--
kernel/sched/core.c=8400=static int __balance_push_cpu_stop(void *arg)
--
kernel/sched/core.c-8417- update_rq_clock(rq);
kernel/sched/core.c:8418: if (task_rq(p) == rq && task_on_rq_queued(p))
kernel/sched/core.c-8419- rq = __migrate_task(rq, &rf, p, cpu);
--
kernel/sched/core.c=9446=static void sched_change_group(struct task_struct *tsk)
--
kernel/sched/core.c-9464-#endif
kernel/sched/core.c:9465: set_task_rq(tsk, task_cpu(tsk));
kernel/sched/core.c-9466-}
--
kernel/sched/core.c=10932=static void mm_cid_fixup_task_to_cpu(struct task_struct *t, struct mm_struct *mm)
--
kernel/sched/core.c-10937- /* If running on the CPU, put the CID in transit mode, otherwise drop it */
kernel/sched/core.c:10938: if (task_rq(t)->curr == t)
kernel/sched/core.c-10939- mm_cid_transit_to_cpu(t, per_cpu_ptr(mm->mm_cid.pcpu, task_cpu(t)));
--
kernel/sched/core.c=11178=struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int flags)
--
kernel/sched/core.c-11180- struct sched_change_ctx *ctx = this_cpu_ptr(&sched_change_ctx);
kernel/sched/core.c:11181: struct rq *rq = task_rq(p);
kernel/sched/core.c-11182-
--
kernel/sched/core.c=11225=void sched_change_end(struct sched_change_ctx *ctx)
--
kernel/sched/core.c-11227- struct task_struct *p = ctx->p;
kernel/sched/core.c:11228: struct rq *rq = task_rq(p);
kernel/sched/core.c-11229-
--
kernel/sched/deadline.c=66=static inline struct rq *rq_of_dl_se(struct sched_dl_entity *dl_se)
--
kernel/sched/deadline.c-70- if (!dl_server(dl_se))
kernel/sched/deadline.c:71: rq = task_rq(dl_task_of(dl_se));
kernel/sched/deadline.c-72-
--
kernel/sched/deadline.c=336=static void dl_change_utilization(struct task_struct *p, u64 new_bw)
--
kernel/sched/deadline.c-342-
kernel/sched/deadline.c:343: dl_rq_change_utilization(task_rq(p), &p->dl, new_bw);
kernel/sched/deadline.c-344-}
--
kernel/sched/deadline.c=1061=static int start_dl_timer(struct sched_dl_entity *dl_se)
--
kernel/sched/deadline.c-1106- * The race against __run_timer() clearing the enqueued state is
kernel/sched/deadline.c:1107: * harmless because we're holding task_rq()->lock, therefore the timer
kernel/sched/deadline.c-1108- * expiring after we've done the check will wait on its task_rq_lock()
--
kernel/sched/deadline.c=2654=static void migrate_task_rq_dl(struct task_struct *p, int new_cpu __maybe_unused)
--
kernel/sched/deadline.c-2661-
kernel/sched/deadline.c:2662: rq = task_rq(p);
kernel/sched/deadline.c-2663- /*
--
kernel/sched/deadline.c=2930=static int find_later_rq(struct task_struct *task)
--
kernel/sched/deadline.c-2947- */
kernel/sched/deadline.c:2948: if (!cpudl_find(&task_rq(task)->rd->cpudl, task, later_mask))
kernel/sched/deadline.c-2949- return -1;
--
kernel/sched/deadline.c=3051=static struct rq *find_lock_later_rq(struct task_struct *task, struct rq *rq)
--
kernel/sched/deadline.c-3100- (task->dl.dl_throttled &&
kernel/sched/deadline.c:3101: (task_rq(task) != rq ||
kernel/sched/deadline.c-3102- task_on_cpu(rq, task) ||
--
kernel/sched/deadline.c=3321=static void set_cpus_allowed_dl(struct task_struct *p,
--
kernel/sched/deadline.c-3327-
kernel/sched/deadline.c:3328: rq = task_rq(p);
kernel/sched/deadline.c-3329- /*
--
kernel/sched/deadline.c=3352=bool dl_task_needs_bw_move(struct task_struct *p,
--
kernel/sched/deadline.c-3357-
kernel/sched/deadline.c:3358: return !cpumask_intersects(task_rq(p)->rd->span, new_mask);
kernel/sched/deadline.c-3359-}
--
kernel/sched/deadline.c=3854=void __getparam_dl(struct task_struct *p, struct sched_attr *attr, unsigned int flags)
--
kernel/sched/deadline.c-3856- struct sched_dl_entity *dl_se = &p->dl;
kernel/sched/deadline.c:3857: struct rq *rq = task_rq(p);
kernel/sched/deadline.c-3858- u64 adj_deadline;
--
kernel/sched/ext.c=766=static bool nldsq_cursor_lost_task(struct scx_dsq_list_node *cursor,
--
kernel/sched/ext.c-782- /* if @p has stayed on @dsq, its rq couldn't have changed */
kernel/sched/ext.c:783: if (WARN_ON_ONCE(rq != task_rq(p)))
kernel/sched/ext.c-784- return true;
--
kernel/sched/ext.c=1836=static void dispatch_dequeue_locked(struct task_struct *p,
--
kernel/sched/ext.c-1838-{
kernel/sched/ext.c:1839: lockdep_assert_rq_held(task_rq(p));
kernel/sched/ext.c-1840- lockdep_assert_held(&dsq->lock);
--
kernel/sched/ext.c=1929=static void direct_dispatch(struct scx_sched *sch, struct task_struct *p,
--
kernel/sched/ext.c-1931-{
kernel/sched/ext.c:1932: struct rq *rq = task_rq(p);
kernel/sched/ext.c-1933- struct scx_dispatch_q *dsq =
--
kernel/sched/ext.c=2517=static bool unlink_dsq_and_lock_src_rq(struct task_struct *p,
--
kernel/sched/ext.c-2533- return likely(p->scx.holding_cpu == cpu) &&
kernel/sched/ext.c:2534: !WARN_ON_ONCE(src_rq != task_rq(p));
kernel/sched/ext.c-2535-}
--
kernel/sched/ext.c=2569=static struct rq *move_task_between_dsqs(struct scx_sched *sch,
--
kernel/sched/ext.c-2573-{
kernel/sched/ext.c:2574: struct rq *src_rq = task_rq(p), *dst_rq;
kernel/sched/ext.c-2575-
--
kernel/sched/ext.c=2623=static bool consume_dispatch_q(struct scx_sched *sch, struct rq *rq,
--
kernel/sched/ext.c-2638- nldsq_for_each_task(p, dsq) {
kernel/sched/ext.c:2639: struct rq *task_rq = task_rq(p);
kernel/sched/ext.c-2640-
--
kernel/sched/ext.c=2692=static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq,
--
kernel/sched/ext.c-2695-{
kernel/sched/ext.c:2696: struct rq *src_rq = task_rq(p);
kernel/sched/ext.c-2697- struct rq *dst_rq = container_of(dst_dsq, struct rq, scx.local_dsq);
--
kernel/sched/ext.c-2743- if (likely(p->scx.holding_cpu == raw_smp_processor_id()) &&
kernel/sched/ext.c:2744: !WARN_ON_ONCE(src_rq != task_rq(p))) {
kernel/sched/ext.c-2745- /*
--
kernel/sched/ext.c=3402=bool scx_prio_less(const struct task_struct *a, const struct task_struct *b,
--
kernel/sched/ext.c-3415- return SCX_CALL_OP_2TASKS_RET(sch_a, core_sched_before,
kernel/sched/ext.c:3416: task_rq(a),
kernel/sched/ext.c-3417- (struct task_struct *)a,
--
kernel/sched/ext.c=3485=static void set_cpus_allowed_scx(struct task_struct *p,
--
kernel/sched/ext.c-3503- if (SCX_HAS_OP(sch, set_cpumask))
kernel/sched/ext.c:3504: scx_call_op_set_cpumask(sch, task_rq(p), p, (struct cpumask *)p->cpus_ptr);
kernel/sched/ext.c-3505-}
--
kernel/sched/ext.c=3727=static void __scx_enable_task(struct scx_sched *sch, struct task_struct *p)
kernel/sched/ext.c-3728-{
kernel/sched/ext.c:3729: struct rq *rq = task_rq(p);
kernel/sched/ext.c-3730- u32 weight;
--
kernel/sched/ext.c=3765=static void scx_disable_task(struct scx_sched *sch, struct task_struct *p)
kernel/sched/ext.c-3766-{
kernel/sched/ext.c:3767: struct rq *rq = task_rq(p);
kernel/sched/ext.c-3768-
--
kernel/sched/ext.c=3786=static void __scx_disable_and_exit_task(struct scx_sched *sch,
--
kernel/sched/ext.c-3793- lockdep_assert_held(&p->pi_lock);
kernel/sched/ext.c:3794: lockdep_assert_rq_held(task_rq(p));
kernel/sched/ext.c-3795-
--
kernel/sched/ext.c-3812- if (SCX_HAS_OP(sch, exit_task))
kernel/sched/ext.c:3813: SCX_CALL_OP_TASK(sch, exit_task, task_rq(p), p, &args);
kernel/sched/ext.c-3814-}
--
kernel/sched/ext.c=3821=static void scx_sub_init_cancel_task(struct scx_sched *sch, struct task_struct *p)
--
kernel/sched/ext.c-3825- lockdep_assert_held(&p->pi_lock);
kernel/sched/ext.c:3826: lockdep_assert_rq_held(task_rq(p));
kernel/sched/ext.c-3827-
kernel/sched/ext.c-3828- if (SCX_HAS_OP(sch, exit_task))
kernel/sched/ext.c:3829: SCX_CALL_OP_TASK(sch, exit_task, task_rq(p), p, &args);
kernel/sched/ext.c-3830-}
--
kernel/sched/ext.c=3990=static bool task_dead_and_done(struct task_struct *p)
kernel/sched/ext.c-3991-{
kernel/sched/ext.c:3992: struct rq *rq = task_rq(p);
kernel/sched/ext.c-3993-
--
kernel/sched/ext.c=4046=static void reweight_task_scx(struct rq *rq, struct task_struct *p,
--
kernel/sched/ext.c-4050-
kernel/sched/ext.c:4051: lockdep_assert_rq_held(task_rq(p));
kernel/sched/ext.c-4052-
--
kernel/sched/ext.c=4102=int scx_check_setscheduler(struct task_struct *p, int policy)
kernel/sched/ext.c-4103-{
kernel/sched/ext.c:4104: lockdep_assert_rq_held(task_rq(p));
kernel/sched/ext.c-4105-
--
kernel/sched/ext.c=4307=static void reenq_user(struct rq *rq, struct scx_dispatch_q *dsq, u64 reenq_flags)
--
kernel/sched/ext.c-4329-
kernel/sched/ext.c:4330: task_rq = task_rq(p);
kernel/sched/ext.c-4331-
--
kernel/sched/ext.c=4546=void scx_cgroup_move_task(struct task_struct *p)
--
kernel/sched/ext.c-4560- if (SCX_HAS_OP(sch, cgroup_move) && p->scx.cgrp_moving_from)
kernel/sched/ext.c:4561: SCX_CALL_OP_TASK(sch, cgroup_move, task_rq(p),
kernel/sched/ext.c-4562- p, p->scx.cgrp_moving_from,
--
kernel/sched/ext.c=5320=static u32 bypass_lb_cpu(struct scx_sched *sch, s32 donor,
--
kernel/sched/ext.c-5362- * CPU and the donee hasn't consumed it yet, @p is still on the
kernel/sched/ext.c:5363: * previous CPU and task_rq(@p) != @donor_rq. @p can't be moved
kernel/sched/ext.c-5364- * without its rq locked. Skip.
kernel/sched/ext.c-5365- */
kernel/sched/ext.c:5366: if (task_rq(p) != donor_rq)
kernel/sched/ext.c-5367- continue;
--
kernel/sched/ext.c=6124=static void scx_root_disable(struct scx_sched *sch)
--
kernel/sched/ext.c-6181-
kernel/sched/ext.c:6182: update_rq_clock(task_rq(p));
kernel/sched/ext.c-6183-
--
kernel/sched/ext.c=8879=static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit,
--
kernel/sched/ext.c-8919- */
kernel/sched/ext.c:8920: src_rq = task_rq(p);
kernel/sched/ext.c-8921-
--
kernel/sched/ext.c=10194=__bpf_kfunc bool scx_bpf_task_running(const struct task_struct *p)
kernel/sched/ext.c-10195-{
kernel/sched/ext.c:10196: return task_rq(p)->curr == p;
kernel/sched/ext.c-10197-}
--
kernel/sched/ext_idle.c=408=static inline bool task_affinity_all(const struct task_struct *p)
--
kernel/sched/ext_idle.c-451- * NOTE: tasks that can only run on 1 CPU are excluded by this logic, because
kernel/sched/ext_idle.c:452: * we never call ops.select_cpu() for them, see select_task_rq().
kernel/sched/ext_idle.c-453- */
--
kernel/sched/ext_idle.c=905=static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
--
kernel/sched/ext_idle.c-937- } else if (scx_locked_rq()) {
kernel/sched/ext_idle.c:938: if (task_rq(p) != scx_locked_rq())
kernel/sched/ext_idle.c-939- goto cross_task;
--
kernel/sched/ext_internal.h=282=struct sched_ext_ops {
--
kernel/sched/ext_internal.h-304- * on a single CPU or tasks with migration disabled, as they don't have
kernel/sched/ext_internal.h:305: * the option to select a different CPU. See select_task_rq() for
kernel/sched/ext_internal.h-306- * details.
--
kernel/sched/ext_internal.h=1564=static inline struct scx_sched *scx_task_sched(const struct task_struct *p)
--
kernel/sched/ext_internal.h-1567- lockdep_is_held(&p->pi_lock) ||
kernel/sched/ext_internal.h:1568: lockdep_is_held(__rq_lockp(task_rq(p))));
kernel/sched/ext_internal.h-1569-}
--
kernel/sched/ext_internal.h=1631=static inline struct scx_sched *scx_task_sched(const struct task_struct *p)
--
kernel/sched/ext_internal.h-1634- lockdep_is_held(&p->pi_lock) ||
kernel/sched/ext_internal.h:1635: lockdep_is_held(__rq_lockp(task_rq(p))));
kernel/sched/ext_internal.h-1636-}
--
kernel/sched/fair.c=2245=static struct numa_group *deref_task_numa_group(struct task_struct *p)
--
kernel/sched/fair.c-2247- return rcu_dereference_check(p->numa_group, p == current ||
kernel/sched/fair.c:2248: (lockdep_is_held(__rq_lockp(task_rq(p))) && !READ_ONCE(p->on_cpu)));
kernel/sched/fair.c-2249-}
--
kernel/sched/fair.c=5025=static void __maybe_unused clear_tg_offline_cfs_rqs(struct rq *rq)
--
kernel/sched/fair.c-5049-/*
kernel/sched/fair.c:5050: * Called within set_task_rq() right before setting a task's CPU. The
kernel/sched/fair.c-5051- * caller only guarantees p->pi_lock is held; no other assumptions,
--
kernel/sched/fair.c=7648=static void hrtick_start_fair(struct rq *rq, struct task_struct *p)
--
kernel/sched/fair.c-7655-
kernel/sched/fair.c:7656: WARN_ON_ONCE(task_rq(p) != rq);
kernel/sched/fair.c-7657-
--
kernel/sched/fair.c=9537=select_task_rq_fair(struct task_struct *p, int prev_cpu, int wake_flags)
--
kernel/sched/fair.c-9606- * cfs_rq_of(p) references at time of call are still valid and identify the
kernel/sched/fair.c:9607: * previous CPU. The caller guarantees p->pi_lock or task_rq(p)->lock is held.
kernel/sched/fair.c-9608- */
--
kernel/sched/fair.c=14770=bool cfs_prio_less(const struct task_struct *a, const struct task_struct *b,
--
kernel/sched/fair.c-14772-{
kernel/sched/fair.c:14773: struct rq *rq = task_rq(a);
kernel/sched/fair.c-14774- const struct sched_entity *sea = &a->se;
--
kernel/sched/fair.c-14779-
kernel/sched/fair.c:14780: WARN_ON_ONCE(task_rq(b)->core != rq->core);
kernel/sched/fair.c-14781-
--
kernel/sched/fair.c-14802-#else /* !CONFIG_FAIR_GROUP_SCHED: */
kernel/sched/fair.c:14803: cfs_rqa = &task_rq(a)->cfs;
kernel/sched/fair.c:14804: cfs_rqb = &task_rq(b)->cfs;
kernel/sched/fair.c-14805-#endif /* !CONFIG_FAIR_GROUP_SCHED */
--
kernel/sched/fair.c=14841=static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
--
kernel/sched/fair.c-14859- update_misfit_status(curr, rq);
kernel/sched/fair.c:14860: check_update_overutilized_status(task_rq(curr));
kernel/sched/fair.c-14861-
--
kernel/sched/fair.c=15072=static void task_change_group_fair(struct task_struct *p)
--
kernel/sched/fair.c-15084- p->se.avg.last_update_time = 0;
kernel/sched/fair.c:15085: set_task_rq(p, task_cpu(p));
kernel/sched/fair.c-15086- attach_task_cfs_rq(p);
--
kernel/sched/rt.c=309=static inline struct rq *rq_of_rt_se(struct sched_rt_entity *rt_se)
--
kernel/sched/rt.c-312-
kernel/sched/rt.c:313: return task_rq(p);
kernel/sched/rt.c-314-}
--
kernel/sched/rt.c=1774=static int find_lowest_rq(struct task_struct *task)
--
kernel/sched/rt.c-1794-
kernel/sched/rt.c:1795: ret = cpupri_find_fitness(&task_rq(task)->rd->cpupri,
kernel/sched/rt.c-1796- task, lowest_mask,
--
kernel/sched/rt.c-1799-
kernel/sched/rt.c:1800: ret = cpupri_find(&task_rq(task)->rd->cpupri,
kernel/sched/rt.c-1801- task, lowest_mask);
--
kernel/sched/sched.h=1410=static __always_inline struct rq *__this_rq(void)
--
kernel/sched/sched.h-1416-#define this_rq() __this_rq()
kernel/sched/sched.h:1417:#define task_rq(p) cpu_rq(task_cpu(p))
kernel/sched/sched.h-1418-#define cpu_curr(cpu) (cpu_rq(cpu)->curr)
--
kernel/sched/sched.h=1735=static inline struct cfs_rq *task_cfs_rq(const struct task_struct *p)
kernel/sched/sched.h-1736-{
kernel/sched/sched.h:1737: return &task_rq(p)->cfs;
kernel/sched/sched.h-1738-}
--
kernel/sched/sched.h=1740=static inline struct cfs_rq *cfs_rq_of(const struct sched_entity *se)
--
kernel/sched/sched.h-1742- const struct task_struct *p = task_of(se);
kernel/sched/sched.h:1743: struct rq *rq = task_rq(p);
kernel/sched/sched.h-1744-
--
kernel/sched/sched.h=2324=static inline struct sched_entity *cfs_rq_se(struct cfs_rq *cfs_rq)
--
kernel/sched/sched.h-2336-/* Change a task's cfs_rq and parent entity if it moves across CPUs/groups */
kernel/sched/sched.h:2337:static inline void set_task_rq(struct task_struct *p, unsigned int cpu)
kernel/sched/sched.h-2338-{
]
|
| 70/2 |
2026/06/09 12:39 |
llm |
expert |
0m
Model:
Tokens:
input: 67770
output: 53
thoughts: 1435
Thoughts: **Analyzing Potential Race Conditions in Core Scheduling**
Okay, let's break this down. My initial thought is about the core relationship: `task_rq(p)` is indeed equal to `cpu_rq(task_cpu(p))`. So, naturally, if the task's runqueue (`task_rq(p)`) isn't the same as the runqueue I'm looking at (`rq`), that *directly* implies that the task's CPU (`task_cpu(p)`) isn't the same as the CPU of the `rq` (`rq->cpu`). That seems straightforward.
Now, why might this be happening? I need to understand the path of a task through the scheduler. The function in question here is `pick_next_task` in `kernel/sched/core.c`. Let's step through it logically.
Within `pick_next_task`, the code iterates across CPUs that can potentially be scheduled on. Each core has a `core_pick` pointer to pick a task from. The code checks `if (!cookie_equals(p, cookie))`. If the cookie does not equal the current one, the task is no longer picked and then a different task will be looked for. Then it either looks for the cookie from the `rq_i->core_tree` via `sched_core_find(rq_i, cookie)` or falls back to `idle_sched_class.pick_task(rq_i, rf)`. I see that `sched_core_find(rq_i, cookie)` is picking a task `p` from `rq_i->core_tree`. Then the core `rq->core_pick` gets the returned task, which is the task picked for the `rq`. Thus, `next` is `rq->core_pick`. The task is found by either `sched_core_find(rq, cookie)` or `pick_task(rq, rf)`.
So, `next` is a task that should be on `rq->core_tree` or on `rq`'s runqueue. Now, if the task is from `rq->core_tree`, it was enqueued by `sched_core_enqueue(rq, next)`. And when a task is enqueued, its `task_cpu` should be set to `rq->cpu`.
So, the question becomes: Could `task_cpu(next)` be changed *while* the task is enqueued in the core tree? The obvious place to look is `set_task_cpu`, which *does* change `p->cpu`. But, crucially, I know that `set_task_cpu` is called during task migration. Migration means the task is **not** on the runqueue, so that seems okay.
Hang on. What about `proxy_migrate_task`?
Here, `deactivate_task(rq, p, DEQUEUE_NOCLOCK)` is called. `deactivate_task` calls `dequeue_task`, which calls `sched_core_dequeue`. This would remove `p` from `rq->core_tree`. Then, `proxy_set_task_cpu` changes `p->cpu`. And finally, `attach_one_task` calls `enqueue_task`, which calls `sched_core_enqueue` on `target_rq`. Thus `p` is added to `target_rq->core_tree`. That seems like the right sequence. The task should be removed before its CPU is changed, then put on the new CPU.
Okay, let me think more about cases. What about if `next` is a proxy task?
In `__schedule`, there's a check for `sched_proxy_exec()`. If this is true, the `rq->donor` is updated with `next` and `next`'s `is_blocked` flag is checked. If it is blocked, then a call to `find_proxy_task` might change `next`.
`pick_next_task` returns `next`. Later, `put_prev_set_next_task` is called, which in turn calls `next->sched_class->set_next_task(rq, next, true)`. So, if `next` is a blocked task, it is the donor. But `next` was picked by either `pick_task(rq, rf)` or `sched_core_find(rq, cookie)`. In this case, if `next` is on `rq`, then `task_rq(next)` is `rq`. Thus, again, why would `task_rq(next)` be different than `rq`?
Wait, what if `next` is `rq->donor`?
In `put_prev_set_next_task(rq, rq->donor, next)`, if `next
|
| 71/2 |
2026/06/09 12:39 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:kernel/sched/core.c IncludeSnippetLines:0 Name:set_task_cpu SourceTreePrefix:kernel/sched/]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:detach_task SourceFile:kernel/sched/fair.c SourceLine:10843] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:dl_task_offline_migration SourceFile:kernel/sched/deadline.c SourceLine:712] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:move_queued_task SourceFile:kernel/sched/core.c SourceLine:2553] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:move_queued_task_locked SourceFile:kernel/sched/sched.h SourceLine:4126] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:sched_ttwu_pending SourceFile:kernel/sched/core.c SourceLine:3910] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:try_to_wake_up SourceFile:kernel/sched/core.c SourceLine:4403]] TruncatedOutput:false]
|
| 72/2 |
2026/06/09 12:39 |
llm |
expert |
0m
Model:
Tokens:
input: 70608
output: 36
thoughts: 28
Thoughts: Here's my take on those thoughts, tailored for an expert audience:
**Diving into `move_queued_task_locked`**
Okay, time to get my hands dirty. I'm focusing on that `move_queued_task_locked` function, specifically within the `kernel/sched/sched.h` file β line 4126, if my memory serves me correctly. I know this function is critical, and it seems like the right place to start digging into the intricacies of task queue management and migration within the scheduler. My goal here is to refresh my understanding of *how* this is implemented in this specific kernel version. I'll be looking to see: What are the conditions that trigger this function's execution? What locks does it hold? How is it coordinated with other scheduler operations? I expect a well-written, lockless implementation given the performance constraints, but I'll see how it's actually handled. Specifically, I need to understand this function to trace some context switches I've been seeing and suspect that something in task queue manipulation is a culprit. Time to get down to the low-level details.
|
| 73/2 |
2026/06/09 12:39 |
tool |
read-file |
0m
Args: map[File:kernel/sched/sched.h FirstLine:4110 LineCount:50]
Results: map[Contents:4110:
4111: #endif
4112:
4113: void sched_domains_free_llc_id(int cpu);
4114:
4115: extern void init_sched_mm(struct task_struct *p);
4116:
4117: extern u64 avg_vruntime(struct cfs_rq *cfs_rq);
4118: extern int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se);
4119: static inline
4120: void move_queued_task_locked(struct rq *src_rq, struct rq *dst_rq, struct task_struct *task)
4121: {
4122: lockdep_assert_rq_held(src_rq);
4123: lockdep_assert_rq_held(dst_rq);
4124:
4125: deactivate_task(src_rq, task, 0);
4126: set_task_cpu(task, dst_rq->cpu);
4127: activate_task(dst_rq, task, 0);
4128: wakeup_preempt(dst_rq, task, 0);
4129: }
4130:
4131: static inline
4132: bool task_is_pushable(struct rq *rq, struct task_struct *p, int cpu)
4133: {
4134: if (!task_on_cpu(rq, p) &&
4135: cpumask_test_cpu(cpu, &p->cpus_mask))
4136: return true;
4137:
4138: return false;
4139: }
4140:
4141: #ifdef CONFIG_RT_MUTEXES
4142:
4143: static inline int __rt_effective_prio(struct task_struct *pi_task, int prio)
4144: {
4145: if (pi_task)
4146: prio = min(prio, pi_task->prio);
4147:
4148: return prio;
4149: }
4150:
4151: static inline int rt_effective_prio(struct task_struct *p, int prio)
4152: {
4153: struct task_struct *pi_task = rt_mutex_get_top_task(p);
4154:
4155: return __rt_effective_prio(pi_task, prio);
4156: }
4157:
4158: #else /* !CONFIG_RT_MUTEXES: */
4159:
]
|
| 74/2 |
2026/06/09 12:39 |
llm |
expert |
0m
Model:
Tokens:
input: 71664
output: 37
thoughts: 111
Thoughts: **Analyzing Task Movement in the Scheduler**
Okay, so `move_queued_task_locked` is the key function here. I see it's calling a sequence: `deactivate_task`, `set_task_cpu`, and then `activate_task`. That makes sense. Let's break it down further. `deactivate_task` does the actual heavy lifting with `dequeue_task`, and `dequeue_task` in turn calls `sched_core_dequeue`. Ah, so what's happening is a dequeue operation using the `sched_core_dequeue` function. That means the task is being taken off the `src_rq->core_tree` and placed onto the `dst_rq->core_tree`. That sounds correct to me, the expected behavior.
I'm comfortable with that, but let's just make sure. Let's take a look at the `detach_task` function, which is located in `kernel/sched/fair.c:10843`. I need to ensure there are no surprises with the task detachment.
|
| 75/2 |
2026/06/09 12:39 |
tool |
read-file |
0m
Args: map[File:kernel/sched/fair.c FirstLine:10830 LineCount:50]
Results: map[Contents:10830: {
10831: lockdep_assert_rq_held(env->src_rq);
10832:
10833: if (p->sched_task_hot) {
10834: p->sched_task_hot = 0;
10835: schedstat_inc(env->sd->lb_hot_gained[env->idle]);
10836: schedstat_inc(p->stats.nr_forced_migrations);
10837: }
10838:
10839: WARN_ON(task_current(env->src_rq, p));
10840: WARN_ON(task_current_donor(env->src_rq, p));
10841:
10842: deactivate_task(env->src_rq, p, DEQUEUE_NOCLOCK);
10843: set_task_cpu(p, env->dst_cpu);
10844: }
10845:
10846: /*
10847: * detach_one_task() -- tries to dequeue exactly one task from env->src_rq, as
10848: * part of active balancing operations within "domain".
10849: *
10850: * Returns a task if successful and NULL otherwise.
10851: */
10852: static struct task_struct *detach_one_task(struct lb_env *env)
10853: {
10854: struct task_struct *p;
10855:
10856: lockdep_assert_rq_held(env->src_rq);
10857:
10858: list_for_each_entry_reverse(p,
10859: &env->src_rq->cfs_tasks, se.group_node) {
10860: if (!can_migrate_task(p, env))
10861: continue;
10862:
10863: detach_task(p, env);
10864:
10865: /*
10866: * Right now, this is only the second place where
10867: * lb_gained[env->idle] is updated (other is detach_tasks)
10868: * so we can safely collect stats here rather than
10869: * inside detach_tasks().
10870: */
10871: schedstat_inc(env->sd->lb_gained[env->idle]);
10872: return p;
10873: }
10874: return NULL;
10875: }
10876:
10877: /*
10878: * detach_tasks() -- tries to detach up to imbalance load/util/tasks from
10879: * busiest_rq, as part of a balancing operation within domain "sd".
]
|
| 76/2 |
2026/06/09 12:39 |
llm |
expert |
0m
Model:
Tokens:
input: 72845
output: 27
thoughts: 1385
Thoughts: **Analyzing Potential Discrepancies in Task Runqueue Affiliation**
Okay, so I'm thinking about how `detach_task` interacts with task placement, specifically the check `task_rq(p) != rq`. I've established that this call sequence is sound: `detach_task` calls `deactivate_task` which is followed by a call to `set_task_cpu`. However, I need to understand why the mentioned condition might be true.
My initial thought was that this could happen if `p` is `rq->donor`. But, if `p` is the donor, then `task_rq(p)` would equal `rq`, so that can't be it. Then I began considering what would happen if `p` was a proxy task. I'm focused on the `__schedule` function now.
I see that `pick_next_task` returns `next`, and within that function, `put_prev_set_next_task` is called, which then uses the `set_next_task` function pointer from the `sched_class` struct. If `next` is a blocked task, it is the donor. But, how could `next` be a blocked task that's also the donor? It would've been picked by `pick_task` or `sched_core_find`. If `next` is on `rq`, then `task_rq(next)` is `rq`. Why would this not be true?
Now, I'm thinking about the scenario where `next` is `rq->core_pick`. This value is set by `sched_core_find` or `pick_task`. I've examined `pick_next_task` and see that `sched_core_find` is called on `rq_i`, which means `p` is from `rq_i->core_tree`, so it would be on `rq_i` and later `rq_i->core_pick = p`. The current CPU's runqueue is `rq`. Thus, `next` is on `rq`. What if `rq` isn't the current CPU's runqueue? Well, it is, because `pick_next_task` is called with `rq` and `rq` is actually `this_rq()`. So `next` is on `rq`.
Now here is something to think about: what if `rq->core_pick` isn't updated? There is a fast path in `pick_next_task` that could be used. If this fast path is taken, `next` is the value of `rq->core_pick`, but could that value have migrated? If a task migrates, it is dequeued. `dequeue_task` calls `sched_core_dequeue`. `sched_core_dequeue` increments `rq->core->core_task_seq`. If this happens, the fast path is not taken, because `rq->core->core_pick_seq == rq->core->core_task_seq` would be false. So, the fast path wouldn't work.
But, what if `p` is migrated, but `sched_core_dequeue` is not called? Under which circumstances does this occur? This happens when `p->se.sched_delayed` is true. If true, `sched_core_dequeue` does an early return! Then, the fast path *could* be taken! And so `next` would be `rq->core_pick`, which is `p`, which has been dequeued and migrated! Thus, `task_rq(p) != rq`! I need to determine under what conditions `p->se.sched_delayed` can be true now. I wonder what uses this flag. I'm going to run a grep to investigate...
|
| 77/2 |
2026/06/09 12:39 |
tool |
grepper |
0m
Args: map[Expression:sched_delayed PathPrefix:kernel/sched/]
Results: map[Output:kernel/sched/core.c=307=void sched_core_enqueue(struct rq *rq, struct task_struct *p)
kernel/sched/core.c-308-{
kernel/sched/core.c:309: if (p->se.sched_delayed)
kernel/sched/core.c-310- return;
--
kernel/sched/core.c=320=void sched_core_dequeue(struct rq *rq, struct task_struct *p, int flags)
kernel/sched/core.c-321-{
kernel/sched/core.c:322: if (p->se.sched_delayed)
kernel/sched/core.c-323- return;
--
kernel/sched/core.c=551=int task_llc(const struct task_struct *p)
--
kernel/sched/core.c-613- * Additionally it is possible to be ->on_rq but still be considered not
kernel/sched/core.c:614: * runnable when p->se.sched_delayed is true. These tasks are on the runqueue
kernel/sched/core.c-615- * but will be dequeued as soon as they get picked again. See the
--
kernel/sched/core.c=1856=static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p, int flags)
--
kernel/sched/core.c-1872- /* Only inc the delayed task which being woken up. */
kernel/sched/core.c:1873: if (p->se.sched_delayed && !(flags & ENQUEUE_DELAYED))
kernel/sched/core.c-1874- return;
--
kernel/sched/core.c=1884=static inline void uclamp_rq_dec(struct rq *rq, struct task_struct *p)
--
kernel/sched/core.c-1899-
kernel/sched/core.c:1900: if (p->se.sched_delayed)
kernel/sched/core.c-1901- return;
--
kernel/sched/core.c=2172=void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
--
kernel/sched/core.c-2178- * Can be before ->enqueue_task() because uclamp considers the
kernel/sched/core.c:2179: * ENQUEUE_DELAYED task before its ->sched_delayed gets cleared
kernel/sched/core.c-2180- * in ->enqueue_task().
--
kernel/sched/core.c=2198=inline bool dequeue_task(struct rq *rq, struct task_struct *p, int flags)
--
kernel/sched/core.c-2212- * Must be before ->dequeue_task() because ->dequeue_task() can 'fail'
kernel/sched/core.c:2213: * and mark the task ->sched_delayed.
kernel/sched/core.c-2214- */
--
kernel/sched/core.c=2343=unsigned long wait_task_inactive(struct task_struct *p, unsigned int match_state)
--
kernel/sched/core.c-2382- /*
kernel/sched/core.c:2383: * If task is sched_delayed, force dequeue it, to avoid always
kernel/sched/core.c-2384- * hitting the tick timeout in the queued case
kernel/sched/core.c-2385- */
kernel/sched/core.c:2386: if (p->se.sched_delayed)
kernel/sched/core.c-2387- dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED);
--
kernel/sched/core.c=3866=static int ttwu_runnable(struct task_struct *p, int wake_flags)
--
kernel/sched/core.c-3875- if (p->is_blocked) {
kernel/sched/core.c:3876: if (p->se.sched_delayed)
kernel/sched/core.c-3877- enqueue_task(rq, p, ENQUEUE_NOCLOCK | ENQUEUE_DELAYED);
--
kernel/sched/core.c=4252=int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
--
kernel/sched/core.c-4267- * schedule()'s block_task(), as such this must not observe
kernel/sched/core.c:4268: * sched_delayed.
kernel/sched/core.c-4269- *
--
kernel/sched/core.c-4274- */
kernel/sched/core.c:4275: WARN_ON_ONCE(p->se.sched_delayed);
kernel/sched/core.c-4276- WARN_ON_ONCE(p->is_blocked);
--
kernel/sched/core.c=4564=static void __sched_fork(u64 clone_flags, struct task_struct *p)
--
kernel/sched/core.c-4578- /* A delayed task cannot be in clone(). */
kernel/sched/core.c:4579: WARN_ON_ONCE(p->se.sched_delayed);
kernel/sched/core.c-4580- WARN_ON_ONCE(p->is_blocked);
--
kernel/sched/core.c=6866=find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
--
kernel/sched/core.c-6923-
kernel/sched/core.c:6924: if (!READ_ONCE(owner->on_rq) || owner->se.sched_delayed) {
kernel/sched/core.c-6925- /* XXX Don't handle blocked owners/delayed dequeue yet */
--
kernel/sched/core.c=7056=static void __sched notrace __schedule(int sched_mode)
--
kernel/sched/core.c-7223- psi_sched_switch(prev, next, !task_on_rq_queued(prev) ||
kernel/sched/core.c:7224: prev->se.sched_delayed);
kernel/sched/core.c-7225-
--
kernel/sched/fair.c=832=static s64 entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se, u64 avruntime)
--
kernel/sched/fair.c-854- * This can be due to clamping in entity_lag() or clamping due to
kernel/sched/fair.c:855: * sched_delayed. Either way, when vlag is modified and the entity is
kernel/sched/fair.c-856- * retained, the tree needs to be adjusted.
--
kernel/sched/fair.c=859=bool update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se)
--
kernel/sched/fair.c-865-
kernel/sched/fair.c:866: if (se->sched_delayed) {
kernel/sched/fair.c-867- /* previous vlag < 0 otherwise se would not be delayed */
--
kernel/sched/fair.c=1136=static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq, bool protect)
--
kernel/sched/fair.c-1155- /* ->next will never be delayed */
kernel/sched/fair.c:1156: WARN_ON_ONCE(cfs_rq->next->sched_delayed);
kernel/sched/fair.c-1157- return cfs_rq->next;
--
kernel/sched/fair.c=6194=static void set_delayed(struct sched_entity *se)
kernel/sched/fair.c-6195-{
kernel/sched/fair.c:6196: se->sched_delayed = 1;
kernel/sched/fair.c-6197-
--
kernel/sched/fair.c=6213=static void clear_delayed(struct sched_entity *se)
kernel/sched/fair.c-6214-{
kernel/sched/fair.c:6215: se->sched_delayed = 0;
kernel/sched/fair.c-6216-
--
kernel/sched/fair.c=6234=dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
--
kernel/sched/fair.c-6242- if (flags & DEQUEUE_DELAYED) {
kernel/sched/fair.c:6243: WARN_ON_ONCE(!se->sched_delayed);
kernel/sched/fair.c-6244- } else {
--
kernel/sched/fair.c-6252-
kernel/sched/fair.c:6253: WARN_ON_ONCE(delay && se->sched_delayed);
kernel/sched/fair.c-6254-
--
kernel/sched/fair.c=6376=pick_next_entity(struct rq *rq, struct cfs_rq *cfs_rq, bool protect)
--
kernel/sched/fair.c-6380- se = pick_eevdf(cfs_rq, protect);
kernel/sched/fair.c:6381: if (se->sched_delayed) {
kernel/sched/fair.c-6382- dequeue_entities(rq, se, DEQUEUE_SLEEP | DEQUEUE_DELAYED);
--
kernel/sched/fair.c=7770=requeue_delayed_entity(struct sched_entity *se)
--
kernel/sched/fair.c-7774- /*
kernel/sched/fair.c:7775: * se->sched_delayed should imply: se->on_rq == 1.
kernel/sched/fair.c-7776- * Because a delayed entity is one that is still on
--
kernel/sched/fair.c-7778- */
kernel/sched/fair.c:7779: WARN_ON_ONCE(!se->sched_delayed);
kernel/sched/fair.c-7780- WARN_ON_ONCE(!se->on_rq);
--
kernel/sched/fair.c=7802=enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
--
kernel/sched/fair.c-7820- */
kernel/sched/fair.c:7821: if (!p->se.sched_delayed || (flags & ENQUEUE_DELAYED))
kernel/sched/fair.c-7822- util_est_enqueue(&rq->cfs, p);
--
kernel/sched/fair.c-7836-
kernel/sched/fair.c:7837: if (task_new && se->sched_delayed)
kernel/sched/fair.c-7838- h_nr_runnable = 0;
--
kernel/sched/fair.c-7841- if (se->on_rq) {
kernel/sched/fair.c:7842: if (se->sched_delayed)
kernel/sched/fair.c-7843- requeue_delayed_entity(se);
--
kernel/sched/fair.c=7927=static int dequeue_entities(struct rq *rq, struct sched_entity *se, int flags)
--
kernel/sched/fair.c-7943- h_nr_idle = task_has_idle_policy(p);
kernel/sched/fair.c:7944: if (task_sleep || task_delayed || !se->sched_delayed)
kernel/sched/fair.c-7945- h_nr_runnable = 1;
--
kernel/sched/fair.c=8036=static bool dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
--
kernel/sched/fair.c-8042-
kernel/sched/fair.c:8043: if (!p->se.sched_delayed)
kernel/sched/fair.c-8044- util_est_dequeue(&rq->cfs, p);
--
kernel/sched/fair.c=9635=static void task_dead_fair(struct task_struct *p)
--
kernel/sched/fair.c-9638-
kernel/sched/fair.c:9639: if (se->sched_delayed) {
kernel/sched/fair.c-9640- struct rq_flags rf;
--
kernel/sched/fair.c-9643- rq = task_rq_lock(p, &rf);
kernel/sched/fair.c:9644: if (se->sched_delayed) {
kernel/sched/fair.c-9645- update_rq_clock(rq);
--
kernel/sched/fair.c=9763=static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_flags)
--
kernel/sched/fair.c-9846- * there is shared data as exec often follow fork. Do not
kernel/sched/fair.c:9847: * preempt for tasks that are sched_delayed as it would violate
kernel/sched/fair.c-9848- * EEVDF to forcibly queue an ineligible task.
kernel/sched/fair.c-9849- */
kernel/sched/fair.c:9850: if ((wake_flags & WF_FORK) || pse->sched_delayed)
kernel/sched/fair.c-9851- return;
--
kernel/sched/fair.c=10700=int can_migrate_task(struct task_struct *p, struct lb_env *env)
--
kernel/sched/fair.c-10716- */
kernel/sched/fair.c:10717: if ((p->se.sched_delayed) && (env->migration_type != migrate_load))
kernel/sched/fair.c-10718- return 0;
--
kernel/sched/fair.c=14985=static void switching_from_fair(struct rq *rq, struct task_struct *p)
kernel/sched/fair.c-14986-{
kernel/sched/fair.c:14987: if (p->se.sched_delayed)
kernel/sched/fair.c-14988- dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
--
kernel/sched/fair.c=14996=static void switched_to_fair(struct rq *rq, struct task_struct *p)
kernel/sched/fair.c-14997-{
kernel/sched/fair.c:14998: WARN_ON_ONCE(p->se.sched_delayed);
kernel/sched/fair.c-14999-
--
kernel/sched/fair.c=15023=static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first)
--
kernel/sched/fair.c-15054-
kernel/sched/fair.c:15055: WARN_ON_ONCE(se->sched_delayed);
kernel/sched/fair.c-15056-
--
kernel/sched/fair.c=15145=void unregister_fair_sched_group(struct task_group *tg)
--
kernel/sched/fair.c-15156- if (se) {
kernel/sched/fair.c:15157: if (se->sched_delayed) {
kernel/sched/fair.c-15158- guard(rq_lock_irqsave)(rq);
kernel/sched/fair.c:15159: if (se->sched_delayed) {
kernel/sched/fair.c-15160- update_rq_clock(rq);
--
kernel/sched/sched.h=946=static inline long se_runnable(struct sched_entity *se)
kernel/sched/sched.h-947-{
kernel/sched/sched.h:948: if (se->sched_delayed)
kernel/sched/sched.h-949- return false;
--
kernel/sched/sched.h=963=static inline long se_runnable(struct sched_entity *se)
kernel/sched/sched.h-964-{
kernel/sched/sched.h:965: if (se->sched_delayed)
kernel/sched/sched.h-966- return false;
--
kernel/sched/sched.h=2515=extern const u32 sched_prio_to_wmult[40];
--
kernel/sched/sched.h-2533- *
kernel/sched/sched.h:2534: * DELAYED - de/re-queue a sched_delayed task
kernel/sched/sched.h-2535- *
--
kernel/sched/stats.h=123=static inline void psi_enqueue(struct task_struct *p, int flags)
--
kernel/sched/stats.h-137-
kernel/sched/stats.h:138: if (p->se.sched_delayed) {
kernel/sched/stats.h-139- /* CPU migration of "sleeping" task */
]
|
| 78/2 |
2026/06/09 12:39 |
llm |
expert |
0m
Model:
Tokens:
input: 79487
output: 36
thoughts: 1324
Thoughts: **Analyzing a Scheduler Migration Bug with EEVDF and sched_delayed**
Okay, so I've been digging into this `sched_delayed` feature within the `fair` scheduler (EEVDF), and I think I've found a critical issue related to task migration. Here's what I've pieced together:
A task marked `sched_delayed` is on the runqueue, but not runnable. The `sched_core_dequeue` function, which is critical for removing a task from the `core_tree` structure, bails out early if `p->se.sched_delayed` is true. Similarly, `sched_core_enqueue` also short-circuits if the task is `sched_delayed`.
Now, here's where it gets interesting: if a task is migrated while `sched_delayed` is set, `sched_core_dequeue` *doesn't* remove it from the source runqueue (`src_rq->core_tree`), and `sched_core_enqueue` *doesn't* add it to the destination runqueue (`dst_rq->core_tree`). So, the task is *still* present in the source runqueue's `core_tree`.
But, wait, even more interestingly, while the task is still in the source runqueue's `core_tree`, the `task_cpu(p)` is updated to the destination runqueue's CPU. That means `task_rq(p)` (the runqueue the task *belongs* to) becomes the destination runqueue.
This creates a scenario where, when `pick_next_task` is called on the source runqueue, a call to `sched_core_find(src_rq, cookie)` can *still* find this migrated task, because it's still lurking in the source runqueue's `core_tree`! `src_rq->core_pick` gets set to that task, and the scheduler continues to prepare to run the "migrated" task on the *wrong* CPU.
Downstream, `hrtick_start_fair` is then called. That function explicitly checks `WARN_ON_ONCE(task_rq(p) != src_rq)`. But, the `task_rq(p)` is *now* the destination runqueue, while `src_rq` is the source one! Bingo, we've perfectly replicated the reported bug!
Let's double-check. Can we migrate a `sched_delayed` task in the first place? `can_migrate_task` function has a check:
```c
if ((p->se.sched_delayed) && (env->migration_type != migrate_load))
return 0;
```
If `env->migration_type == migrate_load`, then it *can* be migrated.
What about tasks migrated using the `proxy_migrate_task` path? Can a blocked task be `sched_delayed`? In `ttwu_runnable`:
```c
if (p->is_blocked) {
if (p->se.sched_delayed)
enqueue_task(rq, p, ENQUEUE_NOCLOCK | ENQUEUE_DELAYED);
```
So yes, blocked tasks *can* be `sched_delayed`.
So let's walk through the migration scenario: when a task is migrated, `deactivate_task` calls `dequeue_task`. If the task is `sched_delayed`, `dequeue_task` calls `sched_core_dequeue`, but as we know, it bails out early, keeping the task in `src_rq->core_tree`. Then, `set_task_cpu` changes the CPU to the destination. `activate_task` then calls `enqueue_task`, which ultimately calls `sched_core_enqueue`, but again, the task is *not* added to `dst_rq->core_tree` because of the `sched_delayed` check. So, the task is stuck in the source runqueue's `core_tree`, but has the destination runqueue assigned to it, which is the bug! It should either be moved to `dst_rq->core_tree` or removed altogether when it is `sched_delayed`.
Now, if a `sched_delayed` task is true, is it in the `core_tree`?
When a task becomes `sched_delayed`, it's dequeued with `DEQUEUE_SLEEP | DEQUEUE_DELAYED`. In `dequeue_task` it calls `sched_core_dequeue`, but returns early. Wait! Is `p->se.sched_delayed` set *before* `dequeue_task`? Let me look at `dequeue_entity` inside `fair.c`:
```
|
| 79/2 |
2026/06/09 12:40 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:2190 LineCount:50]
Results: map[Contents:2190:
2191: if (sched_core_enabled(rq))
2192: sched_core_enqueue(rq, p);
2193: }
2194:
2195: /*
2196: * Must only return false when DEQUEUE_SLEEP.
2197: */
2198: inline bool dequeue_task(struct rq *rq, struct task_struct *p, int flags)
2199: {
2200: if (sched_core_enabled(rq))
2201: sched_core_dequeue(rq, p, flags);
2202:
2203: if (!(flags & DEQUEUE_NOCLOCK))
2204: update_rq_clock(rq);
2205:
2206: if (!(flags & DEQUEUE_SAVE))
2207: sched_info_dequeue(rq, p);
2208:
2209: psi_dequeue(p, flags);
2210:
2211: /*
2212: * Must be before ->dequeue_task() because ->dequeue_task() can 'fail'
2213: * and mark the task ->sched_delayed.
2214: */
2215: uclamp_rq_dec(rq, p);
2216: return p->sched_class->dequeue_task(rq, p, flags);
2217: }
2218:
2219: void activate_task(struct rq *rq, struct task_struct *p, int flags)
2220: {
2221: if (task_on_rq_migrating(p))
2222: flags |= ENQUEUE_MIGRATED;
2223:
2224: enqueue_task(rq, p, flags);
2225:
2226: WRITE_ONCE(p->on_rq, TASK_ON_RQ_QUEUED);
2227: ASSERT_EXCLUSIVE_WRITER(p->on_rq);
2228: }
2229:
2230: void deactivate_task(struct rq *rq, struct task_struct *p, int flags)
2231: {
2232: WARN_ON_ONCE(flags & DEQUEUE_SLEEP);
2233:
2234: WRITE_ONCE(p->on_rq, TASK_ON_RQ_MIGRATING);
2235: ASSERT_EXCLUSIVE_WRITER(p->on_rq);
2236:
2237: /*
2238: * Code explicitly relies on TASK_ON_RQ_MIGRATING begin set *before*
2239: * dequeue_task() and cleared *after* enqueue_task().
]
|
| 80/2 |
2026/06/09 12:40 |
llm |
expert |
0m
Model:
Tokens:
input: 82611
output: 36
thoughts: 588
Thoughts: **Analyzing the Interaction of sched_core_dequeue and Task Deactivation**
Okay, so I'm trying to wrap my head around the timing and implications of `sched_core_dequeue` in the context of task migration and blocking. The initial observation is crucial: `sched_core_dequeue` is invoked *before* `p->sched_class->dequeue_task`. This means that when a task `p` is being dequeued, `sched_core_dequeue` gets a chance to act first.
The first condition to consider is `p->se.sched_delayed`. If this flag is already true, `sched_core_dequeue` exits early. The key is understanding when and how `p->se.sched_delayed` gets set. It looks like it is set to true in `dequeue_task_fair` when the task is delayed.
Now, here's where the confusion arises. If `sched_core_dequeue` is called *before* `dequeue_task_fair`, how can `p->se.sched_delayed` be true at that point? If it's initially FALSE, `sched_core_dequeue` will remove `p` from `core_tree`. Then, `dequeue_task_fair` sets the `sched_delayed` flag. So, at this point, `p` should no longer be in `core_tree`.
Then `p` gets migrated. `deactivate_task` gets called, which in turn calls `dequeue_task`. If `p` is *already* `sched_delayed`, it suggests it was dequeued with `DEQUEUE_SLEEP` but `dequeue_task_fair` returned `false`. This would imply that the task didn't need to be fully dequeued, but it still caused a migration.
Now, if `dequeue_task_fair` returns `false`, `dequeue_task` also returns `false`. However, the task remains on the runqueue. Later, when the task is migrated, `deactivate_task` calls `dequeue_task` with `DEQUEUE_NOCLOCK`. Because `p->se.sched_delayed` is *already* true, `sched_core_dequeue` returns early, and `p` is *not* removed from `core_tree`. That doesn't make sense. If the task became delayed after the initial dequeue, it should have been removed from the core tree.
So it's like a race condition, or something is causing me to miss something. Let me retrace from the point when `p` becomes `sched_delayed`.
1. `p` is running or runnable. It is currently in `core_tree`.
2. `p` blocks (e.g., waiting on a mutex). `__schedule` calls `deactivate_task(rq, p, DEQUEUE_SLEEP)`. Wait, it looks like `__schedule` calls `deactivate_task` directly? Or is it via `try_to_block_task`? I need to double-check this in the code. I'll check `__schedule` to see.
|
| 81/2 |
2026/06/09 12:40 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:7130 LineCount:50]
Results: map[Contents:7130: } else if (!preempt && prev_state) {
7131: /*
7132: * We pass task_is_blocked() as the should_block arg
7133: * in order to keep mutex-blocked tasks on the runqueue
7134: * for slection with proxy-exec (without proxy-exec
7135: * task_is_blocked() will always be false).
7136: */
7137: try_to_block_task(rq, prev, &prev_state,
7138: !task_is_blocked(prev));
7139: switch_count = &prev->nvcsw;
7140: }
7141:
7142: pick_again:
7143: assert_balance_callbacks_empty(rq);
7144: next = pick_next_task(rq, &rf);
7145: rq->next_class = next->sched_class;
7146: if (sched_proxy_exec()) {
7147: struct task_struct *prev_donor = rq->donor;
7148:
7149: rq_set_donor(rq, next);
7150: next->blocked_donor = NULL;
7151: if (unlikely(next->is_blocked)) {
7152: next = find_proxy_task(rq, next, &rf);
7153: if (!next) {
7154: zap_balance_callbacks(rq);
7155: goto pick_again;
7156: }
7157: if (next == rq->idle) {
7158: zap_balance_callbacks(rq);
7159: goto keep_resched;
7160: }
7161: }
7162: if (rq->donor == prev_donor && prev != next) {
7163: struct task_struct *donor = rq->donor;
7164: /*
7165: * When transitioning like:
7166: *
7167: * prev next
7168: * donor: B B
7169: * curr: A B or C
7170: *
7171: * then put_prev_set_next_task() will not have done
7172: * anything, since B == B. However, A might have
7173: * missed a RT/DL balance opportunity due to being
7174: * on_cpu.
7175: */
7176: donor->sched_class->put_prev_task(rq, donor, donor);
7177: donor->sched_class->set_next_task(rq, donor, true);
7178: }
7179: } else {
]
|
| 82/2 |
2026/06/09 12:40 |
llm |
expert |
0m
Model:
Tokens:
input: 84661
output: 36
thoughts: 19
Thoughts: Here's a summary of my immediate thought process, expressed in the first person, assuming I'm already well-versed in the code:
**Unexpected Function Call Sequence**
Wait a second... *try_to_block_task* actually calls *deactivate_task*? That's not immediately obvious from the function names. I need to understand why that would be the case. I had assumed that blocking a task was a separate logical operation. I need to re-evaluate my understanding of how these functions relate to each other. This suggests a tighter coupling than I initially thought β perhaps blocking *implicitly* involves deactivating the task's active state somehow. That potentially changes my mental model of the expected call stack, and I need to investigate the implications of that coupling to avoid unexpected behaviors.
|
| 83/2 |
2026/06/09 12:40 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:7060 LineCount:50]
Results: map[Contents:7060: * On PREEMPT_RT kernel, SM_RTLOCK_WAIT is noted
7061: * as a preemption by schedule_debug() and RCU.
7062: */
7063: bool preempt = sched_mode > SM_NONE;
7064: bool is_switch = false;
7065: unsigned long *switch_count;
7066: unsigned long prev_state;
7067: struct rq_flags rf;
7068: struct rq *rq;
7069: int cpu;
7070:
7071: /* Trace preemptions consistently with task switches */
7072: trace_sched_entry_tp(sched_mode == SM_PREEMPT);
7073:
7074: cpu = smp_processor_id();
7075: rq = cpu_rq(cpu);
7076: prev = rq->curr;
7077:
7078: schedule_debug(prev, preempt);
7079:
7080: klp_sched_try_switch(prev);
7081:
7082: local_irq_disable();
7083: rcu_note_context_switch(preempt);
7084: migrate_disable_switch(rq, prev);
7085:
7086: /*
7087: * Make sure that signal_pending_state()->signal_pending() below
7088: * can't be reordered with __set_current_state(TASK_INTERRUPTIBLE)
7089: * done by the caller to avoid the race with signal_wake_up():
7090: *
7091: * __set_current_state(@state) signal_wake_up()
7092: * schedule() set_tsk_thread_flag(p, TIF_SIGPENDING)
7093: * wake_up_state(p, state)
7094: * LOCK rq->lock LOCK p->pi_state
7095: * smp_mb__after_spinlock() smp_mb__after_spinlock()
7096: * if (signal_pending_state()) if (p->state & @state)
7097: *
7098: * Also, the membarrier system call requires a full memory barrier
7099: * after coming from user-space, before storing to rq->curr; this
7100: * barrier matches a full barrier in the proximity of the membarrier
7101: * system call exit.
7102: */
7103: rq_lock(rq, &rf);
7104: smp_mb__after_spinlock();
7105:
7106: hrtick_schedule_enter(rq);
7107:
7108: /* Promote REQ to ACT */
7109: rq->clock_update_flags <<= 1;
]
|
| 84/2 |
2026/06/09 12:40 |
llm |
expert |
0m
Model:
Tokens:
input: 85677
output: 38
thoughts: 13
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in this domain and am thinking about code execution:
**Analyzing `try_to_block_task`**
Alright, so the focus is now on `try_to_block_task`. I need to assess what's happening here. The name itself suggests a function or method designed to handle blocking operations within a task or thread, perhaps attempting to mitigate a potential stall or deadlock situation. My initial thought is to immediately consider the context: is this a concurrent environment? Am I dealing with multiple threads, asynchronous operations, or something similar? The "try" implies some form of conditional execution or error handling related to the blocking attempt. I'm already formulating questions: What resources are potentially being blocked? What happens if the blocking attempt fails? Is there a timeout mechanism involved? How is this function integrated with the broader task scheduling or event loop? Are there any specific libraries or frameworks in use that inform this blocking attempt, maybe this code is specific to the operating system or environment it's running in? Understanding the full implications of calling `try_to_block_task` demands a deep dive into its implementation and how it coordinates with other processes.
|
| 85/2 |
2026/06/09 12:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/sched/core.c Name:try_to_block_task]
Results: map[SourceCode:6685: /*
6686: * Helper function for __schedule()
6687: *
6688: * Tries to deactivate the task, unless the should_block arg
6689: * is false or if a signal is pending. In the case a signal
6690: * is pending, marks the task's __state as RUNNING (and clear
6691: * blocked_on).
6692: */
6693: static bool try_to_block_task(struct rq *rq, struct task_struct *p,
6694: unsigned long *task_state_p, bool should_block)
6695: {
6696: unsigned long task_state = *task_state_p;
6697:
6698: WARN_ON_ONCE(p->is_blocked);
6699:
6700: if (signal_pending_state(task_state, p)) {
6701: WRITE_ONCE(p->__state, TASK_RUNNING);
6702: *task_state_p = TASK_RUNNING;
6703: clear_task_blocked_on(p, NULL);
6704:
6705: return false;
6706: }
6707:
6708: p->is_blocked = 1;
6709:
6710: /*
6711: * We check should_block after signal_pending because we
6712: * will want to wake the task in that case. But if
6713: * should_block is false, its likely due to the task being
6714: * blocked on a mutex, and we want to keep it on the runqueue
6715: * to be selectable for proxy-execution.
6716: */
6717: if (!should_block)
6718: return false;
6719:
6720: block_task(rq, p, task_state);
6721: return true;
6722: }
SourceFile:kernel/sched/core.c]
|
| 86/2 |
2026/06/09 12:40 |
llm |
expert |
0m
Model:
Tokens:
input: 86560
output: 34
thoughts: 6
Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person:
**Analyzing the `block_task` Structure**
Alright, let's break this down. The core of this system seems to revolve around something I'm interpreting as a `block_task`. I'm immediately curious about its architecture, its internal states, and how it interacts with other modules or processes. Is this a single block that executes independently, or is it part of a larger workflow? What are its dependencies? I need to understand its entry points and exit conditions.
My immediate thoughts are, what is the scope of this `block_task`? Is it operating with a limited set of resources, such as specific memory regions or hardware components? Does it run on a dedicated thread, or is this managed by a scheduler? If a scheduler is involved, Iβll need to understand what its priorities are. How does this system handle any potential errors or exceptions within a `block_task`? Are there mechanisms for retry, rollbacks, or reporting failures? I need to know how these are communicated. Are these asynchronous events, or are they synchronized?
Furthermore, I have to consider the potential for concurrency. Are multiple `block_task` instances able to execute simultaneously, and if so, how is resource contention handled? Are there any locking mechanisms in place? My experience tells me that concurrency bugs can be incredibly subtle and difficult to track down. I'll be looking for any hints of race conditions or deadlocks.
Essentially, I want to understand:
1. **Isolation:** How is the `block_task` isolated from the rest of the system?
2. **Atomicity:** What guarantees exist regarding the atomicity of its operations?
3. **Correctness:** How are we ensuring the correct execution?
4. **Performance:** Is this architecture optimised for our particular needs?
I'm starting to build a mental map of this `block_task`. I need to ask the right questions to fill in the blanks, which should ultimately allow me to predict its behaviour in a wide range of situations.
|
| 87/2 |
2026/06/09 12:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/sched/core.c Name:block_task]
Results: map[SourceCode:2245: static void block_task(struct rq *rq, struct task_struct *p, unsigned long task_state)
2246: {
2247: int flags = DEQUEUE_NOCLOCK;
2248:
2249: p->sched_contributes_to_load =
2250: (task_state & TASK_UNINTERRUPTIBLE) &&
2251: !(task_state & TASK_NOLOAD) &&
2252: !(task_state & TASK_FROZEN);
2253:
2254: if (unlikely(is_special_task_state(task_state)))
2255: flags |= DEQUEUE_SPECIAL;
2256:
2257: /*
2258: * __schedule() ttwu()
2259: * prev_state = prev->state; if (p->on_rq && ...)
2260: * if (prev_state) goto out;
2261: * p->on_rq = 0; smp_acquire__after_ctrl_dep();
2262: * p->state = TASK_WAKING
2263: *
2264: * Where __schedule() and ttwu() have matching control dependencies.
2265: *
2266: * After this, schedule() must not care about p->state any more.
2267: */
2268: if (dequeue_task(rq, p, DEQUEUE_SLEEP | flags))
2269: __block_task(rq, p);
2270: }
SourceFile:kernel/sched/core.c]
|
| 88/2 |
2026/06/09 12:40 |
llm |
expert |
0m
Model:
Tokens:
input: 87508
output: 35
thoughts: 161
Thoughts: **Analyzing the Task Dequeueing Logic**
Okay, so I'm looking at this `dequeue_task` function, and it immediately catches my eye that it returns a boolean. A boolean! Specifically, the code does:
```c
if (dequeue_task(rq, p, DEQUEUE_SLEEP | flags))
__block_task(rq, p);
```
This is crucial. If `dequeue_task` returns `false`, then `__block_task` *isn't* called. That's the key. And, digging deeper, I see that `dequeue_task` itself returns `false` if the specific scheduler class's `dequeue_task` function returns `false`.
For the `fair` scheduler, this means it uses `dequeue_task_fair`. *That* function returns `false` if `delay` is true. Therefore, if the process `p` is "delayed" for whatever reason, `dequeue_task` will return `false`. Hence, `__block_task` doesn't get invoked.
Hmm, interesting. It implies that being delayed somehow prevents the task from being immediately blocked. I need to understand what `__block_task` actually *does*. This is going to be important in the big picture.
|
| 89/2 |
2026/06/09 12:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/sched/core.c Name:__block_task]
Results: map[SourceCode:3032: static inline void __block_task(struct rq *rq, struct task_struct *p)
3033: {
3034: if (p->sched_contributes_to_load)
3035: rq->nr_uninterruptible++;
3036:
3037: if (p->in_iowait) {
3038: atomic_inc(&rq->nr_iowait);
3039: delayacct_blkio_start();
3040: }
3041:
3042: ASSERT_EXCLUSIVE_WRITER(p->on_rq);
3043:
3044: /*
3045: * The moment this write goes through, ttwu() can swoop in and migrate
3046: * this task, rendering our rq->__lock ineffective.
3047: *
3048: * __schedule() try_to_wake_up()
3049: * LOCK rq->__lock LOCK p->pi_lock
3050: * pick_next_task()
3051: * pick_next_task_fair()
3052: * pick_next_entity()
3053: * dequeue_entities()
3054: * __block_task()
3055: * RELEASE p->on_rq = 0 if (p->on_rq && ...)
3056: * break;
3057: *
3058: * ACQUIRE (after ctrl-dep)
3059: *
3060: * cpu = select_task_rq();
3061: * set_task_cpu(p, cpu);
3062: * ttwu_queue()
3063: * ttwu_do_activate()
3064: * LOCK rq->__lock
3065: * activate_task()
3066: * STORE p->on_rq = 1
3067: * UNLOCK rq->__lock
3068: *
3069: * Callers must ensure to not reference @p after this -- we no longer
3070: * own it.
3071: */
3072: smp_store_release(&p->on_rq, 0);
3073: }
SourceFile:kernel/sched/sched.h]
|
| 90/2 |
2026/06/09 12:40 |
llm |
expert |
0m
Model:
Tokens:
input: 88639
output: 36
thoughts: 638
Thoughts: **Understanding Task Migration and `sched_delayed`**
Okay, so I'm digging into this code, specifically how tasks are migrated when they're in a `sched_delayed` state. The key is to understand how `__block_task` interacts with the runqueue and how `try_to_wake_up` handles things. It seems the core problem is that if `dequeue_task` fails, `__block_task` isn't called, and the task's `p->on_rq` flag remains set. This is the crux of the matter.
If `p->on_rq` is set and `try_to_wake_up` runs, it calls `ttwu_runnable`. And if a task is already on the runqueue, `ttwu_runnable` *just enqueues it again on the same runqueue*! No migration happens. I can see that clearly now. It just wakes it up in place. No cross-CPU magic. That's a critical point to understand.
So, the task is on the runqueue but scheduled as delayed. Migration needs to occur somehow. The question is: how does the task get off the CPU that it is currently on? I believe it must be `proxy_migrate_task`.
Okay, I'm pretty sure `proxy_migrate_task` is the key. It calls `deactivate_task`, which I expect will dequeue the task. But here's where it gets interesting: because the task is `sched_delayed`, a conditional inside `dequeue_task` leads to a premature return from `sched_core_dequeue`. *I'm missing something here*. The code should set `p->on_rq` to 0. Is `dequeue_task_fair` returning false?
Now, I think I've spotted the flaw in my thinking! `deactivate_task` calls `dequeue_task` with `flags = DEQUEUE_NOCLOCK`. It *doesn't* use `DEQUEUE_SLEEP`. And, that is very important. Without `DEQUEUE_SLEEP`, `dequeue_task_fair` *doesn't* return early! It proceeds normally.
Okay, I think I understand the flow. Now I have to go through `dequeue_task_fair` to make sure!
|
| 91/2 |
2026/06/09 12:41 |
tool |
read-file |
0m
Args: map[File:kernel/sched/fair.c FirstLine:8030 LineCount:50]
Results: map[Contents:8030:
8031: /*
8032: * The dequeue_task method is called before nr_running is
8033: * decreased. We remove the task from the rbtree and
8034: * update the fair scheduling stats:
8035: */
8036: static bool dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
8037: {
8038: if (task_is_throttled(p)) {
8039: dequeue_throttled_task(p, flags);
8040: return true;
8041: }
8042:
8043: if (!p->se.sched_delayed)
8044: util_est_dequeue(&rq->cfs, p);
8045:
8046: if (dequeue_entities(rq, &p->se, flags) < 0)
8047: return false;
8048:
8049: /*
8050: * Must not reference @p after dequeue_entities(DEQUEUE_DELAYED).
8051: */
8052: return true;
8053: }
8054:
8055: static inline unsigned int cfs_h_nr_delayed(struct rq *rq)
8056: {
8057: return (rq->cfs.h_nr_queued - rq->cfs.h_nr_runnable);
8058: }
8059:
8060: /* Working cpumask for: sched_balance_rq(), sched_balance_newidle(). */
8061: static DEFINE_PER_CPU(cpumask_var_t, load_balance_mask);
8062: static DEFINE_PER_CPU(cpumask_var_t, select_rq_mask);
8063: static DEFINE_PER_CPU(cpumask_var_t, should_we_balance_tmpmask);
8064:
8065: #ifdef CONFIG_NO_HZ_COMMON
8066:
8067: static struct {
8068: cpumask_var_t idle_cpus_mask;
8069: int has_blocked_load; /* Idle CPUS has blocked load */
8070: int needs_update; /* Newly idle CPUs need their next_balance collated */
8071: unsigned long next_balance; /* in jiffy units */
8072: unsigned long next_blocked; /* Next update of blocked load in jiffies */
8073: } nohz ____cacheline_aligned;
8074:
8075: #endif /* CONFIG_NO_HZ_COMMON */
8076:
8077: static unsigned long cpu_load(struct rq *rq)
8078: {
8079: return cfs_rq_load_avg(&rq->cfs);
]
|
| 92/2 |
2026/06/09 12:41 |
llm |
expert |
0m
Model:
Tokens:
input: 90598
output: 36
thoughts: 28
Thoughts: Here's a summary of my thought process, as requested:
**Deconstructing the `dequeue_entities` Return Value**
Okay, hold on a minute. Something about this is bugging me. I'm looking at the return value of `dequeue_entities` and it seems I have it wrong in my head. I thought a `false` return indicated an error. But let's clarify this. The logic suggests that returning a value *less than* zero (`< 0`) actually indicates that `dequeue_entities` has returned `false`. I need to be sure I'm interpreting this correctly because a negative return value is unusual and requires closer inspection. I had better take a look at the code for the `dequeue_entities` function itself to understand what conditions might trigger that. I'm going to quickly review its implementation to verify the specific return values and their meanings.
|
| 93/2 |
2026/06/09 12:41 |
tool |
read-file |
0m
Args: map[File:kernel/sched/fair.c FirstLine:7920 LineCount:50]
Results: map[Contents:7920: * failing half-way through and resume the dequeue later.
7921: *
7922: * Returns:
7923: * -1 - dequeue delayed
7924: * 0 - dequeue throttled
7925: * 1 - dequeue complete
7926: */
7927: static int dequeue_entities(struct rq *rq, struct sched_entity *se, int flags)
7928: {
7929: bool was_sched_idle = sched_idle_rq(rq);
7930: bool task_sleep = flags & DEQUEUE_SLEEP;
7931: bool task_delayed = flags & DEQUEUE_DELAYED;
7932: bool task_throttled = flags & DEQUEUE_THROTTLE;
7933: struct task_struct *p = NULL;
7934: int h_nr_idle = 0;
7935: int h_nr_queued = 0;
7936: int h_nr_runnable = 0;
7937: struct cfs_rq *cfs_rq;
7938: u64 slice = 0;
7939:
7940: if (entity_is_task(se)) {
7941: p = task_of(se);
7942: h_nr_queued = 1;
7943: h_nr_idle = task_has_idle_policy(p);
7944: if (task_sleep || task_delayed || !se->sched_delayed)
7945: h_nr_runnable = 1;
7946: }
7947:
7948: for_each_sched_entity(se) {
7949: cfs_rq = cfs_rq_of(se);
7950:
7951: if (!dequeue_entity(cfs_rq, se, flags)) {
7952: if (p && &p->se == se)
7953: return -1;
7954:
7955: slice = cfs_rq_min_slice(cfs_rq);
7956: break;
7957: }
7958:
7959: cfs_rq->h_nr_runnable -= h_nr_runnable;
7960: cfs_rq->h_nr_queued -= h_nr_queued;
7961: cfs_rq->h_nr_idle -= h_nr_idle;
7962:
7963: if (cfs_rq_is_idle(cfs_rq))
7964: h_nr_idle = h_nr_queued;
7965:
7966: if (throttled_hierarchy(cfs_rq) && task_throttled)
7967: record_throttle_clock(cfs_rq);
7968:
7969: /* Don't dequeue parent if it has other entities besides us */
]
|
| 94/2 |
2026/06/09 12:41 |
llm |
expert |
0m
Model:
Tokens:
input: 91655
output: 36
thoughts: 185
Thoughts: **Analyzing the `dequeue_entity` Function**
Okay, so I'm looking at this `dequeue_entity` function, and something's bugging me. Specifically, I'm trying to wrap my head around the return values in relation to delayed tasks. The code says that if `dequeue_entity` is called with the `DEQUEUE_DELAYED` flag set, it's supposed to return `false` if it delays the task, which makes sense. I see that the code includes `WARN_ON_ONCE(!se->sched_delayed);` within that block which is good, it checks to ensure the value is set correctly.
But then, it hits me. What if the `DEQUEUE_DELAYED` flag *isn't* set? The code goes into another block:
```c
if (delay) {
set_delayed(se);
// ...
return false;
}
```
Essentially, if `delay` is true *and* the `DEQUEUE_DELAYED` flag is absent, the function also returns `false`. This is where I start to see the potential for a problem... or at least a subtlety I need to unpack.
My question is, what happens if `se->sched_delayed` is already set to `true`? If we call `dequeue_entity` with that state, and the `DEQUEUE_DELAYED` flag isn't set, what's the expected behavior? Does `set_delayed` get called again? Is it idempotent? Or do we create a race condition or some other unexpected outcome? I need to clarify what the code is *supposed* to do, versus what it actually *does*.
Right, time to dive back into `fair.c` and trace the execution path. This seems like a potential corner case that needs a closer look. I'll need to understand how `se->sched_delayed` is initialized and managed.
|
| 95/2 |
2026/06/09 12:41 |
tool |
read-file |
0m
Args: map[File:kernel/sched/fair.c FirstLine:6230 LineCount:50]
Results: map[Contents:6230: }
6231: }
6232:
6233: static bool
6234: dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
6235: {
6236: bool sleep = flags & DEQUEUE_SLEEP;
6237: int action = 0;
6238:
6239: update_curr(cfs_rq);
6240: clear_buddies(cfs_rq, se);
6241:
6242: if (flags & DEQUEUE_DELAYED) {
6243: WARN_ON_ONCE(!se->sched_delayed);
6244: } else {
6245: bool delay = sleep;
6246: /*
6247: * DELAY_DEQUEUE relies on spurious wakeups, special task
6248: * states must not suffer spurious wakeups, excempt them.
6249: */
6250: if (flags & (DEQUEUE_SPECIAL | DEQUEUE_THROTTLE))
6251: delay = false;
6252:
6253: WARN_ON_ONCE(delay && se->sched_delayed);
6254:
6255: if (sched_feat(DELAY_DEQUEUE) && delay &&
6256: !entity_eligible(cfs_rq, se)) {
6257: if (entity_is_task(se))
6258: action |= UPDATE_UTIL_EST;
6259: update_load_avg(cfs_rq, se, action);
6260: update_entity_lag(cfs_rq, se);
6261: set_delayed(se);
6262: return false;
6263: }
6264: }
6265:
6266: action = UPDATE_TG;
6267: if (entity_is_task(se)) {
6268: if (task_on_rq_migrating(task_of(se)))
6269: action |= DO_DETACH;
6270:
6271: if (sleep && !(flags & DEQUEUE_DELAYED))
6272: action |= UPDATE_UTIL_EST;
6273: }
6274:
6275: /*
6276: * When dequeuing a sched_entity, we must:
6277: * - Update loads to have both entity and cfs_rq synced with now.
6278: * - For group_entity, update its runnable_weight to reflect the new
6279: * h_nr_runnable of its group cfs_rq.
]
|
| 96/2 |
2026/06/09 12:41 |
llm |
expert |
0m
Model:
Tokens:
input: 93027
output: 36
thoughts: 675
Thoughts: **Analyzing the Interaction of Task Deactivation and Delayed Scheduling**
Okay, here's what's running through my head as I dissect this code. That `WARN_ON_ONCE` caught my eye β it's suggesting a potential issue if a task is being *re*-delayed when it's already marked as delayed. The immediate question is, what happens if `se->sched_delayed` is true, but we *don't* have `DEQUEUE_DELAYED` set in the `flags`?
Following the logic, if `delay` is false (as it will be if we have `DEQUEUE_NOCLOCK` without `DEQUEUE_SLEEP`), then `dequeue_entity` proceeds without delay. This means `dequeue_entity` will return `true`, which then propagates up the call stack to `dequeue_task_fair` and eventually causes `dequeue_task` itself to return `true`, right? So, this means `dequeue_task` doesn't return `false`.
That raises a red flag! Does this mean `__block_task` is called? The puzzle piece I'm missing is how `deactivate_task` behaves. I see that `deactivate_task` calls `dequeue_task` with `DEQUEUE_NOCLOCK`. crucially, it *doesn't* include `DEQUEUE_SLEEP`. So, `dequeue_task` is called, and, as we've already deduced, returns `true`.
But wait⦠I see now that `deactivate_task` doesn't *directly* call `__block_task`! It directly calls `dequeue_task`. So the call chain is `deactivate_task` -> `dequeue_task`. I need to track what happens in the `dequeue_task` call.
Looking closer at `dequeue_task`, it first checks for `sched_core_enabled`. If so, it calls `sched_core_dequeue`. *Ah*, and there it is: `sched_core_dequeue` has a crucial early return if `p->se.sched_delayed` is true! This means that `p` *isn't* removed from `rq->core_tree` and the other functions are called.
So, in the case of `deactivate_task`, the function call chain leads to `dequeue_task_fair` being called. Because we *don't* have `DEQUEUE_SLEEP`, `delay` is false. This leads to `dequeue_entity` executing, which, from what I'm seeing, proceeds to dequeue `p` from `cfs_rq`. But⦠does it clear `p->se.sched_delayed`? I need to confirm that `dequeue_entity` function call clears this flag.
|
| 97/2 |
2026/06/09 12:41 |
tool |
read-file |
0m
Args: map[File:kernel/sched/fair.c FirstLine:6270 LineCount:50]
Results: map[Contents:6270:
6271: if (sleep && !(flags & DEQUEUE_DELAYED))
6272: action |= UPDATE_UTIL_EST;
6273: }
6274:
6275: /*
6276: * When dequeuing a sched_entity, we must:
6277: * - Update loads to have both entity and cfs_rq synced with now.
6278: * - For group_entity, update its runnable_weight to reflect the new
6279: * h_nr_runnable of its group cfs_rq.
6280: * - Subtract its previous weight from cfs_rq->load.weight.
6281: * - For group entity, update its weight to reflect the new share
6282: * of its group cfs_rq.
6283: */
6284: update_load_avg(cfs_rq, se, action);
6285: se_update_runnable(se);
6286:
6287: update_stats_dequeue_fair(cfs_rq, se, flags);
6288:
6289: update_entity_lag(cfs_rq, se);
6290: if (sched_feat(PLACE_REL_DEADLINE) && !sleep) {
6291: se->deadline -= se->vruntime;
6292: se->rel_deadline = 1;
6293: }
6294:
6295: if (se != cfs_rq->curr)
6296: __dequeue_entity(cfs_rq, se);
6297: se->on_rq = 0;
6298: account_entity_dequeue(cfs_rq, se);
6299:
6300: /* return excess runtime on last dequeue */
6301: return_cfs_rq_runtime(cfs_rq);
6302:
6303: update_cfs_group(se);
6304:
6305: if (flags & DEQUEUE_DELAYED)
6306: clear_delayed(se);
6307:
6308: if (cfs_rq->nr_queued == 0) {
6309: update_idle_cfs_rq_clock_pelt(cfs_rq);
6310: #ifdef CONFIG_CFS_BANDWIDTH
6311: if (throttled_hierarchy(cfs_rq)) {
6312: struct rq *rq = rq_of(cfs_rq);
6313:
6314: list_del_leaf_cfs_rq(cfs_rq);
6315: cfs_rq->throttled_clock_pelt = rq_clock_pelt(rq);
6316: cfs_rq->pelt_clock_throttled = 1;
6317: }
6318: #endif
6319: }
]
|
| 98/2 |
2026/06/09 12:41 |
llm |
expert |
0m
Model:
Tokens:
input: 95130
output: 37
thoughts: 1673
Thoughts: **A Deep Dive into a Scheduler Bug: Concurrent Execution and List Corruption**
Okay, so here's what I've figured out. `clear_delayed(se)` is only called when `flags & DEQUEUE_DELAYED`, but `deactivate_task` uses `DEQUEUE_NOCLOCK`, which means `se->sched_delayed` stays at `1`. `deactivate_task` returns, `set_task_cpu` changes the CPU, and then `activate_task` calls `enqueue_task`. Because the task is `sched_delayed`, `sched_core_enqueue` early-returns. This means the task isn't properly removed from the *source* core tree and isn't added to the *destination* core tree.
Hang on, can a `sched_delayed` task even be migrated? Turns out, yes. `can_migrate_task` allows it if the migration isn't a load migration. And `proxy_migrate_task` doesn't even check that, it just moves the task. So this leads to a situation where the task is in the wrong core tree on the source CPU! The system thinks this task is on the source CPU, even though it isn't.
Now, if a task is woken up from blocking, `ttwu_runnable` clears `sched_delayed` by calling `enqueue_task`. But if the task was migrated via proxy execution, it's still blocked and now sched_delayed, which is the vulnerability. So, this bug is triggered by proxy execution or load balancing. This is bad.
This is where things get really bad. The task is still in the source `core_tree` but *should* be running on the destination CPU. When the source CPU runs `pick_next_task`, it finds this "ghost" task. It calls `set_next_task_fair`, which will *run* this task on the source CPU! But at the same time, this task is on the destination's runqueue! So, we're talking about *concurrent* execution of the same task on two CPUs. This is practically a guaranteed path to memory corruption. Stack corruption, register corruption, the whole nine yards. This is *highly* exploitable.
Let's verify the list situation: when activate task enqueues, it adds `p` to `dst_rq->cfs_tasks`. But the task is also in `src_rq->core_tree`. When the source CPU does core scheduling, it finds this ghost task, sets the pick, and then calls `put_prev_set_next_task`. This will call `set_next_task_fair` on the *source* CPU, which does the following: if the task is queued, move it to the `src_rq->cfs_tasks` list. The problem is that the list entry, `se->group_node`, is linked on the destination's `cfs_tasks`. The move will corrupt the list! The `src_rq` lock is held, but the destination is not. The `cfs_tasks` of the destination runqueue is being corrupted! This is a concurrent list operation, and scheduler list corruption is, without question, exploitable.
So, this bug is definitely exploitable. Can an unprivileged user trigger this? It looks like the reproducer uses `prctl(PR_SCHED_CORE, 1, 0, PIDTYPE_PGID, 0)`. Core scheduling is available to unprivileged users if they set the cookie on their own processes. It's unprivileged. The reproducer also involves `sendmsg`, `recvmsg`, and `/dev/video4linux`, which might be to trigger mutex blocking. If the task blocks on a mutex, proxy execution might migrate the delayed task, since load balancing might migrate the delayed task. Proxy is unprivileged, load balancing is unprivileged, so this is definitely reachable. Time to verify no capabilities are needed.
|
| 99/2 |
2026/06/09 12:41 |
tool |
read-file |
0m
Error:
Args: map[File:kernel/sched/core_sched.c FirstLine:330 LineCount:50]
Results:
|
| 100/2 |
2026/06/09 12:41 |
llm |
expert |
0m
Model:
Tokens:
input: 97724
output: 37
thoughts: 22
Thoughts: Here's my summary, as if I were thinking these thoughts:
**Diving into the Core Scheduler**
Okay, so I need to understand this scheduling issue better. My first instinct, given my experience, is to zero in on the core scheduler code. Specifically, I should start by examining `kernel/sched/core_sched.c` and focus around line 250. That area usually holds crucial logic regarding core scheduling decisions, so it's a logical starting point for investigating the current problem. Time to dig in.
|
| 101/2 |
2026/06/09 12:42 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core_sched.c FirstLine:250 LineCount:50]
Results: map[Contents: 250: lockdep_assert_rq_held(rq);
251:
252: WARN_ON_ONCE(!rq->core->core_forceidle_count);
253:
254: if (rq->core->core_forceidle_start == 0)
255: return;
256:
257: delta = now - rq->core->core_forceidle_start;
258: if (unlikely((s64)delta <= 0))
259: return;
260:
261: rq->core->core_forceidle_start = now;
262:
263: if (WARN_ON_ONCE(!rq->core->core_forceidle_occupation)) {
264: /* can't be forced idle without a running task */
265: } else if (rq->core->core_forceidle_count > 1 ||
266: rq->core->core_forceidle_occupation > 1) {
267: /*
268: * For larger SMT configurations, we need to scale the charged
269: * forced idle amount since there can be more than one forced
270: * idle sibling and more than one running cookied task.
271: */
272: delta *= rq->core->core_forceidle_count;
273: delta = div_u64(delta, rq->core->core_forceidle_occupation);
274: }
275:
276: for_each_cpu(i, smt_mask) {
277: rq_i = cpu_rq(i);
278: p = rq_i->core_pick ?: rq_i->curr;
279:
280: if (p == rq_i->idle)
281: continue;
282:
283: /*
284: * Note: this will account forceidle to the current CPU, even
285: * if it comes from our SMT sibling.
286: */
287: __account_forceidle_time(p, delta);
288: }
289: }
290:
291: void __sched_core_tick(struct rq *rq)
292: {
293: if (!rq->core->core_forceidle_count)
294: return;
295:
296: if (rq != rq->core)
297: update_rq_clock(rq->core);
298:
299: __sched_core_account_forceidle(rq);
]
|
| 102/2 |
2026/06/09 12:42 |
llm |
expert |
0m
Model:
Tokens:
input: 98641
output: 28
thoughts: 30
Thoughts: Here's a summary of my immediate thought process, focusing on the key technical elements:
**Processing Kernel Scheduling Code**
Okay, so I'm thinking about where the core scheduling functionality is located. Specifically, I need to pinpoint the implementation for `PR_SCHED_CORE`. My initial thought is it's most likely handled within `kernel/sys.c` or perhaps in `kernel/sched/core.c`. I have a feeling it's one of those two locations.
|
| 103/2 |
2026/06/09 12:42 |
tool |
grepper |
0m
Args: map[Expression:PR_SCHED_CORE PathPrefix:kernel/]
Results: map[Output:kernel/Kconfig.preempt=151=config SCHED_CORE
--
kernel/Kconfig.preempt-156- selection across SMT siblings. When enabled -- see
kernel/Kconfig.preempt:157: prctl(PR_SCHED_CORE) -- task selection ensures that all SMT siblings
kernel/Kconfig.preempt-158- will execute a task from the same 'core group', forcing idle when no
--
kernel/sched/core_sched.c=123=static void __sched_core_set(struct task_struct *p, unsigned long cookie)
--
kernel/sched/core_sched.c-129-
kernel/sched/core_sched.c:130:/* Called from prctl interface: PR_SCHED_CORE */
kernel/sched/core_sched.c-131-int sched_core_share_pid(unsigned int cmd, pid_t pid, enum pid_type type,
--
kernel/sched/core_sched.c-141-
kernel/sched/core_sched.c:142: BUILD_BUG_ON(PR_SCHED_CORE_SCOPE_THREAD != PIDTYPE_PID);
kernel/sched/core_sched.c:143: BUILD_BUG_ON(PR_SCHED_CORE_SCOPE_THREAD_GROUP != PIDTYPE_TGID);
kernel/sched/core_sched.c:144: BUILD_BUG_ON(PR_SCHED_CORE_SCOPE_PROCESS_GROUP != PIDTYPE_PGID);
kernel/sched/core_sched.c-145-
kernel/sched/core_sched.c:146: if (type > PIDTYPE_PGID || cmd >= PR_SCHED_CORE_MAX || pid < 0 ||
kernel/sched/core_sched.c:147: (cmd != PR_SCHED_CORE_GET && uaddr))
kernel/sched/core_sched.c-148- return -EINVAL;
--
kernel/sched/core_sched.c-172- switch (cmd) {
kernel/sched/core_sched.c:173: case PR_SCHED_CORE_GET:
kernel/sched/core_sched.c-174- if (type != PIDTYPE_PID || uaddr & 7) {
--
kernel/sched/core_sched.c-185-
kernel/sched/core_sched.c:186: case PR_SCHED_CORE_CREATE:
kernel/sched/core_sched.c-187- cookie = sched_core_alloc_cookie();
--
kernel/sched/core_sched.c-193-
kernel/sched/core_sched.c:194: case PR_SCHED_CORE_SHARE_TO:
kernel/sched/core_sched.c-195- cookie = sched_core_clone_cookie(current);
--
kernel/sched/core_sched.c-197-
kernel/sched/core_sched.c:198: case PR_SCHED_CORE_SHARE_FROM:
kernel/sched/core_sched.c-199- if (type != PIDTYPE_PID) {
--
kernel/sys.c=2534=SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
--
kernel/sys.c-2806-#ifdef CONFIG_SCHED_CORE
kernel/sys.c:2807: case PR_SCHED_CORE:
kernel/sys.c-2808- error = sched_core_share_pid(arg2, arg3, arg4, arg5);
]
|
| 104/2 |
2026/06/09 12:42 |
llm |
expert |
0m
Model:
Tokens:
input: 99703
output: 37
thoughts: 32
Thoughts: Okay, here's my take on those thoughts, framed as a first-person expert summary:
**Digging into `sched_core_share_pid`**
Alright, let's take a look at `sched_core_share_pid` in `kernel/sched/core_sched.c`. Given the nature of core scheduling and PID sharing, I'm immediately curious about the security implications. Specifically, I need to verify whether the implementation requires specific capabilities. That's my initial question to answer: does this function introduce any potential security vulnerabilities if called by unprivileged processes, or does it correctly gate the functionality behind the necessary security checks? I'll need to carefully examine the surrounding code and any relevant data structures to confirm if it respects the prevailing privilege model.
|
| 105/2 |
2026/06/09 12:42 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core_sched.c FirstLine:150 LineCount:50]
Results: map[Contents: 150: rcu_read_lock();
151: if (pid == 0) {
152: task = current;
153: } else {
154: task = find_task_by_vpid(pid);
155: if (!task) {
156: rcu_read_unlock();
157: return -ESRCH;
158: }
159: }
160: get_task_struct(task);
161: rcu_read_unlock();
162:
163: /*
164: * Check if this process has the right to modify the specified
165: * process. Use the regular "ptrace_may_access()" checks.
166: */
167: if (!ptrace_may_access(task, PTRACE_MODE_READ_REALCREDS)) {
168: err = -EPERM;
169: goto out;
170: }
171:
172: switch (cmd) {
173: case PR_SCHED_CORE_GET:
174: if (type != PIDTYPE_PID || uaddr & 7) {
175: err = -EINVAL;
176: goto out;
177: }
178: cookie = sched_core_clone_cookie(task);
179: if (cookie) {
180: /* XXX improve ? */
181: ptr_to_hashval((void *)cookie, &id);
182: }
183: err = put_user(id, (u64 __user *)uaddr);
184: goto out;
185:
186: case PR_SCHED_CORE_CREATE:
187: cookie = sched_core_alloc_cookie();
188: if (!cookie) {
189: err = -ENOMEM;
190: goto out;
191: }
192: break;
193:
194: case PR_SCHED_CORE_SHARE_TO:
195: cookie = sched_core_clone_cookie(current);
196: break;
197:
198: case PR_SCHED_CORE_SHARE_FROM:
199: if (type != PIDTYPE_PID) {
]
|
| 106/2 |
2026/06/09 12:42 |
llm |
expert |
0m
Model:
Tokens:
input: 100616
output: 66
thoughts: 1656
Thoughts: **Analysis of a Critical Scheduler Bug**
Okay, so here's what I've got. The reproducer leverages `ptrace_may_access` with `pid=0`, which is allowed for unprivileged users, to manipulate its own process group. This lets an unprivileged user access `PR_SCHED_CORE`, which is key here. The heart of the problem lies in the scheduler core, specifically how `sched_delayed` tasks are handled during migration.
The initial thought process revealed that a `sched_delayed` task isn't properly removed from the *source* `core_tree` during migration because `sched_core_dequeue` returns early if `p->se.sched_delayed` is true. Then, it's *also* not added to the *destination* `core_tree` for the same reasonβ`sched_core_enqueue` also bails out early. So, the task is left in the source `core_tree`, but with its `task_cpu` updated to the destination CPU.
This creates a race. The source CPU can pick this task from its `core_tree`, attempt to schedule it on itself (the source CPU), but find that the `task_cpu` points to the destination CPU. This correctly triggers the `WARN_ON_ONCE` in `hrtick_start_fair`.
Furthermore, the task *also* ends up on the destination CPU's runqueue (`cfs_tasks`). Then the source CPU calls `set_next_task_fair` and does `list_move` on a list that exists on the destination CPU. This is the crucial bug; it corrupts the destination's `cfs_tasks` list because we're modifying it on another CPU without holding the correct lock. This list corruption is highly exploitable, opening the door for arbitrary memory reads, writes, and control flow hijacking.
Okay, so I did a code deep dive. `deactivate_task` calls `dequeue_task`, which *does* call `sched_core_dequeue`. *And* since `deactivate_task` does not pass `DEQUEUE_DELAYED`, the call to `clear_delayed(se)` is skipped, so `p->se.sched_delayed` remains set to true! Then `activate_task` is called, which calls `enqueue_task`, which calls `sched_core_enqueue` which also bails out early. Therefore, the task remains in the source `core_tree`, but it's not put in the destination `core_tree`. This confirms the bug mechanism.
I reviewed the code to see if `sched_delayed` could be cleared by other paths, but it looks like load balancing via `detach_task` calls `deactivate_task` and then `attach_task`, so `sched_delayed` is not cleared.
Even better, I checked `can_migrate_task`. If the `migration_type` is `migrate_load`, then a delayed task *can* be migrated! So load balancing triggers this bug! Since load balancing happens automatically in the background, this makes the bug extremely easy to trigger.
So, in summary:
* Exploitable: Yes, list corruption in the scheduler runqueue.
* Denial of Service: Yes, a crash or panic is likely.
* Accessible From Unprivileged Processes: Yes, because of `PR_SCHED_CORE`.
* Accessible From User Namespaces: Yes (follows from being unprivileged).
* Triggered: By load balancing a `sched_delayed` task. This is now confirmed. This is a very serious bug.
I will use my `set-results` tool now.
|
| 107/2 |
2026/06/09 12:42 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 108/2 |
2026/06/09 12:42 |
llm |
expert |
0m
Model:
Tokens:
input: 103186
output: 1003
thoughts: 779
Thoughts: **My Analysis of the Core Scheduling Bug**
Okay, the analysis is done, and the results are in. Now, I need to articulate this bug, its underlying mechanism, and the potential security ramifications. I'll need to be precise, so here's how I see it:
Essentially, this vulnerability arises from a subtle race condition within the EEVDF scheduler, specifically related to the migration of tasks within core scheduling groups. Let me break down the steps, as I understand them:
1. We start with a task, `p`, which belongs to a core scheduling group (`PR_SCHED_CORE`).
2. `p` enters the `sched_delayed` state. This happens in the EEVDF scheduler where a task is dequeued but remains on the runqueue, because it remains eligible to run soon.
3. The crucial part: `p` gets migrated to a new CPU. This migration can be initiated by load balancing (`migrate_load`) or through proxy execution.
4. During the migration process, `deactivate_task` calls `dequeue_task(rq, p, DEQUEUE_NOCLOCK)`.
5. The `dequeue_task` function, in turn, calls `sched_core_dequeue(rq, p)`. Here's the key: because `p->se.sched_delayed` is true, `sched_core_dequeue` *bails out early* and *doesn't* remove `p` from `rq->core_tree`. This is the first deviation from what we expect, a task should be removed from the core tree.
6. `dequeue_task_fair` *doesn't* clear `p->se.sched_delayed` because the `DEQUEUE_DELAYED` flag is not passed. This is critical, since a task in the `sched_delayed` state can be re-enqueued.
7. `set_task_cpu(p, dst_cpu)` then updates `p->cpu` to reflect its new destination CPU.
8. `activate_task` calls `enqueue_task(dst_rq, p, ENQUEUE_MIGRATED)` on the destination runqueue.
9. `enqueue_task` in its turn calls `sched_core_enqueue(dst_rq, p)`. Since `p->se.sched_delayed` is still true, `sched_core_enqueue` also returns early and *does not* add `p` to the `dst_rq->core_tree`. This is the second important piece. `p` should be in the destination runqueue's core tree, but it is not.
10. Now, the state is inconsistent: `p` is still present in the source runqueue (`src_rq->core_tree`), but its `task_cpu` points to the destination CPU (`dst_cpu`). To make matters worse, `p` *is* enqueued on `dst_rq->cfs_tasks`.
11. When `src_rq` performs core scheduling, `sched_core_find(src_rq, cookie)` finds `p` in its own core tree (`src_rq->core_tree`).
12. `src_rq` picks `p` as the next task to run and calls `set_next_task_fair(src_rq, p, true)`.
13. The problem surfaces now: `set_next_task_fair` calls `list_move(&p->se.group_node, &src_rq->cfs_tasks)`. Because `p->se.group_node` is *currently* linked in `dst_rq->cfs_tasks`, this operation concurrently modifies the `dst_rq->cfs_tasks` list *without* appropriate locking. This concurrent modification corrupts the `dst_rq->cfs_tasks` list.
14. Ultimately, `hrtick_start_fair` is called, which triggers the `WARN_ON_ONCE(task_rq(p) != rq)` warning, because `task_rq(p)` is `dst_rq` but the `rq` argument in this context is `src_rq`.
The impact is significant. This concurrent list operation results in list corruption, which is often a gold mine for attackers.
The security implications are substantial:
* **Exploitable: Yes**. The lack of proper locking in concurrent list operations on the scheduler runqueue (`cfs_tasks`), specifically when a task is migrated while in the `sched_delayed` state, opens the door to memory corruption. This can lead to arbitrary read/write, and subsequently to privilege escalation.
* **Denial of Service: Yes**. The list corruption will inevitably lead to crashes, potentially through scheduler infinite loops or
|
| 109/1 |
2026/06/09 12:43 |
action |
format-explanation |
0m
Results: map[Explanation:**Bug Analysis and Mechanism**
The warning `WARN_ON_ONCE(task_rq(p) != rq)` in `hrtick_start_fair()` is
triggered due to a critical flaw in how the scheduler handles the migration of
`sched_delayed` tasks when Core Scheduling (`CONFIG_SCHED_CORE`) is enabled.
Here is the step-by-step mechanism of the bug:
1. **Core Scheduling & Delayed Dequeue:** An unprivileged user creates a core
scheduling group using `prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, ...)`. A task
`p` in this group becomes `sched_delayed` (a feature of the EEVDF scheduler
where a task is logically dequeued but kept on the runqueue because it is still
eligible for execution).
2. **Task Migration:** The task `p` is migrated to another CPU (e.g., via
automatic load balancing `migrate_load` or proxy execution). During migration,
`deactivate_task()` calls `dequeue_task(src_rq, p, DEQUEUE_NOCLOCK)`.
3. **Core Tree Desync:** `dequeue_task()` calls `sched_core_dequeue(src_rq, p)`.
However, because `p->se.sched_delayed` is true, `sched_core_dequeue()` returns
early and **fails to remove the task from `src_rq->core_tree`**.
4. **Incomplete State Update:** `dequeue_task_fair()` does not clear the
`sched_delayed` flag because the `DEQUEUE_DELAYED` flag was not passed during
migration.
5. **Destination Enqueue Failure:** `set_task_cpu(p, dst_cpu)` updates the
task's CPU. Then, `activate_task()` calls `enqueue_task(dst_rq, p,
ENQUEUE_MIGRATED)`. `sched_core_enqueue(dst_rq, p)` is called, but again returns
early because `p->se.sched_delayed` is still true. Thus, the task is **not added
to `dst_rq->core_tree`**.
6. **The Inconsistency:** At this point, task `p` is physically enqueued on
`dst_rq->cfs_tasks`, its `task_cpu` is `dst_cpu`, but it is still lingering in
`src_rq->core_tree`.
7. **Triggering the Bug:** When `src_rq` performs core scheduling,
`sched_core_find(src_rq, cookie)` finds `p` in its `core_tree` and selects it as
the next task to run. `src_rq` then calls `set_next_task_fair(src_rq, p, true)`.
8. **The Warning:** `set_next_task_fair()` calls `hrtick_start_fair(src_rq, p)`,
which checks `WARN_ON_ONCE(task_rq(p) != rq)`. Since `task_rq(p)` is `dst_rq`
and `rq` is `src_rq`, the warning is triggered.
**Security Impact**
* **Exploitable:** **Yes.** The desynchronization leads to a severe race
condition and memory corruption. When `src_rq` picks the task and calls
`set_next_task_fair()`, it executes `list_move(&p->se.group_node,
&src_rq->cfs_tasks)`. Because `p->se.group_node` is currently linked in
`dst_rq->cfs_tasks`, this performs a concurrent list modification on `dst_rq`'s
runqueue without holding `dst_rq`'s lock. Corrupting the scheduler's core
runqueue lists (`cfs_tasks`) provides a strong primitive for arbitrary memory
read/write and control-flow hijacking (privilege escalation).
* **Denial of Service:** **Yes.** Even if not successfully exploited for
privilege escalation, the runqueue list corruption will inevitably cause the
kernel to crash (e.g., infinite loops during runqueue traversal, NULL pointer
dereferences, or `BUG()` halts), leading to a complete system denial of service.
* **Accessible From Unprivileged Processes:** **Yes.** The
`prctl(PR_SCHED_CORE)` syscall is available to unprivileged users to group their
own processes. The subsequent migration (load balancing) happens automatically
in the background. No special capabilities (like `CAP_SYS_ADMIN`) are required
to trigger this vulnerability.
* **Accessible From User Namespaces:** **Yes.** Since it is reachable by a
standard unprivileged user, it is also reachable from within user namespaces.]
|