1.. _sched-ext: 2 3========================== 4Extensible Scheduler Class 5========================== 6 7sched_ext is a scheduler class whose behavior can be defined by a set of BPF 8programs - the BPF scheduler. 9 10* sched_ext exports a full scheduling interface so that any scheduling 11 algorithm can be implemented on top. 12 13* The BPF scheduler can group CPUs however it sees fit and schedule them 14 together, as tasks aren't tied to specific CPUs at the time of wakeup. 15 16* The BPF scheduler can be turned on and off dynamically anytime. 17 18* The system integrity is maintained no matter what the BPF scheduler does. 19 The default scheduling behavior is restored anytime an error is detected, 20 a runnable task stalls, or on invoking the SysRq key sequence 21 `SysRq-S`. 22 23* When the BPF scheduler triggers an error, debug information is dumped to 24 aid debugging. The debug dump is passed to and printed out by the 25 scheduler binary. The debug dump can also be accessed through the 26 `sched_ext_dump` tracepoint. The SysRq key sequence `SysRq-D` 27 triggers a debug dump. This doesn't terminate the BPF scheduler and can 28 only be read through the tracepoint. 29 30Switching to and from sched_ext 31=============================== 32 33``CONFIG_SCHED_CLASS_EXT`` is the config option to enable sched_ext and 34``tools/sched_ext`` contains the example schedulers. The following config 35options should be enabled to use sched_ext: 36 37.. code-block:: none 38 39 CONFIG_BPF=y 40 CONFIG_SCHED_CLASS_EXT=y 41 CONFIG_BPF_SYSCALL=y 42 CONFIG_BPF_JIT=y 43 CONFIG_DEBUG_INFO_BTF=y 44 CONFIG_BPF_JIT_ALWAYS_ON=y 45 CONFIG_BPF_JIT_DEFAULT_ON=y 46 47sched_ext is used only when the BPF scheduler is loaded and running. 48 49If a task explicitly sets its scheduling policy to ``SCHED_EXT``, it will be 50treated as ``SCHED_NORMAL`` and scheduled by the fair-class scheduler until the 51BPF scheduler is loaded. 52 53When the BPF scheduler is loaded and ``SCX_OPS_SWITCH_PARTIAL`` is not set 54in ``ops->flags``, all ``SCHED_NORMAL``, ``SCHED_BATCH``, ``SCHED_IDLE``, and 55``SCHED_EXT`` tasks are scheduled by sched_ext. 56 57However, when the BPF scheduler is loaded and ``SCX_OPS_SWITCH_PARTIAL`` is 58set in ``ops->flags``, only tasks with the ``SCHED_EXT`` policy are scheduled 59by sched_ext, while tasks with ``SCHED_NORMAL``, ``SCHED_BATCH`` and 60``SCHED_IDLE`` policies are scheduled by the fair-class scheduler which has 61higher sched_class precedence than ``SCHED_EXT``. 62 63Terminating the sched_ext scheduler program, triggering `SysRq-S`, or 64detection of any internal error including stalled runnable tasks aborts the 65BPF scheduler and reverts all tasks back to the fair-class scheduler. 66 67.. code-block:: none 68 69 # make -j16 -C tools/sched_ext 70 # tools/sched_ext/build/bin/scx_simple 71 local=0 global=3 72 local=5 global=24 73 local=9 global=44 74 local=13 global=56 75 local=17 global=72 76 ^CEXIT: BPF scheduler unregistered 77 78The current status of the BPF scheduler can be determined as follows: 79 80.. code-block:: none 81 82 # cat /sys/kernel/sched_ext/state 83 enabled 84 # cat /sys/kernel/sched_ext/root/ops 85 simple 86 87You can check if any BPF scheduler has ever been loaded since boot by examining 88this monotonically incrementing counter (a value of zero indicates that no BPF 89scheduler has been loaded): 90 91.. code-block:: none 92 93 # cat /sys/kernel/sched_ext/enable_seq 94 1 95 96Each running scheduler exposes an ``events`` file under its sysfs kobject 97(``/sys/kernel/sched_ext/root/events`` for the root scheduler) that tracks 98diagnostic counters. Each counter occupies one ``name value`` line: 99 100.. code-block:: none 101 102 # cat /sys/kernel/sched_ext/root/events 103 SCX_EV_SELECT_CPU_FALLBACK 0 104 SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE 0 105 SCX_EV_DISPATCH_KEEP_LAST 123 106 SCX_EV_ENQ_SKIP_EXITING 0 107 SCX_EV_ENQ_SKIP_MIGRATION_DISABLED 0 108 SCX_EV_REENQ_IMMED 0 109 SCX_EV_REENQ_REPEAT 0 110 SCX_EV_REFILL_SLICE_DFL 456789 111 SCX_EV_BYPASS_DURATION 0 112 SCX_EV_BYPASS_DISPATCH 0 113 SCX_EV_BYPASS_ACTIVATE 0 114 SCX_EV_INSERT_NOT_OWNED 0 115 SCX_EV_SUB_BYPASS_DISPATCH 0 116 117The counters are described in ``kernel/sched/ext/internal.h``; briefly: 118 119* ``SCX_EV_SELECT_CPU_FALLBACK``: ops.select_cpu() returned a CPU unusable by 120 the task and the core scheduler silently picked a fallback CPU. 121* ``SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE``: a local-DSQ dispatch was redirected 122 to the global DSQ because the target CPU went offline. 123* ``SCX_EV_DISPATCH_KEEP_LAST``: a task continued running because no other 124 task was available (only when ``SCX_OPS_ENQ_LAST`` is not set). 125* ``SCX_EV_ENQ_SKIP_EXITING``: an exiting task was dispatched to the local DSQ 126 directly, bypassing ops.enqueue() (only when ``SCX_OPS_ENQ_EXITING`` is not set). 127* ``SCX_EV_ENQ_SKIP_MIGRATION_DISABLED``: a migration-disabled task was 128 dispatched to its local DSQ directly (only when 129 ``SCX_OPS_ENQ_MIGRATION_DISABLED`` is not set). 130* ``SCX_EV_REENQ_IMMED``: a task dispatched with ``SCX_ENQ_IMMED`` was 131 re-enqueued because the target CPU was not available for immediate execution. 132* ``SCX_EV_REENQ_REPEAT``: a reenqueue led to another reenqueue without the 133 task running in between; recurring counts indicate that the BPF scheduler 134 keeps re-deciding placements it can't honor. 135* ``SCX_EV_REFILL_SLICE_DFL``: a task's time slice was refilled with the 136 default value (``SCX_SLICE_DFL``). 137* ``SCX_EV_BYPASS_DURATION``: total nanoseconds spent in bypass mode. 138* ``SCX_EV_BYPASS_DISPATCH``: number of tasks dispatched while in bypass mode. 139* ``SCX_EV_BYPASS_ACTIVATE``: number of times bypass mode was activated. 140* ``SCX_EV_INSERT_NOT_OWNED``: attempted to insert a task not owned by this 141 scheduler into a DSQ; such attempts are silently ignored. 142* ``SCX_EV_SUB_BYPASS_DISPATCH``: tasks dispatched from sub-scheduler bypass 143 DSQs (only relevant with ``CONFIG_EXT_SUB_SCHED``). 144 145``tools/sched_ext/scx_show_state.py`` is a drgn script which shows more 146detailed information: 147 148.. code-block:: none 149 150 # tools/sched_ext/scx_show_state.py 151 ops : simple 152 enabled : 1 153 switching_all : 1 154 switched_all : 1 155 enable_state : enabled (2) 156 aborting : False 157 bypass_depth : 0 158 nr_rejected : 0 159 enable_seq : 1 160 161Whether a given task is on sched_ext can be determined as follows: 162 163.. code-block:: none 164 165 # grep ext /proc/self/sched 166 ext.enabled : 1 167 168The Basics 169========== 170 171Userspace can implement an arbitrary BPF scheduler by loading a set of BPF 172programs that implement ``struct sched_ext_ops``. The only mandatory field 173is ``ops.name`` which must be a valid BPF object name. All operations are 174optional. The following modified excerpt is from 175``tools/sched_ext/scx_simple.bpf.c`` showing a minimal global FIFO scheduler. 176 177.. code-block:: c 178 179 /* 180 * Decide which CPU a task should be migrated to before being 181 * enqueued (either at wakeup, fork time, or exec time). If an 182 * idle core is found by the default ops.select_cpu() implementation, 183 * then insert the task directly into SCX_DSQ_LOCAL and skip the 184 * ops.enqueue() callback. 185 * 186 * Note that this implementation has exactly the same behavior as the 187 * default ops.select_cpu implementation. The behavior of the scheduler 188 * would be exactly same if the implementation just didn't define the 189 * simple_select_cpu() struct_ops prog. 190 */ 191 s32 BPF_STRUCT_OPS(simple_select_cpu, struct task_struct *p, 192 s32 prev_cpu, u64 wake_flags) 193 { 194 s32 cpu; 195 /* Need to initialize or the BPF verifier will reject the program */ 196 bool direct = false; 197 198 cpu = scx_bpf_select_cpu_dfl(p, prev_cpu, wake_flags, &direct); 199 200 if (direct) 201 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, SCX_SLICE_DFL, 0); 202 203 return cpu; 204 } 205 206 /* 207 * Do a direct insertion of a task to the global DSQ. This ops.enqueue() 208 * callback will only be invoked if we failed to find a core to insert 209 * into in ops.select_cpu() above. 210 * 211 * Note that this implementation has exactly the same behavior as the 212 * default ops.enqueue implementation, which just dispatches the task 213 * to SCX_DSQ_GLOBAL. The behavior of the scheduler would be exactly same 214 * if the implementation just didn't define the simple_enqueue struct_ops 215 * prog. 216 */ 217 void BPF_STRUCT_OPS(simple_enqueue, struct task_struct *p, u64 enq_flags) 218 { 219 scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags); 220 } 221 222 s32 BPF_STRUCT_OPS_SLEEPABLE(simple_init) 223 { 224 /* 225 * By default, all SCHED_EXT, SCHED_OTHER, SCHED_IDLE, and 226 * SCHED_BATCH tasks should use sched_ext. 227 */ 228 return 0; 229 } 230 231 void BPF_STRUCT_OPS(simple_exit, struct scx_exit_info *ei) 232 { 233 exit_type = ei->kind; 234 } 235 236 SEC(".struct_ops") 237 struct sched_ext_ops simple_ops = { 238 .select_cpu = (void *)simple_select_cpu, 239 .enqueue = (void *)simple_enqueue, 240 .init = (void *)simple_init, 241 .exit = (void *)simple_exit, 242 .name = "simple", 243 }; 244 245Scheduler-Dependent Knobs 246------------------------- 247 248The fair-class scheduler enforces CPU controller settings such as 249``cpu.max``, ``cpu.weight`` and ``cpu.idle``. For sched_ext tasks, the 250scheduler core communicates these settings to the BPF scheduler 251through ``ops.cgroup_init()`` and reports subsequent changes through 252the corresponding ``ops.cgroup_set_*()`` callbacks. Similarly, per-task 253nice changes are converted to weights and reported through 254``ops.set_weight()``. 255 256Each BPF scheduler is responsible for implementing the scheduling 257semantics of these settings and may choose to ignore them. Consult the 258loaded scheduler's documentation before relying on these controls. 259 260Dispatch Queues 261--------------- 262 263To match the impedance between the scheduler core and the BPF scheduler, 264sched_ext uses DSQs (dispatch queues) which can operate as both a FIFO and a 265priority queue. By default, there is one global FIFO (``SCX_DSQ_GLOBAL``), 266and one local DSQ per CPU (``SCX_DSQ_LOCAL``). The BPF scheduler can manage 267an arbitrary number of DSQs using ``scx_bpf_create_dsq()`` and 268``scx_bpf_destroy_dsq()``. 269 270A CPU always executes a task from its local DSQ. A task is "inserted" into a 271DSQ. A task in a non-local DSQ is "move"d into the target CPU's local DSQ. 272 273When a CPU is looking for the next task to run, if the local DSQ is not 274empty, the first task is picked. Otherwise, the CPU tries to move a task 275from the global DSQ. If that doesn't yield a runnable task either, 276``ops.dispatch()`` is invoked. 277 278Scheduling Cycle 279---------------- 280 281The following briefly shows how a waking task is scheduled and executed. 282 2831. When a task is waking up, ``ops.select_cpu()`` is the first operation 284 invoked. This serves two purposes. First, CPU selection optimization 285 hint. Second, waking up the selected CPU if idle. 286 287 The CPU selected by ``ops.select_cpu()`` is an optimization hint and not 288 binding. The actual decision is made at the last step of scheduling. 289 However, there is a small performance gain if the CPU 290 ``ops.select_cpu()`` returns matches the CPU the task eventually runs on. 291 292 A side-effect of selecting a CPU is waking it up from idle. While a BPF 293 scheduler can wake up any cpu using the ``scx_bpf_kick_cpu()`` helper, 294 using ``ops.select_cpu()`` judiciously can be simpler and more efficient. 295 296 Note that the scheduler core will ignore an invalid CPU selection, for 297 example, if it's outside the allowed cpumask of the task. 298 299 A task can be immediately inserted into a DSQ from ``ops.select_cpu()`` 300 by calling ``scx_bpf_dsq_insert()`` or ``scx_bpf_dsq_insert_vtime()``. 301 302 If the task is inserted into ``SCX_DSQ_LOCAL`` from 303 ``ops.select_cpu()``, it will be added to the local DSQ of whichever CPU 304 is returned from ``ops.select_cpu()``. Additionally, inserting directly 305 from ``ops.select_cpu()`` will cause the ``ops.enqueue()`` callback to 306 be skipped. 307 308 Any other attempt to store a task in BPF-internal data structures from 309 ``ops.select_cpu()`` does not prevent ``ops.enqueue()`` from being 310 invoked. This is discouraged, as it can introduce racy behavior or 311 inconsistent state. 312 3132. Once the target CPU is selected, ``ops.enqueue()`` is invoked (unless the 314 task was inserted directly from ``ops.select_cpu()``). ``ops.enqueue()`` 315 can make one of the following decisions: 316 317 * Immediately insert the task into either the global or a local DSQ by 318 calling ``scx_bpf_dsq_insert()`` with one of the following options: 319 ``SCX_DSQ_GLOBAL``, ``SCX_DSQ_LOCAL``, or ``SCX_DSQ_LOCAL_ON | cpu``. 320 321 * Immediately insert the task into a custom DSQ by calling 322 ``scx_bpf_dsq_insert()`` with a DSQ ID which is smaller than 2^63. 323 324 * Queue the task on the BPF side. 325 326 **Task State Tracking and ops.dequeue() Semantics** 327 328 A task is in the "BPF scheduler's custody" when the BPF scheduler is 329 responsible for managing its lifecycle. A task enters custody when it is 330 dispatched to a user DSQ or stored in the BPF scheduler's internal data 331 structures. Custody is entered only from ``ops.enqueue()`` for those 332 operations. The only exception is dispatching to a user DSQ from 333 ``ops.select_cpu()``: although the task is not yet technically in BPF 334 scheduler custody at that point, the dispatch has the same semantic 335 effect as dispatching from ``ops.enqueue()`` for custody-related 336 purposes. 337 338 Once ``ops.enqueue()`` is called, the task may or may not enter custody 339 depending on what the scheduler does: 340 341 * **Directly dispatched to terminal DSQs** (``SCX_DSQ_LOCAL``, 342 ``SCX_DSQ_LOCAL_ON | cpu``, or ``SCX_DSQ_GLOBAL``): the BPF scheduler 343 is done with the task - it either goes straight to a CPU's local run 344 queue or to the global DSQ as a fallback. The task never enters (or 345 exits) BPF custody, and ``ops.dequeue()`` will not be called. 346 347 * **Dispatch to user-created DSQs** (custom DSQs): the task enters the 348 BPF scheduler's custody. When the task later leaves BPF custody 349 (dispatched to a terminal DSQ, picked by core-sched, or dequeued for 350 sleep/property changes), ``ops.dequeue()`` will be called exactly 351 once. 352 353 * **Stored in BPF data structures** (e.g., internal BPF queues): the 354 task is in BPF custody. ``ops.dequeue()`` will be called when it 355 leaves (e.g., when ``ops.dispatch()`` moves it to a terminal DSQ, or 356 on property change / sleep). 357 358 Note that ``ops.enqueue()`` can be called multiple times in a row without 359 an intervening call to ``ops.dequeue()``. This can happen, for example, 360 when a task on a user-created DSQ is re-enqueued using 361 ``scx_bpf_dsq_reenq()``. The task stays in BPF custody the entire time. 362 363 When a task leaves BPF scheduler custody, ``ops.dequeue()`` is invoked. 364 The dequeue can happen for different reasons, distinguished by flags: 365 366 1. **Regular dispatch**: when a task in BPF custody is dispatched to a 367 terminal DSQ from ``ops.dispatch()`` (leaving BPF custody for 368 execution), ``ops.dequeue()`` is triggered without any special flags. 369 370 2. **Core scheduling pick**: when ``CONFIG_SCHED_CORE`` is enabled and 371 core scheduling picks a task for execution while it's still in BPF 372 custody, ``ops.dequeue()`` is called with the 373 ``SCX_DEQ_CORE_SCHED_EXEC`` flag. 374 375 3. **Scheduling property change**: when a task property changes (via 376 operations like ``sched_setaffinity()``, ``sched_setscheduler()``, 377 priority changes, CPU migrations, etc.) while the task is still in 378 BPF custody, ``ops.dequeue()`` is called with the 379 ``SCX_DEQ_SCHED_CHANGE`` flag set in ``deq_flags``. 380 381 **Important**: Once a task has left BPF custody (e.g., after being 382 dispatched to a terminal DSQ), property changes will not trigger 383 ``ops.dequeue()``, since the task is no longer managed by the BPF 384 scheduler. 385 3863. When a CPU is ready to schedule, it first looks at its local DSQ. If 387 empty, it then looks at the global DSQ. If there still isn't a task to 388 run, ``ops.dispatch()`` is invoked which can use the following two 389 functions to populate the local DSQ. 390 391 * ``scx_bpf_dsq_insert()`` inserts a task to a DSQ. Any target DSQ can be 392 used - ``SCX_DSQ_LOCAL``, ``SCX_DSQ_LOCAL_ON | cpu``, 393 ``SCX_DSQ_GLOBAL`` or a custom DSQ. While ``scx_bpf_dsq_insert()`` 394 currently can't be called with BPF locks held, this is being worked on 395 and will be supported. ``scx_bpf_dsq_insert()`` schedules insertion 396 rather than performing them immediately. There can be up to 397 ``ops.dispatch_max_batch`` pending tasks. 398 399 * ``scx_bpf_dsq_move_to_local()`` moves a task from the specified non-local 400 DSQ to the dispatching DSQ. This function cannot be called with any BPF 401 locks held. ``scx_bpf_dsq_move_to_local()`` flushes the pending insertions 402 tasks before trying to move from the specified DSQ. 403 4044. After ``ops.dispatch()`` returns, if there are tasks in the local DSQ, 405 the CPU runs the first one. If empty, the following steps are taken: 406 407 * Try to move from the global DSQ. If successful, run the task. 408 409 * If ``ops.dispatch()`` has dispatched any tasks, retry #3. 410 411 * If the previous task is an SCX task and still runnable, keep executing 412 it (see ``SCX_OPS_ENQ_LAST``). 413 414 * Go idle. 415 416Note that the BPF scheduler can always choose to dispatch tasks immediately 417in ``ops.enqueue()`` as illustrated in the above simple example. If only the 418built-in DSQs are used, there is no need to implement ``ops.dispatch()`` as 419a task is never queued on the BPF scheduler and both the local and global 420DSQs are executed automatically. 421 422``scx_bpf_dsq_insert()`` inserts the task on the FIFO of the target DSQ. Use 423``scx_bpf_dsq_insert_vtime()`` for the priority queue. Internal DSQs such as 424``SCX_DSQ_LOCAL`` and ``SCX_DSQ_GLOBAL`` do not support priority-queue 425dispatching, and must be dispatched to with ``scx_bpf_dsq_insert()``. See 426the function documentation and usage in ``tools/sched_ext/scx_simple.bpf.c`` 427for more information. 428 429Task Lifecycle 430-------------- 431 432The following pseudo-code presents a rough overview of the entire lifecycle 433of a task managed by a sched_ext scheduler: 434 435.. code-block:: c 436 437 ops.init_task(); /* A new task is created */ 438 ops.enable(); /* Enable BPF scheduling for the task */ 439 440 while (task in SCHED_EXT) { 441 if (task can migrate) 442 ops.select_cpu(); /* Called on wakeup (optimization) */ 443 444 ops.runnable(); /* Task becomes ready to run */ 445 446 while (task_is_runnable(task)) { 447 if (task is not in a DSQ || task->scx.slice == 0) { 448 ops.enqueue(); /* Task can be added to a DSQ */ 449 450 /* Task property change (i.e., affinity, nice, etc.)? */ 451 if (sched_change(task)) { 452 ops.dequeue(); /* Exiting BPF scheduler custody */ 453 ops.quiescent(); 454 455 /* Property change callback, e.g. ops.set_weight() */ 456 457 ops.runnable(); 458 continue; 459 } 460 461 /* Any usable CPU becomes available */ 462 463 ops.dispatch(); /* Task is moved to a local DSQ */ 464 ops.dequeue(); /* Exiting BPF scheduler custody */ 465 } 466 467 ops.running(); /* Task starts running on its assigned CPU */ 468 469 while (task_is_runnable(task) && task->scx.slice > 0) { 470 ops.tick(); /* Called every 1/HZ seconds */ 471 472 if (task->scx.slice == 0) 473 ops.dispatch(); /* task->scx.slice can be refilled */ 474 } 475 476 ops.stopping(); /* Task stops running (time slice expires or wait) */ 477 } 478 479 ops.quiescent(); /* Task releases its assigned CPU (wait) */ 480 } 481 482 ops.disable(); /* Disable BPF scheduling for the task */ 483 ops.exit_task(); /* Task is destroyed */ 484 485Note that the above pseudo-code does not cover all possible state transitions 486and edge cases, to name a few examples: 487 488* ``ops.dispatch()`` may fail to move the task to a local DSQ due to a racing 489 property change on that task, in which case ``ops.dispatch()`` will be 490 retried. 491 492* The task may be direct-dispatched to a local DSQ from ``ops.enqueue()``, 493 in which case ``ops.dispatch()`` and ``ops.dequeue()`` are skipped and we go 494 straight to ``ops.running()``. 495 496* Property changes may occur at virtually any point during the task's lifecycle, 497 not just when the task is queued and waiting to be dispatched. For example, 498 changing a property of a running task will lead to the callback sequence 499 ``ops.stopping()`` -> ``ops.quiescent()`` -> (property change callback) -> 500 ``ops.runnable()`` -> ``ops.running()``. 501 502* A sched_ext task can be preempted by a task from a higher-priority scheduling 503 class, in which case it will exit the tick-dispatch loop even though it is runnable 504 and has a non-zero slice. 505 506See the "Scheduling Cycle" section for a more detailed description of how 507a freshly woken up task gets on a CPU. 508 509Where to Look 510============= 511 512* ``include/linux/sched/ext.h`` defines the core data structures and 513 constants, while the ops table (``struct sched_ext_ops``) is defined in 514 ``kernel/sched/ext/internal.h``. 515 516* ``kernel/sched/ext/ext.c`` contains sched_ext core implementation and helpers. 517 The functions prefixed with ``scx_bpf_`` can be called from the BPF 518 scheduler. 519 520* ``kernel/sched/ext/idle.c`` contains the built-in idle CPU selection policy. 521 522* ``tools/sched_ext/`` hosts example BPF scheduler implementations. 523 524 * ``scx_simple[.bpf].c``: Minimal global FIFO scheduler example using a 525 custom DSQ. 526 527 * ``scx_qmap[.bpf].c``: A multi-level FIFO scheduler supporting five 528 levels of priority implemented with arena-backed doubly-linked lists. 529 530 * ``scx_central[.bpf].c``: A central FIFO scheduler where all scheduling 531 decisions are made on one CPU, demonstrating ``LOCAL_ON`` dispatching, 532 tickless operation, and kthread preemption. 533 534 * ``scx_cpu0[.bpf].c``: A scheduler that queues all tasks to a shared DSQ 535 and only dispatches them on CPU0 in FIFO order. Useful for testing bypass 536 behavior. 537 538 * ``scx_flatcg[.bpf].c``: A flattened cgroup hierarchy scheduler 539 implementing hierarchical weight-based cgroup CPU control by compounding 540 each cgroup's share at every level into a single flat scheduling layer. 541 542 * ``scx_pair[.bpf].c``: A core-scheduling example that always makes 543 sibling CPU pairs execute tasks from the same CPU cgroup. 544 545 * ``scx_sdt[.bpf].c``: A variation of ``scx_simple`` demonstrating BPF 546 arena memory management for per-task data. 547 548 * ``scx_userland[.bpf].c``: A minimal scheduler demonstrating user space 549 scheduling. Tasks with CPU affinity are direct-dispatched in FIFO order; 550 all others are scheduled in user space by a simple vruntime scheduler. 551 552Module Parameters 553================= 554 555sched_ext exposes two module parameters under the ``sched_ext.`` prefix that 556control bypass-mode behaviour. These knobs are primarily for debugging; there 557is usually no reason to change them during normal operation. They can be read 558and written at runtime (mode 0600) via 559``/sys/module/sched_ext/parameters/``. 560 561``sched_ext.slice_bypass_us`` (default: 5000 µs) 562 The time slice assigned to all tasks when the scheduler is in bypass mode, 563 i.e. during BPF scheduler load, unload, and error recovery. Valid range is 564 100 µs to 100 ms. 565 566``sched_ext.bypass_lb_intv_us`` (default: 500000 µs) 567 The interval at which the bypass-mode load balancer redistributes tasks 568 across CPUs. Set to 0 to disable load balancing during bypass mode. Valid 569 range is 0 to 10 s. 570 571ABI Instability 572=============== 573 574The APIs provided by sched_ext to BPF schedulers programs have no stability 575guarantees. This includes the ops table callbacks defined in 576``kernel/sched/ext/internal.h`` and the constants defined in 577``include/linux/sched/ext.h``, as well as the ``scx_bpf_`` kfuncs defined in 578``kernel/sched/ext/ext.c`` and ``kernel/sched/ext/idle.c``. 579 580While we will attempt to provide a relatively stable API surface when 581possible, they are subject to change without warning between kernel 582versions. 583