mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
Unlike local reenqueues, cap rejections have no repeat limit. A
malfunctioning scheduler can keep re-inserting a task to a cid it lacks caps
on, cycling the task through reject and reenqueue. This was assumed safe
because a task that never runs trips the stall watchdog. However, the
reenqueue irq_work re-arms itself and outranks the timer vector, blocking
everything else on the CPU including stall detection and recovery, until the
NMI hardlockup detector fires.
Local reenqueues already have a repeat cap, SCX_REENQ_LOCAL_MAX_REPEAT,
which needs generalizing to cover all reenqueues. It also has an attribution
problem. Counted per-cpu on root, it tears down the whole hierarchy even
when a sub-scheduler caused the repeated reenqueues.
Generalize by bounding every reenqueue with one per-task counter. reenq_cnt
is bumped in scx_do_enqueue_task() on each SCX_ENQ_REENQ, the single path
every reenqueue producer passes through, and cleared in clr_task_runnable()
when the task is picked to run and in scx_disable_task() when it leaves the
scheduler's control. Past SCX_REENQ_MAX_REPEAT the task's owning scheduler
is ejected with a new SCX_EXIT_ERROR_REENQ and the task is left stranded to
be picked up during sched exit.
The SCX_EV_REENQ_LOCAL_REPEAT event becomes SCX_EV_REENQ_REPEAT, counting
repeat reenqueues from all sources.
v2: Count SCX_EV_REENQ_REPEAT only when a reenqueue leads to another
reenqueue, not on every reenqueue.
v3: - Also clear reenq_cnt in scx_disable_task() so that the count doesn't
carry over to the next owner across sched class switches, scheduler
replacement or sub-scheduler rehoming (Andrea Righi).
- Update the stale SCX_EV_REENQ_LOCAL_REPEAT references in sched-ext.rst
(Andrea Righi).
Signed-off-by: Tejun Heo <tj@kernel.org>
Reviewed-by: Andrea Righi <arighi@nvidia.com>
327 lines
11 KiB
C
327 lines
11 KiB
C
/* 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>
|
|
*/
|
|
#ifndef _LINUX_SCHED_EXT_H
|
|
#define _LINUX_SCHED_EXT_H
|
|
|
|
#ifdef CONFIG_SCHED_CLASS_EXT
|
|
|
|
#include <linux/llist.h>
|
|
#include <linux/rhashtable-types.h>
|
|
|
|
enum scx_public_consts {
|
|
SCX_OPS_NAME_LEN = 128,
|
|
|
|
/*
|
|
* %SCX_SLICE_DFL is used to refill slices when the BPF scheduler misses
|
|
* to set the slice for a task that is selected for execution.
|
|
* %SCX_EV_REFILL_SLICE_DFL counts the number of times the default slice
|
|
* refill has been triggered.
|
|
*
|
|
* %SCX_SLICE_BYPASS is used as the slice for all tasks in the bypass
|
|
* mode. As making forward progress for all tasks is the main goal of
|
|
* the bypass mode, a shorter slice is used.
|
|
*/
|
|
SCX_SLICE_DFL = 20 * 1000000, /* 20ms */
|
|
SCX_SLICE_BYPASS = 5 * 1000000, /* 5ms */
|
|
SCX_SLICE_INF = U64_MAX, /* infinite, implies nohz */
|
|
};
|
|
|
|
/*
|
|
* DSQ (dispatch queue) IDs are 64bit of the format:
|
|
*
|
|
* Bits: [63] [62 .. 0]
|
|
* [ B] [ ID ]
|
|
*
|
|
* B: 1 for IDs for built-in DSQs, 0 for ops-created user DSQs
|
|
* ID: 63 bit ID
|
|
*
|
|
* Built-in IDs:
|
|
*
|
|
* Bits: [63] [62] [61..32] [31 .. 0]
|
|
* [ 1] [ L] [ R ] [ V ]
|
|
*
|
|
* 1: 1 for built-in DSQs.
|
|
* L: 1 for LOCAL_ON DSQ IDs, 0 for others
|
|
* V: For LOCAL_ON DSQ IDs, a CPU number. For others, a pre-defined value.
|
|
*/
|
|
enum scx_dsq_id_flags {
|
|
SCX_DSQ_FLAG_BUILTIN = 1LLU << 63,
|
|
SCX_DSQ_FLAG_LOCAL_ON = 1LLU << 62,
|
|
|
|
SCX_DSQ_INVALID = SCX_DSQ_FLAG_BUILTIN | 0,
|
|
SCX_DSQ_GLOBAL = SCX_DSQ_FLAG_BUILTIN | 1,
|
|
SCX_DSQ_LOCAL = SCX_DSQ_FLAG_BUILTIN | 2,
|
|
SCX_DSQ_BYPASS = SCX_DSQ_FLAG_BUILTIN | 3,
|
|
SCX_DSQ_REJECT = SCX_DSQ_FLAG_BUILTIN | 4, /* internal - see find_dsq_for_dispatch() */
|
|
SCX_DSQ_LOCAL_ON = SCX_DSQ_FLAG_BUILTIN | SCX_DSQ_FLAG_LOCAL_ON,
|
|
SCX_DSQ_LOCAL_CPU_MASK = 0xffffffffLLU,
|
|
};
|
|
|
|
struct scx_deferred_reenq_user {
|
|
struct list_head node;
|
|
u64 flags;
|
|
};
|
|
|
|
struct scx_dsq_pcpu {
|
|
struct scx_dispatch_q *dsq;
|
|
struct scx_deferred_reenq_user deferred_reenq_user;
|
|
};
|
|
|
|
/*
|
|
* A dispatch queue (DSQ) can be either a FIFO or p->scx.dsq_vtime ordered
|
|
* queue. A built-in DSQ is always a FIFO. The built-in local DSQs are used to
|
|
* buffer between the scheduler core and the BPF scheduler. See the
|
|
* documentation for more details.
|
|
*/
|
|
struct scx_dispatch_q {
|
|
raw_spinlock_t lock;
|
|
struct task_struct __rcu *first_task; /* lockless peek at head */
|
|
struct list_head list; /* tasks in dispatch order */
|
|
struct rb_root priq; /* used to order by p->scx.dsq_vtime */
|
|
u32 nr;
|
|
u32 seq; /* used by BPF iter */
|
|
u64 id;
|
|
struct rhash_head hash_node;
|
|
struct llist_node free_node;
|
|
struct scx_sched *sched;
|
|
struct scx_dsq_pcpu __percpu *pcpu;
|
|
struct rcu_head rcu;
|
|
};
|
|
|
|
/* sched_ext_entity.flags */
|
|
enum scx_ent_flags {
|
|
SCX_TASK_QUEUED = 1 << 0, /* on ext runqueue */
|
|
SCX_TASK_IN_CUSTODY = 1 << 1, /* in custody, needs ops.dequeue() when leaving */
|
|
SCX_TASK_RESET_RUNNABLE_AT = 1 << 2, /* runnable_at should be reset */
|
|
SCX_TASK_DEQD_FOR_SLEEP = 1 << 3, /* last dequeue was for SLEEP */
|
|
SCX_TASK_SUB_INIT = 1 << 4, /* task being initialized for a sub sched */
|
|
SCX_TASK_IMMED = 1 << 5, /* task is on local DSQ with %SCX_ENQ_IMMED */
|
|
|
|
/*
|
|
* Bits 8 to 10 are used to carry task state:
|
|
*
|
|
* NONE ops.init_task() not called yet
|
|
* INIT_BEGIN ops.init_task() in flight; see sched_ext_dead()
|
|
* INIT ops.init_task() succeeded, but task can be cancelled
|
|
* READY fully initialized, but not in sched_ext
|
|
* ENABLED fully initialized and in sched_ext
|
|
* DEAD terminal state set by sched_ext_dead()
|
|
*/
|
|
SCX_TASK_STATE_SHIFT = 8,
|
|
SCX_TASK_STATE_BITS = 3,
|
|
SCX_TASK_STATE_MASK = ((1 << SCX_TASK_STATE_BITS) - 1) << SCX_TASK_STATE_SHIFT,
|
|
|
|
SCX_TASK_NONE = 0 << SCX_TASK_STATE_SHIFT,
|
|
SCX_TASK_INIT_BEGIN = 1 << SCX_TASK_STATE_SHIFT,
|
|
SCX_TASK_INIT = 2 << SCX_TASK_STATE_SHIFT,
|
|
SCX_TASK_READY = 3 << SCX_TASK_STATE_SHIFT,
|
|
SCX_TASK_ENABLED = 4 << SCX_TASK_STATE_SHIFT,
|
|
SCX_TASK_DEAD = 5 << SCX_TASK_STATE_SHIFT,
|
|
|
|
/*
|
|
* Bits 12 to 14 are used to carry reenqueue reason. In addition to
|
|
* %SCX_ENQ_REENQ flag, ops.enqueue() can also test for
|
|
* %SCX_TASK_REENQ_REASON_NONE to distinguish reenqueues.
|
|
*
|
|
* NONE not being reenqueued
|
|
* KFUNC reenqueued by scx_bpf_dsq_reenq() and friends
|
|
* IMMED reenqueued due to failed ENQ_IMMED
|
|
* PREEMPTED preempted while running
|
|
* CAP sub-sched cap miss, see p->scx.reenq_reason_*
|
|
*/
|
|
SCX_TASK_REENQ_REASON_SHIFT = 12,
|
|
SCX_TASK_REENQ_REASON_BITS = 3,
|
|
SCX_TASK_REENQ_REASON_MASK = ((1 << SCX_TASK_REENQ_REASON_BITS) - 1) << SCX_TASK_REENQ_REASON_SHIFT,
|
|
|
|
SCX_TASK_REENQ_NONE = 0 << SCX_TASK_REENQ_REASON_SHIFT,
|
|
SCX_TASK_REENQ_KFUNC = 1 << SCX_TASK_REENQ_REASON_SHIFT,
|
|
SCX_TASK_REENQ_IMMED = 2 << SCX_TASK_REENQ_REASON_SHIFT,
|
|
SCX_TASK_REENQ_PREEMPTED = 3 << SCX_TASK_REENQ_REASON_SHIFT,
|
|
SCX_TASK_REENQ_CAP = 4 << SCX_TASK_REENQ_REASON_SHIFT,
|
|
|
|
/* iteration cursor, not a task */
|
|
SCX_TASK_CURSOR = 1 << 31,
|
|
};
|
|
|
|
/* scx_entity.dsq_flags */
|
|
enum scx_ent_dsq_flags {
|
|
SCX_TASK_DSQ_ON_PRIQ = 1 << 0, /* task is queued on the priority queue of a dsq */
|
|
};
|
|
|
|
enum scx_dsq_lnode_flags {
|
|
SCX_DSQ_LNODE_ITER_CURSOR = 1 << 0,
|
|
|
|
/* high 16 bits can be for iter cursor flags */
|
|
__SCX_DSQ_LNODE_PRIV_SHIFT = 16,
|
|
};
|
|
|
|
struct scx_dsq_list_node {
|
|
struct list_head node;
|
|
u32 flags;
|
|
u32 priv; /* can be used by iter cursor */
|
|
};
|
|
|
|
#define INIT_DSQ_LIST_CURSOR(__cursor, __dsq, __flags) \
|
|
(struct scx_dsq_list_node) { \
|
|
.node = LIST_HEAD_INIT((__cursor).node), \
|
|
.flags = SCX_DSQ_LNODE_ITER_CURSOR | (__flags), \
|
|
.priv = READ_ONCE((__dsq)->seq), \
|
|
}
|
|
|
|
struct scx_sched;
|
|
|
|
/*
|
|
* The following is embedded in task_struct and contains all fields necessary
|
|
* for a task to be scheduled by SCX.
|
|
*/
|
|
struct sched_ext_entity {
|
|
#ifdef CONFIG_CGROUPS
|
|
/*
|
|
* Associated scx_sched. Updated either during fork or while holding
|
|
* both p->pi_lock and rq lock.
|
|
*/
|
|
struct scx_sched __rcu *sched;
|
|
#endif
|
|
struct scx_dispatch_q *dsq;
|
|
atomic_long_t ops_state;
|
|
u64 ddsp_dsq_id;
|
|
u64 ddsp_enq_flags;
|
|
struct scx_dsq_list_node dsq_list; /* dispatch order */
|
|
struct rb_node dsq_priq; /* p->scx.dsq_vtime order */
|
|
u32 dsq_seq;
|
|
u32 dsq_flags; /* protected by DSQ lock */
|
|
u32 flags; /* protected by rq lock */
|
|
u32 weight;
|
|
u32 reenq_cnt; /* reenqueues since last run */
|
|
s32 sticky_cpu;
|
|
s32 holding_cpu;
|
|
s32 selected_cpu;
|
|
s32 runnable_cpu; /* cpu @p is runnable on, -1 if not */
|
|
struct task_struct *kf_tasks[2]; /* see SCX_CALL_OP_TASK() */
|
|
|
|
struct list_head runnable_node; /* rq->scx.runnable_list */
|
|
unsigned long runnable_at;
|
|
|
|
#ifdef CONFIG_SCHED_CORE
|
|
u64 core_sched_at; /* see scx_prio_less() */
|
|
#endif
|
|
|
|
/*
|
|
* Unique non-zero task ID assigned at fork. Persists across exec and
|
|
* is never reused. Lets BPF schedulers identify tasks without storing
|
|
* kernel pointers - arena-backed schedulers being one example. See
|
|
* scx_bpf_tid_to_task().
|
|
*/
|
|
u64 tid;
|
|
struct rhash_head tid_hash_node; /* see SCX_OPS_TID_TO_TASK */
|
|
|
|
/* BPF scheduler modifiable fields */
|
|
|
|
/*
|
|
* Runtime budget in nsecs - how long the task may hold its cpu. Owned
|
|
* by the task's scheduler. Set it when enqueuing via
|
|
* scx_bpf_dsq_insert(), or otherwise via scx_bpf_task_set_slice().
|
|
* Automatically decreased as the task executes. On depletion a
|
|
* scheduling event is triggered.
|
|
*
|
|
* This value is cleared to zero if the task is preempted by
|
|
* %SCX_KICK_PREEMPT and shouldn't be used to determine how long the
|
|
* task ran. Use p->se.sum_exec_runtime instead.
|
|
*/
|
|
u64 slice;
|
|
|
|
/*
|
|
* Used to order tasks when dispatching to the vtime-ordered priority
|
|
* queue of a dsq. This is usually set through
|
|
* scx_bpf_dsq_insert_vtime() but can also be modified directly by the
|
|
* BPF scheduler. Modifying it while a task is queued on a dsq may
|
|
* mangle the ordering and is not recommended.
|
|
*/
|
|
u64 dsq_vtime;
|
|
|
|
/*
|
|
* Out-of-band slice request from scx_bpf_task_set_slice() when the
|
|
* caller does not hold the rq lock, applied under the rq lock at the
|
|
* next slice consideration. One atomic64 packs the pending flag, the
|
|
* issuing sch's id, and the requested slice. See scx_slice_oob_consts.
|
|
*/
|
|
atomic64_t slice_oob;
|
|
|
|
/*
|
|
* Sub-sched cap rejected reenq context, valid only while
|
|
* %SCX_TASK_REENQ_CAP is set. @reenq_reason_caps is the SCX_CAP_* bits
|
|
* that were needed but missing. @reenq_reason_cid is the target cid.
|
|
*/
|
|
u64 reenq_reason_caps;
|
|
s32 reenq_reason_cid;
|
|
|
|
/*
|
|
* If set, reject future sched_setscheduler(2) calls updating the policy
|
|
* to %SCHED_EXT with -%EACCES.
|
|
*
|
|
* Can be set from ops.init_task() while the BPF scheduler is being
|
|
* loaded. If set and the task's policy is already %SCHED_EXT, the
|
|
* task's policy is rejected and forcefully reverted to %SCHED_NORMAL.
|
|
* The number of such events are reported through
|
|
* /sys/kernel/sched_ext/nr_rejected. Setting this flag from any other
|
|
* ops.init_task() invocation, such as during fork, fails the scheduler.
|
|
*/
|
|
bool disallow; /* reject switching into SCX */
|
|
|
|
/* cold fields */
|
|
#ifdef CONFIG_EXT_GROUP_SCHED
|
|
struct cgroup *cgrp_moving_from;
|
|
#endif
|
|
struct list_head tasks_node;
|
|
};
|
|
|
|
void sched_ext_dead(struct task_struct *p);
|
|
void print_scx_info(const char *log_lvl, struct task_struct *p);
|
|
void scx_softlockup(u32 dur_s);
|
|
bool scx_hardlockup(int cpu);
|
|
bool scx_rcu_cpu_stall(const struct cpumask *stalled_mask);
|
|
|
|
#else /* !CONFIG_SCHED_CLASS_EXT */
|
|
|
|
static inline void sched_ext_dead(struct task_struct *p) {}
|
|
static inline void print_scx_info(const char *log_lvl, struct task_struct *p) {}
|
|
static inline void scx_softlockup(u32 dur_s) {}
|
|
static inline bool scx_hardlockup(int cpu) { return false; }
|
|
static inline bool scx_rcu_cpu_stall(const struct cpumask *stalled_mask) { return false; }
|
|
|
|
#endif /* CONFIG_SCHED_CLASS_EXT */
|
|
|
|
struct scx_task_group {
|
|
#ifdef CONFIG_EXT_GROUP_SCHED
|
|
/*
|
|
* The sched this tg is on, NULL if none. SCX_TG_INITED tracks whether
|
|
* ops.cgroup_init() succeeded on it. When a child sched exits and its
|
|
* tgs move to the parent, a failed init leaves the tg on the parent
|
|
* with INITED clear (see scx_cgroup_return_subtree()).
|
|
*
|
|
* This is tracked separately from cgrp->scx_sched because the tg
|
|
* hierarchy can diverge from the cgroup2 hierarchy in both lifetime and
|
|
* shape. A tg stays online past its cgroup's removal while the
|
|
* cgrp->scx_sched rewrites visit only live cgroups, leaving a removed
|
|
* cgroup's pointer stale. The cpu controller can also be mounted on
|
|
* cgroup1.
|
|
*/
|
|
struct scx_sched *sched;
|
|
|
|
u32 flags; /* SCX_TG_* */
|
|
u32 weight;
|
|
u64 bw_period_us;
|
|
u64 bw_quota_us;
|
|
u64 bw_burst_us;
|
|
bool idle;
|
|
#endif
|
|
};
|
|
|
|
#endif /* _LINUX_SCHED_EXT_H */
|