summaryrefslogtreecommitdiff
path: root/kernel/sched/ext
AgeCommit message (Collapse)Author
23 hoursMerge branch 'for-next' of ↵Mark Brown
https://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext.git
28 hoursMerge branch into tip/master: 'sched/core'Ingo Molnar
# New commits in sched/core: 4a3b51aab6e2 ("smpboot: Don't park the thread if work is pending") 40dcc9bdbef3 ("irq_work: Flush lazy work CPU down on PREEMPT_RT") 791b1760accd ("irq_work: Update a comment regarding CPU hotplug invocation") 648d44bda731 ("sched/topology: Add asymmetric SMT packing override") c8fc4136fd3c ("sched/fair: Honor asymmetric SMT priority in idle selection") 53bc5c556b82 ("sched: Set TIF_NEED_RESCHED before calling __trace_set_need_resched()") 4b1f75be23c4 ("sched/core: Fix context analysis errors in non-preferred CPU push") 1fb28c664a19 ("virt/steal_governor: Enable the driver") 27d47ebce4d6 ("virt/steal_governor: Implement steal_governor policy loop") 4b9302d494ff ("virt/steal_governor: Add control knobs for handling steal values") 9a8e740ee9f6 ("virt: Introduce steal governor driver") 68957caaa9c0 ("sched/debug: Add migration stats due to non preferred CPUs") 74699f56ebcf ("sched/core: Push current task from non preferred CPU") 4ee29b029058 ("sched/fair: Load balance only among preferred CPUs") d8a3da0de843 ("sched/core: Try to use a preferred CPU in is_cpu_allowed") 620824516557 ("sysfs: Add preferred CPU file") 518b32bd5bb3 ("cpumask: Introduce cpu_preferred_mask") 06a49ef784ac ("sched/docs: Document cpu_preferred_mask and Preferred CPU concept") cfb463b7172d ("cpumask: Introduce cpumask_intersects_and") a8d0854a76a8 ("sched/cputime: Add kcpustat_field_total helper") be100c77178e ("sched: Add sched_ext hooks for proxy execution") 57c75e3ae38c ("sched: Add helper to block retained proxy donors") a49653d0abeb ("sched/core: Mark wakeups completed through ttwu_runnable()") 8f8c0417e973 ("sched/core: Dequeue waking proxy donors before reset") 313b652837d0 ("sched/core: Drop mutex locks before proxy rescheduling") 627ea30aca3b ("sched/wait: Clarify WF_SYNC wakeup semantics") d2e010082757 ("sched/eevdf: Handle more short slice waking cases") 4bf32ec3327d ("sched/eevdf: Align update_protect_slice to set_protect_slice") aae2a33ea662 ("sched/eevdf: Ensure that vprot will never go above a min slice") c9ce69fc43bd ("sched/fair: Randomize equally shallow slow-path candidates") abe440b3770f ("sched/fair: Drop idle recency from slow-path CPU selection") fbbc63fed0b0 ("sched/core: Remove redundant core_sched_seq") 819224e506bc ("sched/fair: Remove dead code on enqueue_task_fair()") c72945693b90 ("sched: Restart fair hrtick after same-task repicks") a9b3c7570564 ("sched/headers: Replace __ASSEMBLY__ with __ASSEMBLER__ in the <uapi/linux/sched.h> header") e81ee0630837 ("sched/fair: Reset NUMA fault locality after scan period update") ef9293b3b797 ("sched: dynamic: Fix preemption model strings") 879eaa76e608 ("sched: Remove unneeded function type cast in do_balance_callbacks()") f549101187c8 ("sched/deadline: check start_dl_timer expiry with ktime_before()") 2a672daa4b27 ("sched/feat: Use the new static key API for sched_feat") a5576ebce920 ("sched: Convert paravirt_steal to new static key APIs") 9650ce11f2e3 ("sched: dynamic: Simplify preempt model accessors") 5b9a28eeed37 ("sched: dynamic: Remove HAVE_PREEMPT_DYNAMIC_{CALL,KEY}") aa4178f63847 ("sched: dynamic: Simplify irqentry_exit_cond_resched()") b9d267b9d632 ("sched: dynamic: Simplify preempt_schedule{,_notrace}()") 88e0b3bb9930 ("sched: dynamic: Simplify {cond,might}_resched()") d3d16750693b ("sched: dynamic: Make PREEMPT_DYNAMIC depend on ARCH_HAS_PREEMPT_LAZY") 772d9ffbfd26 ("sched: Migrate whole chain in proxy_migrate_task()") 6b73a09e943f ("sched: Break out core of attach_tasks() helper into sched.h") 1f8805138593 ("sched: Switch rq->next_class in proxy_reset_donor()") 09351db90a28 ("sched/core: Don't proxy-exec unmatched cookie lock owners") 9be817f991e2 ("sched/core: Avoid migrating blocked_on tasks") 3dd95f077371 ("sched/core: Don't steal a proxy-exec donor") Signed-off-by: Ingo Molnar <mingo@kernel.org>
3 daysMerge branch 'for-7.3-fixes' into for-nextTejun Heo
3 dayssched_ext: idle: Respect task affinity in restricted CPU selectionHui Su
The restricted select_cpu kfunc path handles single-CPU and migration-disabled tasks without going through scx_select_cpu_dfl(). It currently uses prev_cpu as the candidate, even though prev_cpu is only a hint and may no longer be usable after an affinity change. With a caller-provided @allowed mask, this can also return an idle CPU outside p->cpus_ptr. Derive the candidate from task state instead: use the sole CPU in p->cpus_ptr for single-CPU tasks and task_cpu(p) for migration-disabled tasks. Validate it against p->cpus_ptr and @allowed before claiming its idle state. Since the derived candidate may be on a different NUMA node from prev_cpu, also preserve SCX_PICK_IDLE_IN_NODE by rejecting cross-node candidates. Perform all checks before scx_idle_test_and_clear_cpu(), as a successful call consumes the CPU's idle bit. Fixes: 683d2d0faba1 ("sched_ext: idle: Introduce scx_bpf_select_cpu_and()") Cc: stable@vger.kernel.org Signed-off-by: Hui Su <sh_def@163.com> Reviewed-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
3 daysMerge branch 'for-7.3-fixes' into for-nextTejun Heo
3 dayssched_ext: Fix the bypass dispatch claim for schedulers bypassed by an ancestorTejun Heo
scx_bypass() claims bypass dispatch when the scheduler's depth goes from zero to one and releases it when the depth returns to zero, but propagates only the depth to the descendants, not the claim. A descendant that starts bypassing with its ancestor never claims, and one whose own bypass ends under the ancestor's keeps its claim, as its depth does not return to zero. A sub-scheduler enable whose bypass ends under a PM suspend thus leaks its claim: the parent keeps hosting bypass dispatch and the sub-scheduler's next bypass trips the WARN_ON_ONCE() on the claim. A sub-scheduler disable that starts under a PM suspend has no claim, so once the resume lifts the root's bypass its tasks sit on the parent's bypass DSQs with nobody consuming them. Claim for every descendant whose depth goes from zero to one in the propagation and release every scheduler in the subtree whose depth returned to zero once the tasks have moved out of the bypass DSQs. A parent that is bypassing itself when a descendant claims consumes only its own bypass DSQs, empty until it leaves bypass and becomes the host. Fixes: aa2a0a19686c ("sched_ext: Separate bypass dispatch enabling from bypass depth tracking") Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Tejun Heo <tj@kernel.org> Reviewed-by: Andrea Righi <arighi@nvidia.com>
3 dayssched_ext: Propagate every bypass depth change to the descendantsTejun Heo
scx_bypass() propagates a depth change to the descendants only when the scheduler's own depth crosses zero, so an own bypass that starts under an ancestor's never reaches them. When the ancestor's bypass ends first, the descendants leave bypass while the scheduler is still bypassing: a sub-scheduler disabled under a PM suspend has its children re-queue their tasks through their own BPF schedulers at resume, mid-teardown. If the own bypass then ends, every descendant is decremented once more than incremented and dec_bypass_depth() warns, though no current caller gets there. Propagate on every call so that a descendant's depth is the sum of its own bypasses and its ancestors'. The early return follows the walk, as a descendant's depth is at least the scheduler's and nothing changes bypass state unless the scheduler's own does. Fixes: c7f0e467a27a ("sched_ext: Prepare bypass mode for hierarchical operation") Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Tejun Heo <tj@kernel.org> Reviewed-by: Andrea Righi <arighi@nvidia.com>
5 dayssched_ext: Merge branch 'for-7.3-fixes' into for-7.4Tejun Heo
Pull to receive 069403dff337 ("sched_ext: Fix use-after-free of a destroyed user DSQ by a deferred reenqueue") and point its new cancel sweep in free_dsq_irq_workfn() at dsq->pcpu_user, which 92344fad5b54 ("sched_ext: Skip per-CPU data allocation for built-in DSQs") renamed here, so that for-next builds. Signed-off-by: Tejun Heo <tj@kernel.org>
5 dayssched_ext: Fix use-after-free of a destroyed user DSQ by a deferred reenqueueTejun Heo
A deferred reenqueue can read a freed user DSQ. The request stays on its CPU's deferred list until run_deferred() takes it, and the RCU callback that frees the DSQ assumes that the grace period has flushed it, so exit_dsq() checks the per-CPU node without the deferred list lock. A grace period waits for readers in flight, not for a pending run_deferred(). A run_deferred() which starts after the grace period began can detach the request right before the callback looks and then read the freed DSQ's id. Cancel the pending requests before the grace period instead. The irq work that frees the DSQ unlinks every CPU's request under the per-CPU deferred list lock, and schedule_dsq_reenq() ignores a new request under the same lock once the id is invalid. Only a run_deferred() that started before the grace period can hold a request, and it runs with IRQs disabled, so the grace period waits for it and nothing touches the DSQ after the callback. Fixes: 84b1a0ea0b7c ("sched_ext: Implement scx_bpf_dsq_reenq() for user DSQs") Cc: stable@vger.kernel.org # v7.1+ Reported-and-tested-by: Hui Su <sh_def@163.com> Link: https://lore.kernel.org/all/20261008024753.4096008-1-sh_def@163.com/ Signed-off-by: Tejun Heo <tj@kernel.org>
5 dayssched_ext: Fix stall when a task enqueued with SCX_ENQ_LAST lands back on ↵Tejun Heo
its CPU's local DSQ With SCX_OPS_ENQ_LAST, the last runnable task on a CPU is passed to ops.enqueue() instead of being kept running. Exiting tasks without SCX_OPS_ENQ_EXITING, migration disabled tasks without SCX_OPS_ENQ_MIGRATION_DISABLED and tasks on an offline rq skip ops.enqueue() and go straight to the local DSQ. This happens in put_prev_task_scx() after the pick has settled on idle, so nothing reschedules the CPU, which idles with the task queued until an unrelated wakeup lands on it or the watchdog fires. scx_mitosis, which sets SCX_OPS_ENQ_LAST without SCX_OPS_ENQ_EXITING, hit this during mass cgroup teardown: exiting tasks stuck in exit_mmap() for over 40 seconds. A task put on the local DSQ runs, as anywhere else. If the SCX_ENQ_LAST enqueue left the task on this CPU's local DSQ, set need_resched on the task being switched in, as proxy_resched_idle() does. resched_curr() would flag the task being put instead, and __schedule() clears that right after the pick. Fixes: f0e1a0643a59 ("sched_ext: Implement BPF extensible scheduler class") Cc: stable@vger.kernel.org # v6.12+ Reported-by: Xiangyu Bu <xbu@meta.com> Link: https://github.com/sched-ext/scx/pull/3870 Signed-off-by: Tejun Heo <tj@kernel.org> Cc: Dan Schatzberg <schatzberg.dan@gmail.com>
6 dayssched_ext: Add ops.sub_child_ecaps_updated() to report a child's effective ↵Tejun Heo
cap changes A grant or revoke only records the target caps. They take effect on a cid at its next dispatch, and nothing tells the parent when. Without that the parent cannot act on a revoke: a child that ran a cid at a low cpuperf target leaves the target there when PERF is revoked, as the kernel resets targets only at root enable. Nor can the parent tell when it may schedule on the cid again. Add ops.sub_child_ecaps_updated(), delivered to the direct parent right after the child's own ops.sub_ecaps_updated() with the child's cgroup id and the same before and after caps, in the same dispatch context. Both deliveries are suppressed while the child is bypassing and replayed together afterwards. A disabled child reports nothing: ops.sub_detach() is where the parent restores what it had delegated. A sub is now bypassed before it is linked, so that the grants queued for it from ops.sub_attach() are consumed while it is bypassed and replayed to it and the parent once it is enabled. Otherwise a sync consumed before the sub's ops are registered would reach the parent but never the sub. v2: Drop the disable report with its sleep lock and nested-dispatch gate, the parent restores from ops.sub_detach() instead. Signed-off-by: Tejun Heo <tj@kernel.org> Reviewed-by: Andrea Righi <arighi@nvidia.com>
9 dayssched_ext: Add ops.sub_cid_sched_updated() to report the sched running on a cidTejun Heo
A parent sched that delegates cpus to sub-scheds has no way of telling how much each delegatee is using them. A parent that sizes its delegations by demand cannot measure it, and one that wants to hand a cid from one sub to another cannot see which sub it would take the cid from. Add ops.sub_cid_sched_updated(), invoked on a sched when what it sees running on a cid changes: NONE when no task of its subtree runs there, SELF for its own task, or a direct child's cgroup id. Changes inside a child's subtree are not reported. The op costs one pointer compare per context switch when the sched does not change, a notification reaches only the scheds that see a change, and the whole mechanism sits behind a static key that is on only while a loaded sched implements the op. After a sched's tasks are gone, its disable closes every rq session still pointing at it, so nothing refers to it once it is freed. The op is cid-form only like the other sub ops and runs with the rq lock held on the context switch path. Signed-off-by: Tejun Heo <tj@kernel.org>
9 daysMerge tag 'sched_ext-for-7.3-rc6-fixes' of ↵Linus Torvalds
git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext Pull sched_ext fixes from Tejun Heo: - Taking a CPU offline could hang, or stall until the watchdog ejected the BPF scheduler, when tasks on the dying CPU were still held by the scheduler or sitting on a user dispatch queue. Re-enqueue them onto the local queue when the runqueue goes offline so that the CPU pushes them off like the other sched classes. - The sequence number guarding against stale dispatches was per runqueue, so a task re-enqueued on another CPU could get the same number and a dispatch meant for its earlier instance was applied to the new one. Use a per-task counter. - A task dispatched to another CPU's local queue got its ops.dequeue() only when picked to run and flagged as a core-sched pick. Call it at insertion like for same-CPU dispatches. * tag 'sched_ext-for-7.3-rc6-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext: sched_ext: Generate qseq from a per-task counter selftests/sched_ext: Add a test for ops.dequeue() on remote local DSQ moves sched_ext: Call ops.dequeue() when a task arrives on a remote local DSQ sched_ext: Fix CPU hotplug hang when a dying CPU's tasks sit in the BPF scheduler
9 dayssched_ext: Clear a sub-scheduler's caps before ops.sub_detach()Tejun Heo
A sub-scheduler can leave standing state behind on the cids delegated to it, cpuperf targets being the obvious case, and the kernel does not track who wrote it. Restoring it is the parent's job and ops.sub_detach() is the place for it. Today the parent can't rely on that: the dying sub-scheduler keeps every cap until it is marked dead after ops.exit(), so its ops.exit() or a late timer can write after the parent has cleaned up. Clear every cap of the sub-scheduler before ops.sub_detach(). The pshard caps go first as a pending sync recomputes ecaps from them, and ecaps are then zeroed directly. A grant racing the clear is refused under the child's pshard lock, so nothing hands the caps back. Fixes: 86094b95efcf ("sched_ext: Add per-shard cap delegation for sub-schedulers") Signed-off-by: Tejun Heo <tj@kernel.org>
12 dayssched_ext: Generate qseq from a per-task counterKuba Piecuch
finish_dispatch() uses the qseq embedded in p->scx.ops_state to tell whether the QUEUED instance of a task it's about to claim is the one scx_bpf_dsq_insert() saw. qseq is generated from rq->scx.ops_qseq, but the counters of different rqs are independent, so if a task is dequeued and re-enqueued on a different rq between scx_bpf_dsq_insert() and finish_dispatch(), the new QUEUED instance can end up with the same qseq as the old one: CPU X CPU Z ----- ----- enqueue p on rq A, qseq = N ops.dispatch() scx_bpf_dsq_insert(p) records qseq N sched_setaffinity(p) dequeue p from rq A enqueue p on rq B, qseq = N finish_dispatch(p, N) qseq matches, p is claimed The claim itself is still atomic so the core stays consistent, but an insert issued for a previous QUEUED instance gets applied to a new one which the BPF scheduler has just received through ops.enqueue(). This breaks the guarantee that dispatches targeting a stale instance are ignored. Generate qseq from a per-task counter, p->scx.ops_qseq, instead so that consecutive QUEUED instances of a task never share a qseq regardless of which rq they're on. The counter is only updated in scx_do_enqueue_task() with the task's rq locked, so no additional synchronization is needed, and it fits in an existing hole in struct sched_ext_entity on 64bit. Remove the now unused rq->scx.ops_qseq. Never generate qseq 0. NONE and DISPATCHING don't carry a qseq, so scx_bpf_dsq_insert() on a task in either state records 0. With a per-task counter, every task's first QUEUED instance would otherwise get qseq 0 and could be claimed by such an insert. Wrap the counter where the QSEQ field wraps so that it can't reach a value that shifts to 0 on 32bit either. Fixes: f0e1a0643a59 ("sched_ext: Implement BPF extensible scheduler class") Cc: stable@vger.kernel.org # v6.12+ Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kuba Piecuch <jpiecuch@google.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-30sched_ext: Introduce scx_bpf_cgroup_nr_cpus()Andrea Righi
Hierarchical BPF schedulers may need to know how many CPUs a cgroup can run on when distributing group-wide weights. For example, fair's group share calculation scales a group's weight by the smaller of its estimated runnable task count and the number of CPUs in its effective cpuset. Without that count, a BPF scheduler can only use the machine-wide CPU count and may give a group confined to a few CPUs excessive shares. A task's effective affinity can include per-task restrictions, so it cannot reliably represent the group's cpuset. Reading the cpuset directly from BPF would require CO-RE access to private cgroup and cpuset structures, including inherited cpuset resolution, making the approach fragile across kernel versions. Add scx_bpf_cgroup_nr_cpus(), which returns the same count fair uses through cpuset_num_cpus(). The count is independent of CPU or CID numbering, so the kfunc is available to both cpu-form and cid-form schedulers from any context. Also add a compat wrapper which falls back to nr_cpu_ids on older kernels. Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-30sched_ext: Merge branch 'for-7.3-fixes' into for-7.4Tejun Heo
Pull to receive cc07fae1de5d ("selftests/sched_ext: Add a test for ops.dequeue() on remote local DSQ moves") and resolve its conflict with f73ec6791424 ("sched_ext: Add selftest for blocked donor admission") here so that for-next merges clean. Conflicts: kept both auto-test-targets entries in the selftests Makefile, where each side added one after dequeue_iter. Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-30sched_ext: Call ops.dequeue() when a task arrives on a remote local DSQKuba Piecuch
When a task in the BPF scheduler's custody is moved to another CPU's local DSQ, ops.dequeue() is only called once the task is picked for execution, from set_next_task_scx() with SCX_DEQ_CORE_SCHED_EXEC, even though no core-sched pick took place. It should instead be called without flags when the task is inserted into the destination DSQ, as it is for same-rq dispatches. move_remote_task_to_local_dsq() sets p->scx.sticky_cpu across the migration so that deactivate_task() on the source rq doesn't end custody. Since commit b75aaea24c9f ("sched_ext: Properly mark SCX-internal migrations via sticky_cpu"), enqueue_task_scx() only clears it after inserting the task, so task_leave_custody() still sees the migration in progress and skips the custody exit. Clear p->scx.sticky_cpu as soon as enqueue_task_scx() has read it, as was done before that commit. The source side is unaffected. Fixes: ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") Cc: stable@vger.kernel.org # 7.1.x: 18d62044cda7: sched_ext: Preserve rq tracking across local DSQ dispatch Cc: stable@vger.kernel.org # 7.1.x Reviewed-by: Andrea Righi <arighi@nvidia.com> Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kuba Piecuch <jpiecuch@google.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-29sched_ext: Work around pahole 1.32 dropping scx_bpf_task_set_lazy_resched() ↵Tejun Heo
from BTF With clang and pahole 1.32, x86-64 vmlinux BTF lacks scx_bpf_task_set_lazy_resched() and sched_ext fails to initialize: sched_ext: Failed to register kfunc sets (-22) clang describes the lazy argument only after the prologue moves it into a callee-saved register, which pahole 1.32 rejects as not being the argument register. Released pahole is affected whatever the eventual fix. Keep the argument on the stack with barrier_data() so that its location names no register. OPTIMIZER_HIDE_VAR() would fix x86-64 but break arm64. Fixes: f8e5a4e3f3be ("sched_ext: Add lazy preemption support") Link: https://lore.kernel.org/r/20260925213619.2187751-1-yonghong.song@linux.dev Signed-off-by: Tejun Heo <tj@kernel.org> Reviewed-by: Andrea Righi <arighi@nvidia.com>
2026-09-28sched_ext: Delegate proxy donor admission to BPF schedulersAndrea Righi
Proxy execution keeps a mutex-blocked donor runnable so that its scheduling context can execute the mutex owner. Define SCX_OPS_ENQ_BLOCKED as the admission contract for retained donors. Schedulers without the flag block EXT donors normally. Schedulers with the flag continue to own blocked donors and receive them through ops.enqueue() with SCX_ENQ_BLOCKED, allowing BPF to choose their DSQ, CPU and ordering. After BPF dispatches a donor, proxy execution may move its context to the rq of the mutex owner. This move is distinct from BPF placement: proxy_set_task_cpu() changes task_cpu() while preserving the donor's pre-proxy CPU in wake_cpu as the starting point for normal wakeup placement. A proxy-migrated donor returns through the full wakeup activation path when the mutex is released. Do not carry a retained proxy session across BPF scheduler ownership changes. Before root activation, parent-to-child takeover, rehome or punt, fully deactivate a retained donor unconditionally. The incoming scheduler then starts with clean task state and applies its admission policy the next time the task blocks. A retained donor can wake through ttwu_runnable() without another enqueue_task_scx() call. Use WF_TTWU_RQ to identify this path, where the wakeup skipped activation and enqueueing. wakeup_preempt_scx() requests dispatch reconsideration after is_blocked is cleared. A full wakeup activation has already enqueued the task and does not need another reschedule. Blocked-donor enqueueing takes precedence over the exiting and migration-disabled local-DSQ fallbacks, so that an opted-in scheduler sees every eligible donor request. Co-developed-by: John Stultz <jstultz@google.com> Signed-off-by: John Stultz <jstultz@google.com> Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-28sched_ext: Track proxy execution for NOHZ_FULLAndrea Righi
scx_can_stop_tick() currently treats any blocked rq->donor as an active proxy session. However, rq->donor can temporarily still identify an outgoing blocked task while the scheduler selects an ordinary execution context. A dependency update in that window can keep TICK_DEP_BIT_SCHED set after the context switch. Track whether proxy resolution actually selected a different execution context. Keep the tick active while that state is set instead of deriving it from a potentially stale donor. When proxy execution ends, clear the state and queue a balance callback to reevaluate the tick dependency after context_switch() updates rq->curr. This lets sched_can_stop_tick() observe the complete donor and execution selection without adding work outside the existing proxy-execution branch. Conservatively re-enable the periodic scheduler tick for the duration of proxy execution. Allowing proxy execution itself to run tickless would require making the remote NOHZ scheduler tick aware of the split between rq->curr and rq->donor, which is left as a future improvement. Perform the tick update from scx_proxy_reenqueue_retry(), which already runs after proxy resolution, so scheduler core needs no additional hook. Compile the additional runqueue state and callback out when CONFIG_NO_HZ_FULL is disabled. Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-28sched_ext: Split curr|donor references properlyAndrea Righi
With proxy execution, the task selected by the scheduler and the task physically executing can differ. A blocked mutex waiter donates its scheduling context to the lock owner: D -----------------> M -------------> O ----------------> T [donor] blocked on [mutex] owned by [owner] preempted by [task] \_________________________________^ donates scheduling context where: D = blocked donor M = mutex O = mutex owner T = competing runnable task During a proxy execution switch, D supplies the scheduling class, priority, and runtime budget, while O supplies the execution context: O is the task whose code physically executes. T is a competing runnable task which may preempt the D/O proxy execution. Consider FAIR and EXT tasks with sched_ext running in partial mode. FAIR can be replaced with a higher scheduling class such as RT or deadline without changing the class interaction described here. An active proxy pair can only have a donor in the same scheduling class as the owner or in a higher-priority class. If the owner belongs to a higher-priority class, pick_next_task() selects the owner directly before considering the donor, so find_proxy_task() is not entered for that donor. The blocked-on relationship may still exist, but it does not result in proxy execution. The relevant combinations are: 1. D is EXT, O is EXT, T is EXT D can interrupt T according to BPF scheduling policy. O executes with D's EXT priority and runtime budget, while T waits in EXT. 2. D is EXT, O is EXT, T is FAIR D is visible to the BPF scheduler, but cannot preempt T because EXT is below FAIR. Once T stops, BPF can dispatch D and O executes with D's EXT priority and runtime budget. If T becomes runnable again, it preempts the D/O proxy execution. 3. D is FAIR, O is EXT, T is EXT D preempts T as the higher-class scheduling context. O executes with D's FAIR priority and runtime budget, while T waits in EXT. D is not visible to the BPF scheduler. 4. D is FAIR, O is EXT, T is FAIR D competes with T according to its FAIR deadline. When D is selected, O executes with D's FAIR priority and runtime budget. D is not visible to the BPF scheduler. 5. D is FAIR, O is FAIR, T is EXT The D/O proxy execution has a FAIR scheduling context, so T cannot preempt it from the lower EXT class. 6. D is FAIR, O is FAIR, T is FAIR O, T, and D all have FAIR scheduling contexts. D remains runnable as a blocked proxy donor. When CFS selects D, O executes using D's FAIR scheduling context. When CFS selects O, O executes using its own FAIR context, and when CFS selects T, T executes normally. D is not visible to the BPF scheduler. Thus, sched_ext policy and accounting must generally use rq->donor, the scheduler-selected task which supplies the scheduling context, rather than rq->curr, the task whose code physically executes. Without proxy execution they are the same task. On nohz_full CPUs, keep the tick running whenever the selected donor is blocked. A selected blocked donor is necessarily awaiting proxy execution, so this avoids relying on the transient relationship between rq->curr and rq->donor during context switches. It also leaves ordinary sched_ext context switches and non-nohz_full CPUs untouched. Allowing proxy execution to run tickless remains a future improvement. Moreover, extend scx_dump_state() to report both contexts. Each CPU record now includes a donor= line. If an EXT donor differs from rq->curr, also emit its detailed task record. The existing '*' marker continues to identify rq->curr, while the donor= line identifies the otherwise unmarked donor record. Note that at this point in the series, CONFIG_SCHED_PROXY_EXEC still depends on !CONFIG_SCHED_CLASS_EXT, so proxy execution and sched_ext cannot be enabled together. The scheduling changes are therefore preparatory. A later patch removes this restriction. Co-developed-by: John Stultz <jstultz@google.com> Signed-off-by: John Stultz <jstultz@google.com> Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-28sched_ext: Handle proxy-exec races in remote DSQ transfersAndrea Righi
Without proxy execution, the DSQ lock and holding_cpu handshake ensure that a task cannot be dequeued or start running during an rq-lock handoff without clearing holding_cpu. Proxy execution is an exception: a task can start physically executing as a lock owner while its scheduling context remains on a DSQ; its on-CPU or migration-disabled state can therefore change without clearing holding_cpu. Recheck these states after acquiring the source rq lock. If the transfer can no longer proceed, park the task on the source rq's reject DSQ and reenqueue it through its owning scheduler. This preserves the BPF scheduler's placement policy and keeps descendant tasks within their sub-scheduler's cap grants. Queue a deferred drain for every rejection. If a drain finds a proxy-rejected task still running or donating, leave it parked and record a retry request on the rq. scx_proxy_reenqueue_retry() consumes the request and schedules another drain when proxy state changes. Without this change and proxy execution enabled, stress-ng --pipeherd can trigger this race and migrate an active execution context, leading to sleeping-while-atomic warnings and subsequent lockdep corruption. This is a preparatory change to support proxy execution with sched_ext. Suggested-by: Tejun Heo <tj@kernel.org> Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-28sched_ext: Generalize the reject DSQ reenqueue pathAndrea Righi
The reject DSQ is currently specific to sub-scheduler cap failures. Its storage and initialization depend on CONFIG_EXT_SUB_SCHED, and the drain path assumes every rejected task was rejected for SCX_TASK_REENQ_CAP. Other transient placement failures need the same ability to park a task on its source rq and return it to the owning BPF scheduler. Make the reject DSQ unconditional and carry the reenqueue reason directly in p->scx.flags from the rejection site. A reenqueue reason remains valid while ops.enqueue() runs so that BPF can inspect it. Clear it immediately after the callback returns and before resolving a direct dispatch. Paths which bypass ops.enqueue() clear the reason before the kernel-selected placement. A rejection from the new placement can therefore install its reason into a clear field. Route all paths through a common cleanup point to enforce this ordering. This is a preparatory change to support proxy execution with sched_ext. Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-28sched_ext: Move reject DSQ draining into coreAndrea Righi
The reject DSQ drain will be generalized for placement failures outside sub-scheduler capability rejection. Move the drain into the core sched_ext implementation first so the subsequent behavioral changes can be reviewed separately. No functional change. Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-28sched_ext: Fix ops.running/stopping() pairing for proxy-exec donorsAndrea Righi
With proxy execution, pick_next_task() can select a blocked task as the scheduling context before find_proxy_task() resolves the execution context. From the BPF scheduler perspective, that donor is running while its scheduling context drives the lock owner; ops.tick() and other accounting must therefore remain enclosed by a matching ops.running()/ops.stopping() session. In this scenario, the session boundaries do not always match physical task switches. Keep the "running" session open when the same donor continues on the same CPU. When proxy execution migrates a donor's scheduling context to another CPU, end its running session on the source CPU and start a new session on the destination CPU only after proxy resolution succeeds. This prevents a failed resolution from exposing a provisional ops.running() event. Track these sessions with a new SCX_TASK_RUN_TRACKED flag. The explicit running-state tracking is also required by later donor-based accounting: it prevents an EXT owner executing for a non-EXT donor from being treated as the active EXT scheduling context when it is dequeued. This is a preparatory change for enabling proxy execution together with sched_ext. Acked-by: John Stultz <jstultz@google.com> Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-28sched_ext: Block proxy donors before taking controlAndrea Righi
Proxy execution retains mutex-blocked donors on the runqueue so their scheduling context can execute a lock owner. sched_ext cannot safely inherit such donors unless the BPF scheduler explicitly participates in their admission and ordering. Make sched_ext reject retained donors by default. Implement scx_allow_proxy_exec() to force blocked EXT tasks through the regular block path in schedule(). A donor retained by another scheduling class may already be queued when sched_ext is enabled or when a task switches to SCHED_EXT. Fully block the donor before sched_change_begin() records its queued state. This prevents sched_ext from importing a proxy session which the BPF scheduler did not admit, while leaving ordinary FAIR, RT and DL class transitions untouched. This is a preparatory change to support proxy execution with sched_ext. Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-28sched_ext: Merge branch 'sched/core' of ↵Tejun Heo
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip into for-7.4 Pull to receive: 313b652837d0 ("sched/core: Drop mutex locks before proxy rescheduling") 8f8c0417e973 ("sched/core: Dequeue waking proxy donors before reset") a49653d0abeb ("sched/core: Mark wakeups completed through ttwu_runnable()") 57c75e3ae38c ("sched: Add helper to block retained proxy donors") be100c77178e ("sched: Add sched_ext hooks for proxy execution") for the upcoming sched_ext side of proxy execution support. Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-28sched_ext: cid: Represent clusters explicitlyAndrea Righi
The sched_ext CID topology gives each core, LLC and NUMA node a contiguous CID range. Without cluster entries, schedulers cannot obtain their CID range or global index. Walk cores by cache-sharing cluster and add cluster_cid and cluster_idx to scx_cid_topo. Treat the level inclusively: a core without a wider cluster forms one of its own, while an LLC-wide cache-sharing group forms one cluster. This gives every cluster a contiguous CID range and a dense index. The walk now takes cores in cluster order rather than LLC order. Shards are still cut on core boundaries, so a shard doesn't need to contain a whole cluster; only core, cluster, LLC and node are nested, in that order. The level follows whatever the arch reports through topology_cluster_cpumask(), which on x86 is the L2 sharing mask and is populated independently of CONFIG_SCHED_CLUSTER. Tested on a 13th Gen Intel Core i7-13800H: six SMT P-cores occupy CPUs 0-11, eight E-cores occupy CPUs 12-19 in two four-core L2 modules, and all 20 CPUs share one LLC; the P-core pairs form two-CID clusters, the E-core modules form four-CID clusters, and cluster_idx is dense across all eight clusters. Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-28sched_ext: Fix CPU hotplug hang when a dying CPU's tasks sit in the BPF ↵Tejun Heo
scheduler A CPU going down has to empty its own rq. Its hotplug thread waits in sched_cpu_wait_empty() until nothing else is left, and the only thing that wakes it is balance_push(), which runs from __schedule() on the dying CPU and pushes off the migratable tasks the CPU picks. sched_ext breaks this. A task sitting on a user DSQ or held by the BPF scheduler is still counted on its rq, but an inactive CPU no longer calls ops.dispatch() and can't pull the task back. The dying CPU goes idle with the task still counted, and one of two things happens: - Another CPU consumes the task. The rq empties without a __schedule() on the dying CPU, so the hotplug thread is never woken and cpu_down() hangs holding cpu_hotplug_lock. - The task is affine only to the dying CPU. Nothing can move it, and the offline stalls until the watchdog ejects the BPF scheduler. When the rq goes offline, re-enqueue every task on it that isn't already on the local DSQ. An enqueue on an offline rq lands on the local DSQ, so the dying CPU itself picks the tasks and balance_push() pushes them off, the same as for the other sched classes. From then on no sched_ext path on another CPU can pull a task off the rq. ops.dispatch() currently stops as soon as the CPU goes inactive, and CPU hotplug then waits for an RCU grace period before taking the rq offline. A BPF-held task affine only to the dying CPU and preempted inside an RCU read-side critical section would block that grace period, and the rq would never go offline. Test SCX_RQ_ONLINE directly so that ops.dispatch() keeps running until the rq goes offline. Only the dying CPU's own hotplug thread clears the flag during teardown, and task_can_run_on_remote_rq() still keeps other rqs' tasks off the CPU. Fixes: f0e1a0643a59 ("sched_ext: Implement BPF extensible scheduler class") Fixes: 991ef53a4832 ("sched_ext: Make scx_rq_online() also test cpu_active() in addition to SCX_RQ_ONLINE") Cc: stable@vger.kernel.org # v6.12+ Reported-by: Alap Mohan <mohaalap@meta.com> Reported-by: Joonwoo Park <joonwoo@meta.com> Signed-off-by: Tejun Heo <tj@kernel.org> Reviewed-by: Andrea Righi <arighi@nvidia.com>
2026-09-28sched: Add sched_ext hooks for proxy executionAndrea Righi
Proxy execution splits the scheduling context (the donor) from the execution context (the lock owner). sched_ext needs to observe that split at three points in __schedule(): - whether a blocked EXT task can be retained on the runqueue as a donor, - when a donor's scheduling context starts driving a lock owner, - after proxy resolution, when deferred reenqueue work that was blocked by an active proxy relationship can be retried. Introduce scx_allow_proxy_exec(), scx_proxy_donor_start() and scx_proxy_reenqueue_retry(), and add their call sites in __schedule(). The implementations are empty here and are filled in by the sched_ext changes that follow, so that all the sched core changes needed by proxy execution stay together in the preparatory patches. SCHED_PROXY_EXEC still depends on !SCHED_CLASS_EXT, so the new hooks are inert: they are compiled out with CONFIG_SCHED_CLASS_EXT=n and unreachable otherwise. This is a preparatory change to support proxy execution with sched_ext. No functional change. Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Link: https://patch.msgid.link/20260922165445.943315-6-arighi@nvidia.com
2026-09-27Merge tag 'sched_ext-for-7.3-rc4-fixes-2' of ↵Linus Torvalds
git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext Pull sched_ext fix from Tejun Heo: - The CPU topology helper for BPF schedulers took no buffer size, so its structure couldn't grow without breaking schedulers built against the older layout. Add a size argument. * tag 'sched_ext-for-7.3-rc4-fixes-2' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext: sched_ext: Add a size argument to scx_bpf_cid_topo() so struct scx_cid_topo can grow
2026-09-26sched_ext: Merge branch 'for-7.3-fixes' into for-7.4Tejun Heo
Pull to receive 94480606a677 ("sched_ext: Add a size argument to scx_bpf_cid_topo() so struct scx_cid_topo can grow") for the upcoming cluster level. Conflicts: kept both declarations where 5cadf7545cc6 ("sched_ext: Add a CID NUMA node lookup kfunc") added one next to the changed scx_bpf_cid_topo() line, and pointed 29871903f3a3 ("sched_ext: Don't deliver duplicate ops.cgroup_set_idle() for same value") at the renamed tg->scx.sched_idle. Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-26sched_ext: Add a size argument to scx_bpf_cid_topo() so struct scx_cid_topo ↵Tejun Heo
can grow scx_bpf_cid_topo() copies struct scx_cid_topo into a buffer the BPF program sized from its own vmlinux.h while the verifier sizes the write from the running kernel's BTF. The struct may grow and each growth then breaks every scheduler built against the older layout, rejected at load or written past its buffer. This is the usual hole for a struct handed to BPF, closed elsewhere with a size argument, and it was missed here. Take the buffer size, copy the smaller of it and the kernel's struct and set the rest to -1. Accesses to the copy are CO-RE relocated, so the struct can grow by appending fields, which its comment now states. The kfunc changes in place: the cid interface is still being finalized and no released scheduler uses the current form. Fixes: e9b55af47edf ("sched_ext: Add topological CPU IDs (cids)") Cc: stable@vger.kernel.org # v7.2+ Signed-off-by: Tejun Heo <tj@kernel.org> Reviewed-by: Andrea Righi <arighi@nvidia.com>
2026-09-26sched_ext: Add a CID NUMA node lookup kfuncAndrea Righi
CID-form schedulers may need the physical NUMA node for a CID, for example to allocate arena memory on that node. The CID topology's node index is not a physical NUMA node ID, and scx_bpf_cpu_node() is restricted to CPU-form schedulers. Add scx_bpf_cid_node() to return the physical NUMA node for a CID and provide a compatibility helper that returns NUMA_NO_NODE on kernels without this kfunc. Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-25sched: Restart fair hrtick after same-task repicksShubhang Kaushik (Ampere)
Fair hrticks are one-shot timers. A same-task repick after hrtick expiry skips set_next_task_fair() and leaves no hrtick armed for the next fair preemption point: hrtick() -> task_tick_fair(..., queued=1) -> entity_tick() -> resched_curr() -> schedule() -> pick_task_fair() selects current -> put_prev_set_next_task(rq, prev, next) -> next == prev -> return Introduce SNT_REPICK for the next == prev path: put_prev_set_next_task(rq, prev, next) -> next == prev -> next->sched_class->set_next_task(rq, next, SNT_REPICK) -> set_next_task_fair() -> hrtick_start_fair() set_next_task_fair() skips task-transition work for SNT_REPICK and restarts the fair hrtick. hrtick_start() records the delay during schedule(), and hrtick_schedule_exit() rearms the timer. Do not restart the SCHED_DEADLINE hrtick for SNT_REPICK. Same-task repicks skip put_prev_task() for both fair and SCHED_DEADLINE, but pick_task_fair() refreshes the selected fair entity before selecting it again. hrtick_start_fair() reads that refreshed entity, including with group scheduling. The SCHED_DEADLINE picker has no corresponding current-runtime update. Restarting its hrtick on SNT_REPICK could therefore use stale dl_se->runtime and delay the next runtime enforcement point. Suggested-by: Peter Zijlstra (Intel) <peterz@infradead.org> Signed-off-by: Shubhang Kaushik (Ampere) <sh@gentwo.org> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Reviewed-by: Zhan Xusheng <zhanxusheng@xiaomi.com> Link: https://patch.msgid.link/20260917-sched-fair-hrtick-restart-v4-1-4dd1414da81a@gentwo.org
2026-09-24Merge tag 'sched_ext-for-7.3-rc4-fixes' of ↵Linus Torvalds
git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext Pull sched_ext fixes from Tejun Heo: - A task reenqueued while its dispatch was still completing had its queued state clobbered by the dispatcher, dropping every later dispatch of the task. Wait for the in-flight dispatch to settle first - A wakeup activation on another CPU marked the destination runqueue as mid-wakeup, stranding a pending local reenqueue. If the scheduler was unloaded first, the stale request pointed into freed memory that the next scheduler dereferenced - ops.dequeue() ran with the source dispatch queue's lock held, so a scheduler iterating that queue from the callback deadlocked the CPU - Schedulers with their own CPU ID mapping had no way to learn a task's initial CPU mask and rebuilt it themselves, which went wrong across sub-scheduler enable and re-home. Pass it to ops.enable() - A bypass dispatch event counter missed the dispatches made by the end-of-dispatch fallback and under-reported - Selftests for the dequeue locking and initial mask changes * tag 'sched_ext-for-7.3-rc4-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext: sched_ext: Count SCX_EV_SUB_BYPASS_DISPATCH in the dispatch fallback selftests/sched_ext: Check the cmask cid-form ops.enable() receives sched_ext: Pass the initial cmask to cid-form ops.enable() selftests/sched_ext: Test that ops.dequeue() can iterate the consumed DSQ sched_ext: Don't run ops.dequeue() with a DSQ lock held sched_ext: Derive SCX_RQ_IN_WAKEUP from the core enqueue flags sched_ext: Wait for SCX_OPSS_DISPATCHING before reenqueueing a task
2026-09-24sched_ext: Test scx_has_subs() inline before calling sub-sched hooksUsama Arif
scx_reenq_reject(), scx_process_sync_ecaps() and scx_resolve_local_dsq() return early if there are no sub-schedulers, but clang emits the full prologue before the static branch. Without sub-schedulers, we still pay for a call and for saving and restoring up to six registers, on every wakeup, dispatch and local DSQ insert. Let's move the scx_has_subs() test into inline wrappers, so that these calls go away completely. The wrappers keep lockdep_assert_rq_held(). In a pinned perf bench sched pipe test on x86-64 with clang 22, this reduced kernel instructions by 52-54 per context switch with scx_simple, scx_lavd and scx_layered. No functional change intended. Signed-off-by: Usama Arif <usama.arif@linux.dev> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-24sched_ext: Update scx_dispatch_dequeue() commentsUsama Arif
Remote consumption now reaches the !dsq branch with holding_cpu set, just like dispatch_to_local_dsq(). Clearing holding_cpu in this branch also tells dispatch_to_local_dsq() that it lost to a dequeue. The dsq-present branch races with unlink_dsq_and_switch_rq_lock(), not dispatch_to_local_dsq(). Update both comments to describe the current transfer paths and race semantics. No functional change. Suggested-by: Tejun Heo <tj@kernel.org> Signed-off-by: Usama Arif <usama.arif@linux.dev> Reviewed-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-23sched_ext: Avoid relocking DSQ during remote DSQ movesUsama Arif
When scx_bpf_dsq_move*() moves a task from a non-local DSQ to a remote local DSQ, move_task_between_dsqs() drops src_dsq->lock while leaving p->scx.dsq set. The following deactivate_task() then reacquires src_dsq->lock in scx_dispatch_dequeue() to unlink the task and clear p->scx.dsq. Both the source rq and src_dsq are already locked before the source DSQ lock is dropped. Call dispatch_dequeue_locked() there so that deactivate_task() takes the !dsq path and avoids the redundant lock acquisition. Suggested-by: Tejun Heo <tj@kernel.org> Signed-off-by: Usama Arif <usama.arif@linux.dev> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-23sched_ext: Avoid relocking DSQ during remote consumptionUsama Arif
A remote consume unlinks a task from a non-local DSQ before switching to the task's rq. Leaving p->scx.dsq set makes the following deactivate_task() reacquire the DSQ lock in scx_dispatch_dequeue() to clear the transfer state. Keep p->scx.dsq set while switching rq locks so that a racing dequeue can find the DSQ and cancel the transfer. After acquiring the source rq and confirming ownership, clear p->scx.dsq. The subsequent dequeue then takes its !dsq path without reacquiring the DSQ lock. Fleet profiles show that roughly 70% of the cycles attributed to scx_dispatch_dequeue() are spent in queued_spin_lock_slowpath() while taking dsq->lock. Signed-off-by: Usama Arif <usama.arif@linux.dev> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-23sched_ext: Count SCX_EV_SUB_BYPASS_DISPATCH in the dispatch fallbackLiang Luo
When a descendant scheduler enters bypass mode, its tasks are parked in the bypass DSQs of the nearest non-bypassing ancestor, which is then responsible for running them. On behalf of such a non-bypassing host, scx_dispatch_sched() consumes those bypass DSQs from two places: the attempt made every SCX_BYPASS_HOST_NTH dispatches, and the end-of-dispatch fallback that keeps the CPU from going idle while bypassed descendants still have tasks queued. The former increments SCX_EV_SUB_BYPASS_DISPATCH but the latter does not, even though both perform the same scx_consume_dispatch_q() on the same bypass DSQ. The descendant bypass dispatches done by the fallback are therefore missing from the counter exposed via sysfs, scx_dump_state() and the scx_bpf_events() kfunc, which under-reports the actual number of such dispatches. Add the missing __scx_add_event() so the fallback counts them too. When @sch itself is bypassing, scx_dispatch_sched() takes the earlier self-bypass branch and returns before reaching these host paths; that mode is accounted for by SCX_EV_BYPASS_DISPATCH at enqueue time and is intentionally left unchanged. Fixes: 025b1bd41965 ("sched_ext: Implement hierarchical bypass mode") Signed-off-by: Liang Luo <luoliang@kylinos.cn> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-21sched_ext: Specialize the scheduler hashtable compareUsama Arif
scx_sched_hash_params does not provide an object comparison function, so rhashtable falls back to rhashtable_compare(). Although the sub-cgroup ID key is one naturally aligned u64, the generic comparison reads the key offset and length from the table parameters at runtime and emits an out-of-line memcmp() for each element walked. Supply an obj_cmpfn so lookups specialize to a single equality test against ops.sub_cgroup_id. ext.c and sub.c are included in the same build_policy.c translation unit, so the compiler can fold the const callback into scx_find_sub_sched() even though its source is in sub.c. The result is only tested against zero, so the ordering provided by memcmp() is not observable. No functional change intended. Suggested-by: Tejun Heo <tj@kernel.org> Signed-off-by: Usama Arif <usama.arif@linux.dev> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-21sched_ext: Specialize the TID hashtable compareUsama Arif
scx_tid_hash_params does not provide an object comparison function, so rhashtable falls back to rhashtable_compare(). Although the key is one naturally aligned u64, the generic comparison reads the key offset and length from the table parameters at runtime and emits an out-of-line memcmp() for each element walked. scx_bpf_tid_to_task() can sit on hot scheduling paths. Supply an obj_cmpfn so the const parameters passed into the inlined rhashtable lookup specialize the comparison to a single equality test against scx->tid. The result is only tested against zero, so the ordering provided by memcmp() is not observable. No functional change intended. Suggested-by: Tejun Heo <tj@kernel.org> Signed-off-by: Usama Arif <usama.arif@linux.dev> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-21sched_ext: Specialize the DSQ hashtable compareUsama Arif
rhashtable is a generic container: it does not know what a key is, so it carries the key's shape as data. dsq_hash_params declares key_len, key_offset and head_offset and nothing else, so lookups fall back to rhashtable_compare(), which reads both the offset and the length back out of ht->p at runtime: memcmp(ptr + ht->p.key_offset, arg->key, ht->p.key_len) The key is one naturally aligned u64, but neither the offset nor the length is a compile-time constant there, so the compiler cannot narrow the call and emits an out-of-line memcmp() for every element walked - two loads, a call and a length dispatch to compare eight bytes. The interpretation costs more than the comparison it is interpreting. find_user_dsq() sits on the __schedule() path, and scx_layered calls scx_bpf_dsq_nr_queued() once per layer per dispatch decision. On Meta's fleet the lookup has a significant cost and shows up in fleet wide profile. Let's supply an obj_cmpfn. dsq_hash_params is a const object passed by value into the __always_inline __rhashtable_lookup(), so params.obj_cmpfn is a compile-time constant, the ternary that selects it folds away and the callback inlines: the compare becomes a single cmp against dsq->id. Every consumer of the result only tests it against zero, so the ordering memcmp() also carries is never observed. No functional change intended. An in-kernel A/B over the DSQ ids scx_layered creates, both parameter sets compiled into the same kernel, makes the lookup 2.9x faster. Signed-off-by: Usama Arif <usama.arif@linux.dev> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-19sched_ext: Pass the initial cmask to cid-form ops.enable()Tejun Heo
The cid-form API has an obvious hole. A task's cid mask is only visible through ops.set_cmask(), which fires on affinity changes and class switches but not when a task enters a scheduler through fork, sub-sched enable or re-home, and there is no p->cpus_ptr equivalent to fall back on. Schedulers work around it by seeding the mask in ops.init_task() from p->cpus_ptr cid by cid, which is subtly wrong: on sub-sched enable and re-home, an affinity change between init_task() and enable() is delivered to the sched the task is still on, and nothing corrects the new sched's copy afterwards. Fix it by adding struct scx_enable_args to cid-form ops.enable() carrying the task's cmask, built in the per-cpu scratch under the rq lock as the task enters the scheduler, and calling set_cmask() with the same mask right after enable(), ahead of set_weight(). A scheduler can then track affinity in set_cmask() alone, and scx_qmap drops its init_task() seed. set_cmask() no longer fires for a cid-form task before it is enabled, and the class-switch republish in switching_to_scx() is limited to the cpu form. This changes the cid-form ops.enable() signature, which is fine as the cid-form API is still considered unreleased. An args struct rather than a bare cmask argument leaves room for more initial state without another signature change, and the cmask travels as a plain arena address because BTF can't type arena struct members yet. v2: The cmask travels as a u64 arena address, cmask_arena_addr, instead of a kernel-typed pointer, with the typing limitation and the planned typed alias documented (Sashiko review). v3: The initial set_cmask() is delivered before set_weight() so that the mask is in place when weight-dependent state is derived (Andrea Righi). Selftest added. Signed-off-by: Tejun Heo <tj@kernel.org> Reviewed-by: Andrea Righi <arighi@nvidia.com>
2026-09-18sched_ext: Add lazy preemption supportAndrea Righi
The fair scheduling class can request lazy rescheduling, deferring an in-kernel scheduling boundary until returning to user space or until the next scheduler tick. sched_ext only exposes immediate preemption, preventing BPF schedulers from making the same trade-off. Add SCX_ENQ_PREEMPT_LAZY and SCX_KICK_PREEMPT_LAZY. Both expire the current sched_ext task slice but request lazy rescheduling. Immediate preemption, WAIT and plain kicks take precedence when requests are combined, while a lazy enqueue to a non-local DSQ retains the head-insertion semantics of SCX_ENQ_PREEMPT. Add SCX_OPS_LAZY_RESCHED as the default slice-expiry policy for newly enabled tasks and initialize it before ops.enable(). Add scx_bpf_task_set_lazy_resched() so the owning scheduler can override the persistent policy per task from any callback while preserving sub-scheduler task ownership boundaries. Bypass continues to force immediate rescheduling. For an infinite-slice task whose tick is stopped, set the lazy reschedule request before restoring the scheduler tick dependency. This lets the dependency update's IPI serve the request on return to user space and guarantees that the next tick promotes it otherwise. Use pinned rq locking for remote kick delivery so RQCF_UPDATED reflects clock updates since lock acquisition. Accumulate kick requests independently and resolve precedence while holding the target rq lock. Reject invalid SCX_KICK_IDLE combinations and unknown kick flags, which were previously silently ignored. Tested-by: Tao Cui <cuitao@kylinos.cn> Signed-off-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-17sched_ext: Don't run ops.dequeue() with a DSQ lock heldfangqiurong
ops.dequeue() is invoked with the source user DSQ's lock still held on the consume and move paths (scx_consume_dispatch_q(), move_task_between_dsqs()). A BPF scheduler which locks the source user DSQ from ops.dequeue() - e.g. by iterating it with bpf_iter_scx_dsq - self-deadlocks. ops.dequeue() can only call the "any" kfuncs and none of them can lock a builtin DSQ, so the global and bypass paths can't deadlock; however, all DSQ locks share one lockdep class, so iterating any user DSQ from ops.dequeue() on those paths trips the recursion check. Move the invocation after the DSQ unlock on all three paths. SCX_TASK_IN_CUSTODY is cleared under the lock serializing the transfer so that the callback is invoked exactly once. Fixes: ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") Cc: stable@vger.kernel.org # v7.1+ Acked-by: Andrea Righi <arighi@nvidia.com> Signed-off-by: fangqiurong <fangqiurong@kylinos.cn> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-09-17sched_ext: Derive SCX_RQ_IN_WAKEUP from the core enqueue flagsTejun Heo
schedule_deferred_locked() skips scheduling a deferred action while SCX_RQ_IN_WAKEUP is set and relies on the task_woken_scx() call that follows a wakeup enqueue to run it. enqueue_task_scx() sets the flag from the merged enqueue flags, which include the flags stashed for a remote activation. move_remote_task_to_local_dsq() thus sets SCX_RQ_IN_WAKEUP on the destination rq when the moved task was woken up, although no task_woken_scx() follows that activation. An IMMED insert into a busy destination requests a local reenqueue during that enqueue. The request gets linked but not scheduled and stays pending until an unrelated wakeup or preemption on that CPU runs the deferred actions. The IMMED task sits behind the running task in the meantime. If nothing runs them before the scheduler is disabled, the request outlives the scheduler and points into its freed per-cpu area, which the next scheduler dereferences from run_deferred(). Test the core enqueue flags for the wakeup bit. Only the core's wakeup path is followed by task_woken_scx(). Fixes: 57ccf5ccdc56 ("sched_ext: Fix enqueue_task_scx() truncation of upper enqueue flags") Cc: stable@vger.kernel.org # v7.1+ Reported-by: Andrea Righi <arighi@nvidia.com> Link: https://lore.kernel.org/all/20260916145807.3250167-1-arighi@nvidia.com/ Signed-off-by: Tejun Heo <tj@kernel.org> Reviewed-by: Andrea Righi <arighi@nvidia.com>
2026-09-15sched_ext: Wait for SCX_OPSS_DISPATCHING before reenqueueing a taskTejun Heo
ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") moved the final ops_state store in scx_dispatch_enqueue() after the DSQ unlock so that the custody update and ops.dequeue() precede it. A task can thus be found on a DSQ while still SCX_OPSS_DISPATCHING. The dequeue and core-sched pick paths wait for the state to clear in ops_dequeue() but the reenqueue paths don't. A reenqueue in that window runs ops.enqueue() and sets SCX_OPSS_QUEUED before the dispatch has completed. The dispatcher's final store then overwrites it with SCX_OPSS_NONE and finish_dispatch() drops every later dispatch of the task. Wait for SCX_OPSS_DISPATCHING to clear before dequeueing a task for reenqueue, the same way ops_dequeue() does. Fixes: ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Tejun Heo <tj@kernel.org> Reviewed-by: Andrea Righi <arighi@nvidia.com>