2681 lines · 78.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst * * Sub-scheduler hierarchy support. * * A sub-scheduler is an scx_sched attached to a cgroup subtree under another * scx_sched. This file holds the sub-scheduler implementation: the scheduler * tree walk, capability delegation, per-shard cap state and its sync, and the * sub-scheduler enable/disable paths. The core dispatch/enqueue machinery it * builds on lives in ext.c. * * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. * Copyright (c) 2026 Tejun Heo <tj@kernel.org> */#include <linux/rhashtable.h>#include "internal.h"#include "cid.h"#include "arena.h"#include "sub.h"#include "inlines.h" #ifdef CONFIG_EXT_SUB_SCHED /* * On while any sub-scheduler exists so that a root-only system doesn't pay for * the sub-sched portions of hot paths. See scx_has_subs(). */DEFINE_STATIC_KEY_FALSE(__scx_has_subs); /* latched at root enable before any rescue runs */static s32 scx_rescue_bw_1024;static s64 scx_rescue_quantum_ns;static s64 scx_rescue_sat_delta_ns;static unsigned long scx_rescue_decay_halflife;static unsigned long scx_rescue_overload_after; /** * scx_skip_subtree_pre - Skip @pos's subtree in a pre-order walk * @pos: current position * @root: walk root * * In a walk started by scx_next_descendant_pre(), continue past @pos's subtree: * return @pos's next sibling, or the closest ancestor's next sibling, or NULL * if @pos's subtree is the last under @root. Same locking rules. */struct scx_sched *scx_skip_subtree_pre(struct scx_sched *pos, struct scx_sched *root){	struct scx_sched *next; 	lockdep_assert(lockdep_is_held(&scx_enable_mutex) ||		       lockdep_is_held(&scx_sched_lock) ||		       rcu_read_lock_any_held()); 	while (pos != root) {		next = list_next_or_null_rcu(&scx_parent(pos)->children, &pos->sibling,					     struct scx_sched, sibling);		if (next)			return next;		pos = scx_parent(pos);	}	return NULL;} /** * scx_next_descendant_pre - find the next descendant for pre-order walk * @pos: the current position (%NULL to initiate traversal) * @root: sched whose descendants to walk * * To be used by scx_for_each_descendant_pre(). Find the next descendant to * visit for pre-order traversal of @root's descendants. @root is included in * the iteration and the first node to be visited. */struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root){	struct scx_sched *next; 	lockdep_assert(lockdep_is_held(&scx_enable_mutex) ||		       lockdep_is_held(&scx_sched_lock) ||		       rcu_read_lock_any_held()); 	/* if first iteration, visit @root */	if (!pos)		return root; 	/* visit the first child if exists */	next = list_first_or_null_rcu(&pos->children, struct scx_sched, sibling);	if (next)		return next; 	/* no child, visit my or the closest ancestor's next sibling */	return scx_skip_subtree_pre(pos, root);} static struct scx_sched *scx_find_sub_sched(u64 cgroup_id){	return rhashtable_lookup(&scx_sched_hash, &cgroup_id,				 scx_sched_hash_params);} void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch){	rcu_assign_pointer(p->scx.sched, sch);} struct cgroup *sch_cgroup(struct scx_sched *sch){	return sch->cgrp;} /* for each descendant of @cgrp including self, set ->scx_sched to @sch */void set_cgroup_sched(struct cgroup *cgrp, struct scx_sched *sch){	struct cgroup *pos;	struct cgroup_subsys_state *css; 	cgroup_for_each_live_descendant_pre(pos, css, cgrp)		rcu_assign_pointer(pos->scx_sched, sch);} static void free_pshard(struct scx_pshard *pshard){	struct scx_caps_updated *cu; 	if (!pshard)		return;	cu = &pshard->caps_updated;	if (cu->cmask_arena_out)		scx_arena_free(pshard->sch, cu->cmask_arena_out,			       struct_size_t(struct scx_cmask, bits,					     SCX_CMASK_NR_WORDS(pshard->nr_cids)));	kfree(pshard);} void scx_free_pshards(struct scx_sched *sch){	s32 si; 	if (!sch->pshard)		return;	for (si = 0; si < sch->nr_pshards; si++)		free_pshard(sch->pshard[si]);	kfree(sch->pshard);} static struct scx_pshard *alloc_pshard(struct scx_sched *sch, s32 shard_idx, s32 node){	const struct scx_cid_shard *shard =		&rcu_dereference_protected(scx_cid_shard_ranges,					   lockdep_is_held(&scx_enable_mutex))[shard_idx];	size_t cmask_size = struct_size_t(struct scx_cmask, bits,					  SCX_CMASK_NR_WORDS(shard->nr_cids));	struct scx_pshard *pshard;	struct scx_caps_updated *cu;	s32 i; 	pshard = kzalloc_node(sizeof(*pshard), GFP_KERNEL, node);	if (!pshard)		return NULL; 	raw_spin_lock_init(&pshard->lock);	pshard->sch = sch;	pshard->base = shard->base_cid;	pshard->nr_cids = shard->nr_cids; 	for (i = 0; i < __SCX_NR_CAPS; i++)		scx_cmask_init(&pshard->caps[i].cmask, shard->base_cid, shard->nr_cids); 	cu = &pshard->caps_updated;	raw_spin_lock_init(&cu->lock);	INIT_LIST_HEAD(&cu->node_in_flight);	__scx_cmask_init(&cu->cmask, shard->base_cid, shard->nr_cids, SCX_CID_SHARD_MAX_CPUS); 	cu->cmask_arena_out = scx_arena_alloc(sch, cmask_size);	if (!cu->cmask_arena_out) {		free_pshard(pshard);		return NULL;	} 	scx_cmask_init(cu->cmask_arena_out, shard->base_cid, shard->nr_cids); 	return pshard;} s32 scx_alloc_pshards(struct scx_sched *sch){	struct scx_pshard **pshard;	s32 *shard_node;	s32 si; 	if (!sch->is_cid_type || !sch->arena_pool)		return 0; 	shard_node = rcu_dereference_protected(scx_shard_node,					       lockdep_is_held(&scx_enable_mutex)); 	pshard = kzalloc_objs(pshard[0], scx_nr_cid_shards);	if (!pshard)		return -ENOMEM; 	for (si = 0; si < scx_nr_cid_shards; si++) {		pshard[si] = alloc_pshard(sch, si, shard_node[si]);		if (!pshard[si]) {			while (--si >= 0)				free_pshard(pshard[si]);			kfree(pshard);			return -ENOMEM;		}	} 	sch->nr_pshards = scx_nr_cid_shards;	/*	 * Publish only after every entry is built so a reader observing	 * @sch->pshard never sees a partially-filled array or unpublished cid	 * tables. Pair the store with a barrier and an acquire load on the	 * read side.	 */	smp_wmb();	WRITE_ONCE(sch->pshard, pshard);	return 0;} /* * Seed the root's caps fully. Root owns all cids on all caps at enable time. * Children acquire caps via scx_bpf_sub_grant(). */void scx_init_root_caps(struct scx_sched *sch){	s32 si, i; 	for (si = 0; si < sch->nr_pshards; si++) {		struct scx_pshard *ps = sch->pshard[si]; 		for (i = 0; i < __SCX_NR_CAPS; i++)			scx_cmask_fill(&ps->caps[i].cmask);	}} /* unserved remainder of @rq's rescuee's admitted slice, 0 once fully served */static s64 scx_rescue_slice_remaining(struct rq *rq){	s64 served = rq->scx.rescue.curr->se.sum_exec_runtime - rq->scx.rescue.exec_snap; 	return max(rq->scx.rescue.slice - served, 0);} /* * Decay @pcpu's rescue usage average in place, halving per the knob-derived * halflife, see scx_rescue_set_knobs(). The timestamp advances only by whole * halflives. */static u64 scx_rescue_decay_avg(struct scx_sched_pcpu *pcpu){	unsigned long halflife = scx_rescue_decay_halflife;	u64 n = div_u64(get_jiffies_64() - pcpu->rescue_avg_at, halflife); 	if (n) {		pcpu->rescue_avg = n < 64 ? pcpu->rescue_avg >> n : 0;		pcpu->rescue_avg_at += n * halflife;	}	return pcpu->rescue_avg;} /** * scx_rescue_charge - Charge the rescuee's runtime * @rq: rq the rescuee is running on * @delta_exec: runtime being charged * * Also ends the rescue once the admitted slice has been served in full. Ending * on served time rather than slice exhaustion bounds both the rescue and the * charging when a scheduler extends the rescuee's slice. */void scx_rescue_charge(struct rq *rq, s64 delta_exec){	struct scx_sched_pcpu *pcpu; 	lockdep_assert_rq_held(rq); 	/*	 * A rescue slice is bounded by one quantum and tick-driven expiry can	 * overshoot by up to a tick. Clamp to avoid wild over-charges on VMs.	 */	delta_exec = min_t(s64, delta_exec, scx_rescue_quantum_ns + TICK_NSEC); 	rq->scx.rescue.budget -= delta_exec; 	/* per-cpu usage average feeds the overload victim pick */	pcpu = per_cpu_ptr(scx_task_sched(rq->curr)->pcpu, cpu_of(rq));	pcpu->rescue_avg = scx_rescue_decay_avg(pcpu) + delta_exec; 	if (!scx_rescue_slice_remaining(rq))		scx_task_slice_ended(rq, rq->scx.rescue.curr);} /** * scx_rescue_end - End the rescue execution on @rq * @rq: rq of interest * * When no rescuee is left pending, the session is over and the balance above * one quantum dies with it - it would otherwise become a banked license to * preempt the cid owner long after the starvation ended. While waiters remain, * the accrued deficit belongs to the queue and carries into the next rescue. */void scx_rescue_end(struct rq *rq){	lockdep_assert_rq_held(rq); 	rq->scx.rescue.curr = NULL;	if (list_empty(&rq->scx.rescue.dsq.list))		rq->scx.rescue.budget = min(rq->scx.rescue.budget, scx_rescue_quantum_ns);} /** * scx_rescue_keep - Keep the rescue going for a preempted-out rescuee * @rq: rq @p is running on * @p: task under rescue whose slice is exhausted * * Called from put_prev_task_scx() to decide what an exhausted slice means for * the rescuee. scx_rescue_charge() ends the rescue the moment the admitted * slice is fully served, so arriving here with the rescue still open means @p * was preempted. Restore the unserved remainder and return %true - @p stays the * rescuee and the caller reinserts it at the tail of the local DSQ, behind * whatever preempted the rescuee. * * Return %false to end the rescue instead - the slice is already fully served, * @p is leaving the rq or bypass is dismantling rescues. */bool scx_rescue_keep(struct rq *rq, struct task_struct *p){	s64 remaining = scx_rescue_slice_remaining(rq); 	lockdep_assert_rq_held(rq); 	if (!remaining || !(p->scx.flags & SCX_TASK_QUEUED) ||	    scx_bypassing(scx_task_sched(p), cpu_of(rq)))		return false; 	scx_set_task_slice(p, remaining);	return true;} /** * scx_rescue_accrue - Accrue budget at the configured fraction of elapsed time * @rq: rq of interest * * A session spans from the first arrival until no rescuee is left, pending or * admitted. While one is active the cap is three quanta and the balance drives * escalation, see scx_rescue_timerfn(). Outside a session the cap is one * quantum, so an idle gap funds the next arrival's admission but never an * escalation. */static void scx_rescue_accrue(struct rq *rq){	bool in_session = rq->scx.rescue.curr || !list_empty(&rq->scx.rescue.dsq.list);	s64 cap = in_session ? 3 * scx_rescue_quantum_ns : scx_rescue_quantum_ns;	s64 delta;	u64 now; 	lockdep_assert_rq_held(rq); 	/* not every path here holds an updated rq clock, use __scx_bpf_now() */	now = __scx_bpf_now(rq);	delta = now - rq->scx.rescue.clock;	rq->scx.rescue.clock = now; 	/*	 * Avoid multiplication overflows by taking a shortcut when the gap is	 * large enough to fill the budget.	 */	if (delta >= scx_rescue_sat_delta_ns)		rq->scx.rescue.budget = cap;	else		rq->scx.rescue.budget =			min(cap, rq->scx.rescue.budget +			    ((delta * scx_rescue_bw_1024) >> SCHED_CAPACITY_SHIFT));} /* * The slice for the next admission - the quantum divided across the stranded * tasks so that a crowded queue round-robins on shorter slices. */static s64 scx_rescue_next_slice(struct rq *rq){	s64 min_slice = max_t(s64, SCX_RESCUE_MIN_SLICE_US * NSEC_PER_USEC, TICK_NSEC);	u32 depth = rq->scx.rescue.dsq.nr ?: 1; 	return clamp(div_s64(scx_rescue_quantum_ns, depth), min_slice, scx_rescue_quantum_ns);} static void scx_rescue_timer_arm(struct rq *rq){	struct timer_list *timer = &rq->scx.rescue.timer;	s64 delay = scx_rescue_quantum_ns / 4;	/* should be granular enough */ 	if (timer_pending(timer))		return; 	/*	 * While the head waiter can't be admitted because the bucket is short	 * of a full quantum, stretch to the full funding delay.	 */	if (!rq->scx.rescue.curr && rq->scx.rescue.budget < scx_rescue_quantum_ns) {		s64 deficit = scx_rescue_quantum_ns - rq->scx.rescue.budget; 		delay = max(delay,			    div_s64(deficit << SCHED_CAPACITY_SHIFT, scx_rescue_bw_1024));	} 	/* +1 rounds up so the beat is due by the time the timer fires */	timer->expires = jiffies + nsecs_to_jiffies(delay) + 1;	add_timer_on(timer, cpu_of(rq));} /** * scx_rescue_admit - Start rescuing @p on @rq * @rq: rq @p is being admitted on * @p: task being admitted, off any DSQ * @slice: CPU time to grant * * The schedulers keep their normal control over @p and may preempt or reslice * it. @slice is measured on served CPU time against the snapshot taken here, so * neither shortens the rescue, see scx_rescue_charge() and scx_rescue_keep(). * Prolonged denial escalates into protected execution, see * scx_rescue_timerfn(). */static void scx_rescue_admit(struct rq *rq, struct task_struct *p, s64 slice){	lockdep_assert_rq_held(rq);	WARN_ON_ONCE(rq->scx.rescue.curr); 	rq->scx.rescue.curr = p;	rq->scx.rescue.slice = slice;	rq->scx.rescue.exec_snap = p->se.sum_exec_runtime;	scx_set_task_slice(p, slice);	scx_rescue_timer_arm(rq);} /** * scx_rescue_try_admit - Try to admit a freshly stranded task * @rq: rq @p is being inserted on * @p: stranded task being diverted to rescue * * One rescue at a time and earlier arrivals go first. Admission needs a full * quantum of budget, spent as the rescue runs. Return %true if @p was admitted * and should be inserted at the tail of @rq's local DSQ, %false if it has to * park on the rescue DSQ, with the timer armed to admit it later. */static bool scx_rescue_try_admit(struct rq *rq, struct task_struct *p){	scx_rescue_accrue(rq); 	if (!rq->scx.rescue.curr && list_empty(&rq->scx.rescue.dsq.list) &&	    rq->scx.rescue.budget >= scx_rescue_quantum_ns) {		scx_rescue_admit(rq, p, scx_rescue_quantum_ns);		return true;	} 	scx_rescue_timer_arm(rq);	return false;} /** * scx_rescue_check_overload - Eject the top rescue consumer on a stuck rescue * @rq: rq whose rescue timer fired * * If the oldest waiter on @rq's rescue DSQ has been queued for too long, rescue * demand on this cpu persistently exceeds the configured bandwidth. Eject the * sub with the highest recent rescue consumption instead of letting the * scheduler stall path blame the waiter's owner, who may just be crowded out. */static void scx_rescue_check_overload(struct rq *rq){	struct scx_sched *victim = NULL, *pos;	struct task_struct *p;	int cpu = cpu_of(rq);	u64 max_avg = 0;	u32 dur_ms; 	lockdep_assert_rq_held(rq); 	p = list_first_entry_or_null(&rq->scx.rescue.dsq.list, struct task_struct,				     scx.dsq_list.node);	if (!p)		return; 	/* has the head waiter been queued for longer than the threshold? */	if (time_before(jiffies, p->scx.rescue_at + scx_rescue_overload_after))		return; 	/*	 * Grace period after the last ejection on this cpu - the freed	 * bandwidth gets one threshold's worth of time to drain the backlog	 * before another sub is judged.	 */	if (time_before64(get_jiffies_64(), rq->scx.rescue.kill_at +			  scx_rescue_overload_after))		return; 	list_for_each_entry_rcu(pos, &scx_sched_all, all) {		u64 avg = scx_rescue_decay_avg(per_cpu_ptr(pos->pcpu, cpu));

Lines 1–500 of 2681.