mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
Merge tag 'sched_ext-for-7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext
Pull sched_ext updates from Tejun Heo: - cgroup sub-scheduler groundwork Multiple BPF schedulers can be attached to cgroups and the dispatch path is made hierarchical. This involves substantial restructuring of the core dispatch, bypass, watchdog, and dump paths to be per-scheduler, along with new infrastructure for scheduler ownership enforcement, lifecycle management, and cgroup subtree iteration The enqueue path is not yet updated and will follow in a later cycle - scx_bpf_dsq_reenq() generalized to support any DSQ including remote local DSQs and user DSQs Built on top of this, SCX_ENQ_IMMED guarantees that tasks dispatched to local DSQs either run immediately or get reenqueued back through ops.enqueue(), giving schedulers tighter control over queueing latency Also useful for opportunistic CPU sharing across sub-schedulers - ops.dequeue() was only invoked when the core knew a task was in BPF data structures, missing scheduling property change events and skipping callbacks for non-local DSQ dispatches from ops.select_cpu() Fixed to guarantee exactly one ops.dequeue() call when a task leaves BPF scheduler custody - Kfunc access validation moved from runtime to BPF verifier time, removing runtime mask enforcement - Idle SMT sibling prioritization in the idle CPU selection path - Documentation, selftest, and tooling updates. Misc bug fixes and cleanups * tag 'sched_ext-for-7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext: (134 commits) tools/sched_ext: Add explicit cast from void* in RESIZE_ARRAY() sched_ext: Make string params of __ENUM_set() const tools/sched_ext: Kick home CPU for stranded tasks in scx_qmap sched_ext: Drop spurious warning on kick during scheduler disable sched_ext: Warn on task-based SCX op recursion sched_ext: Rename scx_kf_allowed_on_arg_tasks() to scx_kf_arg_task_ok() sched_ext: Remove runtime kfunc mask enforcement sched_ext: Add verifier-time kfunc context filter sched_ext: Drop redundant rq-locked check from scx_bpf_task_cgroup() sched_ext: Decouple kfunc unlocked-context check from kf_mask sched_ext: Fix ops.cgroup_move() invocation kf_mask and rq tracking sched_ext: Track @p's rq lock across set_cpus_allowed_scx -> ops.set_cpumask sched_ext: Add select_cpu kfuncs to scx_kfunc_ids_unlocked sched_ext: Drop TRACING access to select_cpu kfuncs selftests/sched_ext: Fix wrong DSQ ID in peek_dsq error message sched_ext: Documentation: improve accuracy of task lifecycle pseudo-code selftests/sched_ext: Improve runner error reporting for invalid arguments sched_ext: Documentation: Fix scx_bpf_move_to_local kfunc name sched_ext: Documentation: Add ops.dequeue() to task lifecycle tools/sched_ext: Fix off-by-one in scx_sdt payload zeroing ...
This commit is contained in:
@@ -93,6 +93,55 @@ scheduler has been loaded):
|
||||
# cat /sys/kernel/sched_ext/enable_seq
|
||||
1
|
||||
|
||||
Each running scheduler also exposes a per-scheduler ``events`` file under
|
||||
``/sys/kernel/sched_ext/<scheduler-name>/events`` that tracks diagnostic
|
||||
counters. Each counter occupies one ``name value`` line:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
# cat /sys/kernel/sched_ext/simple/events
|
||||
SCX_EV_SELECT_CPU_FALLBACK 0
|
||||
SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE 0
|
||||
SCX_EV_DISPATCH_KEEP_LAST 123
|
||||
SCX_EV_ENQ_SKIP_EXITING 0
|
||||
SCX_EV_ENQ_SKIP_MIGRATION_DISABLED 0
|
||||
SCX_EV_REENQ_IMMED 0
|
||||
SCX_EV_REENQ_LOCAL_REPEAT 0
|
||||
SCX_EV_REFILL_SLICE_DFL 456789
|
||||
SCX_EV_BYPASS_DURATION 0
|
||||
SCX_EV_BYPASS_DISPATCH 0
|
||||
SCX_EV_BYPASS_ACTIVATE 0
|
||||
SCX_EV_INSERT_NOT_OWNED 0
|
||||
SCX_EV_SUB_BYPASS_DISPATCH 0
|
||||
|
||||
The counters are described in ``kernel/sched/ext_internal.h``; briefly:
|
||||
|
||||
* ``SCX_EV_SELECT_CPU_FALLBACK``: ops.select_cpu() returned a CPU unusable by
|
||||
the task and the core scheduler silently picked a fallback CPU.
|
||||
* ``SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE``: a local-DSQ dispatch was redirected
|
||||
to the global DSQ because the target CPU went offline.
|
||||
* ``SCX_EV_DISPATCH_KEEP_LAST``: a task continued running because no other
|
||||
task was available (only when ``SCX_OPS_ENQ_LAST`` is not set).
|
||||
* ``SCX_EV_ENQ_SKIP_EXITING``: an exiting task was dispatched to the local DSQ
|
||||
directly, bypassing ops.enqueue() (only when ``SCX_OPS_ENQ_EXITING`` is not set).
|
||||
* ``SCX_EV_ENQ_SKIP_MIGRATION_DISABLED``: a migration-disabled task was
|
||||
dispatched to its local DSQ directly (only when
|
||||
``SCX_OPS_ENQ_MIGRATION_DISABLED`` is not set).
|
||||
* ``SCX_EV_REENQ_IMMED``: a task dispatched with ``SCX_ENQ_IMMED`` was
|
||||
re-enqueued because the target CPU was not available for immediate execution.
|
||||
* ``SCX_EV_REENQ_LOCAL_REPEAT``: a reenqueue of the local DSQ triggered
|
||||
another reenqueue; recurring counts indicate incorrect ``SCX_ENQ_REENQ``
|
||||
handling in the BPF scheduler.
|
||||
* ``SCX_EV_REFILL_SLICE_DFL``: a task's time slice was refilled with the
|
||||
default value (``SCX_SLICE_DFL``).
|
||||
* ``SCX_EV_BYPASS_DURATION``: total nanoseconds spent in bypass mode.
|
||||
* ``SCX_EV_BYPASS_DISPATCH``: number of tasks dispatched while in bypass mode.
|
||||
* ``SCX_EV_BYPASS_ACTIVATE``: number of times bypass mode was activated.
|
||||
* ``SCX_EV_INSERT_NOT_OWNED``: attempted to insert a task not owned by this
|
||||
scheduler into a DSQ; such attempts are silently ignored.
|
||||
* ``SCX_EV_SUB_BYPASS_DISPATCH``: tasks dispatched from sub-scheduler bypass
|
||||
DSQs (only relevant with ``CONFIG_EXT_SUB_SCHED``).
|
||||
|
||||
``tools/sched_ext/scx_show_state.py`` is a drgn script which shows more
|
||||
detailed information:
|
||||
|
||||
@@ -228,16 +277,23 @@ The following briefly shows how a waking task is scheduled and executed.
|
||||
scheduler can wake up any cpu using the ``scx_bpf_kick_cpu()`` helper,
|
||||
using ``ops.select_cpu()`` judiciously can be simpler and more efficient.
|
||||
|
||||
A task can be immediately inserted into a DSQ from ``ops.select_cpu()``
|
||||
by calling ``scx_bpf_dsq_insert()``. If the task is inserted into
|
||||
``SCX_DSQ_LOCAL`` from ``ops.select_cpu()``, it will be inserted into the
|
||||
local DSQ of whichever CPU is returned from ``ops.select_cpu()``.
|
||||
Additionally, inserting directly from ``ops.select_cpu()`` will cause the
|
||||
``ops.enqueue()`` callback to be skipped.
|
||||
|
||||
Note that the scheduler core will ignore an invalid CPU selection, for
|
||||
example, if it's outside the allowed cpumask of the task.
|
||||
|
||||
A task can be immediately inserted into a DSQ from ``ops.select_cpu()``
|
||||
by calling ``scx_bpf_dsq_insert()`` or ``scx_bpf_dsq_insert_vtime()``.
|
||||
|
||||
If the task is inserted into ``SCX_DSQ_LOCAL`` from
|
||||
``ops.select_cpu()``, it will be added to the local DSQ of whichever CPU
|
||||
is returned from ``ops.select_cpu()``. Additionally, inserting directly
|
||||
from ``ops.select_cpu()`` will cause the ``ops.enqueue()`` callback to
|
||||
be skipped.
|
||||
|
||||
Any other attempt to store a task in BPF-internal data structures from
|
||||
``ops.select_cpu()`` does not prevent ``ops.enqueue()`` from being
|
||||
invoked. This is discouraged, as it can introduce racy behavior or
|
||||
inconsistent state.
|
||||
|
||||
2. Once the target CPU is selected, ``ops.enqueue()`` is invoked (unless the
|
||||
task was inserted directly from ``ops.select_cpu()``). ``ops.enqueue()``
|
||||
can make one of the following decisions:
|
||||
@@ -251,6 +307,61 @@ The following briefly shows how a waking task is scheduled and executed.
|
||||
|
||||
* Queue the task on the BPF side.
|
||||
|
||||
**Task State Tracking and ops.dequeue() Semantics**
|
||||
|
||||
A task is in the "BPF scheduler's custody" when the BPF scheduler is
|
||||
responsible for managing its lifecycle. A task enters custody when it is
|
||||
dispatched to a user DSQ or stored in the BPF scheduler's internal data
|
||||
structures. Custody is entered only from ``ops.enqueue()`` for those
|
||||
operations. The only exception is dispatching to a user DSQ from
|
||||
``ops.select_cpu()``: although the task is not yet technically in BPF
|
||||
scheduler custody at that point, the dispatch has the same semantic
|
||||
effect as dispatching from ``ops.enqueue()`` for custody-related
|
||||
purposes.
|
||||
|
||||
Once ``ops.enqueue()`` is called, the task may or may not enter custody
|
||||
depending on what the scheduler does:
|
||||
|
||||
* **Directly dispatched to terminal DSQs** (``SCX_DSQ_LOCAL``,
|
||||
``SCX_DSQ_LOCAL_ON | cpu``, or ``SCX_DSQ_GLOBAL``): the BPF scheduler
|
||||
is done with the task - it either goes straight to a CPU's local run
|
||||
queue or to the global DSQ as a fallback. The task never enters (or
|
||||
exits) BPF custody, and ``ops.dequeue()`` will not be called.
|
||||
|
||||
* **Dispatch to user-created DSQs** (custom DSQs): the task enters the
|
||||
BPF scheduler's custody. When the task later leaves BPF custody
|
||||
(dispatched to a terminal DSQ, picked by core-sched, or dequeued for
|
||||
sleep/property changes), ``ops.dequeue()`` will be called exactly
|
||||
once.
|
||||
|
||||
* **Stored in BPF data structures** (e.g., internal BPF queues): the
|
||||
task is in BPF custody. ``ops.dequeue()`` will be called when it
|
||||
leaves (e.g., when ``ops.dispatch()`` moves it to a terminal DSQ, or
|
||||
on property change / sleep).
|
||||
|
||||
When a task leaves BPF scheduler custody, ``ops.dequeue()`` is invoked.
|
||||
The dequeue can happen for different reasons, distinguished by flags:
|
||||
|
||||
1. **Regular dispatch**: when a task in BPF custody is dispatched to a
|
||||
terminal DSQ from ``ops.dispatch()`` (leaving BPF custody for
|
||||
execution), ``ops.dequeue()`` is triggered without any special flags.
|
||||
|
||||
2. **Core scheduling pick**: when ``CONFIG_SCHED_CORE`` is enabled and
|
||||
core scheduling picks a task for execution while it's still in BPF
|
||||
custody, ``ops.dequeue()`` is called with the
|
||||
``SCX_DEQ_CORE_SCHED_EXEC`` flag.
|
||||
|
||||
3. **Scheduling property change**: when a task property changes (via
|
||||
operations like ``sched_setaffinity()``, ``sched_setscheduler()``,
|
||||
priority changes, CPU migrations, etc.) while the task is still in
|
||||
BPF custody, ``ops.dequeue()`` is called with the
|
||||
``SCX_DEQ_SCHED_CHANGE`` flag set in ``deq_flags``.
|
||||
|
||||
**Important**: Once a task has left BPF custody (e.g., after being
|
||||
dispatched to a terminal DSQ), property changes will not trigger
|
||||
``ops.dequeue()``, since the task is no longer managed by the BPF
|
||||
scheduler.
|
||||
|
||||
3. When a CPU is ready to schedule, it first looks at its local DSQ. If
|
||||
empty, it then looks at the global DSQ. If there still isn't a task to
|
||||
run, ``ops.dispatch()`` is invoked which can use the following two
|
||||
@@ -264,9 +375,9 @@ The following briefly shows how a waking task is scheduled and executed.
|
||||
rather than performing them immediately. There can be up to
|
||||
``ops.dispatch_max_batch`` pending tasks.
|
||||
|
||||
* ``scx_bpf_move_to_local()`` moves a task from the specified non-local
|
||||
* ``scx_bpf_dsq_move_to_local()`` moves a task from the specified non-local
|
||||
DSQ to the dispatching DSQ. This function cannot be called with any BPF
|
||||
locks held. ``scx_bpf_move_to_local()`` flushes the pending insertions
|
||||
locks held. ``scx_bpf_dsq_move_to_local()`` flushes the pending insertions
|
||||
tasks before trying to move from the specified DSQ.
|
||||
|
||||
4. After ``ops.dispatch()`` returns, if there are tasks in the local DSQ,
|
||||
@@ -297,8 +408,8 @@ for more information.
|
||||
Task Lifecycle
|
||||
--------------
|
||||
|
||||
The following pseudo-code summarizes the entire lifecycle of a task managed
|
||||
by a sched_ext scheduler:
|
||||
The following pseudo-code presents a rough overview of the entire lifecycle
|
||||
of a task managed by a sched_ext scheduler:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
@@ -311,22 +422,37 @@ by a sched_ext scheduler:
|
||||
|
||||
ops.runnable(); /* Task becomes ready to run */
|
||||
|
||||
while (task is runnable) {
|
||||
if (task is not in a DSQ && task->scx.slice == 0) {
|
||||
while (task_is_runnable(task)) {
|
||||
if (task is not in a DSQ || task->scx.slice == 0) {
|
||||
ops.enqueue(); /* Task can be added to a DSQ */
|
||||
|
||||
/* Task property change (i.e., affinity, nice, etc.)? */
|
||||
if (sched_change(task)) {
|
||||
ops.dequeue(); /* Exiting BPF scheduler custody */
|
||||
ops.quiescent();
|
||||
|
||||
/* Property change callback, e.g. ops.set_weight() */
|
||||
|
||||
ops.runnable();
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Any usable CPU becomes available */
|
||||
|
||||
ops.dispatch(); /* Task is moved to a local DSQ */
|
||||
ops.dispatch(); /* Task is moved to a local DSQ */
|
||||
ops.dequeue(); /* Exiting BPF scheduler custody */
|
||||
}
|
||||
|
||||
ops.running(); /* Task starts running on its assigned CPU */
|
||||
while (task->scx.slice > 0 && task is runnable)
|
||||
|
||||
while (task_is_runnable(task) && task->scx.slice > 0) {
|
||||
ops.tick(); /* Called every 1/HZ seconds */
|
||||
|
||||
if (task->scx.slice == 0)
|
||||
ops.dispatch(); /* task->scx.slice can be refilled */
|
||||
}
|
||||
|
||||
ops.stopping(); /* Task stops running (time slice expires or wait) */
|
||||
|
||||
/* Task's CPU becomes available */
|
||||
|
||||
ops.dispatch(); /* task->scx.slice can be refilled */
|
||||
}
|
||||
|
||||
ops.quiescent(); /* Task releases its assigned CPU (wait) */
|
||||
@@ -335,6 +461,30 @@ by a sched_ext scheduler:
|
||||
ops.disable(); /* Disable BPF scheduling for the task */
|
||||
ops.exit_task(); /* Task is destroyed */
|
||||
|
||||
Note that the above pseudo-code does not cover all possible state transitions
|
||||
and edge cases, to name a few examples:
|
||||
|
||||
* ``ops.dispatch()`` may fail to move the task to a local DSQ due to a racing
|
||||
property change on that task, in which case ``ops.dispatch()`` will be
|
||||
retried.
|
||||
|
||||
* The task may be direct-dispatched to a local DSQ from ``ops.enqueue()``,
|
||||
in which case ``ops.dispatch()`` and ``ops.dequeue()`` are skipped and we go
|
||||
straight to ``ops.running()``.
|
||||
|
||||
* Property changes may occur at virtually any point during the task's lifecycle,
|
||||
not just when the task is queued and waiting to be dispatched. For example,
|
||||
changing a property of a running task will lead to the callback sequence
|
||||
``ops.stopping()`` -> ``ops.quiescent()`` -> (property change callback) ->
|
||||
``ops.runnable()`` -> ``ops.running()``.
|
||||
|
||||
* A sched_ext task can be preempted by a task from a higher-priority scheduling
|
||||
class, in which case it will exit the tick-dispatch loop even though it is runnable
|
||||
and has a non-zero slice.
|
||||
|
||||
See the "Scheduling Cycle" section for a more detailed description of how
|
||||
a freshly woken up task gets on a CPU.
|
||||
|
||||
Where to Look
|
||||
=============
|
||||
|
||||
@@ -377,6 +527,25 @@ Where to Look
|
||||
scheduling. Tasks with CPU affinity are direct-dispatched in FIFO order;
|
||||
all others are scheduled in user space by a simple vruntime scheduler.
|
||||
|
||||
Module Parameters
|
||||
=================
|
||||
|
||||
sched_ext exposes two module parameters under the ``sched_ext.`` prefix that
|
||||
control bypass-mode behaviour. These knobs are primarily for debugging; there
|
||||
is usually no reason to change them during normal operation. They can be read
|
||||
and written at runtime (mode 0600) via
|
||||
``/sys/module/sched_ext/parameters/``.
|
||||
|
||||
``sched_ext.slice_bypass_us`` (default: 5000 µs)
|
||||
The time slice assigned to all tasks when the scheduler is in bypass mode,
|
||||
i.e. during BPF scheduler load, unload, and error recovery. Valid range is
|
||||
100 µs to 100 ms.
|
||||
|
||||
``sched_ext.bypass_lb_intv_us`` (default: 500000 µs)
|
||||
The interval at which the bypass-mode load balancer redistributes tasks
|
||||
across CPUs. Set to 0 to disable load balancing during bypass mode. Valid
|
||||
range is 0 to 10 s.
|
||||
|
||||
ABI Instability
|
||||
===============
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <linux/refcount.h>
|
||||
#include <linux/percpu-refcount.h>
|
||||
#include <linux/percpu-rwsem.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/u64_stats_sync.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/bpf-cgroup-defs.h>
|
||||
@@ -628,6 +629,9 @@ struct cgroup {
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
struct bpf_local_storage __rcu *bpf_cgrp_storage;
|
||||
#endif
|
||||
#ifdef CONFIG_EXT_SUB_SCHED
|
||||
struct scx_sched __rcu *scx_sched;
|
||||
#endif
|
||||
|
||||
/* All ancestors including self */
|
||||
union {
|
||||
|
||||
@@ -62,6 +62,16 @@ enum scx_dsq_id_flags {
|
||||
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
|
||||
@@ -78,30 +88,58 @@ struct scx_dispatch_q {
|
||||
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;
|
||||
};
|
||||
|
||||
/* scx_entity.flags */
|
||||
/* 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 */
|
||||
|
||||
SCX_TASK_STATE_SHIFT = 8, /* bit 8 and 9 are used to carry scx_task_state */
|
||||
/*
|
||||
* Bits 8 and 9 are used to carry task state:
|
||||
*
|
||||
* NONE ops.init_task() not called yet
|
||||
* 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
|
||||
*/
|
||||
SCX_TASK_STATE_SHIFT = 8, /* bits 8 and 9 are used to carry task state */
|
||||
SCX_TASK_STATE_BITS = 2,
|
||||
SCX_TASK_STATE_MASK = ((1 << SCX_TASK_STATE_BITS) - 1) << SCX_TASK_STATE_SHIFT,
|
||||
|
||||
SCX_TASK_CURSOR = 1 << 31, /* iteration cursor, not a task */
|
||||
};
|
||||
SCX_TASK_NONE = 0 << SCX_TASK_STATE_SHIFT,
|
||||
SCX_TASK_INIT = 1 << SCX_TASK_STATE_SHIFT,
|
||||
SCX_TASK_READY = 2 << SCX_TASK_STATE_SHIFT,
|
||||
SCX_TASK_ENABLED = 3 << SCX_TASK_STATE_SHIFT,
|
||||
|
||||
/* scx_entity.flags & SCX_TASK_STATE_MASK */
|
||||
enum scx_task_state {
|
||||
SCX_TASK_NONE, /* ops.init_task() not called yet */
|
||||
SCX_TASK_INIT, /* ops.init_task() succeeded, but task can be cancelled */
|
||||
SCX_TASK_READY, /* fully initialized, but not in sched_ext */
|
||||
SCX_TASK_ENABLED, /* fully initialized and in sched_ext */
|
||||
/*
|
||||
* Bits 12 and 13 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
|
||||
*/
|
||||
SCX_TASK_REENQ_REASON_SHIFT = 12,
|
||||
SCX_TASK_REENQ_REASON_BITS = 2,
|
||||
SCX_TASK_REENQ_REASON_MASK = ((1 << SCX_TASK_REENQ_REASON_BITS) - 1) << SCX_TASK_REENQ_REASON_SHIFT,
|
||||
|
||||
SCX_TASK_NR_STATES,
|
||||
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,
|
||||
|
||||
/* iteration cursor, not a task */
|
||||
SCX_TASK_CURSOR = 1 << 31,
|
||||
};
|
||||
|
||||
/* scx_entity.dsq_flags */
|
||||
@@ -109,33 +147,6 @@ enum scx_ent_dsq_flags {
|
||||
SCX_TASK_DSQ_ON_PRIQ = 1 << 0, /* task is queued on the priority queue of a dsq */
|
||||
};
|
||||
|
||||
/*
|
||||
* Mask bits for scx_entity.kf_mask. Not all kfuncs can be called from
|
||||
* everywhere and the following bits track which kfunc sets are currently
|
||||
* allowed for %current. This simple per-task tracking works because SCX ops
|
||||
* nest in a limited way. BPF will likely implement a way to allow and disallow
|
||||
* kfuncs depending on the calling context which will replace this manual
|
||||
* mechanism. See scx_kf_allow().
|
||||
*/
|
||||
enum scx_kf_mask {
|
||||
SCX_KF_UNLOCKED = 0, /* sleepable and not rq locked */
|
||||
/* ENQUEUE and DISPATCH may be nested inside CPU_RELEASE */
|
||||
SCX_KF_CPU_RELEASE = 1 << 0, /* ops.cpu_release() */
|
||||
/*
|
||||
* ops.dispatch() may release rq lock temporarily and thus ENQUEUE and
|
||||
* SELECT_CPU may be nested inside. ops.dequeue (in REST) may also be
|
||||
* nested inside DISPATCH.
|
||||
*/
|
||||
SCX_KF_DISPATCH = 1 << 1, /* ops.dispatch() */
|
||||
SCX_KF_ENQUEUE = 1 << 2, /* ops.enqueue() and ops.select_cpu() */
|
||||
SCX_KF_SELECT_CPU = 1 << 3, /* ops.select_cpu() */
|
||||
SCX_KF_REST = 1 << 4, /* other rq-locked operations */
|
||||
|
||||
__SCX_KF_RQ_LOCKED = SCX_KF_CPU_RELEASE | SCX_KF_DISPATCH |
|
||||
SCX_KF_ENQUEUE | SCX_KF_SELECT_CPU | SCX_KF_REST,
|
||||
__SCX_KF_TERMINAL = SCX_KF_ENQUEUE | SCX_KF_SELECT_CPU | SCX_KF_REST,
|
||||
};
|
||||
|
||||
enum scx_dsq_lnode_flags {
|
||||
SCX_DSQ_LNODE_ITER_CURSOR = 1 << 0,
|
||||
|
||||
@@ -149,19 +160,31 @@ struct scx_dsq_list_node {
|
||||
u32 priv; /* can be used by iter cursor */
|
||||
};
|
||||
|
||||
#define INIT_DSQ_LIST_CURSOR(__node, __flags, __priv) \
|
||||
#define INIT_DSQ_LIST_CURSOR(__cursor, __dsq, __flags) \
|
||||
(struct scx_dsq_list_node) { \
|
||||
.node = LIST_HEAD_INIT((__node).node), \
|
||||
.node = LIST_HEAD_INIT((__cursor).node), \
|
||||
.flags = SCX_DSQ_LNODE_ITER_CURSOR | (__flags), \
|
||||
.priv = (__priv), \
|
||||
.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;
|
||||
@@ -171,9 +194,7 @@ struct sched_ext_entity {
|
||||
s32 sticky_cpu;
|
||||
s32 holding_cpu;
|
||||
s32 selected_cpu;
|
||||
u32 kf_mask; /* see scx_kf_mask above */
|
||||
struct task_struct *kf_tasks[2]; /* see SCX_CALL_OP_TASK() */
|
||||
atomic_long_t ops_state;
|
||||
|
||||
struct list_head runnable_node; /* rq->scx.runnable_list */
|
||||
unsigned long runnable_at;
|
||||
@@ -181,8 +202,6 @@ struct sched_ext_entity {
|
||||
#ifdef CONFIG_SCHED_CORE
|
||||
u64 core_sched_at; /* see scx_prio_less() */
|
||||
#endif
|
||||
u64 ddsp_dsq_id;
|
||||
u64 ddsp_enq_flags;
|
||||
|
||||
/* BPF scheduler modifiable fields */
|
||||
|
||||
|
||||
@@ -1190,6 +1190,10 @@ config EXT_GROUP_SCHED
|
||||
|
||||
endif #CGROUP_SCHED
|
||||
|
||||
config EXT_SUB_SCHED
|
||||
def_bool y
|
||||
depends on SCHED_CLASS_EXT && CGROUPS
|
||||
|
||||
config SCHED_MM_CID
|
||||
def_bool y
|
||||
depends on SMP && RSEQ
|
||||
|
||||
@@ -2514,8 +2514,12 @@ __latent_entropy struct task_struct *copy_process(
|
||||
fd_install(pidfd, pidfile);
|
||||
|
||||
proc_fork_connector(p);
|
||||
sched_post_fork(p);
|
||||
/*
|
||||
* sched_ext needs @p to be associated with its cgroup in its post_fork
|
||||
* hook. cgroup_post_fork() should come before sched_post_fork().
|
||||
*/
|
||||
cgroup_post_fork(p, args);
|
||||
sched_post_fork(p);
|
||||
perf_event_fork(p);
|
||||
|
||||
trace_task_newtask(p, clone_flags);
|
||||
|
||||
@@ -4776,7 +4776,7 @@ int sched_cgroup_fork(struct task_struct *p, struct kernel_clone_args *kargs)
|
||||
p->sched_class->task_fork(p);
|
||||
raw_spin_unlock_irqrestore(&p->pi_lock, flags);
|
||||
|
||||
return scx_fork(p);
|
||||
return scx_fork(p, kargs);
|
||||
}
|
||||
|
||||
void sched_cancel_fork(struct task_struct *p)
|
||||
|
||||
3999
kernel/sched/ext.c
3999
kernel/sched/ext.c
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@
|
||||
void scx_tick(struct rq *rq);
|
||||
void init_scx_entity(struct sched_ext_entity *scx);
|
||||
void scx_pre_fork(struct task_struct *p);
|
||||
int scx_fork(struct task_struct *p);
|
||||
int scx_fork(struct task_struct *p, struct kernel_clone_args *kargs);
|
||||
void scx_post_fork(struct task_struct *p);
|
||||
void scx_cancel_fork(struct task_struct *p);
|
||||
bool scx_can_stop_tick(struct rq *rq);
|
||||
@@ -44,7 +44,7 @@ bool scx_prio_less(const struct task_struct *a, const struct task_struct *b,
|
||||
|
||||
static inline void scx_tick(struct rq *rq) {}
|
||||
static inline void scx_pre_fork(struct task_struct *p) {}
|
||||
static inline int scx_fork(struct task_struct *p) { return 0; }
|
||||
static inline int scx_fork(struct task_struct *p, struct kernel_clone_args *kargs) { return 0; }
|
||||
static inline void scx_post_fork(struct task_struct *p) {}
|
||||
static inline void scx_cancel_fork(struct task_struct *p) {}
|
||||
static inline u32 scx_cpuperf_target(s32 cpu) { return 0; }
|
||||
|
||||
@@ -368,7 +368,7 @@ void scx_idle_update_selcpu_topology(struct sched_ext_ops *ops)
|
||||
|
||||
/*
|
||||
* Enable NUMA optimization only when there are multiple NUMA domains
|
||||
* among the online CPUs and the NUMA domains don't perfectly overlaps
|
||||
* among the online CPUs and the NUMA domains don't perfectly overlap
|
||||
* with the LLC domains.
|
||||
*
|
||||
* If all CPUs belong to the same NUMA node and the same LLC domain,
|
||||
@@ -424,18 +424,24 @@ static inline bool task_affinity_all(const struct task_struct *p)
|
||||
* - prefer the last used CPU to take advantage of cached data (L1, L2) and
|
||||
* branch prediction optimizations.
|
||||
*
|
||||
* 3. Pick a CPU within the same LLC (Last-Level Cache):
|
||||
* 3. Prefer @prev_cpu's SMT sibling:
|
||||
* - if @prev_cpu is busy and no fully idle core is available, try to
|
||||
* place the task on an idle SMT sibling of @prev_cpu; keeping the
|
||||
* task on the same core makes migration cheaper, preserves L1 cache
|
||||
* locality and reduces wakeup latency.
|
||||
*
|
||||
* 4. Pick a CPU within the same LLC (Last-Level Cache):
|
||||
* - if the above conditions aren't met, pick a CPU that shares the same
|
||||
* LLC, if the LLC domain is a subset of @cpus_allowed, to maintain
|
||||
* cache locality.
|
||||
*
|
||||
* 4. Pick a CPU within the same NUMA node, if enabled:
|
||||
* 5. Pick a CPU within the same NUMA node, if enabled:
|
||||
* - choose a CPU from the same NUMA node, if the node cpumask is a
|
||||
* subset of @cpus_allowed, to reduce memory access latency.
|
||||
*
|
||||
* 5. Pick any idle CPU within the @cpus_allowed domain.
|
||||
* 6. Pick any idle CPU within the @cpus_allowed domain.
|
||||
*
|
||||
* Step 3 and 4 are performed only if the system has, respectively,
|
||||
* Step 4 and 5 are performed only if the system has, respectively,
|
||||
* multiple LLCs / multiple NUMA nodes (see scx_selcpu_topo_llc and
|
||||
* scx_selcpu_topo_numa) and they don't contain the same subset of CPUs.
|
||||
*
|
||||
@@ -616,6 +622,20 @@ s32 scx_select_cpu_dfl(struct task_struct *p, s32 prev_cpu, u64 wake_flags,
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SCHED_SMT
|
||||
/*
|
||||
* Use @prev_cpu's sibling if it's idle.
|
||||
*/
|
||||
if (sched_smt_active()) {
|
||||
for_each_cpu_and(cpu, cpu_smt_mask(prev_cpu), allowed) {
|
||||
if (cpu == prev_cpu)
|
||||
continue;
|
||||
if (scx_idle_test_and_clear_cpu(cpu))
|
||||
goto out_unlock;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Search for any idle CPU in the same LLC domain.
|
||||
*/
|
||||
@@ -767,8 +787,9 @@ void __scx_update_idle(struct rq *rq, bool idle, bool do_notify)
|
||||
* either enqueue() sees the idle bit or update_idle() sees the task
|
||||
* that enqueue() queued.
|
||||
*/
|
||||
if (SCX_HAS_OP(sch, update_idle) && do_notify && !scx_rq_bypassing(rq))
|
||||
SCX_CALL_OP(sch, SCX_KF_REST, update_idle, rq, cpu_of(rq), idle);
|
||||
if (SCX_HAS_OP(sch, update_idle) && do_notify &&
|
||||
!scx_bypassing(sch, cpu_of(rq)))
|
||||
SCX_CALL_OP(sch, update_idle, rq, cpu_of(rq), idle);
|
||||
}
|
||||
|
||||
static void reset_idle_masks(struct sched_ext_ops *ops)
|
||||
@@ -892,8 +913,8 @@ static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
|
||||
s32 prev_cpu, u64 wake_flags,
|
||||
const struct cpumask *allowed, u64 flags)
|
||||
{
|
||||
struct rq *rq;
|
||||
struct rq_flags rf;
|
||||
unsigned long irq_flags;
|
||||
bool we_locked = false;
|
||||
s32 cpu;
|
||||
|
||||
if (!ops_cpu_valid(sch, prev_cpu, NULL))
|
||||
@@ -903,27 +924,20 @@ static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
|
||||
return -EBUSY;
|
||||
|
||||
/*
|
||||
* If called from an unlocked context, acquire the task's rq lock,
|
||||
* so that we can safely access p->cpus_ptr and p->nr_cpus_allowed.
|
||||
* Accessing p->cpus_ptr / p->nr_cpus_allowed needs either @p's rq
|
||||
* lock or @p's pi_lock. Three cases:
|
||||
*
|
||||
* Otherwise, allow to use this kfunc only from ops.select_cpu()
|
||||
* and ops.select_enqueue().
|
||||
* - inside ops.select_cpu(): try_to_wake_up() holds @p's pi_lock.
|
||||
* - other rq-locked SCX op: scx_locked_rq() points at the held rq.
|
||||
* - truly unlocked (UNLOCKED ops, SYSCALL, non-SCX struct_ops):
|
||||
* nothing held, take pi_lock ourselves.
|
||||
*/
|
||||
if (scx_kf_allowed_if_unlocked()) {
|
||||
rq = task_rq_lock(p, &rf);
|
||||
} else {
|
||||
if (!scx_kf_allowed(sch, SCX_KF_SELECT_CPU | SCX_KF_ENQUEUE))
|
||||
return -EPERM;
|
||||
rq = scx_locked_rq();
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate locking correctness to access p->cpus_ptr and
|
||||
* p->nr_cpus_allowed: if we're holding an rq lock, we're safe;
|
||||
* otherwise, assert that p->pi_lock is held.
|
||||
*/
|
||||
if (!rq)
|
||||
if (this_rq()->scx.in_select_cpu) {
|
||||
lockdep_assert_held(&p->pi_lock);
|
||||
} else if (!scx_locked_rq()) {
|
||||
raw_spin_lock_irqsave(&p->pi_lock, irq_flags);
|
||||
we_locked = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* This may also be called from ops.enqueue(), so we need to handle
|
||||
@@ -942,8 +956,8 @@ static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
|
||||
allowed ?: p->cpus_ptr, flags);
|
||||
}
|
||||
|
||||
if (scx_kf_allowed_if_unlocked())
|
||||
task_rq_unlock(rq, p, &rf);
|
||||
if (we_locked)
|
||||
raw_spin_unlock_irqrestore(&p->pi_lock, irq_flags);
|
||||
|
||||
return cpu;
|
||||
}
|
||||
@@ -952,14 +966,15 @@ static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
|
||||
* scx_bpf_cpu_node - Return the NUMA node the given @cpu belongs to, or
|
||||
* trigger an error if @cpu is invalid
|
||||
* @cpu: target CPU
|
||||
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
|
||||
*/
|
||||
__bpf_kfunc int scx_bpf_cpu_node(s32 cpu)
|
||||
__bpf_kfunc s32 scx_bpf_cpu_node(s32 cpu, const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct scx_sched *sch;
|
||||
|
||||
guard(rcu)();
|
||||
|
||||
sch = rcu_dereference(scx_root);
|
||||
sch = scx_prog_sched(aux);
|
||||
if (unlikely(!sch) || !ops_cpu_valid(sch, cpu, NULL))
|
||||
return NUMA_NO_NODE;
|
||||
return cpu_to_node(cpu);
|
||||
@@ -971,6 +986,7 @@ __bpf_kfunc int scx_bpf_cpu_node(s32 cpu)
|
||||
* @prev_cpu: CPU @p was on previously
|
||||
* @wake_flags: %SCX_WAKE_* flags
|
||||
* @is_idle: out parameter indicating whether the returned CPU is idle
|
||||
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
|
||||
*
|
||||
* Can be called from ops.select_cpu(), ops.enqueue(), or from an unlocked
|
||||
* context such as a BPF test_run() call, as long as built-in CPU selection
|
||||
@@ -981,14 +997,15 @@ __bpf_kfunc int scx_bpf_cpu_node(s32 cpu)
|
||||
* currently idle and thus a good candidate for direct dispatching.
|
||||
*/
|
||||
__bpf_kfunc s32 scx_bpf_select_cpu_dfl(struct task_struct *p, s32 prev_cpu,
|
||||
u64 wake_flags, bool *is_idle)
|
||||
u64 wake_flags, bool *is_idle,
|
||||
const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct scx_sched *sch;
|
||||
s32 cpu;
|
||||
|
||||
guard(rcu)();
|
||||
|
||||
sch = rcu_dereference(scx_root);
|
||||
sch = scx_prog_sched(aux);
|
||||
if (unlikely(!sch))
|
||||
return -ENODEV;
|
||||
|
||||
@@ -1016,6 +1033,7 @@ struct scx_bpf_select_cpu_and_args {
|
||||
* @args->prev_cpu: CPU @p was on previously
|
||||
* @args->wake_flags: %SCX_WAKE_* flags
|
||||
* @args->flags: %SCX_PICK_IDLE* flags
|
||||
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
|
||||
*
|
||||
* Wrapper kfunc that takes arguments via struct to work around BPF's 5 argument
|
||||
* limit. BPF programs should use scx_bpf_select_cpu_and() which is provided
|
||||
@@ -1034,13 +1052,14 @@ struct scx_bpf_select_cpu_and_args {
|
||||
*/
|
||||
__bpf_kfunc s32
|
||||
__scx_bpf_select_cpu_and(struct task_struct *p, const struct cpumask *cpus_allowed,
|
||||
struct scx_bpf_select_cpu_and_args *args)
|
||||
struct scx_bpf_select_cpu_and_args *args,
|
||||
const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct scx_sched *sch;
|
||||
|
||||
guard(rcu)();
|
||||
|
||||
sch = rcu_dereference(scx_root);
|
||||
sch = scx_prog_sched(aux);
|
||||
if (unlikely(!sch))
|
||||
return -ENODEV;
|
||||
|
||||
@@ -1062,6 +1081,17 @@ __bpf_kfunc s32 scx_bpf_select_cpu_and(struct task_struct *p, s32 prev_cpu, u64
|
||||
if (unlikely(!sch))
|
||||
return -ENODEV;
|
||||
|
||||
#ifdef CONFIG_EXT_SUB_SCHED
|
||||
/*
|
||||
* Disallow if any sub-scheds are attached. There is no way to tell
|
||||
* which scheduler called us, just error out @p's scheduler.
|
||||
*/
|
||||
if (unlikely(!list_empty(&sch->children))) {
|
||||
scx_error(scx_task_sched(p), "__scx_bpf_select_cpu_and() must be used");
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
return select_cpu_from_kfunc(sch, p, prev_cpu, wake_flags,
|
||||
cpus_allowed, flags);
|
||||
}
|
||||
@@ -1070,18 +1100,20 @@ __bpf_kfunc s32 scx_bpf_select_cpu_and(struct task_struct *p, s32 prev_cpu, u64
|
||||
* scx_bpf_get_idle_cpumask_node - Get a referenced kptr to the
|
||||
* idle-tracking per-CPU cpumask of a target NUMA node.
|
||||
* @node: target NUMA node
|
||||
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
|
||||
*
|
||||
* Returns an empty cpumask if idle tracking is not enabled, if @node is
|
||||
* not valid, or running on a UP kernel. In this case the actual error will
|
||||
* be reported to the BPF scheduler via scx_error().
|
||||
*/
|
||||
__bpf_kfunc const struct cpumask *scx_bpf_get_idle_cpumask_node(int node)
|
||||
__bpf_kfunc const struct cpumask *
|
||||
scx_bpf_get_idle_cpumask_node(s32 node, const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct scx_sched *sch;
|
||||
|
||||
guard(rcu)();
|
||||
|
||||
sch = rcu_dereference(scx_root);
|
||||
sch = scx_prog_sched(aux);
|
||||
if (unlikely(!sch))
|
||||
return cpu_none_mask;
|
||||
|
||||
@@ -1095,17 +1127,18 @@ __bpf_kfunc const struct cpumask *scx_bpf_get_idle_cpumask_node(int node)
|
||||
/**
|
||||
* scx_bpf_get_idle_cpumask - Get a referenced kptr to the idle-tracking
|
||||
* per-CPU cpumask.
|
||||
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
|
||||
*
|
||||
* Returns an empty mask if idle tracking is not enabled, or running on a
|
||||
* UP kernel.
|
||||
*/
|
||||
__bpf_kfunc const struct cpumask *scx_bpf_get_idle_cpumask(void)
|
||||
__bpf_kfunc const struct cpumask *scx_bpf_get_idle_cpumask(const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct scx_sched *sch;
|
||||
|
||||
guard(rcu)();
|
||||
|
||||
sch = rcu_dereference(scx_root);
|
||||
sch = scx_prog_sched(aux);
|
||||
if (unlikely(!sch))
|
||||
return cpu_none_mask;
|
||||
|
||||
@@ -1125,18 +1158,20 @@ __bpf_kfunc const struct cpumask *scx_bpf_get_idle_cpumask(void)
|
||||
* idle-tracking, per-physical-core cpumask of a target NUMA node. Can be
|
||||
* used to determine if an entire physical core is free.
|
||||
* @node: target NUMA node
|
||||
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
|
||||
*
|
||||
* Returns an empty cpumask if idle tracking is not enabled, if @node is
|
||||
* not valid, or running on a UP kernel. In this case the actual error will
|
||||
* be reported to the BPF scheduler via scx_error().
|
||||
*/
|
||||
__bpf_kfunc const struct cpumask *scx_bpf_get_idle_smtmask_node(int node)
|
||||
__bpf_kfunc const struct cpumask *
|
||||
scx_bpf_get_idle_smtmask_node(s32 node, const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct scx_sched *sch;
|
||||
|
||||
guard(rcu)();
|
||||
|
||||
sch = rcu_dereference(scx_root);
|
||||
sch = scx_prog_sched(aux);
|
||||
if (unlikely(!sch))
|
||||
return cpu_none_mask;
|
||||
|
||||
@@ -1154,17 +1189,18 @@ __bpf_kfunc const struct cpumask *scx_bpf_get_idle_smtmask_node(int node)
|
||||
* scx_bpf_get_idle_smtmask - Get a referenced kptr to the idle-tracking,
|
||||
* per-physical-core cpumask. Can be used to determine if an entire physical
|
||||
* core is free.
|
||||
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
|
||||
*
|
||||
* Returns an empty mask if idle tracking is not enabled, or running on a
|
||||
* UP kernel.
|
||||
*/
|
||||
__bpf_kfunc const struct cpumask *scx_bpf_get_idle_smtmask(void)
|
||||
__bpf_kfunc const struct cpumask *scx_bpf_get_idle_smtmask(const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct scx_sched *sch;
|
||||
|
||||
guard(rcu)();
|
||||
|
||||
sch = rcu_dereference(scx_root);
|
||||
sch = scx_prog_sched(aux);
|
||||
if (unlikely(!sch))
|
||||
return cpu_none_mask;
|
||||
|
||||
@@ -1200,6 +1236,7 @@ __bpf_kfunc void scx_bpf_put_idle_cpumask(const struct cpumask *idle_mask)
|
||||
/**
|
||||
* scx_bpf_test_and_clear_cpu_idle - Test and clear @cpu's idle state
|
||||
* @cpu: cpu to test and clear idle for
|
||||
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
|
||||
*
|
||||
* Returns %true if @cpu was idle and its idle state was successfully cleared.
|
||||
* %false otherwise.
|
||||
@@ -1207,13 +1244,13 @@ __bpf_kfunc void scx_bpf_put_idle_cpumask(const struct cpumask *idle_mask)
|
||||
* Unavailable if ops.update_idle() is implemented and
|
||||
* %SCX_OPS_KEEP_BUILTIN_IDLE is not set.
|
||||
*/
|
||||
__bpf_kfunc bool scx_bpf_test_and_clear_cpu_idle(s32 cpu)
|
||||
__bpf_kfunc bool scx_bpf_test_and_clear_cpu_idle(s32 cpu, const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct scx_sched *sch;
|
||||
|
||||
guard(rcu)();
|
||||
|
||||
sch = rcu_dereference(scx_root);
|
||||
sch = scx_prog_sched(aux);
|
||||
if (unlikely(!sch))
|
||||
return false;
|
||||
|
||||
@@ -1231,6 +1268,7 @@ __bpf_kfunc bool scx_bpf_test_and_clear_cpu_idle(s32 cpu)
|
||||
* @cpus_allowed: Allowed cpumask
|
||||
* @node: target NUMA node
|
||||
* @flags: %SCX_PICK_IDLE_* flags
|
||||
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
|
||||
*
|
||||
* Pick and claim an idle cpu in @cpus_allowed from the NUMA node @node.
|
||||
*
|
||||
@@ -1246,13 +1284,14 @@ __bpf_kfunc bool scx_bpf_test_and_clear_cpu_idle(s32 cpu)
|
||||
* %SCX_OPS_BUILTIN_IDLE_PER_NODE is not set.
|
||||
*/
|
||||
__bpf_kfunc s32 scx_bpf_pick_idle_cpu_node(const struct cpumask *cpus_allowed,
|
||||
int node, u64 flags)
|
||||
s32 node, u64 flags,
|
||||
const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct scx_sched *sch;
|
||||
|
||||
guard(rcu)();
|
||||
|
||||
sch = rcu_dereference(scx_root);
|
||||
sch = scx_prog_sched(aux);
|
||||
if (unlikely(!sch))
|
||||
return -ENODEV;
|
||||
|
||||
@@ -1267,6 +1306,7 @@ __bpf_kfunc s32 scx_bpf_pick_idle_cpu_node(const struct cpumask *cpus_allowed,
|
||||
* scx_bpf_pick_idle_cpu - Pick and claim an idle cpu
|
||||
* @cpus_allowed: Allowed cpumask
|
||||
* @flags: %SCX_PICK_IDLE_CPU_* flags
|
||||
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
|
||||
*
|
||||
* Pick and claim an idle cpu in @cpus_allowed. Returns the picked idle cpu
|
||||
* number on success. -%EBUSY if no matching cpu was found.
|
||||
@@ -1286,13 +1326,13 @@ __bpf_kfunc s32 scx_bpf_pick_idle_cpu_node(const struct cpumask *cpus_allowed,
|
||||
* scx_bpf_pick_idle_cpu_node() instead.
|
||||
*/
|
||||
__bpf_kfunc s32 scx_bpf_pick_idle_cpu(const struct cpumask *cpus_allowed,
|
||||
u64 flags)
|
||||
u64 flags, const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct scx_sched *sch;
|
||||
|
||||
guard(rcu)();
|
||||
|
||||
sch = rcu_dereference(scx_root);
|
||||
sch = scx_prog_sched(aux);
|
||||
if (unlikely(!sch))
|
||||
return -ENODEV;
|
||||
|
||||
@@ -1313,6 +1353,7 @@ __bpf_kfunc s32 scx_bpf_pick_idle_cpu(const struct cpumask *cpus_allowed,
|
||||
* @cpus_allowed: Allowed cpumask
|
||||
* @node: target NUMA node
|
||||
* @flags: %SCX_PICK_IDLE_CPU_* flags
|
||||
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
|
||||
*
|
||||
* Pick and claim an idle cpu in @cpus_allowed. If none is available, pick any
|
||||
* CPU in @cpus_allowed. Guaranteed to succeed and returns the picked idle cpu
|
||||
@@ -1329,14 +1370,15 @@ __bpf_kfunc s32 scx_bpf_pick_idle_cpu(const struct cpumask *cpus_allowed,
|
||||
* CPU.
|
||||
*/
|
||||
__bpf_kfunc s32 scx_bpf_pick_any_cpu_node(const struct cpumask *cpus_allowed,
|
||||
int node, u64 flags)
|
||||
s32 node, u64 flags,
|
||||
const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct scx_sched *sch;
|
||||
s32 cpu;
|
||||
|
||||
guard(rcu)();
|
||||
|
||||
sch = rcu_dereference(scx_root);
|
||||
sch = scx_prog_sched(aux);
|
||||
if (unlikely(!sch))
|
||||
return -ENODEV;
|
||||
|
||||
@@ -1362,6 +1404,7 @@ __bpf_kfunc s32 scx_bpf_pick_any_cpu_node(const struct cpumask *cpus_allowed,
|
||||
* scx_bpf_pick_any_cpu - Pick and claim an idle cpu if available or pick any CPU
|
||||
* @cpus_allowed: Allowed cpumask
|
||||
* @flags: %SCX_PICK_IDLE_CPU_* flags
|
||||
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
|
||||
*
|
||||
* Pick and claim an idle cpu in @cpus_allowed. If none is available, pick any
|
||||
* CPU in @cpus_allowed. Guaranteed to succeed and returns the picked idle cpu
|
||||
@@ -1376,14 +1419,14 @@ __bpf_kfunc s32 scx_bpf_pick_any_cpu_node(const struct cpumask *cpus_allowed,
|
||||
* scx_bpf_pick_any_cpu_node() instead.
|
||||
*/
|
||||
__bpf_kfunc s32 scx_bpf_pick_any_cpu(const struct cpumask *cpus_allowed,
|
||||
u64 flags)
|
||||
u64 flags, const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct scx_sched *sch;
|
||||
s32 cpu;
|
||||
|
||||
guard(rcu)();
|
||||
|
||||
sch = rcu_dereference(scx_root);
|
||||
sch = scx_prog_sched(aux);
|
||||
if (unlikely(!sch))
|
||||
return -ENODEV;
|
||||
|
||||
@@ -1408,20 +1451,17 @@ __bpf_kfunc s32 scx_bpf_pick_any_cpu(const struct cpumask *cpus_allowed,
|
||||
__bpf_kfunc_end_defs();
|
||||
|
||||
BTF_KFUNCS_START(scx_kfunc_ids_idle)
|
||||
BTF_ID_FLAGS(func, scx_bpf_cpu_node)
|
||||
BTF_ID_FLAGS(func, scx_bpf_get_idle_cpumask_node, KF_ACQUIRE)
|
||||
BTF_ID_FLAGS(func, scx_bpf_get_idle_cpumask, KF_ACQUIRE)
|
||||
BTF_ID_FLAGS(func, scx_bpf_get_idle_smtmask_node, KF_ACQUIRE)
|
||||
BTF_ID_FLAGS(func, scx_bpf_get_idle_smtmask, KF_ACQUIRE)
|
||||
BTF_ID_FLAGS(func, scx_bpf_cpu_node, KF_IMPLICIT_ARGS)
|
||||
BTF_ID_FLAGS(func, scx_bpf_get_idle_cpumask_node, KF_IMPLICIT_ARGS | KF_ACQUIRE)
|
||||
BTF_ID_FLAGS(func, scx_bpf_get_idle_cpumask, KF_IMPLICIT_ARGS | KF_ACQUIRE)
|
||||
BTF_ID_FLAGS(func, scx_bpf_get_idle_smtmask_node, KF_IMPLICIT_ARGS | KF_ACQUIRE)
|
||||
BTF_ID_FLAGS(func, scx_bpf_get_idle_smtmask, KF_IMPLICIT_ARGS | KF_ACQUIRE)
|
||||
BTF_ID_FLAGS(func, scx_bpf_put_idle_cpumask, KF_RELEASE)
|
||||
BTF_ID_FLAGS(func, scx_bpf_test_and_clear_cpu_idle)
|
||||
BTF_ID_FLAGS(func, scx_bpf_pick_idle_cpu_node, KF_RCU)
|
||||
BTF_ID_FLAGS(func, scx_bpf_pick_idle_cpu, KF_RCU)
|
||||
BTF_ID_FLAGS(func, scx_bpf_pick_any_cpu_node, KF_RCU)
|
||||
BTF_ID_FLAGS(func, scx_bpf_pick_any_cpu, KF_RCU)
|
||||
BTF_ID_FLAGS(func, __scx_bpf_select_cpu_and, KF_RCU)
|
||||
BTF_ID_FLAGS(func, scx_bpf_select_cpu_and, KF_RCU)
|
||||
BTF_ID_FLAGS(func, scx_bpf_select_cpu_dfl, KF_RCU)
|
||||
BTF_ID_FLAGS(func, scx_bpf_test_and_clear_cpu_idle, KF_IMPLICIT_ARGS)
|
||||
BTF_ID_FLAGS(func, scx_bpf_pick_idle_cpu_node, KF_IMPLICIT_ARGS | KF_RCU)
|
||||
BTF_ID_FLAGS(func, scx_bpf_pick_idle_cpu, KF_IMPLICIT_ARGS | KF_RCU)
|
||||
BTF_ID_FLAGS(func, scx_bpf_pick_any_cpu_node, KF_IMPLICIT_ARGS | KF_RCU)
|
||||
BTF_ID_FLAGS(func, scx_bpf_pick_any_cpu, KF_IMPLICIT_ARGS | KF_RCU)
|
||||
BTF_KFUNCS_END(scx_kfunc_ids_idle)
|
||||
|
||||
static const struct btf_kfunc_id_set scx_kfunc_set_idle = {
|
||||
@@ -1429,13 +1469,38 @@ static const struct btf_kfunc_id_set scx_kfunc_set_idle = {
|
||||
.set = &scx_kfunc_ids_idle,
|
||||
};
|
||||
|
||||
/*
|
||||
* The select_cpu kfuncs internally call task_rq_lock() when invoked from an
|
||||
* rq-unlocked context, and thus cannot be safely called from arbitrary tracing
|
||||
* contexts where @p's pi_lock state is unknown. Keep them out of
|
||||
* BPF_PROG_TYPE_TRACING by registering them in their own set which is exposed
|
||||
* only to STRUCT_OPS and SYSCALL programs.
|
||||
*
|
||||
* These kfuncs are also members of scx_kfunc_ids_unlocked (see ext.c) because
|
||||
* they're callable from unlocked contexts in addition to ops.select_cpu() and
|
||||
* ops.enqueue().
|
||||
*/
|
||||
BTF_KFUNCS_START(scx_kfunc_ids_select_cpu)
|
||||
BTF_ID_FLAGS(func, __scx_bpf_select_cpu_and, KF_IMPLICIT_ARGS | KF_RCU)
|
||||
BTF_ID_FLAGS(func, scx_bpf_select_cpu_and, KF_RCU)
|
||||
BTF_ID_FLAGS(func, scx_bpf_select_cpu_dfl, KF_IMPLICIT_ARGS | KF_RCU)
|
||||
BTF_KFUNCS_END(scx_kfunc_ids_select_cpu)
|
||||
|
||||
static const struct btf_kfunc_id_set scx_kfunc_set_select_cpu = {
|
||||
.owner = THIS_MODULE,
|
||||
.set = &scx_kfunc_ids_select_cpu,
|
||||
.filter = scx_kfunc_context_filter,
|
||||
};
|
||||
|
||||
int scx_idle_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &scx_kfunc_set_idle) ||
|
||||
register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &scx_kfunc_set_idle) ||
|
||||
register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &scx_kfunc_set_idle);
|
||||
register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &scx_kfunc_set_idle) ||
|
||||
register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &scx_kfunc_set_select_cpu) ||
|
||||
register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &scx_kfunc_set_select_cpu);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
struct sched_ext_ops;
|
||||
|
||||
extern struct btf_id_set8 scx_kfunc_ids_select_cpu;
|
||||
|
||||
void scx_idle_update_selcpu_topology(struct sched_ext_ops *ops);
|
||||
void scx_idle_init_masks(void);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* Copyright (c) 2025 Tejun Heo <tj@kernel.org>
|
||||
*/
|
||||
#define SCX_OP_IDX(op) (offsetof(struct sched_ext_ops, op) / sizeof(void (*)(void)))
|
||||
#define SCX_MOFF_IDX(moff) ((moff) / sizeof(void (*)(void)))
|
||||
|
||||
enum scx_consts {
|
||||
SCX_DSP_DFL_MAX_BATCH = 32,
|
||||
@@ -24,10 +25,16 @@ enum scx_consts {
|
||||
*/
|
||||
SCX_TASK_ITER_BATCH = 32,
|
||||
|
||||
SCX_BYPASS_HOST_NTH = 2,
|
||||
|
||||
SCX_BYPASS_LB_DFL_INTV_US = 500 * USEC_PER_MSEC,
|
||||
SCX_BYPASS_LB_DONOR_PCT = 125,
|
||||
SCX_BYPASS_LB_MIN_DELTA_DIV = 4,
|
||||
SCX_BYPASS_LB_BATCH = 256,
|
||||
|
||||
SCX_REENQ_LOCAL_MAX_REPEAT = 256,
|
||||
|
||||
SCX_SUB_MAX_DEPTH = 4,
|
||||
};
|
||||
|
||||
enum scx_exit_kind {
|
||||
@@ -38,6 +45,7 @@ enum scx_exit_kind {
|
||||
SCX_EXIT_UNREG_BPF, /* BPF-initiated unregistration */
|
||||
SCX_EXIT_UNREG_KERN, /* kernel-initiated unregistration */
|
||||
SCX_EXIT_SYSRQ, /* requested by 'S' sysrq */
|
||||
SCX_EXIT_PARENT, /* parent exiting */
|
||||
|
||||
SCX_EXIT_ERROR = 1024, /* runtime error, error msg contains details */
|
||||
SCX_EXIT_ERROR_BPF, /* ERROR but triggered through scx_bpf_error() */
|
||||
@@ -62,6 +70,7 @@ enum scx_exit_kind {
|
||||
enum scx_exit_code {
|
||||
/* Reasons */
|
||||
SCX_ECODE_RSN_HOTPLUG = 1LLU << 32,
|
||||
SCX_ECODE_RSN_CGROUP_OFFLINE = 2LLU << 32,
|
||||
|
||||
/* Actions */
|
||||
SCX_ECODE_ACT_RESTART = 1LLU << 48,
|
||||
@@ -175,9 +184,10 @@ enum scx_ops_flags {
|
||||
SCX_OPS_BUILTIN_IDLE_PER_NODE = 1LLU << 6,
|
||||
|
||||
/*
|
||||
* CPU cgroup support flags
|
||||
* If set, %SCX_ENQ_IMMED is assumed to be set on all local DSQ
|
||||
* enqueues.
|
||||
*/
|
||||
SCX_OPS_HAS_CGROUP_WEIGHT = 1LLU << 16, /* DEPRECATED, will be removed on 6.18 */
|
||||
SCX_OPS_ALWAYS_ENQ_IMMED = 1LLU << 7,
|
||||
|
||||
SCX_OPS_ALL_FLAGS = SCX_OPS_KEEP_BUILTIN_IDLE |
|
||||
SCX_OPS_ENQ_LAST |
|
||||
@@ -186,7 +196,7 @@ enum scx_ops_flags {
|
||||
SCX_OPS_ALLOW_QUEUED_WAKEUP |
|
||||
SCX_OPS_SWITCH_PARTIAL |
|
||||
SCX_OPS_BUILTIN_IDLE_PER_NODE |
|
||||
SCX_OPS_HAS_CGROUP_WEIGHT,
|
||||
SCX_OPS_ALWAYS_ENQ_IMMED,
|
||||
|
||||
/* high 8 bits are internal, don't include in SCX_OPS_ALL_FLAGS */
|
||||
__SCX_OPS_INTERNAL_MASK = 0xffLLU << 56,
|
||||
@@ -213,7 +223,7 @@ struct scx_exit_task_args {
|
||||
bool cancelled;
|
||||
};
|
||||
|
||||
/* argument container for ops->cgroup_init() */
|
||||
/* argument container for ops.cgroup_init() */
|
||||
struct scx_cgroup_init_args {
|
||||
/* the weight of the cgroup [1..10000] */
|
||||
u32 weight;
|
||||
@@ -236,12 +246,12 @@ enum scx_cpu_preempt_reason {
|
||||
};
|
||||
|
||||
/*
|
||||
* Argument container for ops->cpu_acquire(). Currently empty, but may be
|
||||
* Argument container for ops.cpu_acquire(). Currently empty, but may be
|
||||
* expanded in the future.
|
||||
*/
|
||||
struct scx_cpu_acquire_args {};
|
||||
|
||||
/* argument container for ops->cpu_release() */
|
||||
/* argument container for ops.cpu_release() */
|
||||
struct scx_cpu_release_args {
|
||||
/* the reason the CPU was preempted */
|
||||
enum scx_cpu_preempt_reason reason;
|
||||
@@ -250,9 +260,7 @@ struct scx_cpu_release_args {
|
||||
struct task_struct *task;
|
||||
};
|
||||
|
||||
/*
|
||||
* Informational context provided to dump operations.
|
||||
*/
|
||||
/* informational context provided to dump operations */
|
||||
struct scx_dump_ctx {
|
||||
enum scx_exit_kind kind;
|
||||
s64 exit_code;
|
||||
@@ -261,6 +269,18 @@ struct scx_dump_ctx {
|
||||
u64 at_jiffies;
|
||||
};
|
||||
|
||||
/* argument container for ops.sub_attach() */
|
||||
struct scx_sub_attach_args {
|
||||
struct sched_ext_ops *ops;
|
||||
char *cgroup_path;
|
||||
};
|
||||
|
||||
/* argument container for ops.sub_detach() */
|
||||
struct scx_sub_detach_args {
|
||||
struct sched_ext_ops *ops;
|
||||
char *cgroup_path;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct sched_ext_ops - Operation table for BPF scheduler implementation
|
||||
*
|
||||
@@ -721,6 +741,20 @@ struct sched_ext_ops {
|
||||
|
||||
#endif /* CONFIG_EXT_GROUP_SCHED */
|
||||
|
||||
/**
|
||||
* @sub_attach: Attach a sub-scheduler
|
||||
* @args: argument container, see the struct definition
|
||||
*
|
||||
* Return 0 to accept the sub-scheduler. -errno to reject.
|
||||
*/
|
||||
s32 (*sub_attach)(struct scx_sub_attach_args *args);
|
||||
|
||||
/**
|
||||
* @sub_detach: Detach a sub-scheduler
|
||||
* @args: argument container, see the struct definition
|
||||
*/
|
||||
void (*sub_detach)(struct scx_sub_detach_args *args);
|
||||
|
||||
/*
|
||||
* All online ops must come before ops.cpu_online().
|
||||
*/
|
||||
@@ -762,6 +796,10 @@ struct sched_ext_ops {
|
||||
*/
|
||||
void (*exit)(struct scx_exit_info *info);
|
||||
|
||||
/*
|
||||
* Data fields must comes after all ops fields.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @dispatch_max_batch: Max nr of tasks that dispatch() can dispatch
|
||||
*/
|
||||
@@ -796,6 +834,12 @@ struct sched_ext_ops {
|
||||
*/
|
||||
u64 hotplug_seq;
|
||||
|
||||
/**
|
||||
* @cgroup_id: When >1, attach the scheduler as a sub-scheduler on the
|
||||
* specified cgroup.
|
||||
*/
|
||||
u64 sub_cgroup_id;
|
||||
|
||||
/**
|
||||
* @name: BPF scheduler's name
|
||||
*
|
||||
@@ -806,7 +850,7 @@ struct sched_ext_ops {
|
||||
char name[SCX_OPS_NAME_LEN];
|
||||
|
||||
/* internal use only, must be NULL */
|
||||
void *priv;
|
||||
void __rcu *priv;
|
||||
};
|
||||
|
||||
enum scx_opi {
|
||||
@@ -853,6 +897,24 @@ struct scx_event_stats {
|
||||
*/
|
||||
s64 SCX_EV_ENQ_SKIP_MIGRATION_DISABLED;
|
||||
|
||||
/*
|
||||
* The number of times a task, enqueued on a local DSQ with
|
||||
* SCX_ENQ_IMMED, was re-enqueued because the CPU was not available for
|
||||
* immediate execution.
|
||||
*/
|
||||
s64 SCX_EV_REENQ_IMMED;
|
||||
|
||||
/*
|
||||
* The number of times a reenq of local DSQ caused another reenq of
|
||||
* local DSQ. This can happen when %SCX_ENQ_IMMED races against a higher
|
||||
* priority class task even if the BPF scheduler always satisfies the
|
||||
* prerequisites for %SCX_ENQ_IMMED at the time of enqueue. However,
|
||||
* that scenario is very unlikely and this count going up regularly
|
||||
* indicates that the BPF scheduler is handling %SCX_ENQ_REENQ
|
||||
* incorrectly causing recursive reenqueues.
|
||||
*/
|
||||
s64 SCX_EV_REENQ_LOCAL_REPEAT;
|
||||
|
||||
/*
|
||||
* Total number of times a task's time slice was refilled with the
|
||||
* default value (SCX_SLICE_DFL).
|
||||
@@ -873,15 +935,77 @@ struct scx_event_stats {
|
||||
* The number of times the bypassing mode has been activated.
|
||||
*/
|
||||
s64 SCX_EV_BYPASS_ACTIVATE;
|
||||
|
||||
/*
|
||||
* The number of times the scheduler attempted to insert a task that it
|
||||
* doesn't own into a DSQ. Such attempts are ignored.
|
||||
*
|
||||
* As BPF schedulers are allowed to ignore dequeues, it's difficult to
|
||||
* tell whether such an attempt is from a scheduler malfunction or an
|
||||
* ignored dequeue around sub-sched enabling. If this count keeps going
|
||||
* up regardless of sub-sched enabling, it likely indicates a bug in the
|
||||
* scheduler.
|
||||
*/
|
||||
s64 SCX_EV_INSERT_NOT_OWNED;
|
||||
|
||||
/*
|
||||
* The number of times tasks from bypassing descendants are scheduled
|
||||
* from sub_bypass_dsq's.
|
||||
*/
|
||||
s64 SCX_EV_SUB_BYPASS_DISPATCH;
|
||||
};
|
||||
|
||||
struct scx_sched;
|
||||
|
||||
enum scx_sched_pcpu_flags {
|
||||
SCX_SCHED_PCPU_BYPASSING = 1LLU << 0,
|
||||
};
|
||||
|
||||
/* dispatch buf */
|
||||
struct scx_dsp_buf_ent {
|
||||
struct task_struct *task;
|
||||
unsigned long qseq;
|
||||
u64 dsq_id;
|
||||
u64 enq_flags;
|
||||
};
|
||||
|
||||
struct scx_dsp_ctx {
|
||||
struct rq *rq;
|
||||
u32 cursor;
|
||||
u32 nr_tasks;
|
||||
struct scx_dsp_buf_ent buf[];
|
||||
};
|
||||
|
||||
struct scx_deferred_reenq_local {
|
||||
struct list_head node;
|
||||
u64 flags;
|
||||
u64 seq;
|
||||
u32 cnt;
|
||||
};
|
||||
|
||||
struct scx_sched_pcpu {
|
||||
struct scx_sched *sch;
|
||||
u64 flags; /* protected by rq lock */
|
||||
|
||||
/*
|
||||
* The event counters are in a per-CPU variable to minimize the
|
||||
* accounting overhead. A system-wide view on the event counter is
|
||||
* constructed when requested by scx_bpf_events().
|
||||
*/
|
||||
struct scx_event_stats event_stats;
|
||||
|
||||
struct scx_deferred_reenq_local deferred_reenq_local;
|
||||
struct scx_dispatch_q bypass_dsq;
|
||||
#ifdef CONFIG_EXT_SUB_SCHED
|
||||
u32 bypass_host_seq;
|
||||
#endif
|
||||
|
||||
/* must be the last entry - contains flex array */
|
||||
struct scx_dsp_ctx dsp_ctx;
|
||||
};
|
||||
|
||||
struct scx_sched_pnode {
|
||||
struct scx_dispatch_q global_dsq;
|
||||
};
|
||||
|
||||
struct scx_sched {
|
||||
@@ -897,15 +1021,50 @@ struct scx_sched {
|
||||
* per-node split isn't sufficient, it can be further split.
|
||||
*/
|
||||
struct rhashtable dsq_hash;
|
||||
struct scx_dispatch_q **global_dsqs;
|
||||
struct scx_sched_pnode **pnode;
|
||||
struct scx_sched_pcpu __percpu *pcpu;
|
||||
|
||||
u64 slice_dfl;
|
||||
u64 bypass_timestamp;
|
||||
s32 bypass_depth;
|
||||
|
||||
/* bypass dispatch path enable state, see bypass_dsp_enabled() */
|
||||
unsigned long bypass_dsp_claim;
|
||||
atomic_t bypass_dsp_enable_depth;
|
||||
|
||||
bool aborting;
|
||||
bool dump_disabled; /* protected by scx_dump_lock */
|
||||
u32 dsp_max_batch;
|
||||
s32 level;
|
||||
|
||||
/*
|
||||
* Updates to the following warned bitfields can race causing RMW issues
|
||||
* but it doesn't really matter.
|
||||
*/
|
||||
bool warned_zero_slice:1;
|
||||
bool warned_deprecated_rq:1;
|
||||
bool warned_unassoc_progs:1;
|
||||
|
||||
struct list_head all;
|
||||
|
||||
#ifdef CONFIG_EXT_SUB_SCHED
|
||||
struct rhash_head hash_node;
|
||||
|
||||
struct list_head children;
|
||||
struct list_head sibling;
|
||||
struct cgroup *cgrp;
|
||||
char *cgrp_path;
|
||||
struct kset *sub_kset;
|
||||
|
||||
bool sub_attached;
|
||||
#endif /* CONFIG_EXT_SUB_SCHED */
|
||||
|
||||
/*
|
||||
* The maximum amount of time in jiffies that a task may be runnable
|
||||
* without being scheduled on a CPU. If this timeout is exceeded, it
|
||||
* will trigger scx_error().
|
||||
*/
|
||||
unsigned long watchdog_timeout;
|
||||
|
||||
atomic_t exit_kind;
|
||||
struct scx_exit_info *exit_info;
|
||||
@@ -913,9 +1072,13 @@ struct scx_sched {
|
||||
struct kobject kobj;
|
||||
|
||||
struct kthread_worker *helper;
|
||||
struct irq_work error_irq_work;
|
||||
struct irq_work disable_irq_work;
|
||||
struct kthread_work disable_work;
|
||||
struct timer_list bypass_lb_timer;
|
||||
struct rcu_work rcu_work;
|
||||
|
||||
/* all ancestors including self */
|
||||
struct scx_sched *ancestors[];
|
||||
};
|
||||
|
||||
enum scx_wake_flags {
|
||||
@@ -942,13 +1105,27 @@ enum scx_enq_flags {
|
||||
SCX_ENQ_PREEMPT = 1LLU << 32,
|
||||
|
||||
/*
|
||||
* The task being enqueued was previously enqueued on the current CPU's
|
||||
* %SCX_DSQ_LOCAL, but was removed from it in a call to the
|
||||
* scx_bpf_reenqueue_local() kfunc. If scx_bpf_reenqueue_local() was
|
||||
* invoked in a ->cpu_release() callback, and the task is again
|
||||
* dispatched back to %SCX_LOCAL_DSQ by this current ->enqueue(), the
|
||||
* task will not be scheduled on the CPU until at least the next invocation
|
||||
* of the ->cpu_acquire() callback.
|
||||
* Only allowed on local DSQs. Guarantees that the task either gets
|
||||
* on the CPU immediately and stays on it, or gets reenqueued back
|
||||
* to the BPF scheduler. It will never linger on a local DSQ or be
|
||||
* silently put back after preemption.
|
||||
*
|
||||
* The protection persists until the next fresh enqueue - it
|
||||
* survives SAVE/RESTORE cycles, slice extensions and preemption.
|
||||
* If the task can't stay on the CPU for any reason, it gets
|
||||
* reenqueued back to the BPF scheduler.
|
||||
*
|
||||
* Exiting and migration-disabled tasks bypass ops.enqueue() and
|
||||
* are placed directly on a local DSQ without IMMED protection
|
||||
* unless %SCX_OPS_ENQ_EXITING and %SCX_OPS_ENQ_MIGRATION_DISABLED
|
||||
* are set respectively.
|
||||
*/
|
||||
SCX_ENQ_IMMED = 1LLU << 33,
|
||||
|
||||
/*
|
||||
* The task being enqueued was previously enqueued on a DSQ, but was
|
||||
* removed and is being re-enqueued. See SCX_TASK_REENQ_* flags to find
|
||||
* out why a given task is being reenqueued.
|
||||
*/
|
||||
SCX_ENQ_REENQ = 1LLU << 40,
|
||||
|
||||
@@ -969,6 +1146,7 @@ enum scx_enq_flags {
|
||||
SCX_ENQ_CLEAR_OPSS = 1LLU << 56,
|
||||
SCX_ENQ_DSQ_PRIQ = 1LLU << 57,
|
||||
SCX_ENQ_NESTED = 1LLU << 58,
|
||||
SCX_ENQ_GDSQ_FALLBACK = 1LLU << 59, /* fell back to global DSQ */
|
||||
};
|
||||
|
||||
enum scx_deq_flags {
|
||||
@@ -982,6 +1160,28 @@ enum scx_deq_flags {
|
||||
* it hasn't been dispatched yet. Dequeue from the BPF side.
|
||||
*/
|
||||
SCX_DEQ_CORE_SCHED_EXEC = 1LLU << 32,
|
||||
|
||||
/*
|
||||
* The task is being dequeued due to a property change (e.g.,
|
||||
* sched_setaffinity(), sched_setscheduler(), set_user_nice(),
|
||||
* etc.).
|
||||
*/
|
||||
SCX_DEQ_SCHED_CHANGE = 1LLU << 33,
|
||||
};
|
||||
|
||||
enum scx_reenq_flags {
|
||||
/* low 16bits determine which tasks should be reenqueued */
|
||||
SCX_REENQ_ANY = 1LLU << 0, /* all tasks */
|
||||
|
||||
__SCX_REENQ_FILTER_MASK = 0xffffLLU,
|
||||
|
||||
__SCX_REENQ_USER_MASK = SCX_REENQ_ANY,
|
||||
|
||||
/* bits 32-35 used by task_should_reenq() */
|
||||
SCX_REENQ_TSR_RQ_OPEN = 1LLU << 32,
|
||||
SCX_REENQ_TSR_NOT_FIRST = 1LLU << 33,
|
||||
|
||||
__SCX_REENQ_TSR_MASK = 0xfLLU << 32,
|
||||
};
|
||||
|
||||
enum scx_pick_idle_cpu_flags {
|
||||
@@ -1161,8 +1361,11 @@ enum scx_ops_state {
|
||||
#define SCX_OPSS_STATE_MASK ((1LU << SCX_OPSS_QSEQ_SHIFT) - 1)
|
||||
#define SCX_OPSS_QSEQ_MASK (~SCX_OPSS_STATE_MASK)
|
||||
|
||||
extern struct scx_sched __rcu *scx_root;
|
||||
DECLARE_PER_CPU(struct rq *, scx_locked_rq_state);
|
||||
|
||||
int scx_kfunc_context_filter(const struct bpf_prog *prog, u32 kfunc_id);
|
||||
|
||||
/*
|
||||
* Return the rq currently locked from an scx callback, or NULL if no rq is
|
||||
* locked.
|
||||
@@ -1172,12 +1375,107 @@ static inline struct rq *scx_locked_rq(void)
|
||||
return __this_cpu_read(scx_locked_rq_state);
|
||||
}
|
||||
|
||||
static inline bool scx_kf_allowed_if_unlocked(void)
|
||||
static inline bool scx_bypassing(struct scx_sched *sch, s32 cpu)
|
||||
{
|
||||
return !current->scx.kf_mask;
|
||||
return unlikely(per_cpu_ptr(sch->pcpu, cpu)->flags &
|
||||
SCX_SCHED_PCPU_BYPASSING);
|
||||
}
|
||||
|
||||
static inline bool scx_rq_bypassing(struct rq *rq)
|
||||
#ifdef CONFIG_EXT_SUB_SCHED
|
||||
/**
|
||||
* scx_task_sched - Find scx_sched scheduling a task
|
||||
* @p: task of interest
|
||||
*
|
||||
* Return @p's scheduler instance. Must be called with @p's pi_lock or rq lock
|
||||
* held.
|
||||
*/
|
||||
static inline struct scx_sched *scx_task_sched(const struct task_struct *p)
|
||||
{
|
||||
return unlikely(rq->scx.flags & SCX_RQ_BYPASSING);
|
||||
return rcu_dereference_protected(p->scx.sched,
|
||||
lockdep_is_held(&p->pi_lock) ||
|
||||
lockdep_is_held(__rq_lockp(task_rq(p))));
|
||||
}
|
||||
|
||||
/**
|
||||
* scx_task_sched_rcu - Find scx_sched scheduling a task
|
||||
* @p: task of interest
|
||||
*
|
||||
* Return @p's scheduler instance. The returned scx_sched is RCU protected.
|
||||
*/
|
||||
static inline struct scx_sched *scx_task_sched_rcu(const struct task_struct *p)
|
||||
{
|
||||
return rcu_dereference_all(p->scx.sched);
|
||||
}
|
||||
|
||||
/**
|
||||
* scx_task_on_sched - Is a task on the specified sched?
|
||||
* @sch: sched to test against
|
||||
* @p: task of interest
|
||||
*
|
||||
* Returns %true if @p is on @sch, %false otherwise.
|
||||
*/
|
||||
static inline bool scx_task_on_sched(struct scx_sched *sch,
|
||||
const struct task_struct *p)
|
||||
{
|
||||
return rcu_access_pointer(p->scx.sched) == sch;
|
||||
}
|
||||
|
||||
/**
|
||||
* scx_prog_sched - Find scx_sched associated with a BPF prog
|
||||
* @aux: aux passed in from BPF to a kfunc
|
||||
*
|
||||
* To be called from kfuncs. Return the scheduler instance associated with the
|
||||
* BPF program given the implicit kfunc argument aux. The returned scx_sched is
|
||||
* RCU protected.
|
||||
*/
|
||||
static inline struct scx_sched *scx_prog_sched(const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct sched_ext_ops *ops;
|
||||
struct scx_sched *root;
|
||||
|
||||
ops = bpf_prog_get_assoc_struct_ops(aux);
|
||||
if (likely(ops))
|
||||
return rcu_dereference_all(ops->priv);
|
||||
|
||||
root = rcu_dereference_all(scx_root);
|
||||
if (root) {
|
||||
/*
|
||||
* COMPAT-v6.19: Schedulers built before sub-sched support was
|
||||
* introduced may have unassociated non-struct_ops programs.
|
||||
*/
|
||||
if (!root->ops.sub_attach)
|
||||
return root;
|
||||
|
||||
if (!root->warned_unassoc_progs) {
|
||||
printk_deferred(KERN_WARNING "sched_ext: Unassociated program %s (id %d)\n",
|
||||
aux->name, aux->id);
|
||||
root->warned_unassoc_progs = true;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#else /* CONFIG_EXT_SUB_SCHED */
|
||||
static inline struct scx_sched *scx_task_sched(const struct task_struct *p)
|
||||
{
|
||||
return rcu_dereference_protected(scx_root,
|
||||
lockdep_is_held(&p->pi_lock) ||
|
||||
lockdep_is_held(__rq_lockp(task_rq(p))));
|
||||
}
|
||||
|
||||
static inline struct scx_sched *scx_task_sched_rcu(const struct task_struct *p)
|
||||
{
|
||||
return rcu_dereference_all(scx_root);
|
||||
}
|
||||
|
||||
static inline bool scx_task_on_sched(struct scx_sched *sch,
|
||||
const struct task_struct *p)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct scx_sched *scx_prog_sched(const struct bpf_prog_aux *aux)
|
||||
{
|
||||
return rcu_dereference_all(scx_root);
|
||||
}
|
||||
#endif /* CONFIG_EXT_SUB_SCHED */
|
||||
|
||||
@@ -783,7 +783,6 @@ enum scx_rq_flags {
|
||||
SCX_RQ_ONLINE = 1 << 0,
|
||||
SCX_RQ_CAN_STOP_TICK = 1 << 1,
|
||||
SCX_RQ_BAL_KEEP = 1 << 3, /* balance decided to keep current */
|
||||
SCX_RQ_BYPASSING = 1 << 4,
|
||||
SCX_RQ_CLK_VALID = 1 << 5, /* RQ clock is fresh and valid */
|
||||
SCX_RQ_BAL_CB_PENDING = 1 << 6, /* must queue a cb after dispatching */
|
||||
|
||||
@@ -799,8 +798,10 @@ struct scx_rq {
|
||||
u64 extra_enq_flags; /* see move_task_to_local_dsq() */
|
||||
u32 nr_running;
|
||||
u32 cpuperf_target; /* [0, SCHED_CAPACITY_SCALE] */
|
||||
bool in_select_cpu;
|
||||
bool cpu_released;
|
||||
u32 flags;
|
||||
u32 nr_immed; /* ENQ_IMMED tasks on local_dsq */
|
||||
u64 clock; /* current per-rq clock -- see scx_bpf_now() */
|
||||
cpumask_var_t cpus_to_kick;
|
||||
cpumask_var_t cpus_to_kick_if_idle;
|
||||
@@ -809,12 +810,17 @@ struct scx_rq {
|
||||
cpumask_var_t cpus_to_sync;
|
||||
bool kick_sync_pending;
|
||||
unsigned long kick_sync;
|
||||
local_t reenq_local_deferred;
|
||||
|
||||
struct task_struct *sub_dispatch_prev;
|
||||
|
||||
raw_spinlock_t deferred_reenq_lock;
|
||||
u64 deferred_reenq_locals_seq;
|
||||
struct list_head deferred_reenq_locals; /* scheds requesting reenq of local DSQ */
|
||||
struct list_head deferred_reenq_users; /* user DSQs requesting reenq */
|
||||
struct balance_callback deferred_bal_cb;
|
||||
struct balance_callback kick_sync_bal_cb;
|
||||
struct irq_work deferred_irq_work;
|
||||
struct irq_work kick_cpus_irq_work;
|
||||
struct scx_dispatch_q bypass_dsq;
|
||||
};
|
||||
#endif /* CONFIG_SCHED_CLASS_EXT */
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
#endif
|
||||
|
||||
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) && !defined(BPF_ARENA_FORCE_ASM)
|
||||
#ifndef __arena
|
||||
#define __arena __attribute__((address_space(1)))
|
||||
#endif
|
||||
#define __arena_global __attribute__((address_space(1)))
|
||||
#define cast_kern(ptr) /* nop for bpf prog. emitted by LLVM */
|
||||
#define cast_user(ptr) /* nop for bpf prog. emitted by LLVM */
|
||||
@@ -81,12 +83,13 @@
|
||||
void __arena* bpf_arena_alloc_pages(void *map, void __arena *addr, __u32 page_cnt,
|
||||
int node_id, __u64 flags) __ksym __weak;
|
||||
void bpf_arena_free_pages(void *map, void __arena *ptr, __u32 page_cnt) __ksym __weak;
|
||||
int bpf_arena_reserve_pages(void *map, void __arena *ptr, __u32 page_cnt) __ksym __weak;
|
||||
|
||||
/*
|
||||
* Note that cond_break can only be portably used in the body of a breakable
|
||||
* construct, whereas can_loop can be used anywhere.
|
||||
*/
|
||||
#ifdef TEST
|
||||
#ifdef SCX_BPF_UNITTEST
|
||||
#define can_loop true
|
||||
#define __cond_break(expr) expr
|
||||
#else
|
||||
@@ -165,7 +168,7 @@ void bpf_arena_free_pages(void *map, void __arena *ptr, __u32 page_cnt) __ksym _
|
||||
})
|
||||
#endif /* __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ */
|
||||
#endif /* __BPF_FEATURE_MAY_GOTO */
|
||||
#endif /* TEST */
|
||||
#endif /* SCX_BPF_UNITTEST */
|
||||
|
||||
#define cond_break __cond_break(break)
|
||||
#define cond_break_label(label) __cond_break(goto label)
|
||||
@@ -173,3 +176,4 @@ void bpf_arena_free_pages(void *map, void __arena *ptr, __u32 page_cnt) __ksym _
|
||||
|
||||
void bpf_preempt_disable(void) __weak __ksym;
|
||||
void bpf_preempt_enable(void) __weak __ksym;
|
||||
ssize_t bpf_arena_mapping_nr_pages(void *p__map) __weak __ksym;
|
||||
|
||||
@@ -291,6 +291,50 @@ BPF_PROG(name, ##args)
|
||||
})
|
||||
#endif /* ARRAY_ELEM_PTR */
|
||||
|
||||
/**
|
||||
* __sink - Hide @expr's value from the compiler and BPF verifier
|
||||
* @expr: The expression whose value should be opacified
|
||||
*
|
||||
* No-op at runtime. The empty inline assembly with a read-write constraint
|
||||
* ("+g") has two effects at compile/verify time:
|
||||
*
|
||||
* 1. Compiler: treats @expr as both read and written, preventing dead-code
|
||||
* elimination and keeping @expr (and any side effects that produced it)
|
||||
* alive.
|
||||
*
|
||||
* 2. BPF verifier: forgets the precise value/range of @expr ("makes it
|
||||
* imprecise"). The verifier normally tracks exact ranges for every register
|
||||
* and stack slot. While useful, precision means each distinct value creates a
|
||||
* separate verifier state. Inside loops this leads to state explosion - each
|
||||
* iteration carries different precise values so states never merge and the
|
||||
* verifier explores every iteration individually.
|
||||
*
|
||||
* Example - preventing loop state explosion::
|
||||
*
|
||||
* u32 nr_intersects = 0, nr_covered = 0;
|
||||
* __sink(nr_intersects);
|
||||
* __sink(nr_covered);
|
||||
* bpf_for(i, 0, nr_nodes) {
|
||||
* if (intersects(cpumask, node_mask[i]))
|
||||
* nr_intersects++;
|
||||
* if (covers(cpumask, node_mask[i]))
|
||||
* nr_covered++;
|
||||
* }
|
||||
*
|
||||
* Without __sink(), the verifier tracks every possible (nr_intersects,
|
||||
* nr_covered) pair across iterations, causing "BPF program is too large". With
|
||||
* __sink(), the values become unknown scalars so all iterations collapse into
|
||||
* one reusable state.
|
||||
*
|
||||
* Example - keeping a reference alive::
|
||||
*
|
||||
* struct task_struct *t = bpf_task_acquire(task);
|
||||
* __sink(t);
|
||||
*
|
||||
* Follows the convention from BPF selftests (bpf_misc.h).
|
||||
*/
|
||||
#define __sink(expr) asm volatile ("" : "+g"(expr))
|
||||
|
||||
/*
|
||||
* BPF declarations and helpers
|
||||
*/
|
||||
@@ -336,6 +380,7 @@ void bpf_task_release(struct task_struct *p) __ksym;
|
||||
|
||||
/* cgroup */
|
||||
struct cgroup *bpf_cgroup_ancestor(struct cgroup *cgrp, int level) __ksym;
|
||||
struct cgroup *bpf_cgroup_acquire(struct cgroup *cgrp) __ksym;
|
||||
void bpf_cgroup_release(struct cgroup *cgrp) __ksym;
|
||||
struct cgroup *bpf_cgroup_from_id(u64 cgid) __ksym;
|
||||
|
||||
@@ -741,6 +786,73 @@ static inline u64 __sqrt_u64(u64 x)
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* ctzll -- Counts trailing zeros in an unsigned long long. If the input value
|
||||
* is zero, the return value is undefined.
|
||||
*/
|
||||
static inline int ctzll(u64 v)
|
||||
{
|
||||
#if (!defined(__BPF__) && defined(__SCX_TARGET_ARCH_x86)) || \
|
||||
(defined(__BPF__) && defined(__clang_major__) && __clang_major__ >= 19)
|
||||
/*
|
||||
* Use the ctz builtin when: (1) building for native x86, or
|
||||
* (2) building for BPF with clang >= 19 (BPF backend supports
|
||||
* the intrinsic from clang 19 onward; earlier versions hit
|
||||
* "unimplemented opcode" in the backend).
|
||||
*/
|
||||
return __builtin_ctzll(v);
|
||||
#else
|
||||
/*
|
||||
* If neither the target architecture nor the toolchains support ctzll,
|
||||
* use software-based emulation. Let's use the De Bruijn sequence-based
|
||||
* approach to find LSB fastly. See the details of De Bruijn sequence:
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/De_Bruijn_sequence
|
||||
* https://www.chessprogramming.org/BitScan#De_Bruijn_Multiplication
|
||||
*/
|
||||
const int lookup_table[64] = {
|
||||
0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4,
|
||||
62, 55, 59, 36, 53, 51, 43, 22, 45, 39, 33, 30, 24, 18, 12, 5,
|
||||
63, 47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, 44, 32, 23, 11,
|
||||
46, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6,
|
||||
};
|
||||
const u64 DEBRUIJN_CONSTANT = 0x03f79d71b4cb0a89ULL;
|
||||
unsigned int index;
|
||||
u64 lowest_bit;
|
||||
const int *lt;
|
||||
|
||||
if (v == 0)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* Isolate the least significant bit (LSB).
|
||||
* For example, if v = 0b...10100, then v & -v = 0b...00100
|
||||
*/
|
||||
lowest_bit = v & -v;
|
||||
|
||||
/*
|
||||
* Each isolated bit produces a unique 6-bit value, guaranteed by the
|
||||
* De Bruijn property. Calculate a unique index into the lookup table
|
||||
* using the magic constant and a right shift.
|
||||
*
|
||||
* Multiplying by the 64-bit constant "spreads out" that 1-bit into a
|
||||
* unique pattern in the top 6 bits. This uniqueness property is
|
||||
* exactly what a De Bruijn sequence guarantees: Every possible 6-bit
|
||||
* pattern (in top bits) occurs exactly once for each LSB position. So,
|
||||
* the constant 0x03f79d71b4cb0a89ULL is carefully chosen to be a
|
||||
* De Bruijn sequence, ensuring no collisions in the table index.
|
||||
*/
|
||||
index = (lowest_bit * DEBRUIJN_CONSTANT) >> 58;
|
||||
|
||||
/*
|
||||
* Lookup in a precomputed table. No collision is guaranteed by the
|
||||
* De Bruijn property.
|
||||
*/
|
||||
lt = MEMBER_VPTR(lookup_table, [index]);
|
||||
return (lt)? *lt : -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a value proportionally scaled to the task's weight.
|
||||
*/
|
||||
@@ -758,6 +870,171 @@ static inline u64 scale_by_task_weight_inverse(const struct task_struct *p, u64
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get a random u64 from the kernel's pseudo-random generator.
|
||||
*/
|
||||
static inline u64 get_prandom_u64()
|
||||
{
|
||||
return ((u64)bpf_get_prandom_u32() << 32) | bpf_get_prandom_u32();
|
||||
}
|
||||
|
||||
/*
|
||||
* Define the shadow structure to avoid a compilation error when
|
||||
* vmlinux.h does not enable necessary kernel configs. The ___local
|
||||
* suffix is a CO-RE convention that tells the loader to match this
|
||||
* against the base struct rq in the kernel. The attribute
|
||||
* preserve_access_index tells the compiler to generate a CO-RE
|
||||
* relocation for these fields.
|
||||
*/
|
||||
struct rq___local {
|
||||
/*
|
||||
* A monotonically increasing clock per CPU. It is rq->clock minus
|
||||
* cumulative IRQ time and hypervisor steal time. Unlike rq->clock,
|
||||
* it does not advance during IRQ processing or hypervisor preemption.
|
||||
* It does advance during idle (the idle task counts as a running task
|
||||
* for this purpose).
|
||||
*/
|
||||
u64 clock_task;
|
||||
/*
|
||||
* Invariant version of clock_task scaled by CPU capacity and
|
||||
* frequency. For example, clock_pelt advances 2x slower on a CPU
|
||||
* with half the capacity.
|
||||
*
|
||||
* At idle exit, rq->clock_pelt jumps forward to resync with
|
||||
* clock_task. The kernel's rq_clock_pelt() corrects for this jump
|
||||
* by subtracting lost_idle_time, yielding a clock that appears
|
||||
* continuous across idle transitions. scx_clock_pelt() mirrors
|
||||
* rq_clock_pelt() by performing the same subtraction.
|
||||
*/
|
||||
u64 clock_pelt;
|
||||
/*
|
||||
* Accumulates the magnitude of each clock_pelt jump at idle exit.
|
||||
* Subtracting this from clock_pelt gives rq_clock_pelt(): a
|
||||
* continuous, capacity-invariant clock suitable for both task
|
||||
* execution time stamping and cross-idle measurements.
|
||||
*/
|
||||
unsigned long lost_idle_time;
|
||||
/*
|
||||
* Shadow of paravirt_steal_clock() (the hypervisor's cumulative
|
||||
* stolen time counter). Stays frozen while the hypervisor preempts
|
||||
* the vCPU; catches up the next time update_rq_clock_task() is
|
||||
* called. The delta is the stolen time not yet subtracted from
|
||||
* clock_task.
|
||||
*
|
||||
* Unlike irqtime->total (a plain kernel-side field), the live stolen
|
||||
* time counter lives in hypervisor-specific shared memory and has no
|
||||
* kernel-side equivalent readable from BPF in a hypervisor-agnostic
|
||||
* way. This field is therefore the only portable BPF-accessible
|
||||
* approximation of cumulative steal time.
|
||||
*
|
||||
* Available only when CONFIG_PARAVIRT_TIME_ACCOUNTING is on.
|
||||
*/
|
||||
u64 prev_steal_time_rq;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
extern struct rq runqueues __ksym;
|
||||
|
||||
/*
|
||||
* Define the shadow structure to avoid a compilation error when
|
||||
* vmlinux.h does not enable necessary kernel configs.
|
||||
*/
|
||||
struct irqtime___local {
|
||||
/*
|
||||
* Cumulative IRQ time counter for this CPU, in nanoseconds. Advances
|
||||
* immediately at the exit of every hardirq and non-ksoftirqd softirq
|
||||
* via irqtime_account_irq(). ksoftirqd time is counted as normal
|
||||
* task time and is NOT included. NMI time is also NOT included.
|
||||
*
|
||||
* The companion field irqtime->sync (struct u64_stats_sync) protects
|
||||
* against 64-bit tearing on 32-bit architectures. On 64-bit kernels,
|
||||
* u64_stats_sync is an empty struct and all seqcount operations are
|
||||
* no-ops, so a plain BPF_CORE_READ of this field is safe.
|
||||
*
|
||||
* Available only when CONFIG_IRQ_TIME_ACCOUNTING is on.
|
||||
*/
|
||||
u64 total;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
/*
|
||||
* cpu_irqtime is a per-CPU variable defined only when
|
||||
* CONFIG_IRQ_TIME_ACCOUNTING is on. Declare it as __weak so the BPF
|
||||
* loader sets its address to 0 (rather than failing) when the symbol
|
||||
* is absent from the running kernel.
|
||||
*/
|
||||
extern struct irqtime___local cpu_irqtime __ksym __weak;
|
||||
|
||||
static inline struct rq___local *get_current_rq(u32 cpu)
|
||||
{
|
||||
/*
|
||||
* This is a workaround to get an rq pointer since we decided to
|
||||
* deprecate scx_bpf_cpu_rq().
|
||||
*
|
||||
* WARNING: The caller must hold the rq lock for @cpu. This is
|
||||
* guaranteed when called from scheduling callbacks (ops.running,
|
||||
* ops.stopping, ops.enqueue, ops.dequeue, ops.dispatch, etc.).
|
||||
* There is no runtime check available in BPF for kernel spinlock
|
||||
* state — correctness is enforced by calling context only.
|
||||
*/
|
||||
return (void *)bpf_per_cpu_ptr(&runqueues, cpu);
|
||||
}
|
||||
|
||||
static inline u64 scx_clock_task(u32 cpu)
|
||||
{
|
||||
struct rq___local *rq = get_current_rq(cpu);
|
||||
|
||||
/* Equivalent to the kernel's rq_clock_task(). */
|
||||
return rq ? rq->clock_task : 0;
|
||||
}
|
||||
|
||||
static inline u64 scx_clock_pelt(u32 cpu)
|
||||
{
|
||||
struct rq___local *rq = get_current_rq(cpu);
|
||||
|
||||
/*
|
||||
* Equivalent to the kernel's rq_clock_pelt(): subtracts
|
||||
* lost_idle_time from clock_pelt to absorb the jump that occurs
|
||||
* when clock_pelt resyncs with clock_task at idle exit. The result
|
||||
* is a continuous, capacity-invariant clock safe for both task
|
||||
* execution time stamping and cross-idle measurements.
|
||||
*/
|
||||
return rq ? (rq->clock_pelt - rq->lost_idle_time) : 0;
|
||||
}
|
||||
|
||||
static inline u64 scx_clock_virt(u32 cpu)
|
||||
{
|
||||
struct rq___local *rq;
|
||||
|
||||
/*
|
||||
* Check field existence before calling get_current_rq() so we avoid
|
||||
* the per_cpu lookup entirely on kernels built without
|
||||
* CONFIG_PARAVIRT_TIME_ACCOUNTING.
|
||||
*/
|
||||
if (!bpf_core_field_exists(((struct rq___local *)0)->prev_steal_time_rq))
|
||||
return 0;
|
||||
|
||||
/* Lagging shadow of the kernel's paravirt_steal_clock(). */
|
||||
rq = get_current_rq(cpu);
|
||||
return rq ? BPF_CORE_READ(rq, prev_steal_time_rq) : 0;
|
||||
}
|
||||
|
||||
static inline u64 scx_clock_irq(u32 cpu)
|
||||
{
|
||||
struct irqtime___local *irqt;
|
||||
|
||||
/*
|
||||
* bpf_core_type_exists() resolves at load time: if struct irqtime is
|
||||
* absent from kernel BTF (CONFIG_IRQ_TIME_ACCOUNTING off), the loader
|
||||
* patches this into an unconditional return 0, making the
|
||||
* bpf_per_cpu_ptr() call below dead code that the verifier never sees.
|
||||
*/
|
||||
if (!bpf_core_type_exists(struct irqtime___local))
|
||||
return 0;
|
||||
|
||||
/* Equivalent to the kernel's irq_time_read(). */
|
||||
irqt = bpf_per_cpu_ptr(&cpu_irqtime, cpu);
|
||||
return irqt ? BPF_CORE_READ(irqt, total) : 0;
|
||||
}
|
||||
|
||||
#include "compat.bpf.h"
|
||||
#include "enums.bpf.h"
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ typedef int64_t s64;
|
||||
bpf_map__set_value_size((__skel)->maps.elfsec##_##arr, \
|
||||
sizeof((__skel)->elfsec##_##arr->arr[0]) * (n)); \
|
||||
(__skel)->elfsec##_##arr = \
|
||||
(typeof((__skel)->elfsec##_##arr)) \
|
||||
bpf_map__initial_value((__skel)->maps.elfsec##_##arr, &__sz); \
|
||||
} while (0)
|
||||
|
||||
@@ -74,10 +75,6 @@ typedef int64_t s64;
|
||||
#include "compat.h"
|
||||
#include "enums.h"
|
||||
|
||||
/* not available when building kernel tools/sched_ext */
|
||||
#if __has_include(<lib/sdt_task_defs.h>)
|
||||
#include "bpf_arena_common.h"
|
||||
#include <lib/sdt_task_defs.h>
|
||||
#endif
|
||||
|
||||
#endif /* __SCHED_EXT_COMMON_H */
|
||||
|
||||
@@ -28,8 +28,11 @@ struct cgroup *scx_bpf_task_cgroup___new(struct task_struct *p) __ksym __weak;
|
||||
*
|
||||
* scx_bpf_dispatch_from_dsq() and friends were added during v6.12 by
|
||||
* 4c30f5ce4f7a ("sched_ext: Implement scx_bpf_dispatch[_vtime]_from_dsq()").
|
||||
*
|
||||
* v7.1: scx_bpf_dsq_move_to_local___v2() to add @enq_flags.
|
||||
*/
|
||||
bool scx_bpf_dsq_move_to_local___new(u64 dsq_id) __ksym __weak;
|
||||
bool scx_bpf_dsq_move_to_local___v2(u64 dsq_id, u64 enq_flags) __ksym __weak;
|
||||
bool scx_bpf_dsq_move_to_local___v1(u64 dsq_id) __ksym __weak;
|
||||
void scx_bpf_dsq_move_set_slice___new(struct bpf_iter_scx_dsq *it__iter, u64 slice) __ksym __weak;
|
||||
void scx_bpf_dsq_move_set_vtime___new(struct bpf_iter_scx_dsq *it__iter, u64 vtime) __ksym __weak;
|
||||
bool scx_bpf_dsq_move___new(struct bpf_iter_scx_dsq *it__iter, struct task_struct *p, u64 dsq_id, u64 enq_flags) __ksym __weak;
|
||||
@@ -41,10 +44,12 @@ void scx_bpf_dispatch_from_dsq_set_vtime___old(struct bpf_iter_scx_dsq *it__iter
|
||||
bool scx_bpf_dispatch_from_dsq___old(struct bpf_iter_scx_dsq *it__iter, struct task_struct *p, u64 dsq_id, u64 enq_flags) __ksym __weak;
|
||||
bool scx_bpf_dispatch_vtime_from_dsq___old(struct bpf_iter_scx_dsq *it__iter, struct task_struct *p, u64 dsq_id, u64 enq_flags) __ksym __weak;
|
||||
|
||||
#define scx_bpf_dsq_move_to_local(dsq_id) \
|
||||
(bpf_ksym_exists(scx_bpf_dsq_move_to_local___new) ? \
|
||||
scx_bpf_dsq_move_to_local___new((dsq_id)) : \
|
||||
scx_bpf_consume___old((dsq_id)))
|
||||
#define scx_bpf_dsq_move_to_local(dsq_id, enq_flags) \
|
||||
(bpf_ksym_exists(scx_bpf_dsq_move_to_local___v2) ? \
|
||||
scx_bpf_dsq_move_to_local___v2((dsq_id), (enq_flags)) : \
|
||||
(bpf_ksym_exists(scx_bpf_dsq_move_to_local___v1) ? \
|
||||
scx_bpf_dsq_move_to_local___v1((dsq_id)) : \
|
||||
scx_bpf_consume___old((dsq_id))))
|
||||
|
||||
#define scx_bpf_dsq_move_set_slice(it__iter, slice) \
|
||||
(bpf_ksym_exists(scx_bpf_dsq_move_set_slice___new) ? \
|
||||
@@ -103,6 +108,19 @@ static inline struct task_struct *__COMPAT_scx_bpf_dsq_peek(u64 dsq_id)
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
* v7.1: scx_bpf_sub_dispatch() for sub-sched dispatch. Preserve until
|
||||
* we drop the compat layer for older kernels that lack the kfunc.
|
||||
*/
|
||||
bool scx_bpf_sub_dispatch___compat(u64 cgroup_id) __ksym __weak;
|
||||
|
||||
static inline bool scx_bpf_sub_dispatch(u64 cgroup_id)
|
||||
{
|
||||
if (bpf_ksym_exists(scx_bpf_sub_dispatch___compat))
|
||||
return scx_bpf_sub_dispatch___compat(cgroup_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* __COMPAT_is_enq_cpu_selected - Test if SCX_ENQ_CPU_SELECTED is on
|
||||
* in a compatible way. We will preserve this __COMPAT helper until v6.16.
|
||||
@@ -266,6 +284,14 @@ scx_bpf_select_cpu_and(struct task_struct *p, s32 prev_cpu, u64 wake_flags,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* scx_bpf_select_cpu_and() is now an inline wrapper. Use this instead of
|
||||
* bpf_ksym_exists(scx_bpf_select_cpu_and) to test availability.
|
||||
*/
|
||||
#define __COMPAT_HAS_scx_bpf_select_cpu_and \
|
||||
(bpf_core_type_exists(struct scx_bpf_select_cpu_and_args) || \
|
||||
bpf_ksym_exists(scx_bpf_select_cpu_and___compat))
|
||||
|
||||
/**
|
||||
* scx_bpf_dsq_insert_vtime - Insert a task into the vtime priority queue of a DSQ
|
||||
* @p: task_struct to insert
|
||||
@@ -375,6 +401,27 @@ static inline void scx_bpf_reenqueue_local(void)
|
||||
scx_bpf_reenqueue_local___v1();
|
||||
}
|
||||
|
||||
/*
|
||||
* v6.20: New scx_bpf_dsq_reenq() that allows re-enqueues on more DSQs. This
|
||||
* will eventually deprecate scx_bpf_reenqueue_local().
|
||||
*/
|
||||
void scx_bpf_dsq_reenq___compat(u64 dsq_id, u64 reenq_flags, const struct bpf_prog_aux *aux__prog) __ksym __weak;
|
||||
|
||||
static inline bool __COMPAT_has_generic_reenq(void)
|
||||
{
|
||||
return bpf_ksym_exists(scx_bpf_dsq_reenq___compat);
|
||||
}
|
||||
|
||||
static inline void scx_bpf_dsq_reenq(u64 dsq_id, u64 reenq_flags)
|
||||
{
|
||||
if (bpf_ksym_exists(scx_bpf_dsq_reenq___compat))
|
||||
scx_bpf_dsq_reenq___compat(dsq_id, reenq_flags, NULL);
|
||||
else if (dsq_id == SCX_DSQ_LOCAL && reenq_flags == 0)
|
||||
scx_bpf_reenqueue_local();
|
||||
else
|
||||
scx_bpf_error("kernel too old to reenqueue foreign local or user DSQs");
|
||||
}
|
||||
|
||||
/*
|
||||
* Define sched_ext_ops. This may be expanded to define multiple variants for
|
||||
* backward compatibility. See compat.h::SCX_OPS_LOAD/ATTACH().
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#define __SCX_COMPAT_H
|
||||
|
||||
#include <bpf/btf.h>
|
||||
#include <bpf/libbpf.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
@@ -115,6 +116,7 @@ static inline bool __COMPAT_struct_has_field(const char *type, const char *field
|
||||
#define SCX_OPS_ENQ_MIGRATION_DISABLED SCX_OPS_FLAG(SCX_OPS_ENQ_MIGRATION_DISABLED)
|
||||
#define SCX_OPS_ALLOW_QUEUED_WAKEUP SCX_OPS_FLAG(SCX_OPS_ALLOW_QUEUED_WAKEUP)
|
||||
#define SCX_OPS_BUILTIN_IDLE_PER_NODE SCX_OPS_FLAG(SCX_OPS_BUILTIN_IDLE_PER_NODE)
|
||||
#define SCX_OPS_ALWAYS_ENQ_IMMED SCX_OPS_FLAG(SCX_OPS_ALWAYS_ENQ_IMMED)
|
||||
|
||||
#define SCX_PICK_IDLE_FLAG(name) __COMPAT_ENUM_OR_ZERO("scx_pick_idle_cpu_flags", #name)
|
||||
|
||||
@@ -158,6 +160,7 @@ static inline long scx_hotplug_seq(void)
|
||||
* COMPAT:
|
||||
* - v6.17: ops.cgroup_set_bandwidth()
|
||||
* - v6.19: ops.cgroup_set_idle()
|
||||
* - v7.1: ops.sub_attach(), ops.sub_detach(), ops.sub_cgroup_id
|
||||
*/
|
||||
#define SCX_OPS_OPEN(__ops_name, __scx_name) ({ \
|
||||
struct __scx_name *__skel; \
|
||||
@@ -179,18 +182,65 @@ static inline long scx_hotplug_seq(void)
|
||||
fprintf(stderr, "WARNING: kernel doesn't support ops.cgroup_set_idle()\n"); \
|
||||
__skel->struct_ops.__ops_name->cgroup_set_idle = NULL; \
|
||||
} \
|
||||
if (__skel->struct_ops.__ops_name->sub_attach && \
|
||||
!__COMPAT_struct_has_field("sched_ext_ops", "sub_attach")) { \
|
||||
fprintf(stderr, "WARNING: kernel doesn't support ops.sub_attach()\n"); \
|
||||
__skel->struct_ops.__ops_name->sub_attach = NULL; \
|
||||
} \
|
||||
if (__skel->struct_ops.__ops_name->sub_detach && \
|
||||
!__COMPAT_struct_has_field("sched_ext_ops", "sub_detach")) { \
|
||||
fprintf(stderr, "WARNING: kernel doesn't support ops.sub_detach()\n"); \
|
||||
__skel->struct_ops.__ops_name->sub_detach = NULL; \
|
||||
} \
|
||||
if (__skel->struct_ops.__ops_name->sub_cgroup_id > 0 && \
|
||||
!__COMPAT_struct_has_field("sched_ext_ops", "sub_cgroup_id")) { \
|
||||
fprintf(stderr, "WARNING: kernel doesn't support ops.sub_cgroup_id\n"); \
|
||||
__skel->struct_ops.__ops_name->sub_cgroup_id = 0; \
|
||||
} \
|
||||
__skel; \
|
||||
})
|
||||
|
||||
/*
|
||||
* Associate non-struct_ops BPF programs with the scheduler's struct_ops map so
|
||||
* that scx_prog_sched() can determine which scheduler a BPF program belongs
|
||||
* to. Requires libbpf >= 1.7.
|
||||
*/
|
||||
#if LIBBPF_MAJOR_VERSION > 1 || \
|
||||
(LIBBPF_MAJOR_VERSION == 1 && LIBBPF_MINOR_VERSION >= 7)
|
||||
static inline void __scx_ops_assoc_prog(struct bpf_program *prog,
|
||||
struct bpf_map *map,
|
||||
const char *ops_name)
|
||||
{
|
||||
s32 err = bpf_program__assoc_struct_ops(prog, map, NULL);
|
||||
if (err)
|
||||
fprintf(stderr,
|
||||
"ERROR: Failed to associate %s with %s: %d\n",
|
||||
bpf_program__name(prog), ops_name, err);
|
||||
}
|
||||
#else
|
||||
static inline void __scx_ops_assoc_prog(struct bpf_program *prog,
|
||||
struct bpf_map *map,
|
||||
const char *ops_name)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#define SCX_OPS_LOAD(__skel, __ops_name, __scx_name, __uei_name) ({ \
|
||||
struct bpf_program *__prog; \
|
||||
UEI_SET_SIZE(__skel, __ops_name, __uei_name); \
|
||||
SCX_BUG_ON(__scx_name##__load((__skel)), "Failed to load skel"); \
|
||||
bpf_object__for_each_program(__prog, (__skel)->obj) { \
|
||||
if (bpf_program__type(__prog) == BPF_PROG_TYPE_STRUCT_OPS) \
|
||||
continue; \
|
||||
__scx_ops_assoc_prog(__prog, (__skel)->maps.__ops_name, \
|
||||
#__ops_name); \
|
||||
} \
|
||||
})
|
||||
|
||||
/*
|
||||
* New versions of bpftool now emit additional link placeholders for BPF maps,
|
||||
* and set up BPF skeleton in such a way that libbpf will auto-attach BPF maps
|
||||
* automatically, assumming libbpf is recent enough (v1.5+). Old libbpf will do
|
||||
* automatically, assuming libbpf is recent enough (v1.5+). Old libbpf will do
|
||||
* nothing with those links and won't attempt to auto-attach maps.
|
||||
*
|
||||
* To maintain compatibility with older libbpf while avoiding trying to attach
|
||||
|
||||
@@ -14,18 +14,27 @@
|
||||
#define HAVE_SCX_EXIT_MSG_LEN
|
||||
#define HAVE_SCX_EXIT_DUMP_DFL_LEN
|
||||
#define HAVE_SCX_CPUPERF_ONE
|
||||
#define HAVE_SCX_OPS_TASK_ITER_BATCH
|
||||
#define HAVE_SCX_TASK_ITER_BATCH
|
||||
#define HAVE_SCX_BYPASS_HOST_NTH
|
||||
#define HAVE_SCX_BYPASS_LB_DFL_INTV_US
|
||||
#define HAVE_SCX_BYPASS_LB_DONOR_PCT
|
||||
#define HAVE_SCX_BYPASS_LB_MIN_DELTA_DIV
|
||||
#define HAVE_SCX_BYPASS_LB_BATCH
|
||||
#define HAVE_SCX_REENQ_LOCAL_MAX_REPEAT
|
||||
#define HAVE_SCX_SUB_MAX_DEPTH
|
||||
#define HAVE_SCX_CPU_PREEMPT_RT
|
||||
#define HAVE_SCX_CPU_PREEMPT_DL
|
||||
#define HAVE_SCX_CPU_PREEMPT_STOP
|
||||
#define HAVE_SCX_CPU_PREEMPT_UNKNOWN
|
||||
#define HAVE_SCX_DEQ_SLEEP
|
||||
#define HAVE_SCX_DEQ_CORE_SCHED_EXEC
|
||||
#define HAVE_SCX_DEQ_SCHED_CHANGE
|
||||
#define HAVE_SCX_DSQ_FLAG_BUILTIN
|
||||
#define HAVE_SCX_DSQ_FLAG_LOCAL_ON
|
||||
#define HAVE_SCX_DSQ_INVALID
|
||||
#define HAVE_SCX_DSQ_GLOBAL
|
||||
#define HAVE_SCX_DSQ_LOCAL
|
||||
#define HAVE_SCX_DSQ_BYPASS
|
||||
#define HAVE_SCX_DSQ_LOCAL_ON
|
||||
#define HAVE_SCX_DSQ_LOCAL_CPU_MASK
|
||||
#define HAVE_SCX_DSQ_ITER_REV
|
||||
@@ -35,31 +44,55 @@
|
||||
#define HAVE___SCX_DSQ_ITER_ALL_FLAGS
|
||||
#define HAVE_SCX_DSQ_LNODE_ITER_CURSOR
|
||||
#define HAVE___SCX_DSQ_LNODE_PRIV_SHIFT
|
||||
#define HAVE_SCX_ENABLING
|
||||
#define HAVE_SCX_ENABLED
|
||||
#define HAVE_SCX_DISABLING
|
||||
#define HAVE_SCX_DISABLED
|
||||
#define HAVE_SCX_ENQ_WAKEUP
|
||||
#define HAVE_SCX_ENQ_HEAD
|
||||
#define HAVE_SCX_ENQ_CPU_SELECTED
|
||||
#define HAVE_SCX_ENQ_PREEMPT
|
||||
#define HAVE_SCX_ENQ_IMMED
|
||||
#define HAVE_SCX_ENQ_REENQ
|
||||
#define HAVE_SCX_ENQ_LAST
|
||||
#define HAVE___SCX_ENQ_INTERNAL_MASK
|
||||
#define HAVE_SCX_ENQ_CLEAR_OPSS
|
||||
#define HAVE_SCX_ENQ_DSQ_PRIQ
|
||||
#define HAVE_SCX_ENQ_NESTED
|
||||
#define HAVE_SCX_ENQ_GDSQ_FALLBACK
|
||||
#define HAVE_SCX_TASK_DSQ_ON_PRIQ
|
||||
#define HAVE_SCX_TASK_QUEUED
|
||||
#define HAVE_SCX_TASK_IN_CUSTODY
|
||||
#define HAVE_SCX_TASK_RESET_RUNNABLE_AT
|
||||
#define HAVE_SCX_TASK_DEQD_FOR_SLEEP
|
||||
#define HAVE_SCX_TASK_SUB_INIT
|
||||
#define HAVE_SCX_TASK_IMMED
|
||||
#define HAVE_SCX_TASK_STATE_SHIFT
|
||||
#define HAVE_SCX_TASK_STATE_BITS
|
||||
#define HAVE_SCX_TASK_STATE_MASK
|
||||
#define HAVE_SCX_TASK_NONE
|
||||
#define HAVE_SCX_TASK_INIT
|
||||
#define HAVE_SCX_TASK_READY
|
||||
#define HAVE_SCX_TASK_ENABLED
|
||||
#define HAVE_SCX_TASK_REENQ_REASON_SHIFT
|
||||
#define HAVE_SCX_TASK_REENQ_REASON_BITS
|
||||
#define HAVE_SCX_TASK_REENQ_REASON_MASK
|
||||
#define HAVE_SCX_TASK_REENQ_NONE
|
||||
#define HAVE_SCX_TASK_REENQ_KFUNC
|
||||
#define HAVE_SCX_TASK_REENQ_IMMED
|
||||
#define HAVE_SCX_TASK_REENQ_PREEMPTED
|
||||
#define HAVE_SCX_TASK_CURSOR
|
||||
#define HAVE_SCX_ECODE_RSN_HOTPLUG
|
||||
#define HAVE_SCX_ECODE_RSN_CGROUP_OFFLINE
|
||||
#define HAVE_SCX_ECODE_ACT_RESTART
|
||||
#define HAVE_SCX_EFLAG_INITIALIZED
|
||||
#define HAVE_SCX_EXIT_NONE
|
||||
#define HAVE_SCX_EXIT_DONE
|
||||
#define HAVE_SCX_EXIT_UNREG
|
||||
#define HAVE_SCX_EXIT_UNREG_BPF
|
||||
#define HAVE_SCX_EXIT_UNREG_KERN
|
||||
#define HAVE_SCX_EXIT_SYSRQ
|
||||
#define HAVE_SCX_EXIT_PARENT
|
||||
#define HAVE_SCX_EXIT_ERROR
|
||||
#define HAVE_SCX_EXIT_ERROR_BPF
|
||||
#define HAVE_SCX_EXIT_ERROR_STALL
|
||||
@@ -80,40 +113,42 @@
|
||||
#define HAVE_SCX_OPI_CPU_HOTPLUG_BEGIN
|
||||
#define HAVE_SCX_OPI_CPU_HOTPLUG_END
|
||||
#define HAVE_SCX_OPI_END
|
||||
#define HAVE_SCX_OPS_ENABLING
|
||||
#define HAVE_SCX_OPS_ENABLED
|
||||
#define HAVE_SCX_OPS_DISABLING
|
||||
#define HAVE_SCX_OPS_DISABLED
|
||||
#define HAVE_SCX_OPS_KEEP_BUILTIN_IDLE
|
||||
#define HAVE_SCX_OPS_ENQ_LAST
|
||||
#define HAVE_SCX_OPS_ENQ_EXITING
|
||||
#define HAVE_SCX_OPS_SWITCH_PARTIAL
|
||||
#define HAVE_SCX_OPS_ENQ_MIGRATION_DISABLED
|
||||
#define HAVE_SCX_OPS_ALLOW_QUEUED_WAKEUP
|
||||
#define HAVE_SCX_OPS_HAS_CGROUP_WEIGHT
|
||||
#define HAVE_SCX_OPS_BUILTIN_IDLE_PER_NODE
|
||||
#define HAVE_SCX_OPS_ALWAYS_ENQ_IMMED
|
||||
#define HAVE_SCX_OPS_ALL_FLAGS
|
||||
#define HAVE___SCX_OPS_INTERNAL_MASK
|
||||
#define HAVE_SCX_OPS_HAS_CPU_PREEMPT
|
||||
#define HAVE_SCX_OPSS_NONE
|
||||
#define HAVE_SCX_OPSS_QUEUEING
|
||||
#define HAVE_SCX_OPSS_QUEUED
|
||||
#define HAVE_SCX_OPSS_DISPATCHING
|
||||
#define HAVE_SCX_OPSS_QSEQ_SHIFT
|
||||
#define HAVE_SCX_PICK_IDLE_CORE
|
||||
#define HAVE_SCX_PICK_IDLE_IN_NODE
|
||||
#define HAVE_SCX_OPS_NAME_LEN
|
||||
#define HAVE_SCX_SLICE_DFL
|
||||
#define HAVE_SCX_SLICE_BYPASS
|
||||
#define HAVE_SCX_SLICE_INF
|
||||
#define HAVE_SCX_REENQ_ANY
|
||||
#define HAVE___SCX_REENQ_FILTER_MASK
|
||||
#define HAVE___SCX_REENQ_USER_MASK
|
||||
#define HAVE_SCX_REENQ_TSR_RQ_OPEN
|
||||
#define HAVE_SCX_REENQ_TSR_NOT_FIRST
|
||||
#define HAVE___SCX_REENQ_TSR_MASK
|
||||
#define HAVE_SCX_RQ_ONLINE
|
||||
#define HAVE_SCX_RQ_CAN_STOP_TICK
|
||||
#define HAVE_SCX_RQ_BAL_PENDING
|
||||
#define HAVE_SCX_RQ_BAL_KEEP
|
||||
#define HAVE_SCX_RQ_BYPASSING
|
||||
#define HAVE_SCX_RQ_CLK_VALID
|
||||
#define HAVE_SCX_RQ_BAL_CB_PENDING
|
||||
#define HAVE_SCX_RQ_IN_WAKEUP
|
||||
#define HAVE_SCX_RQ_IN_BALANCE
|
||||
#define HAVE_SCX_TASK_NONE
|
||||
#define HAVE_SCX_TASK_INIT
|
||||
#define HAVE_SCX_TASK_READY
|
||||
#define HAVE_SCX_TASK_ENABLED
|
||||
#define HAVE_SCX_TASK_NR_STATES
|
||||
#define HAVE_SCX_SCHED_PCPU_BYPASSING
|
||||
#define HAVE_SCX_TG_ONLINE
|
||||
#define HAVE_SCX_TG_INITED
|
||||
#define HAVE_SCX_WAKE_FORK
|
||||
|
||||
@@ -67,6 +67,12 @@ const volatile u64 __SCX_TASK_RESET_RUNNABLE_AT __weak;
|
||||
const volatile u64 __SCX_TASK_DEQD_FOR_SLEEP __weak;
|
||||
#define SCX_TASK_DEQD_FOR_SLEEP __SCX_TASK_DEQD_FOR_SLEEP
|
||||
|
||||
const volatile u64 __SCX_TASK_SUB_INIT __weak;
|
||||
#define SCX_TASK_SUB_INIT __SCX_TASK_SUB_INIT
|
||||
|
||||
const volatile u64 __SCX_TASK_IMMED __weak;
|
||||
#define SCX_TASK_IMMED __SCX_TASK_IMMED
|
||||
|
||||
const volatile u64 __SCX_TASK_STATE_SHIFT __weak;
|
||||
#define SCX_TASK_STATE_SHIFT __SCX_TASK_STATE_SHIFT
|
||||
|
||||
@@ -115,6 +121,9 @@ const volatile u64 __SCX_ENQ_HEAD __weak;
|
||||
const volatile u64 __SCX_ENQ_PREEMPT __weak;
|
||||
#define SCX_ENQ_PREEMPT __SCX_ENQ_PREEMPT
|
||||
|
||||
const volatile u64 __SCX_ENQ_IMMED __weak;
|
||||
#define SCX_ENQ_IMMED __SCX_ENQ_IMMED
|
||||
|
||||
const volatile u64 __SCX_ENQ_REENQ __weak;
|
||||
#define SCX_ENQ_REENQ __SCX_ENQ_REENQ
|
||||
|
||||
@@ -127,3 +136,5 @@ const volatile u64 __SCX_ENQ_CLEAR_OPSS __weak;
|
||||
const volatile u64 __SCX_ENQ_DSQ_PRIQ __weak;
|
||||
#define SCX_ENQ_DSQ_PRIQ __SCX_ENQ_DSQ_PRIQ
|
||||
|
||||
const volatile u64 __SCX_DEQ_SCHED_CHANGE __weak;
|
||||
#define SCX_DEQ_SCHED_CHANGE __SCX_DEQ_SCHED_CHANGE
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
SCX_ENUM_SET(skel, scx_ent_flags, SCX_TASK_QUEUED); \
|
||||
SCX_ENUM_SET(skel, scx_ent_flags, SCX_TASK_RESET_RUNNABLE_AT); \
|
||||
SCX_ENUM_SET(skel, scx_ent_flags, SCX_TASK_DEQD_FOR_SLEEP); \
|
||||
SCX_ENUM_SET(skel, scx_ent_flags, SCX_TASK_SUB_INIT); \
|
||||
SCX_ENUM_SET(skel, scx_ent_flags, SCX_TASK_IMMED); \
|
||||
SCX_ENUM_SET(skel, scx_ent_flags, SCX_TASK_STATE_SHIFT); \
|
||||
SCX_ENUM_SET(skel, scx_ent_flags, SCX_TASK_STATE_BITS); \
|
||||
SCX_ENUM_SET(skel, scx_ent_flags, SCX_TASK_STATE_MASK); \
|
||||
@@ -42,8 +44,10 @@
|
||||
SCX_ENUM_SET(skel, scx_enq_flags, SCX_ENQ_WAKEUP); \
|
||||
SCX_ENUM_SET(skel, scx_enq_flags, SCX_ENQ_HEAD); \
|
||||
SCX_ENUM_SET(skel, scx_enq_flags, SCX_ENQ_PREEMPT); \
|
||||
SCX_ENUM_SET(skel, scx_enq_flags, SCX_ENQ_IMMED); \
|
||||
SCX_ENUM_SET(skel, scx_enq_flags, SCX_ENQ_REENQ); \
|
||||
SCX_ENUM_SET(skel, scx_enq_flags, SCX_ENQ_LAST); \
|
||||
SCX_ENUM_SET(skel, scx_enq_flags, SCX_ENQ_CLEAR_OPSS); \
|
||||
SCX_ENUM_SET(skel, scx_enq_flags, SCX_ENQ_DSQ_PRIQ); \
|
||||
SCX_ENUM_SET(skel, scx_deq_flags, SCX_DEQ_SCHED_CHANGE); \
|
||||
} while (0)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#ifndef __SCX_ENUMS_H
|
||||
#define __SCX_ENUMS_H
|
||||
|
||||
static inline void __ENUM_set(u64 *val, char *type, char *name)
|
||||
static inline void __ENUM_set(u64 *val, const char *type, const char *name)
|
||||
{
|
||||
bool res;
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ const volatile u32 nr_cpu_ids = 1; /* !0 for veristat, set during init */
|
||||
const volatile u64 slice_ns;
|
||||
|
||||
bool timer_pinned = true;
|
||||
bool timer_started;
|
||||
u64 nr_total, nr_locals, nr_queued, nr_lost_pids;
|
||||
u64 nr_timers, nr_dispatches, nr_mismatches, nr_retries;
|
||||
u64 nr_overflows;
|
||||
@@ -179,9 +180,47 @@ static bool dispatch_to_cpu(s32 cpu)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void start_central_timer(void)
|
||||
{
|
||||
struct bpf_timer *timer;
|
||||
u32 key = 0;
|
||||
int ret;
|
||||
|
||||
if (likely(timer_started))
|
||||
return;
|
||||
|
||||
timer = bpf_map_lookup_elem(¢ral_timer, &key);
|
||||
if (!timer) {
|
||||
scx_bpf_error("failed to lookup central timer");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = bpf_timer_start(timer, TIMER_INTERVAL_NS, BPF_F_TIMER_CPU_PIN);
|
||||
/*
|
||||
* BPF_F_TIMER_CPU_PIN is pretty new (>=6.7). If we're running in a
|
||||
* kernel which doesn't have it, bpf_timer_start() will return -EINVAL.
|
||||
* Retry without the PIN. This would be the perfect use case for
|
||||
* bpf_core_enum_value_exists() but the enum type doesn't have a name
|
||||
* and can't be used with bpf_core_enum_value_exists(). Oh well...
|
||||
*/
|
||||
if (ret == -EINVAL) {
|
||||
timer_pinned = false;
|
||||
ret = bpf_timer_start(timer, TIMER_INTERVAL_NS, 0);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
scx_bpf_error("bpf_timer_start failed (%d)", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
timer_started = true;
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(central_dispatch, s32 cpu, struct task_struct *prev)
|
||||
{
|
||||
if (cpu == central_cpu) {
|
||||
start_central_timer();
|
||||
|
||||
/* dispatch for all other CPUs first */
|
||||
__sync_fetch_and_add(&nr_dispatches, 1);
|
||||
|
||||
@@ -214,13 +253,13 @@ void BPF_STRUCT_OPS(central_dispatch, s32 cpu, struct task_struct *prev)
|
||||
}
|
||||
|
||||
/* look for a task to run on the central CPU */
|
||||
if (scx_bpf_dsq_move_to_local(FALLBACK_DSQ_ID))
|
||||
if (scx_bpf_dsq_move_to_local(FALLBACK_DSQ_ID, 0))
|
||||
return;
|
||||
dispatch_to_cpu(central_cpu);
|
||||
} else {
|
||||
bool *gimme;
|
||||
|
||||
if (scx_bpf_dsq_move_to_local(FALLBACK_DSQ_ID))
|
||||
if (scx_bpf_dsq_move_to_local(FALLBACK_DSQ_ID, 0))
|
||||
return;
|
||||
|
||||
gimme = ARRAY_ELEM_PTR(cpu_gimme_task, cpu, nr_cpu_ids);
|
||||
@@ -310,29 +349,12 @@ int BPF_STRUCT_OPS_SLEEPABLE(central_init)
|
||||
if (!timer)
|
||||
return -ESRCH;
|
||||
|
||||
if (bpf_get_smp_processor_id() != central_cpu) {
|
||||
scx_bpf_error("init from non-central CPU");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bpf_timer_init(timer, ¢ral_timer, CLOCK_MONOTONIC);
|
||||
bpf_timer_set_callback(timer, central_timerfn);
|
||||
|
||||
ret = bpf_timer_start(timer, TIMER_INTERVAL_NS, BPF_F_TIMER_CPU_PIN);
|
||||
/*
|
||||
* BPF_F_TIMER_CPU_PIN is pretty new (>=6.7). If we're running in a
|
||||
* kernel which doesn't have it, bpf_timer_start() will return -EINVAL.
|
||||
* Retry without the PIN. This would be the perfect use case for
|
||||
* bpf_core_enum_value_exists() but the enum type doesn't have a name
|
||||
* and can't be used with bpf_core_enum_value_exists(). Oh well...
|
||||
*/
|
||||
if (ret == -EINVAL) {
|
||||
timer_pinned = false;
|
||||
ret = bpf_timer_start(timer, TIMER_INTERVAL_NS, 0);
|
||||
}
|
||||
if (ret)
|
||||
scx_bpf_error("bpf_timer_start failed (%d)", ret);
|
||||
return ret;
|
||||
scx_bpf_kick_cpu(central_cpu, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(central_exit, struct scx_exit_info *ei)
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* Copyright (c) 2022 David Vernet <dvernet@meta.com>
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <inttypes.h>
|
||||
@@ -21,7 +20,7 @@ const char help_fmt[] =
|
||||
"\n"
|
||||
"See the top-level comment in .bpf.c for more details.\n"
|
||||
"\n"
|
||||
"Usage: %s [-s SLICE_US] [-c CPU]\n"
|
||||
"Usage: %s [-s SLICE_US] [-c CPU] [-v]\n"
|
||||
"\n"
|
||||
" -s SLICE_US Override slice duration\n"
|
||||
" -c CPU Override the central CPU (default: 0)\n"
|
||||
@@ -49,8 +48,6 @@ int main(int argc, char **argv)
|
||||
struct bpf_link *link;
|
||||
__u64 seq = 0, ecode;
|
||||
__s32 opt;
|
||||
cpu_set_t *cpuset;
|
||||
size_t cpuset_size;
|
||||
|
||||
libbpf_set_print(libbpf_print_fn);
|
||||
signal(SIGINT, sigint_handler);
|
||||
@@ -96,27 +93,6 @@ restart:
|
||||
|
||||
SCX_OPS_LOAD(skel, central_ops, scx_central, uei);
|
||||
|
||||
/*
|
||||
* Affinitize the loading thread to the central CPU, as:
|
||||
* - That's where the BPF timer is first invoked in the BPF program.
|
||||
* - We probably don't want this user space component to take up a core
|
||||
* from a task that would benefit from avoiding preemption on one of
|
||||
* the tickless cores.
|
||||
*
|
||||
* Until BPF supports pinning the timer, it's not guaranteed that it
|
||||
* will always be invoked on the central CPU. In practice, this
|
||||
* suffices the majority of the time.
|
||||
*/
|
||||
cpuset = CPU_ALLOC(skel->rodata->nr_cpu_ids);
|
||||
SCX_BUG_ON(!cpuset, "Failed to allocate cpuset");
|
||||
cpuset_size = CPU_ALLOC_SIZE(skel->rodata->nr_cpu_ids);
|
||||
CPU_ZERO_S(cpuset_size, cpuset);
|
||||
CPU_SET_S(skel->rodata->central_cpu, cpuset_size, cpuset);
|
||||
SCX_BUG_ON(sched_setaffinity(0, cpuset_size, cpuset),
|
||||
"Failed to affinitize to central CPU %d (max %d)",
|
||||
skel->rodata->central_cpu, skel->rodata->nr_cpu_ids - 1);
|
||||
CPU_FREE(cpuset);
|
||||
|
||||
link = SCX_OPS_ATTACH(skel, central_ops, scx_central);
|
||||
|
||||
if (!skel->data->timer_pinned)
|
||||
|
||||
@@ -66,7 +66,7 @@ void BPF_STRUCT_OPS(cpu0_enqueue, struct task_struct *p, u64 enq_flags)
|
||||
void BPF_STRUCT_OPS(cpu0_dispatch, s32 cpu, struct task_struct *prev)
|
||||
{
|
||||
if (cpu == 0)
|
||||
scx_bpf_dsq_move_to_local(DSQ_CPU0);
|
||||
scx_bpf_dsq_move_to_local(DSQ_CPU0, 0);
|
||||
}
|
||||
|
||||
s32 BPF_STRUCT_OPS_SLEEPABLE(cpu0_init)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* 100/(100+100) == 1/2. At its parent level, A is competing against D and A's
|
||||
* share in that competition is 100/(200+100) == 1/3. B's eventual share in the
|
||||
* system can be calculated by multiplying the two shares, 1/2 * 1/3 == 1/6. C's
|
||||
* eventual shaer is the same at 1/6. D is only competing at the top level and
|
||||
* eventual share is the same at 1/6. D is only competing at the top level and
|
||||
* its share is 200/(100+200) == 2/3.
|
||||
*
|
||||
* So, instead of hierarchically scheduling level-by-level, we can consider it
|
||||
@@ -551,9 +551,11 @@ void BPF_STRUCT_OPS(fcg_stopping, struct task_struct *p, bool runnable)
|
||||
* too much, determine the execution time by taking explicit timestamps
|
||||
* instead of depending on @p->scx.slice.
|
||||
*/
|
||||
if (!fifo_sched)
|
||||
p->scx.dsq_vtime +=
|
||||
(SCX_SLICE_DFL - p->scx.slice) * 100 / p->scx.weight;
|
||||
if (!fifo_sched) {
|
||||
u64 delta = scale_by_task_weight_inverse(p, SCX_SLICE_DFL - p->scx.slice);
|
||||
|
||||
scx_bpf_task_set_dsq_vtime(p, p->scx.dsq_vtime + delta);
|
||||
}
|
||||
|
||||
taskc = bpf_task_storage_get(&task_ctx, p, 0, 0);
|
||||
if (!taskc) {
|
||||
@@ -660,7 +662,7 @@ static bool try_pick_next_cgroup(u64 *cgidp)
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
if (!scx_bpf_dsq_move_to_local(cgid)) {
|
||||
if (!scx_bpf_dsq_move_to_local(cgid, 0)) {
|
||||
bpf_cgroup_release(cgrp);
|
||||
stat_inc(FCG_STAT_PNC_EMPTY);
|
||||
goto out_stash;
|
||||
@@ -740,7 +742,7 @@ void BPF_STRUCT_OPS(fcg_dispatch, s32 cpu, struct task_struct *prev)
|
||||
goto pick_next_cgroup;
|
||||
|
||||
if (time_before(now, cpuc->cur_at + cgrp_slice_ns)) {
|
||||
if (scx_bpf_dsq_move_to_local(cpuc->cur_cgid)) {
|
||||
if (scx_bpf_dsq_move_to_local(cpuc->cur_cgid, 0)) {
|
||||
stat_inc(FCG_STAT_CNS_KEEP);
|
||||
return;
|
||||
}
|
||||
@@ -780,7 +782,7 @@ void BPF_STRUCT_OPS(fcg_dispatch, s32 cpu, struct task_struct *prev)
|
||||
pick_next_cgroup:
|
||||
cpuc->cur_at = now;
|
||||
|
||||
if (scx_bpf_dsq_move_to_local(FALLBACK_DSQ)) {
|
||||
if (scx_bpf_dsq_move_to_local(FALLBACK_DSQ, 0)) {
|
||||
cpuc->cur_cgid = 0;
|
||||
return;
|
||||
}
|
||||
@@ -822,7 +824,7 @@ s32 BPF_STRUCT_OPS(fcg_init_task, struct task_struct *p,
|
||||
if (!(cgc = find_cgrp_ctx(args->cgroup)))
|
||||
return -ENOENT;
|
||||
|
||||
p->scx.dsq_vtime = cgc->tvtime_now;
|
||||
scx_bpf_task_set_dsq_vtime(p, cgc->tvtime_now);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -919,12 +921,12 @@ void BPF_STRUCT_OPS(fcg_cgroup_move, struct task_struct *p,
|
||||
struct fcg_cgrp_ctx *from_cgc, *to_cgc;
|
||||
s64 delta;
|
||||
|
||||
/* find_cgrp_ctx() triggers scx_ops_error() on lookup failures */
|
||||
/* find_cgrp_ctx() triggers scx_bpf_error() on lookup failures */
|
||||
if (!(from_cgc = find_cgrp_ctx(from)) || !(to_cgc = find_cgrp_ctx(to)))
|
||||
return;
|
||||
|
||||
delta = time_delta(p->scx.dsq_vtime, from_cgc->tvtime_now);
|
||||
p->scx.dsq_vtime = to_cgc->tvtime_now + delta;
|
||||
scx_bpf_task_set_dsq_vtime(p, to_cgc->tvtime_now + delta);
|
||||
}
|
||||
|
||||
s32 BPF_STRUCT_OPS_SLEEPABLE(fcg_init)
|
||||
@@ -960,5 +962,5 @@ SCX_OPS_DEFINE(flatcg_ops,
|
||||
.cgroup_move = (void *)fcg_cgroup_move,
|
||||
.init = (void *)fcg_init,
|
||||
.exit = (void *)fcg_exit,
|
||||
.flags = SCX_OPS_HAS_CGROUP_WEIGHT | SCX_OPS_ENQ_EXITING,
|
||||
.flags = SCX_OPS_ENQ_EXITING,
|
||||
.name = "flatcg");
|
||||
|
||||
@@ -21,7 +21,7 @@ const char help_fmt[] =
|
||||
"\n"
|
||||
"See the top-level comment in .bpf.c for more details.\n"
|
||||
"\n"
|
||||
"Usage: %s [-S STRIDE]\n"
|
||||
"Usage: %s [-S STRIDE] [-v]\n"
|
||||
"\n"
|
||||
" -S STRIDE Override CPU pair stride (default: nr_cpus_ids / 2)\n"
|
||||
" -v Print libbpf debug messages\n"
|
||||
@@ -48,6 +48,7 @@ int main(int argc, char **argv)
|
||||
struct bpf_link *link;
|
||||
__u64 seq = 0, ecode;
|
||||
__s32 stride, i, opt, outer_fd;
|
||||
__u32 pair_id = 0;
|
||||
|
||||
libbpf_set_print(libbpf_print_fn);
|
||||
signal(SIGINT, sigint_handler);
|
||||
@@ -82,6 +83,14 @@ restart:
|
||||
scx_pair__destroy(skel);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (skel->rodata->nr_cpu_ids & 1) {
|
||||
fprintf(stderr, "scx_pair requires an even CPU count, got %u\n",
|
||||
skel->rodata->nr_cpu_ids);
|
||||
scx_pair__destroy(skel);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bpf_map__set_max_entries(skel->maps.pair_ctx, skel->rodata->nr_cpu_ids / 2);
|
||||
|
||||
/* Resize arrays so their element count is equal to cpu count. */
|
||||
@@ -109,10 +118,11 @@ restart:
|
||||
|
||||
skel->rodata_pair_cpu->pair_cpu[i] = j;
|
||||
skel->rodata_pair_cpu->pair_cpu[j] = i;
|
||||
skel->rodata_pair_id->pair_id[i] = i;
|
||||
skel->rodata_pair_id->pair_id[j] = i;
|
||||
skel->rodata_pair_id->pair_id[i] = pair_id;
|
||||
skel->rodata_pair_id->pair_id[j] = pair_id;
|
||||
skel->rodata_in_pair_idx->in_pair_idx[i] = 0;
|
||||
skel->rodata_in_pair_idx->in_pair_idx[j] = 1;
|
||||
pair_id++;
|
||||
|
||||
printf("[%d, %d] ", i, j);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
*
|
||||
* - BPF-side queueing using PIDs.
|
||||
* - Sleepable per-task storage allocation using ops.prep_enable().
|
||||
* - Using ops.cpu_release() to handle a higher priority scheduling class taking
|
||||
* the CPU away.
|
||||
* - Core-sched support.
|
||||
*
|
||||
* This scheduler is primarily for demonstration and testing of sched_ext
|
||||
@@ -26,8 +24,11 @@
|
||||
|
||||
enum consts {
|
||||
ONE_SEC_IN_NS = 1000000000,
|
||||
ONE_MSEC_IN_NS = 1000000,
|
||||
LOWPRI_INTV_NS = 10 * ONE_MSEC_IN_NS,
|
||||
SHARED_DSQ = 0,
|
||||
HIGHPRI_DSQ = 1,
|
||||
LOWPRI_DSQ = 2,
|
||||
HIGHPRI_WEIGHT = 8668, /* this is what -20 maps to */
|
||||
};
|
||||
|
||||
@@ -41,12 +42,18 @@ const volatile u32 dsp_batch;
|
||||
const volatile bool highpri_boosting;
|
||||
const volatile bool print_dsqs_and_events;
|
||||
const volatile bool print_msgs;
|
||||
const volatile u64 sub_cgroup_id;
|
||||
const volatile s32 disallow_tgid;
|
||||
const volatile bool suppress_dump;
|
||||
const volatile bool always_enq_immed;
|
||||
const volatile u32 immed_stress_nth;
|
||||
|
||||
u64 nr_highpri_queued;
|
||||
u32 test_error_cnt;
|
||||
|
||||
#define MAX_SUB_SCHEDS 8
|
||||
u64 sub_sched_cgroup_ids[MAX_SUB_SCHEDS];
|
||||
|
||||
UEI_DEFINE(uei);
|
||||
|
||||
struct qmap {
|
||||
@@ -127,7 +134,7 @@ struct {
|
||||
} cpu_ctx_stor SEC(".maps");
|
||||
|
||||
/* Statistics */
|
||||
u64 nr_enqueued, nr_dispatched, nr_reenqueued, nr_dequeued, nr_ddsp_from_enq;
|
||||
u64 nr_enqueued, nr_dispatched, nr_reenqueued, nr_reenqueued_cpu0, nr_dequeued, nr_ddsp_from_enq;
|
||||
u64 nr_core_sched_execed;
|
||||
u64 nr_expedited_local, nr_expedited_remote, nr_expedited_lost, nr_expedited_from_timer;
|
||||
u32 cpuperf_min, cpuperf_avg, cpuperf_max;
|
||||
@@ -137,8 +144,10 @@ static s32 pick_direct_dispatch_cpu(struct task_struct *p, s32 prev_cpu)
|
||||
{
|
||||
s32 cpu;
|
||||
|
||||
if (p->nr_cpus_allowed == 1 ||
|
||||
scx_bpf_test_and_clear_cpu_idle(prev_cpu))
|
||||
if (!always_enq_immed && p->nr_cpus_allowed == 1)
|
||||
return prev_cpu;
|
||||
|
||||
if (scx_bpf_test_and_clear_cpu_idle(prev_cpu))
|
||||
return prev_cpu;
|
||||
|
||||
cpu = scx_bpf_pick_idle_cpu(p->cpus_ptr, 0);
|
||||
@@ -168,6 +177,9 @@ s32 BPF_STRUCT_OPS(qmap_select_cpu, struct task_struct *p,
|
||||
if (!(tctx = lookup_task_ctx(p)))
|
||||
return -ESRCH;
|
||||
|
||||
if (p->scx.weight < 2 && !(p->flags & PF_KTHREAD))
|
||||
return prev_cpu;
|
||||
|
||||
cpu = pick_direct_dispatch_cpu(p, prev_cpu);
|
||||
|
||||
if (cpu >= 0) {
|
||||
@@ -202,8 +214,11 @@ void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags)
|
||||
void *ring;
|
||||
s32 cpu;
|
||||
|
||||
if (enq_flags & SCX_ENQ_REENQ)
|
||||
if (enq_flags & SCX_ENQ_REENQ) {
|
||||
__sync_fetch_and_add(&nr_reenqueued, 1);
|
||||
if (scx_bpf_task_cpu(p) == 0)
|
||||
__sync_fetch_and_add(&nr_reenqueued_cpu0, 1);
|
||||
}
|
||||
|
||||
if (p->flags & PF_KTHREAD) {
|
||||
if (stall_kernel_nth && !(++kernel_cnt % stall_kernel_nth))
|
||||
@@ -225,6 +240,22 @@ void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags)
|
||||
*/
|
||||
tctx->core_sched_seq = core_sched_tail_seqs[idx]++;
|
||||
|
||||
/*
|
||||
* IMMED stress testing: Every immed_stress_nth'th enqueue, dispatch
|
||||
* directly to prev_cpu's local DSQ even when busy to force dsq->nr > 1
|
||||
* and exercise the kernel IMMED reenqueue trigger paths.
|
||||
*/
|
||||
if (immed_stress_nth && !(enq_flags & SCX_ENQ_REENQ)) {
|
||||
static u32 immed_stress_cnt;
|
||||
|
||||
if (!(++immed_stress_cnt % immed_stress_nth)) {
|
||||
tctx->force_local = false;
|
||||
scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | scx_bpf_task_cpu(p),
|
||||
slice_ns, enq_flags);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If qmap_select_cpu() is telling us to or this is the last runnable
|
||||
* task on the CPU, enqueue locally.
|
||||
@@ -235,6 +266,13 @@ void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags)
|
||||
return;
|
||||
}
|
||||
|
||||
/* see lowpri_timerfn() */
|
||||
if (__COMPAT_has_generic_reenq() &&
|
||||
p->scx.weight < 2 && !(p->flags & PF_KTHREAD) && !(enq_flags & SCX_ENQ_REENQ)) {
|
||||
scx_bpf_dsq_insert(p, LOWPRI_DSQ, slice_ns, enq_flags);
|
||||
return;
|
||||
}
|
||||
|
||||
/* if select_cpu() wasn't called, try direct dispatch */
|
||||
if (!__COMPAT_is_enq_cpu_selected(enq_flags) &&
|
||||
(cpu = pick_direct_dispatch_cpu(p, scx_bpf_task_cpu(p))) >= 0) {
|
||||
@@ -375,7 +413,7 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cpu, struct task_struct *prev)
|
||||
if (dispatch_highpri(false))
|
||||
return;
|
||||
|
||||
if (!nr_highpri_queued && scx_bpf_dsq_move_to_local(SHARED_DSQ))
|
||||
if (!nr_highpri_queued && scx_bpf_dsq_move_to_local(SHARED_DSQ, 0))
|
||||
return;
|
||||
|
||||
if (dsp_inf_loop_after && nr_dispatched > dsp_inf_loop_after) {
|
||||
@@ -433,6 +471,46 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cpu, struct task_struct *prev)
|
||||
__sync_fetch_and_add(&nr_dispatched, 1);
|
||||
|
||||
scx_bpf_dsq_insert(p, SHARED_DSQ, slice_ns, 0);
|
||||
|
||||
/*
|
||||
* scx_qmap uses a global BPF queue that any CPU's
|
||||
* dispatch can pop from. If this CPU popped a task that
|
||||
* can't run here, it gets stranded on SHARED_DSQ after
|
||||
* consume_dispatch_q() skips it. Kick the task's home
|
||||
* CPU so it drains SHARED_DSQ.
|
||||
*
|
||||
* There's a race between the pop and the flush of the
|
||||
* buffered dsq_insert:
|
||||
*
|
||||
* CPU 0 (dispatching) CPU 1 (home, idle)
|
||||
* ~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
|
||||
* pop from BPF queue
|
||||
* dsq_insert(buffered)
|
||||
* balance:
|
||||
* SHARED_DSQ empty
|
||||
* BPF queue empty
|
||||
* -> goes idle
|
||||
* flush -> on SHARED
|
||||
* kick CPU 1
|
||||
* wakes, drains task
|
||||
*
|
||||
* The kick prevents indefinite stalls but a per-CPU
|
||||
* kthread like ksoftirqd can be briefly stranded when
|
||||
* its home CPU enters idle with softirq pending,
|
||||
* triggering:
|
||||
*
|
||||
* "NOHZ tick-stop error: local softirq work is pending, handler #N!!!"
|
||||
*
|
||||
* from report_idle_softirq(). The kick lands shortly
|
||||
* after and the home CPU drains the task. This could be
|
||||
* avoided by e.g. dispatching pinned tasks to local or
|
||||
* global DSQs, but the current code is left as-is to
|
||||
* document this class of issue -- other schedulers
|
||||
* seeing similar warnings can use this as a reference.
|
||||
*/
|
||||
if (!bpf_cpumask_test_cpu(cpu, p->cpus_ptr))
|
||||
scx_bpf_kick_cpu(scx_bpf_task_cpu(p), 0);
|
||||
|
||||
bpf_task_release(p);
|
||||
|
||||
batch--;
|
||||
@@ -440,7 +518,7 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cpu, struct task_struct *prev)
|
||||
if (!batch || !scx_bpf_dispatch_nr_slots()) {
|
||||
if (dispatch_highpri(false))
|
||||
return;
|
||||
scx_bpf_dsq_move_to_local(SHARED_DSQ);
|
||||
scx_bpf_dsq_move_to_local(SHARED_DSQ, 0);
|
||||
return;
|
||||
}
|
||||
if (!cpuc->dsp_cnt)
|
||||
@@ -450,6 +528,12 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cpu, struct task_struct *prev)
|
||||
cpuc->dsp_cnt = 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_SUB_SCHEDS; i++) {
|
||||
if (sub_sched_cgroup_ids[i] &&
|
||||
scx_bpf_sub_dispatch(sub_sched_cgroup_ids[i]))
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* No other tasks. @prev will keep running. Update its core_sched_seq as
|
||||
* if the task were enqueued and dispatched immediately.
|
||||
@@ -532,36 +616,11 @@ bool BPF_STRUCT_OPS(qmap_core_sched_before,
|
||||
return task_qdist(a) > task_qdist(b);
|
||||
}
|
||||
|
||||
SEC("tp_btf/sched_switch")
|
||||
int BPF_PROG(qmap_sched_switch, bool preempt, struct task_struct *prev,
|
||||
struct task_struct *next, unsigned long prev_state)
|
||||
{
|
||||
if (!__COMPAT_scx_bpf_reenqueue_local_from_anywhere())
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If @cpu is taken by a higher priority scheduling class, it is no
|
||||
* longer available for executing sched_ext tasks. As we don't want the
|
||||
* tasks in @cpu's local dsq to sit there until @cpu becomes available
|
||||
* again, re-enqueue them into the global dsq. See %SCX_ENQ_REENQ
|
||||
* handling in qmap_enqueue().
|
||||
*/
|
||||
switch (next->policy) {
|
||||
case 1: /* SCHED_FIFO */
|
||||
case 2: /* SCHED_RR */
|
||||
case 6: /* SCHED_DEADLINE */
|
||||
scx_bpf_reenqueue_local();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(qmap_cpu_release, s32 cpu, struct scx_cpu_release_args *args)
|
||||
{
|
||||
/* see qmap_sched_switch() to learn how to do this on newer kernels */
|
||||
if (!__COMPAT_scx_bpf_reenqueue_local_from_anywhere())
|
||||
scx_bpf_reenqueue_local();
|
||||
}
|
||||
/*
|
||||
* sched_switch tracepoint and cpu_release handlers are no longer needed.
|
||||
* With SCX_OPS_ALWAYS_ENQ_IMMED, wakeup_preempt_scx() reenqueues IMMED
|
||||
* tasks when a higher-priority scheduling class takes the CPU.
|
||||
*/
|
||||
|
||||
s32 BPF_STRUCT_OPS(qmap_init_task, struct task_struct *p,
|
||||
struct scx_init_task_args *args)
|
||||
@@ -856,13 +915,35 @@ static int monitor_timerfn(void *map, int *key, struct bpf_timer *timer)
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct lowpri_timer {
|
||||
struct bpf_timer timer;
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__uint(max_entries, 1);
|
||||
__type(key, u32);
|
||||
__type(value, struct lowpri_timer);
|
||||
} lowpri_timer SEC(".maps");
|
||||
|
||||
/*
|
||||
* Nice 19 tasks are put into the lowpri DSQ. Every 10ms, reenq is triggered and
|
||||
* the tasks are transferred to SHARED_DSQ.
|
||||
*/
|
||||
static int lowpri_timerfn(void *map, int *key, struct bpf_timer *timer)
|
||||
{
|
||||
scx_bpf_dsq_reenq(LOWPRI_DSQ, 0);
|
||||
bpf_timer_start(timer, LOWPRI_INTV_NS, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init)
|
||||
{
|
||||
u32 key = 0;
|
||||
struct bpf_timer *timer;
|
||||
s32 ret;
|
||||
|
||||
if (print_msgs)
|
||||
if (print_msgs && !sub_cgroup_id)
|
||||
print_cpus();
|
||||
|
||||
ret = scx_bpf_create_dsq(SHARED_DSQ, -1);
|
||||
@@ -877,14 +958,32 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = scx_bpf_create_dsq(LOWPRI_DSQ, -1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
timer = bpf_map_lookup_elem(&monitor_timer, &key);
|
||||
if (!timer)
|
||||
return -ESRCH;
|
||||
|
||||
bpf_timer_init(timer, &monitor_timer, CLOCK_MONOTONIC);
|
||||
bpf_timer_set_callback(timer, monitor_timerfn);
|
||||
ret = bpf_timer_start(timer, ONE_SEC_IN_NS, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return bpf_timer_start(timer, ONE_SEC_IN_NS, 0);
|
||||
if (__COMPAT_has_generic_reenq()) {
|
||||
/* see lowpri_timerfn() */
|
||||
timer = bpf_map_lookup_elem(&lowpri_timer, &key);
|
||||
if (!timer)
|
||||
return -ESRCH;
|
||||
bpf_timer_init(timer, &lowpri_timer, CLOCK_MONOTONIC);
|
||||
bpf_timer_set_callback(timer, lowpri_timerfn);
|
||||
ret = bpf_timer_start(timer, LOWPRI_INTV_NS, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(qmap_exit, struct scx_exit_info *ei)
|
||||
@@ -892,6 +991,36 @@ void BPF_STRUCT_OPS(qmap_exit, struct scx_exit_info *ei)
|
||||
UEI_RECORD(uei, ei);
|
||||
}
|
||||
|
||||
s32 BPF_STRUCT_OPS(qmap_sub_attach, struct scx_sub_attach_args *args)
|
||||
{
|
||||
s32 i;
|
||||
|
||||
for (i = 0; i < MAX_SUB_SCHEDS; i++) {
|
||||
if (!sub_sched_cgroup_ids[i]) {
|
||||
sub_sched_cgroup_ids[i] = args->ops->sub_cgroup_id;
|
||||
bpf_printk("attaching sub-sched[%d] on %s",
|
||||
i, args->cgroup_path);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(qmap_sub_detach, struct scx_sub_detach_args *args)
|
||||
{
|
||||
s32 i;
|
||||
|
||||
for (i = 0; i < MAX_SUB_SCHEDS; i++) {
|
||||
if (sub_sched_cgroup_ids[i] == args->ops->sub_cgroup_id) {
|
||||
sub_sched_cgroup_ids[i] = 0;
|
||||
bpf_printk("detaching sub-sched[%d] on %s",
|
||||
i, args->cgroup_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCX_OPS_DEFINE(qmap_ops,
|
||||
.select_cpu = (void *)qmap_select_cpu,
|
||||
.enqueue = (void *)qmap_enqueue,
|
||||
@@ -899,7 +1028,6 @@ SCX_OPS_DEFINE(qmap_ops,
|
||||
.dispatch = (void *)qmap_dispatch,
|
||||
.tick = (void *)qmap_tick,
|
||||
.core_sched_before = (void *)qmap_core_sched_before,
|
||||
.cpu_release = (void *)qmap_cpu_release,
|
||||
.init_task = (void *)qmap_init_task,
|
||||
.dump = (void *)qmap_dump,
|
||||
.dump_cpu = (void *)qmap_dump_cpu,
|
||||
@@ -907,6 +1035,8 @@ SCX_OPS_DEFINE(qmap_ops,
|
||||
.cgroup_init = (void *)qmap_cgroup_init,
|
||||
.cgroup_set_weight = (void *)qmap_cgroup_set_weight,
|
||||
.cgroup_set_bandwidth = (void *)qmap_cgroup_set_bandwidth,
|
||||
.sub_attach = (void *)qmap_sub_attach,
|
||||
.sub_detach = (void *)qmap_sub_detach,
|
||||
.cpu_online = (void *)qmap_cpu_online,
|
||||
.cpu_offline = (void *)qmap_cpu_offline,
|
||||
.init = (void *)qmap_init,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <signal.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/stat.h>
|
||||
#include <bpf/bpf.h>
|
||||
#include <scx/common.h>
|
||||
#include "scx_qmap.bpf.skel.h"
|
||||
@@ -20,7 +21,7 @@ const char help_fmt[] =
|
||||
"See the top-level comment in .bpf.c for more details.\n"
|
||||
"\n"
|
||||
"Usage: %s [-s SLICE_US] [-e COUNT] [-t COUNT] [-T COUNT] [-l COUNT] [-b COUNT]\n"
|
||||
" [-P] [-M] [-d PID] [-D LEN] [-p] [-v]\n"
|
||||
" [-P] [-M] [-H] [-d PID] [-D LEN] [-S] [-p] [-I] [-F COUNT] [-v]\n"
|
||||
"\n"
|
||||
" -s SLICE_US Override slice duration\n"
|
||||
" -e COUNT Trigger scx_bpf_error() after COUNT enqueues\n"
|
||||
@@ -35,6 +36,8 @@ const char help_fmt[] =
|
||||
" -D LEN Set scx_exit_info.dump buffer length\n"
|
||||
" -S Suppress qmap-specific debug dump\n"
|
||||
" -p Switch only tasks on SCHED_EXT policy instead of all\n"
|
||||
" -I Turn on SCX_OPS_ALWAYS_ENQ_IMMED\n"
|
||||
" -F COUNT IMMED stress: force every COUNT'th enqueue to a busy local DSQ (use with -I)\n"
|
||||
" -v Print libbpf debug messages\n"
|
||||
" -h Display this help and exit\n";
|
||||
|
||||
@@ -67,7 +70,7 @@ int main(int argc, char **argv)
|
||||
|
||||
skel->rodata->slice_ns = __COMPAT_ENUM_OR_ZERO("scx_public_consts", "SCX_SLICE_DFL");
|
||||
|
||||
while ((opt = getopt(argc, argv, "s:e:t:T:l:b:PMHd:D:Spvh")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "s:e:t:T:l:b:PMHc:d:D:SpIF:vh")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
skel->rodata->slice_ns = strtoull(optarg, NULL, 0) * 1000;
|
||||
@@ -96,6 +99,16 @@ int main(int argc, char **argv)
|
||||
case 'H':
|
||||
skel->rodata->highpri_boosting = true;
|
||||
break;
|
||||
case 'c': {
|
||||
struct stat st;
|
||||
if (stat(optarg, &st) < 0) {
|
||||
perror("stat");
|
||||
return 1;
|
||||
}
|
||||
skel->struct_ops.qmap_ops->sub_cgroup_id = st.st_ino;
|
||||
skel->rodata->sub_cgroup_id = st.st_ino;
|
||||
break;
|
||||
}
|
||||
case 'd':
|
||||
skel->rodata->disallow_tgid = strtol(optarg, NULL, 0);
|
||||
if (skel->rodata->disallow_tgid < 0)
|
||||
@@ -110,6 +123,13 @@ int main(int argc, char **argv)
|
||||
case 'p':
|
||||
skel->struct_ops.qmap_ops->flags |= SCX_OPS_SWITCH_PARTIAL;
|
||||
break;
|
||||
case 'I':
|
||||
skel->rodata->always_enq_immed = true;
|
||||
skel->struct_ops.qmap_ops->flags |= SCX_OPS_ALWAYS_ENQ_IMMED;
|
||||
break;
|
||||
case 'F':
|
||||
skel->rodata->immed_stress_nth = strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
@@ -126,9 +146,10 @@ int main(int argc, char **argv)
|
||||
long nr_enqueued = skel->bss->nr_enqueued;
|
||||
long nr_dispatched = skel->bss->nr_dispatched;
|
||||
|
||||
printf("stats : enq=%lu dsp=%lu delta=%ld reenq=%"PRIu64" deq=%"PRIu64" core=%"PRIu64" enq_ddsp=%"PRIu64"\n",
|
||||
printf("stats : enq=%lu dsp=%lu delta=%ld reenq/cpu0=%"PRIu64"/%"PRIu64" deq=%"PRIu64" core=%"PRIu64" enq_ddsp=%"PRIu64"\n",
|
||||
nr_enqueued, nr_dispatched, nr_enqueued - nr_dispatched,
|
||||
skel->bss->nr_reenqueued, skel->bss->nr_dequeued,
|
||||
skel->bss->nr_reenqueued, skel->bss->nr_reenqueued_cpu0,
|
||||
skel->bss->nr_dequeued,
|
||||
skel->bss->nr_core_sched_execed,
|
||||
skel->bss->nr_ddsp_from_enq);
|
||||
printf(" exp_local=%"PRIu64" exp_remote=%"PRIu64" exp_timer=%"PRIu64" exp_lost=%"PRIu64"\n",
|
||||
|
||||
@@ -317,7 +317,8 @@ int scx_alloc_free_idx(struct scx_allocator *alloc, __u64 idx)
|
||||
};
|
||||
|
||||
/* Zero out one word at a time. */
|
||||
for (i = zero; i < alloc->pool.elem_size / 8 && can_loop; i++) {
|
||||
for (i = zero; i < (alloc->pool.elem_size - sizeof(struct sdt_data)) / 8
|
||||
&& can_loop; i++) {
|
||||
data->payload[i] = 0;
|
||||
}
|
||||
}
|
||||
@@ -643,7 +644,7 @@ void BPF_STRUCT_OPS(sdt_enqueue, struct task_struct *p, u64 enq_flags)
|
||||
|
||||
void BPF_STRUCT_OPS(sdt_dispatch, s32 cpu, struct task_struct *prev)
|
||||
{
|
||||
scx_bpf_dsq_move_to_local(SHARED_DSQ);
|
||||
scx_bpf_dsq_move_to_local(SHARED_DSQ, 0);
|
||||
}
|
||||
|
||||
s32 BPF_STRUCT_OPS_SLEEPABLE(sdt_init_task, struct task_struct *p,
|
||||
|
||||
@@ -20,7 +20,7 @@ const char help_fmt[] =
|
||||
"\n"
|
||||
"Modified version of scx_simple that demonstrates arena-based data structures.\n"
|
||||
"\n"
|
||||
"Usage: %s [-f] [-v]\n"
|
||||
"Usage: %s [-v]\n"
|
||||
"\n"
|
||||
" -v Print libbpf debug messages\n"
|
||||
" -h Display this help and exit\n";
|
||||
|
||||
@@ -89,7 +89,7 @@ void BPF_STRUCT_OPS(simple_enqueue, struct task_struct *p, u64 enq_flags)
|
||||
|
||||
void BPF_STRUCT_OPS(simple_dispatch, s32 cpu, struct task_struct *prev)
|
||||
{
|
||||
scx_bpf_dsq_move_to_local(SHARED_DSQ);
|
||||
scx_bpf_dsq_move_to_local(SHARED_DSQ, 0);
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(simple_running, struct task_struct *p)
|
||||
@@ -121,12 +121,14 @@ void BPF_STRUCT_OPS(simple_stopping, struct task_struct *p, bool runnable)
|
||||
* too much, determine the execution time by taking explicit timestamps
|
||||
* instead of depending on @p->scx.slice.
|
||||
*/
|
||||
p->scx.dsq_vtime += (SCX_SLICE_DFL - p->scx.slice) * 100 / p->scx.weight;
|
||||
u64 delta = scale_by_task_weight_inverse(p, SCX_SLICE_DFL - p->scx.slice);
|
||||
|
||||
scx_bpf_task_set_dsq_vtime(p, p->scx.dsq_vtime + delta);
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(simple_enable, struct task_struct *p)
|
||||
{
|
||||
p->scx.dsq_vtime = vtime_now;
|
||||
scx_bpf_task_set_dsq_vtime(p, vtime_now);
|
||||
}
|
||||
|
||||
s32 BPF_STRUCT_OPS_SLEEPABLE(simple_init)
|
||||
|
||||
@@ -38,7 +38,7 @@ const char help_fmt[] =
|
||||
"\n"
|
||||
"Try to reduce `sysctl kernel.pid_max` if this program triggers OOMs.\n"
|
||||
"\n"
|
||||
"Usage: %s [-b BATCH]\n"
|
||||
"Usage: %s [-b BATCH] [-v]\n"
|
||||
"\n"
|
||||
" -b BATCH The number of tasks to batch when dispatching (default: 8)\n"
|
||||
" -v Print libbpf debug messages\n"
|
||||
|
||||
@@ -163,6 +163,7 @@ all_test_bpfprogs := $(foreach prog,$(wildcard *.bpf.c),$(INCLUDE_DIR)/$(patsubs
|
||||
|
||||
auto-test-targets := \
|
||||
create_dsq \
|
||||
dequeue \
|
||||
enq_last_no_enq_fails \
|
||||
ddsp_bogus_dsq_fail \
|
||||
ddsp_vtimelocal_fail \
|
||||
|
||||
389
tools/testing/selftests/sched_ext/dequeue.bpf.c
Normal file
389
tools/testing/selftests/sched_ext/dequeue.bpf.c
Normal file
@@ -0,0 +1,389 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* A scheduler that validates ops.dequeue() is called correctly:
|
||||
* - Tasks dispatched to terminal DSQs (local, global) bypass the BPF
|
||||
* scheduler entirely: no ops.dequeue() should be called
|
||||
* - Tasks dispatched to user DSQs from ops.enqueue() enter BPF custody:
|
||||
* ops.dequeue() must be called when they leave custody
|
||||
* - Every ops.enqueue() dispatch to non-terminal DSQs is followed by
|
||||
* exactly one ops.dequeue() (validate 1:1 pairing and state machine)
|
||||
*
|
||||
* Copyright (c) 2026 NVIDIA Corporation.
|
||||
*/
|
||||
|
||||
#include <scx/common.bpf.h>
|
||||
|
||||
#define SHARED_DSQ 0
|
||||
|
||||
/*
|
||||
* BPF internal queue.
|
||||
*
|
||||
* Tasks are stored here and consumed from ops.dispatch(), validating that
|
||||
* tasks on BPF internal structures still get ops.dequeue() when they
|
||||
* leave.
|
||||
*/
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_QUEUE);
|
||||
__uint(max_entries, 32768);
|
||||
__type(value, s32);
|
||||
} global_queue SEC(".maps");
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
UEI_DEFINE(uei);
|
||||
|
||||
/*
|
||||
* Counters to track the lifecycle of tasks:
|
||||
* - enqueue_cnt: Number of times ops.enqueue() was called
|
||||
* - dequeue_cnt: Number of times ops.dequeue() was called (any type)
|
||||
* - dispatch_dequeue_cnt: Number of regular dispatch dequeues (no flag)
|
||||
* - change_dequeue_cnt: Number of property change dequeues
|
||||
* - bpf_queue_full: Number of times the BPF internal queue was full
|
||||
*/
|
||||
u64 enqueue_cnt, dequeue_cnt, dispatch_dequeue_cnt, change_dequeue_cnt, bpf_queue_full;
|
||||
|
||||
/*
|
||||
* Test scenarios:
|
||||
* 0) Dispatch to local DSQ from ops.select_cpu() (terminal DSQ, bypasses BPF
|
||||
* scheduler, no dequeue callbacks)
|
||||
* 1) Dispatch to global DSQ from ops.select_cpu() (terminal DSQ, bypasses BPF
|
||||
* scheduler, no dequeue callbacks)
|
||||
* 2) Dispatch to shared user DSQ from ops.select_cpu() (enters BPF scheduler,
|
||||
* dequeue callbacks expected)
|
||||
* 3) Dispatch to local DSQ from ops.enqueue() (terminal DSQ, bypasses BPF
|
||||
* scheduler, no dequeue callbacks)
|
||||
* 4) Dispatch to global DSQ from ops.enqueue() (terminal DSQ, bypasses BPF
|
||||
* scheduler, no dequeue callbacks)
|
||||
* 5) Dispatch to shared user DSQ from ops.enqueue() (enters BPF scheduler,
|
||||
* dequeue callbacks expected)
|
||||
* 6) BPF internal queue from ops.enqueue(): store task PIDs in ops.enqueue(),
|
||||
* consume in ops.dispatch() and dispatch to local DSQ (validates dequeue
|
||||
* for tasks stored in internal BPF data structures)
|
||||
*/
|
||||
u32 test_scenario;
|
||||
|
||||
/*
|
||||
* Per-task state to track lifecycle and validate workflow semantics.
|
||||
* State transitions:
|
||||
* NONE -> ENQUEUED (on enqueue)
|
||||
* NONE -> DISPATCHED (on direct dispatch to terminal DSQ)
|
||||
* ENQUEUED -> DISPATCHED (on dispatch dequeue)
|
||||
* DISPATCHED -> NONE (on property change dequeue or re-enqueue)
|
||||
* ENQUEUED -> NONE (on property change dequeue before dispatch)
|
||||
*/
|
||||
enum task_state {
|
||||
TASK_NONE = 0,
|
||||
TASK_ENQUEUED,
|
||||
TASK_DISPATCHED,
|
||||
};
|
||||
|
||||
struct task_ctx {
|
||||
enum task_state state; /* Current state in the workflow */
|
||||
u64 enqueue_seq; /* Sequence number for debugging */
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_TASK_STORAGE);
|
||||
__uint(map_flags, BPF_F_NO_PREALLOC);
|
||||
__type(key, int);
|
||||
__type(value, struct task_ctx);
|
||||
} task_ctx_stor SEC(".maps");
|
||||
|
||||
static struct task_ctx *try_lookup_task_ctx(struct task_struct *p)
|
||||
{
|
||||
return bpf_task_storage_get(&task_ctx_stor, p, 0, 0);
|
||||
}
|
||||
|
||||
s32 BPF_STRUCT_OPS(dequeue_select_cpu, struct task_struct *p,
|
||||
s32 prev_cpu, u64 wake_flags)
|
||||
{
|
||||
struct task_ctx *tctx;
|
||||
|
||||
tctx = try_lookup_task_ctx(p);
|
||||
if (!tctx)
|
||||
return prev_cpu;
|
||||
|
||||
switch (test_scenario) {
|
||||
case 0:
|
||||
/*
|
||||
* Direct dispatch to the local DSQ.
|
||||
*
|
||||
* Task bypasses BPF scheduler entirely: no enqueue
|
||||
* tracking, no ops.dequeue() callbacks.
|
||||
*/
|
||||
scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, SCX_SLICE_DFL, 0);
|
||||
tctx->state = TASK_DISPATCHED;
|
||||
break;
|
||||
case 1:
|
||||
/*
|
||||
* Direct dispatch to the global DSQ.
|
||||
*
|
||||
* Task bypasses BPF scheduler entirely: no enqueue
|
||||
* tracking, no ops.dequeue() callbacks.
|
||||
*/
|
||||
scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, 0);
|
||||
tctx->state = TASK_DISPATCHED;
|
||||
break;
|
||||
case 2:
|
||||
/*
|
||||
* Dispatch to a shared user DSQ.
|
||||
*
|
||||
* Task enters BPF scheduler management: track
|
||||
* enqueue/dequeue lifecycle and validate state
|
||||
* transitions.
|
||||
*/
|
||||
if (tctx->state == TASK_ENQUEUED)
|
||||
scx_bpf_error("%d (%s): enqueue while in ENQUEUED state seq=%llu",
|
||||
p->pid, p->comm, tctx->enqueue_seq);
|
||||
|
||||
scx_bpf_dsq_insert(p, SHARED_DSQ, SCX_SLICE_DFL, 0);
|
||||
|
||||
__sync_fetch_and_add(&enqueue_cnt, 1);
|
||||
|
||||
tctx->state = TASK_ENQUEUED;
|
||||
tctx->enqueue_seq++;
|
||||
break;
|
||||
}
|
||||
|
||||
return prev_cpu;
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(dequeue_enqueue, struct task_struct *p, u64 enq_flags)
|
||||
{
|
||||
struct task_ctx *tctx;
|
||||
s32 pid = p->pid;
|
||||
|
||||
tctx = try_lookup_task_ctx(p);
|
||||
if (!tctx)
|
||||
return;
|
||||
|
||||
switch (test_scenario) {
|
||||
case 3:
|
||||
/*
|
||||
* Direct dispatch to the local DSQ.
|
||||
*
|
||||
* Task bypasses BPF scheduler entirely: no enqueue
|
||||
* tracking, no ops.dequeue() callbacks.
|
||||
*/
|
||||
scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, SCX_SLICE_DFL, enq_flags);
|
||||
tctx->state = TASK_DISPATCHED;
|
||||
break;
|
||||
case 4:
|
||||
/*
|
||||
* Direct dispatch to the global DSQ.
|
||||
*
|
||||
* Task bypasses BPF scheduler entirely: no enqueue
|
||||
* tracking, no ops.dequeue() callbacks.
|
||||
*/
|
||||
scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags);
|
||||
tctx->state = TASK_DISPATCHED;
|
||||
break;
|
||||
case 5:
|
||||
/*
|
||||
* Dispatch to shared user DSQ.
|
||||
*
|
||||
* Task enters BPF scheduler management: track
|
||||
* enqueue/dequeue lifecycle and validate state
|
||||
* transitions.
|
||||
*/
|
||||
if (tctx->state == TASK_ENQUEUED)
|
||||
scx_bpf_error("%d (%s): enqueue while in ENQUEUED state seq=%llu",
|
||||
p->pid, p->comm, tctx->enqueue_seq);
|
||||
|
||||
scx_bpf_dsq_insert(p, SHARED_DSQ, SCX_SLICE_DFL, enq_flags);
|
||||
|
||||
__sync_fetch_and_add(&enqueue_cnt, 1);
|
||||
|
||||
tctx->state = TASK_ENQUEUED;
|
||||
tctx->enqueue_seq++;
|
||||
break;
|
||||
case 6:
|
||||
/*
|
||||
* Store task in BPF internal queue.
|
||||
*
|
||||
* Task enters BPF scheduler management: track
|
||||
* enqueue/dequeue lifecycle and validate state
|
||||
* transitions.
|
||||
*/
|
||||
if (tctx->state == TASK_ENQUEUED)
|
||||
scx_bpf_error("%d (%s): enqueue while in ENQUEUED state seq=%llu",
|
||||
p->pid, p->comm, tctx->enqueue_seq);
|
||||
|
||||
if (bpf_map_push_elem(&global_queue, &pid, 0)) {
|
||||
scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags);
|
||||
__sync_fetch_and_add(&bpf_queue_full, 1);
|
||||
|
||||
tctx->state = TASK_DISPATCHED;
|
||||
} else {
|
||||
__sync_fetch_and_add(&enqueue_cnt, 1);
|
||||
|
||||
tctx->state = TASK_ENQUEUED;
|
||||
tctx->enqueue_seq++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* For all other scenarios, dispatch to the global DSQ */
|
||||
scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags);
|
||||
tctx->state = TASK_DISPATCHED;
|
||||
break;
|
||||
}
|
||||
|
||||
scx_bpf_kick_cpu(scx_bpf_task_cpu(p), SCX_KICK_IDLE);
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(dequeue_dequeue, struct task_struct *p, u64 deq_flags)
|
||||
{
|
||||
struct task_ctx *tctx;
|
||||
|
||||
__sync_fetch_and_add(&dequeue_cnt, 1);
|
||||
|
||||
tctx = try_lookup_task_ctx(p);
|
||||
if (!tctx)
|
||||
return;
|
||||
|
||||
/*
|
||||
* For scenarios 0, 1, 3, and 4 (terminal DSQs: local and global),
|
||||
* ops.dequeue() should never be called because tasks bypass the
|
||||
* BPF scheduler entirely. If we get here, it's a kernel bug.
|
||||
*/
|
||||
if (test_scenario == 0 || test_scenario == 3) {
|
||||
scx_bpf_error("%d (%s): dequeue called for local DSQ scenario",
|
||||
p->pid, p->comm);
|
||||
return;
|
||||
}
|
||||
|
||||
if (test_scenario == 1 || test_scenario == 4) {
|
||||
scx_bpf_error("%d (%s): dequeue called for global DSQ scenario",
|
||||
p->pid, p->comm);
|
||||
return;
|
||||
}
|
||||
|
||||
if (deq_flags & SCX_DEQ_SCHED_CHANGE) {
|
||||
/*
|
||||
* Property change interrupting the workflow. Valid from
|
||||
* both ENQUEUED and DISPATCHED states. Transitions task
|
||||
* back to NONE state.
|
||||
*/
|
||||
__sync_fetch_and_add(&change_dequeue_cnt, 1);
|
||||
|
||||
/* Validate state transition */
|
||||
if (tctx->state != TASK_ENQUEUED && tctx->state != TASK_DISPATCHED)
|
||||
scx_bpf_error("%d (%s): invalid property change dequeue state=%d seq=%llu",
|
||||
p->pid, p->comm, tctx->state, tctx->enqueue_seq);
|
||||
|
||||
/*
|
||||
* Transition back to NONE: task outside scheduler control.
|
||||
*
|
||||
* Scenario 6: dispatch() checks tctx->state after popping a
|
||||
* PID, if the task is in state NONE, it was dequeued by
|
||||
* property change and must not be dispatched (this
|
||||
* prevents "target CPU not allowed").
|
||||
*/
|
||||
tctx->state = TASK_NONE;
|
||||
} else {
|
||||
/*
|
||||
* Regular dispatch dequeue: kernel is moving the task from
|
||||
* BPF custody to a terminal DSQ. Normally we come from
|
||||
* ENQUEUED state. We can also see TASK_NONE if the task
|
||||
* was dequeued by property change (SCX_DEQ_SCHED_CHANGE)
|
||||
* while it was already on a DSQ (dispatched but not yet
|
||||
* consumed); in that case we just leave state as NONE.
|
||||
*/
|
||||
__sync_fetch_and_add(&dispatch_dequeue_cnt, 1);
|
||||
|
||||
/*
|
||||
* Must be ENQUEUED (normal path) or NONE (already dequeued
|
||||
* by property change while on a DSQ).
|
||||
*/
|
||||
if (tctx->state != TASK_ENQUEUED && tctx->state != TASK_NONE)
|
||||
scx_bpf_error("%d (%s): dispatch dequeue from state %d seq=%llu",
|
||||
p->pid, p->comm, tctx->state, tctx->enqueue_seq);
|
||||
|
||||
if (tctx->state == TASK_ENQUEUED)
|
||||
tctx->state = TASK_DISPATCHED;
|
||||
|
||||
/* NONE: leave as-is, task was already property-change dequeued */
|
||||
}
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(dequeue_dispatch, s32 cpu, struct task_struct *prev)
|
||||
{
|
||||
if (test_scenario == 6) {
|
||||
struct task_ctx *tctx;
|
||||
struct task_struct *p;
|
||||
s32 pid;
|
||||
|
||||
if (bpf_map_pop_elem(&global_queue, &pid))
|
||||
return;
|
||||
|
||||
p = bpf_task_from_pid(pid);
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
/*
|
||||
* If the task was dequeued by property change
|
||||
* (ops.dequeue() set tctx->state = TASK_NONE), skip
|
||||
* dispatch.
|
||||
*/
|
||||
tctx = try_lookup_task_ctx(p);
|
||||
if (!tctx || tctx->state == TASK_NONE) {
|
||||
bpf_task_release(p);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dispatch to this CPU's local DSQ if allowed, otherwise
|
||||
* fallback to the global DSQ.
|
||||
*/
|
||||
if (bpf_cpumask_test_cpu(cpu, p->cpus_ptr))
|
||||
scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | cpu, SCX_SLICE_DFL, 0);
|
||||
else
|
||||
scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, 0);
|
||||
|
||||
bpf_task_release(p);
|
||||
} else {
|
||||
scx_bpf_dsq_move_to_local(SHARED_DSQ, 0);
|
||||
}
|
||||
}
|
||||
|
||||
s32 BPF_STRUCT_OPS(dequeue_init_task, struct task_struct *p,
|
||||
struct scx_init_task_args *args)
|
||||
{
|
||||
struct task_ctx *tctx;
|
||||
|
||||
tctx = bpf_task_storage_get(&task_ctx_stor, p, 0,
|
||||
BPF_LOCAL_STORAGE_GET_F_CREATE);
|
||||
if (!tctx)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 BPF_STRUCT_OPS_SLEEPABLE(dequeue_init)
|
||||
{
|
||||
s32 ret;
|
||||
|
||||
ret = scx_bpf_create_dsq(SHARED_DSQ, -1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(dequeue_exit, struct scx_exit_info *ei)
|
||||
{
|
||||
UEI_RECORD(uei, ei);
|
||||
}
|
||||
|
||||
SEC(".struct_ops.link")
|
||||
struct sched_ext_ops dequeue_ops = {
|
||||
.select_cpu = (void *)dequeue_select_cpu,
|
||||
.enqueue = (void *)dequeue_enqueue,
|
||||
.dequeue = (void *)dequeue_dequeue,
|
||||
.dispatch = (void *)dequeue_dispatch,
|
||||
.init_task = (void *)dequeue_init_task,
|
||||
.init = (void *)dequeue_init,
|
||||
.exit = (void *)dequeue_exit,
|
||||
.flags = SCX_OPS_ENQ_LAST,
|
||||
.name = "dequeue_test",
|
||||
};
|
||||
274
tools/testing/selftests/sched_ext/dequeue.c
Normal file
274
tools/testing/selftests/sched_ext/dequeue.c
Normal file
@@ -0,0 +1,274 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2025 NVIDIA Corporation.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <bpf/bpf.h>
|
||||
#include <scx/common.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sched.h>
|
||||
#include <pthread.h>
|
||||
#include "scx_test.h"
|
||||
#include "dequeue.bpf.skel.h"
|
||||
|
||||
#define NUM_WORKERS 8
|
||||
#define AFFINITY_HAMMER_MS 500
|
||||
|
||||
/*
|
||||
* Worker function that creates enqueue/dequeue events via CPU work and
|
||||
* sleep.
|
||||
*/
|
||||
static void worker_fn(int id)
|
||||
{
|
||||
int i;
|
||||
volatile int sum = 0;
|
||||
|
||||
for (i = 0; i < 1000; i++) {
|
||||
volatile int j;
|
||||
|
||||
/* Do some work to trigger scheduling events */
|
||||
for (j = 0; j < 10000; j++)
|
||||
sum += j;
|
||||
|
||||
/* Sleep to trigger dequeue */
|
||||
usleep(1000 + (id * 100));
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* This thread changes workers' affinity from outside so that some changes
|
||||
* hit tasks while they are still in the scheduler's queue and trigger
|
||||
* property-change dequeues.
|
||||
*/
|
||||
static void *affinity_hammer_fn(void *arg)
|
||||
{
|
||||
pid_t *pids = arg;
|
||||
cpu_set_t cpuset;
|
||||
int i = 0, n = NUM_WORKERS;
|
||||
struct timespec start, now;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
while (1) {
|
||||
int w = i % n;
|
||||
int cpu = (i / n) % 4;
|
||||
|
||||
CPU_ZERO(&cpuset);
|
||||
CPU_SET(cpu, &cpuset);
|
||||
sched_setaffinity(pids[w], sizeof(cpuset), &cpuset);
|
||||
i++;
|
||||
|
||||
/* Check elapsed time every 256 iterations to limit gettime cost */
|
||||
if ((i & 255) == 0) {
|
||||
long long elapsed_ms;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
elapsed_ms = (now.tv_sec - start.tv_sec) * 1000LL +
|
||||
(now.tv_nsec - start.tv_nsec) / 1000000;
|
||||
if (elapsed_ms >= AFFINITY_HAMMER_MS)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static enum scx_test_status run_scenario(struct dequeue *skel, u32 scenario,
|
||||
const char *scenario_name)
|
||||
{
|
||||
struct bpf_link *link;
|
||||
pid_t pids[NUM_WORKERS];
|
||||
pthread_t hammer;
|
||||
|
||||
int i, status;
|
||||
u64 enq_start, deq_start,
|
||||
dispatch_deq_start, change_deq_start, bpf_queue_full_start;
|
||||
u64 enq_delta, deq_delta,
|
||||
dispatch_deq_delta, change_deq_delta, bpf_queue_full_delta;
|
||||
|
||||
/* Set the test scenario */
|
||||
skel->bss->test_scenario = scenario;
|
||||
|
||||
/* Record starting counts */
|
||||
enq_start = skel->bss->enqueue_cnt;
|
||||
deq_start = skel->bss->dequeue_cnt;
|
||||
dispatch_deq_start = skel->bss->dispatch_dequeue_cnt;
|
||||
change_deq_start = skel->bss->change_dequeue_cnt;
|
||||
bpf_queue_full_start = skel->bss->bpf_queue_full;
|
||||
|
||||
link = bpf_map__attach_struct_ops(skel->maps.dequeue_ops);
|
||||
SCX_FAIL_IF(!link, "Failed to attach struct_ops for scenario %s", scenario_name);
|
||||
|
||||
/* Fork worker processes to generate enqueue/dequeue events */
|
||||
for (i = 0; i < NUM_WORKERS; i++) {
|
||||
pids[i] = fork();
|
||||
SCX_FAIL_IF(pids[i] < 0, "Failed to fork worker %d", i);
|
||||
|
||||
if (pids[i] == 0) {
|
||||
worker_fn(i);
|
||||
/* Should not reach here */
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Run an "affinity hammer" so that some property changes hit tasks
|
||||
* while they are still in BPF custody (e.g., in user DSQ or BPF
|
||||
* queue), triggering SCX_DEQ_SCHED_CHANGE dequeues.
|
||||
*/
|
||||
SCX_FAIL_IF(pthread_create(&hammer, NULL, affinity_hammer_fn, pids) != 0,
|
||||
"Failed to create affinity hammer thread");
|
||||
pthread_join(hammer, NULL);
|
||||
|
||||
/* Wait for all workers to complete */
|
||||
for (i = 0; i < NUM_WORKERS; i++) {
|
||||
SCX_FAIL_IF(waitpid(pids[i], &status, 0) != pids[i],
|
||||
"Failed to wait for worker %d", i);
|
||||
SCX_FAIL_IF(status != 0, "Worker %d exited with status %d", i, status);
|
||||
}
|
||||
|
||||
bpf_link__destroy(link);
|
||||
|
||||
SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG));
|
||||
|
||||
/* Calculate deltas */
|
||||
enq_delta = skel->bss->enqueue_cnt - enq_start;
|
||||
deq_delta = skel->bss->dequeue_cnt - deq_start;
|
||||
dispatch_deq_delta = skel->bss->dispatch_dequeue_cnt - dispatch_deq_start;
|
||||
change_deq_delta = skel->bss->change_dequeue_cnt - change_deq_start;
|
||||
bpf_queue_full_delta = skel->bss->bpf_queue_full - bpf_queue_full_start;
|
||||
|
||||
printf("%s:\n", scenario_name);
|
||||
printf(" enqueues: %lu\n", (unsigned long)enq_delta);
|
||||
printf(" dequeues: %lu (dispatch: %lu, property_change: %lu)\n",
|
||||
(unsigned long)deq_delta,
|
||||
(unsigned long)dispatch_deq_delta,
|
||||
(unsigned long)change_deq_delta);
|
||||
printf(" BPF queue full: %lu\n", (unsigned long)bpf_queue_full_delta);
|
||||
|
||||
/*
|
||||
* Validate enqueue/dequeue lifecycle tracking.
|
||||
*
|
||||
* For scenarios 0, 1, 3, 4 (local and global DSQs from
|
||||
* ops.select_cpu() and ops.enqueue()), both enqueues and dequeues
|
||||
* should be 0 because tasks bypass the BPF scheduler entirely:
|
||||
* tasks never enter BPF scheduler's custody.
|
||||
*
|
||||
* For scenarios 2, 5, 6 (user DSQ or BPF internal queue) we expect
|
||||
* both enqueues and dequeues.
|
||||
*
|
||||
* The BPF code does strict state machine validation with
|
||||
* scx_bpf_error() to ensure the workflow semantics are correct.
|
||||
*
|
||||
* If we reach this point without errors, the semantics are
|
||||
* validated correctly.
|
||||
*/
|
||||
if (scenario == 0 || scenario == 1 ||
|
||||
scenario == 3 || scenario == 4) {
|
||||
/* Tasks bypass BPF scheduler completely */
|
||||
SCX_EQ(enq_delta, 0);
|
||||
SCX_EQ(deq_delta, 0);
|
||||
SCX_EQ(dispatch_deq_delta, 0);
|
||||
SCX_EQ(change_deq_delta, 0);
|
||||
} else {
|
||||
/*
|
||||
* User DSQ from ops.enqueue() or ops.select_cpu(): tasks
|
||||
* enter BPF scheduler's custody.
|
||||
*
|
||||
* Also validate 1:1 enqueue/dequeue pairing.
|
||||
*/
|
||||
SCX_GT(enq_delta, 0);
|
||||
SCX_GT(deq_delta, 0);
|
||||
SCX_EQ(enq_delta, deq_delta);
|
||||
}
|
||||
|
||||
return SCX_TEST_PASS;
|
||||
}
|
||||
|
||||
static enum scx_test_status setup(void **ctx)
|
||||
{
|
||||
struct dequeue *skel;
|
||||
|
||||
skel = dequeue__open();
|
||||
SCX_FAIL_IF(!skel, "Failed to open skel");
|
||||
SCX_ENUM_INIT(skel);
|
||||
SCX_FAIL_IF(dequeue__load(skel), "Failed to load skel");
|
||||
|
||||
*ctx = skel;
|
||||
|
||||
return SCX_TEST_PASS;
|
||||
}
|
||||
|
||||
static enum scx_test_status run(void *ctx)
|
||||
{
|
||||
struct dequeue *skel = ctx;
|
||||
enum scx_test_status status;
|
||||
|
||||
status = run_scenario(skel, 0, "Scenario 0: Local DSQ from ops.select_cpu()");
|
||||
if (status != SCX_TEST_PASS)
|
||||
return status;
|
||||
|
||||
status = run_scenario(skel, 1, "Scenario 1: Global DSQ from ops.select_cpu()");
|
||||
if (status != SCX_TEST_PASS)
|
||||
return status;
|
||||
|
||||
status = run_scenario(skel, 2, "Scenario 2: User DSQ from ops.select_cpu()");
|
||||
if (status != SCX_TEST_PASS)
|
||||
return status;
|
||||
|
||||
status = run_scenario(skel, 3, "Scenario 3: Local DSQ from ops.enqueue()");
|
||||
if (status != SCX_TEST_PASS)
|
||||
return status;
|
||||
|
||||
status = run_scenario(skel, 4, "Scenario 4: Global DSQ from ops.enqueue()");
|
||||
if (status != SCX_TEST_PASS)
|
||||
return status;
|
||||
|
||||
status = run_scenario(skel, 5, "Scenario 5: User DSQ from ops.enqueue()");
|
||||
if (status != SCX_TEST_PASS)
|
||||
return status;
|
||||
|
||||
status = run_scenario(skel, 6, "Scenario 6: BPF queue from ops.enqueue()");
|
||||
if (status != SCX_TEST_PASS)
|
||||
return status;
|
||||
|
||||
printf("\n=== Summary ===\n");
|
||||
printf("Total enqueues: %lu\n", (unsigned long)skel->bss->enqueue_cnt);
|
||||
printf("Total dequeues: %lu\n", (unsigned long)skel->bss->dequeue_cnt);
|
||||
printf(" Dispatch dequeues: %lu (no flag, normal workflow)\n",
|
||||
(unsigned long)skel->bss->dispatch_dequeue_cnt);
|
||||
printf(" Property change dequeues: %lu (SCX_DEQ_SCHED_CHANGE flag)\n",
|
||||
(unsigned long)skel->bss->change_dequeue_cnt);
|
||||
printf(" BPF queue full: %lu\n",
|
||||
(unsigned long)skel->bss->bpf_queue_full);
|
||||
printf("\nAll scenarios passed - no state machine violations detected\n");
|
||||
printf("-> Validated: Local DSQ dispatch bypasses BPF scheduler\n");
|
||||
printf("-> Validated: Global DSQ dispatch bypasses BPF scheduler\n");
|
||||
printf("-> Validated: User DSQ dispatch triggers ops.dequeue() callbacks\n");
|
||||
printf("-> Validated: Dispatch dequeues have no flags (normal workflow)\n");
|
||||
printf("-> Validated: Property change dequeues have SCX_DEQ_SCHED_CHANGE flag\n");
|
||||
printf("-> Validated: No duplicate enqueues or invalid state transitions\n");
|
||||
|
||||
return SCX_TEST_PASS;
|
||||
}
|
||||
|
||||
static void cleanup(void *ctx)
|
||||
{
|
||||
struct dequeue *skel = ctx;
|
||||
|
||||
dequeue__destroy(skel);
|
||||
}
|
||||
|
||||
struct scx_test dequeue_test = {
|
||||
.name = "dequeue",
|
||||
.description = "Verify ops.dequeue() semantics",
|
||||
.setup = setup,
|
||||
.run = run,
|
||||
.cleanup = cleanup,
|
||||
};
|
||||
|
||||
REGISTER_SCX_TEST(&dequeue_test)
|
||||
@@ -41,7 +41,7 @@ void BPF_STRUCT_OPS(exit_dispatch, s32 cpu, struct task_struct *p)
|
||||
if (exit_point == EXIT_DISPATCH)
|
||||
EXIT_CLEANLY();
|
||||
|
||||
scx_bpf_dsq_move_to_local(DSQ_ID);
|
||||
scx_bpf_dsq_move_to_local(DSQ_ID, 0);
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(exit_enable, struct task_struct *p)
|
||||
|
||||
@@ -33,7 +33,7 @@ static enum scx_test_status run(void *ctx)
|
||||
skel = exit__open();
|
||||
SCX_ENUM_INIT(skel);
|
||||
skel->rodata->exit_point = tc;
|
||||
exit__load(skel);
|
||||
SCX_FAIL_IF(exit__load(skel), "Failed to load skel");
|
||||
link = bpf_map__attach_struct_ops(skel->maps.exit_ops);
|
||||
if (!link) {
|
||||
SCX_ERR("Failed to attach scheduler");
|
||||
|
||||
@@ -17,4 +17,4 @@ enum exit_test_case {
|
||||
NUM_EXITS,
|
||||
};
|
||||
|
||||
#endif // # __EXIT_TEST_H__
|
||||
#endif // __EXIT_TEST_H__
|
||||
|
||||
@@ -30,7 +30,7 @@ void BPF_STRUCT_OPS(maximal_dequeue, struct task_struct *p, u64 deq_flags)
|
||||
|
||||
void BPF_STRUCT_OPS(maximal_dispatch, s32 cpu, struct task_struct *prev)
|
||||
{
|
||||
scx_bpf_dsq_move_to_local(DSQ_ID);
|
||||
scx_bpf_dsq_move_to_local(DSQ_ID, 0);
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(maximal_runnable, struct task_struct *p, u64 enq_flags)
|
||||
@@ -67,13 +67,12 @@ void BPF_STRUCT_OPS(maximal_set_cpumask, struct task_struct *p,
|
||||
void BPF_STRUCT_OPS(maximal_update_idle, s32 cpu, bool idle)
|
||||
{}
|
||||
|
||||
void BPF_STRUCT_OPS(maximal_cpu_acquire, s32 cpu,
|
||||
struct scx_cpu_acquire_args *args)
|
||||
{}
|
||||
|
||||
void BPF_STRUCT_OPS(maximal_cpu_release, s32 cpu,
|
||||
struct scx_cpu_release_args *args)
|
||||
{}
|
||||
SEC("tp_btf/sched_switch")
|
||||
int BPF_PROG(maximal_sched_switch, bool preempt, struct task_struct *prev,
|
||||
struct task_struct *next, unsigned int prev_state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(maximal_cpu_online, s32 cpu)
|
||||
{}
|
||||
@@ -150,8 +149,6 @@ struct sched_ext_ops maximal_ops = {
|
||||
.set_weight = (void *) maximal_set_weight,
|
||||
.set_cpumask = (void *) maximal_set_cpumask,
|
||||
.update_idle = (void *) maximal_update_idle,
|
||||
.cpu_acquire = (void *) maximal_cpu_acquire,
|
||||
.cpu_release = (void *) maximal_cpu_release,
|
||||
.cpu_online = (void *) maximal_cpu_online,
|
||||
.cpu_offline = (void *) maximal_cpu_offline,
|
||||
.init_task = (void *) maximal_init_task,
|
||||
|
||||
@@ -19,6 +19,9 @@ static enum scx_test_status setup(void **ctx)
|
||||
SCX_ENUM_INIT(skel);
|
||||
SCX_FAIL_IF(maximal__load(skel), "Failed to load skel");
|
||||
|
||||
bpf_map__set_autoattach(skel->maps.maximal_ops, false);
|
||||
SCX_FAIL_IF(maximal__attach(skel), "Failed to attach skel");
|
||||
|
||||
*ctx = skel;
|
||||
|
||||
return SCX_TEST_PASS;
|
||||
|
||||
@@ -68,7 +68,7 @@ void BPF_STRUCT_OPS(numa_dispatch, s32 cpu, struct task_struct *prev)
|
||||
{
|
||||
int node = __COMPAT_scx_bpf_cpu_node(cpu);
|
||||
|
||||
scx_bpf_dsq_move_to_local(node);
|
||||
scx_bpf_dsq_move_to_local(node, 0);
|
||||
}
|
||||
|
||||
s32 BPF_STRUCT_OPS_SLEEPABLE(numa_init)
|
||||
|
||||
@@ -95,7 +95,7 @@ static int scan_dsq_pool(void)
|
||||
record_peek_result(task->pid);
|
||||
|
||||
/* Try to move this task to local */
|
||||
if (!moved && scx_bpf_dsq_move_to_local(dsq_id) == 0) {
|
||||
if (!moved && scx_bpf_dsq_move_to_local(dsq_id, 0) == 0) {
|
||||
moved = 1;
|
||||
break;
|
||||
}
|
||||
@@ -156,19 +156,19 @@ void BPF_STRUCT_OPS(peek_dsq_dispatch, s32 cpu, struct task_struct *prev)
|
||||
dsq_peek_result2_pid = peek_result ? peek_result->pid : -1;
|
||||
|
||||
/* Now consume the task since we've peeked at it */
|
||||
scx_bpf_dsq_move_to_local(test_dsq_id);
|
||||
scx_bpf_dsq_move_to_local(test_dsq_id, 0);
|
||||
|
||||
/* Mark phase 1 as complete */
|
||||
phase1_complete = 1;
|
||||
bpf_printk("Phase 1 complete, starting phase 2 stress testing");
|
||||
} else if (!phase1_complete) {
|
||||
/* Still in phase 1, use real DSQ */
|
||||
scx_bpf_dsq_move_to_local(real_dsq_id);
|
||||
scx_bpf_dsq_move_to_local(real_dsq_id, 0);
|
||||
} else {
|
||||
/* Phase 2: Scan all DSQs in the pool and try to move a task */
|
||||
if (!scan_dsq_pool()) {
|
||||
/* No tasks found in DSQ pool, fall back to real DSQ */
|
||||
scx_bpf_dsq_move_to_local(real_dsq_id);
|
||||
scx_bpf_dsq_move_to_local(real_dsq_id, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +197,7 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(peek_dsq_init)
|
||||
}
|
||||
err = scx_bpf_create_dsq(real_dsq_id, -1);
|
||||
if (err) {
|
||||
scx_bpf_error("Failed to create DSQ %d: %d", test_dsq_id, err);
|
||||
scx_bpf_error("Failed to create DSQ %d: %d", real_dsq_id, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ static enum scx_test_status setup(void **ctx)
|
||||
SCX_ENUM_INIT(skel);
|
||||
SCX_FAIL_IF(maximal__load(skel), "Failed to load skel");
|
||||
|
||||
bpf_map__set_autoattach(skel->maps.maximal_ops, false);
|
||||
SCX_FAIL_IF(maximal__attach(skel), "Failed to attach skel");
|
||||
|
||||
return SCX_TEST_PASS;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +119,11 @@ static enum scx_test_status setup(void **ctx)
|
||||
{
|
||||
struct rt_stall *skel;
|
||||
|
||||
if (!__COMPAT_struct_has_field("rq", "ext_server")) {
|
||||
fprintf(stderr, "SKIP: ext DL server not supported\n");
|
||||
return SCX_TEST_SKIP;
|
||||
}
|
||||
|
||||
skel = rt_stall__open();
|
||||
SCX_FAIL_IF(!skel, "Failed to open");
|
||||
SCX_ENUM_INIT(skel);
|
||||
|
||||
@@ -18,7 +18,7 @@ const char help_fmt[] =
|
||||
"It's required for the testcases to be serial, as only a single host-wide sched_ext\n"
|
||||
"scheduler may be loaded at any given time."
|
||||
"\n"
|
||||
"Usage: %s [-t TEST] [-h]\n"
|
||||
"Usage: %s [-t TEST] [-s] [-l] [-q]\n"
|
||||
"\n"
|
||||
" -t TEST Only run tests whose name includes this string\n"
|
||||
" -s Include print output for skipped tests\n"
|
||||
@@ -133,6 +133,8 @@ static bool test_valid(const struct scx_test *test)
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *filter = NULL;
|
||||
const char *failed_tests[MAX_SCX_TESTS];
|
||||
const char *skipped_tests[MAX_SCX_TESTS];
|
||||
unsigned testnum = 0, i;
|
||||
unsigned passed = 0, skipped = 0, failed = 0;
|
||||
int opt;
|
||||
@@ -162,6 +164,26 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc) {
|
||||
fprintf(stderr, "Unexpected argument '%s'. Use -t to filter tests.\n",
|
||||
argv[optind]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
for (i = 0; i < __scx_num_tests; i++) {
|
||||
if (!should_skip_test(&__scx_tests[i], filter))
|
||||
break;
|
||||
}
|
||||
if (i == __scx_num_tests) {
|
||||
fprintf(stderr, "No tests matched filter '%s'\n", filter);
|
||||
fprintf(stderr, "Available tests (use -l to list):\n");
|
||||
for (i = 0; i < __scx_num_tests; i++)
|
||||
fprintf(stderr, " %s\n", __scx_tests[i].name);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < __scx_num_tests; i++) {
|
||||
enum scx_test_status status;
|
||||
struct scx_test *test = &__scx_tests[i];
|
||||
@@ -198,10 +220,10 @@ int main(int argc, char **argv)
|
||||
passed++;
|
||||
break;
|
||||
case SCX_TEST_SKIP:
|
||||
skipped++;
|
||||
skipped_tests[skipped++] = test->name;
|
||||
break;
|
||||
case SCX_TEST_FAIL:
|
||||
failed++;
|
||||
failed_tests[failed++] = test->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -210,8 +232,18 @@ int main(int argc, char **argv)
|
||||
printf("PASSED: %u\n", passed);
|
||||
printf("SKIPPED: %u\n", skipped);
|
||||
printf("FAILED: %u\n", failed);
|
||||
if (skipped > 0) {
|
||||
printf("\nSkipped tests:\n");
|
||||
for (i = 0; i < skipped; i++)
|
||||
printf(" - %s\n", skipped_tests[i]);
|
||||
}
|
||||
if (failed > 0) {
|
||||
printf("\nFailed tests:\n");
|
||||
for (i = 0; i < failed; i++)
|
||||
printf(" - %s\n", failed_tests[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return failed > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
void scx_test_register(struct scx_test *test)
|
||||
|
||||
@@ -53,7 +53,7 @@ ddsp:
|
||||
|
||||
void BPF_STRUCT_OPS(select_cpu_vtime_dispatch, s32 cpu, struct task_struct *p)
|
||||
{
|
||||
if (scx_bpf_dsq_move_to_local(VTIME_DSQ))
|
||||
if (scx_bpf_dsq_move_to_local(VTIME_DSQ, 0))
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
@@ -66,12 +66,14 @@ void BPF_STRUCT_OPS(select_cpu_vtime_running, struct task_struct *p)
|
||||
void BPF_STRUCT_OPS(select_cpu_vtime_stopping, struct task_struct *p,
|
||||
bool runnable)
|
||||
{
|
||||
p->scx.dsq_vtime += (SCX_SLICE_DFL - p->scx.slice) * 100 / p->scx.weight;
|
||||
u64 delta = scale_by_task_weight_inverse(p, SCX_SLICE_DFL - p->scx.slice);
|
||||
|
||||
scx_bpf_task_set_dsq_vtime(p, p->scx.dsq_vtime + delta);
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(select_cpu_vtime_enable, struct task_struct *p)
|
||||
{
|
||||
p->scx.dsq_vtime = vtime_now;
|
||||
scx_bpf_task_set_dsq_vtime(p, vtime_now);
|
||||
}
|
||||
|
||||
s32 BPF_STRUCT_OPS_SLEEPABLE(select_cpu_vtime_init)
|
||||
|
||||
@@ -10,4 +10,4 @@
|
||||
long file_read_long(const char *path);
|
||||
int file_write_long(const char *path, long val);
|
||||
|
||||
#endif // __SCX_TEST_H__
|
||||
#endif // __SCX_TEST_UTIL_H__
|
||||
|
||||
Reference in New Issue
Block a user