11064 lines · 327.2 KB · C
RawDownload
/* SPDX-License-Identifier: GPL-2.0 *//* * BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst * * Copyright (c) 2022 Meta Platforms, Inc. and affiliates. * Copyright (c) 2022 Tejun Heo <tj@kernel.org> * Copyright (c) 2022 David Vernet <dvernet@meta.com> */#include <linux/bitmap.h>#include <linux/btf_ids.h>#include <linux/rhashtable.h>#include <linux/sched/clock.h>#include <linux/sched/isolation.h>#include <linux/suspend.h>#include <linux/sysrq.h> #include "../pelt.h"#include "internal.h"#include "cid.h"#include "arena.h"#include "idle.h"#include "sub.h"#include "inlines.h" DEFINE_RAW_SPINLOCK(scx_sched_lock); /* * NOTE: sched_ext is in the process of growing multiple scheduler support and * scx_root usage is in a transitional state. Naked dereferences are safe if the * caller is one of the tasks attached to SCX and explicit RCU dereference is * necessary otherwise. Naked scx_root dereferences trigger sparse warnings but * are used as temporary markers to indicate that the dereferences need to be * updated to point to the associated scheduler instances rather than scx_root. */struct scx_sched __rcu *scx_root; /* * All scheds, writers must hold both scx_enable_mutex and scx_sched_lock. * Readers can hold either or rcu_read_lock(). */LIST_HEAD(scx_sched_all); #ifdef CONFIG_EXT_SUB_SCHEDconst struct rhashtable_params scx_sched_hash_params = {	.key_len		= sizeof_field(struct scx_sched, ops.sub_cgroup_id),	.key_offset		= offsetof(struct scx_sched, ops.sub_cgroup_id),	.head_offset		= offsetof(struct scx_sched, hash_node),	.insecure_elasticity	= true,	/* inserted under scx_sched_lock */}; struct rhashtable scx_sched_hash;#endif /* see SCX_OPS_TID_TO_TASK */static const struct rhashtable_params scx_tid_hash_params = {	.key_len		= sizeof_field(struct sched_ext_entity, tid),	.key_offset		= offsetof(struct sched_ext_entity, tid),	.head_offset		= offsetof(struct sched_ext_entity, tid_hash_node),	.insecure_elasticity	= true,	/* inserted/removed under scx_tasks_lock */};static struct rhashtable scx_tid_hash; /* * During exit, a task may schedule after losing its PIDs. When disabling the * BPF scheduler, we need to be able to iterate tasks in every state to * guarantee system safety. Maintain a dedicated task list which contains every * task between its fork and eventual free. */static DEFINE_RAW_SPINLOCK(scx_tasks_lock);static LIST_HEAD(scx_tasks); /* ops enable/disable */DEFINE_MUTEX(scx_enable_mutex);DEFINE_STATIC_KEY_FALSE(__scx_enabled);DEFINE_PERCPU_RWSEM(scx_fork_rwsem);static atomic_t scx_enable_state_var = ATOMIC_INIT(SCX_DISABLED);static DEFINE_RAW_SPINLOCK(scx_bypass_lock);static bool scx_init_task_enabled;static bool scx_switching_all;DEFINE_STATIC_KEY_FALSE(__scx_switched_all);static DEFINE_STATIC_KEY_FALSE(__scx_tid_to_task_enabled); /* * Gates cgroup ops delivery. Set at the end of the cgroup init phase of root * enable and cleared before root disable starts tearing down tasks, both under * scx_cgroup_lock(). Holding cgroup_lock() and seeing %true guarantees no race * against root tearing down tasks. */bool scx_cgroup_enabled; /* * True once SCX_OPS_TID_TO_TASK has been negotiated with the root scheduler * and the tid->task table is live. Wraps the static key so callers don't * take the address, and hints "likely enabled" for the common case where * the feature is in use. */static inline bool scx_tid_to_task_enabled(void){	return static_branch_likely(&__scx_tid_to_task_enabled);} static atomic_long_t scx_nr_rejected = ATOMIC_LONG_INIT(0);static atomic_long_t scx_hotplug_seq = ATOMIC_LONG_INIT(0); /* Global cursor for the per-CPU tid allocator. Starts at 1; tid 0 is reserved. */static atomic64_t scx_tid_cursor = ATOMIC64_INIT(1); /* is @dsq synchronized by the containing rq lock instead of dsq->lock? */static bool dsq_is_rq_owned(struct scx_dispatch_q *dsq){	switch (dsq->id) {	case SCX_DSQ_LOCAL:	case SCX_DSQ_REJECT:	case SCX_DSQ_RESCUE:		return true;	default:		return false;	}} /* Cursor for unique scx_sched instance ids. id 0 is reserved. */static atomic64_t scx_sched_id_cursor = ATOMIC64_INIT(0); #ifdef CONFIG_EXT_SUB_SCHED/* * The sub sched being enabled. Used by scx_disable_and_exit_task() to exit * tasks for the sub-sched being enabled. Use a global variable instead of a * per-task field as all enables are serialized. */struct scx_sched *scx_enabling_sub_sched;#else#define scx_enabling_sub_sched	(struct scx_sched *)NULL#endif	/* CONFIG_EXT_SUB_SCHED */ /* * A monotonically increasing sequence number that is incremented every time a * scheduler is enabled. This can be used to check if any custom sched_ext * scheduler has ever been used in the system. */static atomic_long_t scx_enable_seq = ATOMIC_LONG_INIT(0); /* * Watchdog interval. All scx_sched's share a single watchdog timer and the * interval is half of the shortest sch->watchdog_timeout. */static unsigned long scx_watchdog_interval; /* * The last time the delayed work was run. This delayed work relies on * ksoftirqd being able to run to service timer interrupts, so it's possible * that this work itself could get wedged. To account for this, we check that * it's not stalled in the timer tick, and trigger an error if it is. */static unsigned long scx_watchdog_timestamp = INITIAL_JIFFIES; static struct delayed_work scx_watchdog_work; /* * For %SCX_KICK_WAIT: Each CPU has a pointer to an array of kick_sync sequence * numbers. The arrays are allocated with kvzalloc() as size can exceed percpu * allocator limits on large machines. O(nr_cpu_ids^2) allocation, allocated * lazily when enabling and freed when disabling to avoid waste when sched_ext * isn't active. */struct scx_kick_syncs {	struct rcu_head		rcu;	unsigned long		syncs[];}; static DEFINE_PER_CPU(struct scx_kick_syncs __rcu *, scx_kick_syncs); /* * Per-CPU buffered allocator state for p->scx.tid. Each CPU pulls a chunk of * SCX_TID_CHUNK ids from scx_tid_cursor and hands them out locally without * further synchronization. See scx_alloc_tid(). */struct scx_tid_alloc {	u64	next;	u64	end;};static DEFINE_PER_CPU(struct scx_tid_alloc, scx_tid_alloc); /* * Direct dispatch marker. * * Non-NULL values are used for direct dispatch from enqueue path. A valid * pointer points to the task currently being enqueued. An ERR_PTR value is used * to indicate that direct dispatch has already happened. */static DEFINE_PER_CPU(struct task_struct *, direct_dispatch_task); static const struct rhashtable_params dsq_hash_params = {	.key_len		= sizeof_field(struct scx_dispatch_q, id),	.key_offset		= offsetof(struct scx_dispatch_q, id),	.head_offset		= offsetof(struct scx_dispatch_q, hash_node),}; static LLIST_HEAD(dsqs_to_free); /* ops debug dump */static DEFINE_RAW_SPINLOCK(scx_dump_lock); struct scx_dump_data {	s32			cpu;	bool			first;	s32			cursor;	struct seq_buf		*s;	const char		*prefix;	struct scx_bstr_buf	buf;}; static struct scx_dump_data scx_dump_data = {	.cpu			= -1,}; /* /sys/kernel/sched_ext interface */static struct kset *scx_kset; /* * Parameters that can be adjusted through /sys/module/sched_ext/parameters. * There usually is no reason to modify these as normal scheduler operation * shouldn't be affected by them. The knobs are primarily for debugging. */static unsigned int scx_slice_bypass_us = SCX_SLICE_BYPASS / NSEC_PER_USEC;static unsigned int scx_bypass_lb_intv_us = SCX_BYPASS_LB_DFL_INTV_US; static int set_slice_us(const char *val, const struct kernel_param *kp){	return param_set_uint_minmax(val, kp, 100, 100 * USEC_PER_MSEC);} static const struct kernel_param_ops slice_us_param_ops = {	.set = set_slice_us,	.get = param_get_uint,}; static int set_bypass_lb_intv_us(const char *val, const struct kernel_param *kp){	return param_set_uint_minmax(val, kp, 0, 10 * USEC_PER_SEC);} static const struct kernel_param_ops bypass_lb_intv_us_param_ops = {	.set = set_bypass_lb_intv_us,	.get = param_get_uint,}; #undef MODULE_PARAM_PREFIX#define MODULE_PARAM_PREFIX	"sched_ext." module_param_cb(slice_bypass_us, &slice_us_param_ops, &scx_slice_bypass_us, 0600);MODULE_PARM_DESC(slice_bypass_us, "bypass slice in microseconds, applied on [un]load (100us to 100ms)");module_param_cb(bypass_lb_intv_us, &bypass_lb_intv_us_param_ops, &scx_bypass_lb_intv_us, 0600);MODULE_PARM_DESC(bypass_lb_intv_us, "bypass load balance interval in microseconds (0 (disable) to 10s)"); #undef MODULE_PARAM_PREFIX #define CREATE_TRACE_POINTS#include <trace/events/sched_ext.h> static void run_deferred(struct rq *rq);static bool task_dead_and_done(struct task_struct *p);static void scx_disable(struct scx_sched *sch, enum scx_exit_kind kind); __printf(5, 6) bool __scx_exit(struct scx_sched *sch,			       enum scx_exit_kind kind, s64 exit_code,			       s32 exit_cpu, const char *fmt, ...){	va_list args;	bool ret; 	va_start(args, fmt);	ret = scx_vexit(sch, kind, exit_code, exit_cpu, fmt, args);	va_end(args); 	return ret;} static long jiffies_delta_msecs(unsigned long at, unsigned long now){	if (time_after(at, now))		return jiffies_to_msecs(at - now);	else		return -(long)jiffies_to_msecs(now - at);} static bool u32_before(u32 a, u32 b){	return (s32)(a - b) < 0;} /** * scx_is_descendant - Test whether sched is a descendant * @sch: sched to test * @ancestor: ancestor sched to test against * * Test whether @sch is a descendant of @ancestor. */bool scx_is_descendant(struct scx_sched *sch, struct scx_sched *ancestor){	if (sch->level < ancestor->level)		return false;	return sch->ancestors[ancestor->level] == ancestor;} static struct scx_dispatch_q *find_global_dsq(struct scx_sched *sch, s32 cpu){	return &sch->pnode[cpu_to_node(cpu)]->global_dsq;} static struct scx_dispatch_q *find_user_dsq(struct scx_sched *sch, u64 dsq_id){	return rhashtable_lookup(&sch->dsq_hash, &dsq_id, dsq_hash_params);} static const struct sched_class *scx_setscheduler_class(struct task_struct *p){	if (p->sched_class == &stop_sched_class)		return &stop_sched_class; 	return __setscheduler_class(p->policy, p->prio);} static struct scx_dispatch_q *bypass_enq_target_dsq(struct scx_sched *sch, s32 cpu){#ifdef CONFIG_EXT_SUB_SCHED	/*	 * If @sch is a sub-sched which is bypassing, its tasks should go into	 * the bypass DSQs of the nearest ancestor which is not bypassing. The	 * not-bypassing ancestor is responsible for scheduling all tasks from	 * bypassing sub-trees. If all ancestors including root are bypassing,	 * all tasks should go to the root's bypass DSQs.	 *	 * Whenever a sched starts bypassing, all runnable tasks in its subtree	 * are re-enqueued after scx_bypassing() is turned on, guaranteeing that	 * all tasks are transferred to the right DSQs.	 */	while (scx_parent(sch) && scx_bypassing(sch, cpu))		sch = scx_parent(sch);#endif	/* CONFIG_EXT_SUB_SCHED */ 	return scx_bypass_dsq(sch, cpu);} /** * rq_is_open - Is the rq available for immediate execution of an SCX task? * @rq: rq to test * @enq_flags: optional %SCX_ENQ_* of the task being enqueued * * Returns %true if @rq is currently open for executing an SCX task. After a * %false return, @rq is guaranteed to invoke SCX dispatch path at least once * before going to idle and not inserting a task into @rq's local DSQ after a * %false return doesn't cause @rq to stall. */static bool rq_is_open(struct rq *rq, u64 enq_flags){	lockdep_assert_rq_held(rq); 	/*	 * A higher-priority class task is either running or in the process of	 * waking up on @rq.	 */	if (sched_class_above(rq->next_class, &ext_sched_class))		return false; 	/*	 * @rq is either in transition to or in idle and there is no	 * higher-priority class task waking up on it.	 */	if (sched_class_above(&ext_sched_class, rq->next_class))		return true; 	/*	 * @rq is either picking, in transition to, or running an SCX task.	 */ 	/*	 * If we're in the dispatch path holding rq lock, $curr may or may not	 * be ready depending on whether the on-going dispatch decides to extend	 * $curr's slice. We say yes here and resolve it at the end of dispatch.	 * See dispatch_one().	 */	if (rq->scx.flags & SCX_RQ_IN_DISPATCH)		return true; 	/*	 * %SCX_ENQ_PREEMPT clears $curr's slice if on SCX and kicks dispatch,	 * so allow it to avoid spuriously triggering reenq on a combined	 * PREEMPT|IMMED insertion.	 */	if (enq_flags & SCX_ENQ_PREEMPT) {		struct task_struct *curr = rq->curr; 		/*		 * A protected slice refuses the preemption and the cpu stays		 * occupied. See rq_owned_post_enq().		 */		return curr->sched_class != &ext_sched_class ||			likely(!(curr->scx.flags & SCX_TASK_PROTECTED));	} 	/*	 * @rq is either in transition to or running an SCX task and can't go	 * idle without another SCX dispatch cycle.	 */	return false;} /* * Track the rq currently locked. * * This allows kfuncs to safely operate on rq from any scx ops callback, * knowing which rq is already locked. */DEFINE_PER_CPU(struct rq *, scx_locked_rq_state); /* * Under core scheduling, a pick that releases the rq lock invalidates the * core-wide selection it is part of. Count the releases so that the core-sched * pick can tell whether one happened across dispatch. */static void scx_rq_lock_drop(struct rq *rq){	lockdep_assert_rq_held(rq);#ifdef CONFIG_SCHED_CORE	if (sched_core_enabled(rq))		rq->scx.lock_drop_seq++;#endif} static void switch_rq_lock(struct rq *from, struct rq *to){	bool tracked = scx_locked_rq() == from; 	if (tracked)		update_locked_rq(NULL);	scx_rq_lock_drop(from);	raw_spin_rq_unlock(from);	raw_spin_rq_lock(to);	if (tracked)		update_locked_rq(to);} /* * Flipped on enable per sch->is_cid_type. Declared in internal.h so * subsystem inlines can read it. */DEFINE_STATIC_KEY_FALSE(__scx_is_cid_type); /** * scx_call_op_set_cpumask - invoke ops.set_cpumask / ops_cid.set_cmask for @task * @sch: scx_sched being invoked * @rq: rq to update as the currently-locked rq, or NULL * @task: task whose affinity is changing * @cpumask: new cpumask * * For cid-form schedulers, translate @cpumask to a cmask via the per-cpu * scratch in cid.c and dispatch through the ops_cid union view. Caller * must hold @rq's rq lock so this_cpu_ptr is stable across the call. */static inline void scx_call_op_set_cpumask(struct scx_sched *sch, struct rq *rq,					   struct task_struct *task,					   const struct cpumask *cpumask){	if (scx_is_cid_type()) {		struct scx_cmask *kern_va = *this_cpu_ptr(sch->set_cmask_scratch);		struct scx_cmask_ref ref; 		/*		 * Build the per-cpu arena cmask from kernel geometry via @ref,		 * never reading its BPF-writable header. set_cmask()'s __arena		 * argument takes the kernel address and the struct_ops		 * trampoline rebases it into BPF's arena pointer form. The rq		 * lock makes this cpu the sole kernel writer.		 */		scx_cmask_ref_init_kern(sch, kern_va, 0, num_possible_cpus(), &ref);		scx_cmask_ref_from_cpumask(&ref, cpumask);		SCX_CALL_CID_OP_TASK(sch, set_cmask, rq, task, kern_va);	} else {		SCX_CALL_OP_TASK(sch, set_cpumask, rq, task, cpumask);	}} enum scx_dsq_iter_flags {	/* iterate in the reverse dispatch order */	SCX_DSQ_ITER_REV		= 1U << 16, 	__SCX_DSQ_ITER_HAS_SLICE	= 1U << 30,	__SCX_DSQ_ITER_HAS_VTIME	= 1U << 31, 	__SCX_DSQ_ITER_USER_FLAGS	= SCX_DSQ_ITER_REV,	__SCX_DSQ_ITER_ALL_FLAGS	= __SCX_DSQ_ITER_USER_FLAGS |					  __SCX_DSQ_ITER_HAS_SLICE |					  __SCX_DSQ_ITER_HAS_VTIME,}; /** * nldsq_next_task - Iterate to the next task in a non-local DSQ * @dsq: non-local dsq being iterated * @cur: current position, %NULL to start iteration * @rev: walk backwards

Lines 1–500 of 11064.