STRUCT SCHED_EXT_OPS(9)

πŸ“¦ version 7.3.1, in nixpkgs unstable

hiii bestie πŸ’– welcome to struct sched_ext_ops(9) from the Kernel API Manual β€” the operation table page for a BPF scheduler implementation. This struct is literally the scheduler’s main-character vibe sheet: every callback and every knob for β€œwhat do I do when X happens.” We’re keeping every single detail, fr fr. ✨

🌸 NAME 🌸

struct sched_ext_ops β€” operation table for BPF scheduler implementation. That’s the whole name, no cap: it’s the ops table your BPF scheduler implementation fills out. πŸ‘‘

✨ SYNOPSIS ✨

okay so here’s the struct in all its glory β€” code is verbatim, bestie, don’t let the line wraps scare you 🫢

       struct sched_ext_ops {
           s32 (*select_cpu)(struct task_struct *p, s32 prev_cpu, u64
       wake_flags);
           void (*enqueue)(struct task_struct *p, u64 enq_flags);
           void (*dequeue)(struct task_struct *p, u64 deq_flags);
           void (*dispatch)(s32 cpu, struct task_struct *prev);
           void (*tick)(struct task_struct *p);
           void (*runnable)(struct task_struct *p, u64 enq_flags);
           void (*running)(struct task_struct *p);
           void (*stopping)(struct task_struct *p, bool runnable);
           void (*quiescent)(struct task_struct *p, u64 deq_flags);
           bool (*yield)(struct task_struct *from, struct task_struct *to);
           bool (*core_sched_before)(struct task_struct *a, struct task_struct
       *b);
           void (*set_weight)(struct task_struct *p, u32 weight);
           void (*set_cpumask)(struct task_struct *p, const struct cpumask
       *cpumask);
           void (*update_idle)(s32 cpu, bool idle);
           s32 (*init_task)(struct task_struct *p, struct scx_init_task_args
       *args);
           void (*exit_task)(struct task_struct *p, struct scx_exit_task_args
       *args);
           void (*enable)(struct task_struct *p);
           void (*disable)(struct task_struct *p);
           void (*dump)(struct scx_dump_ctx *ctx);
           void (*dump_cpu)(struct scx_dump_ctx *ctx, s32 cpu, bool idle);
           void (*dump_task)(struct scx_dump_ctx *ctx, struct task_struct *p);
         #ifdef CONFIG_EXT_GROUP_SCHED;
           s32 (*cgroup_init)(struct cgroup *cgrp, struct scx_cgroup_init_args
       *args);
           void (*cgroup_exit)(struct cgroup *cgrp);
           s32 (*cgroup_prep_move)(struct task_struct *p, struct cgroup *from,
       struct cgroup *to);
           void (*cgroup_move)(struct task_struct *p, struct cgroup *from,
       struct cgroup *to);
           void (*cgroup_cancel_move)(struct task_struct *p, struct cgroup
       *from, struct cgroup *to);
           void (*cgroup_set_weight)(struct cgroup *cgrp, u32 weight);
           void (*cgroup_set_bandwidth)(struct cgroup *cgrp, u64 period_us, u64
       quota_us, u64 burst_us);
           void (*cgroup_set_idle)(struct cgroup *cgrp, bool idle);
         #endif ;
           s32 (*sub_attach)(struct scx_sub_attach_args *args);
           void (*sub_detach)(struct scx_sub_detach_args *args);
           void (*sub_caps_updated)(const struct scx_cmask *cmask, u64 caps);
           void (*sub_ecaps_updated)(s32 cid, u64 before, u64 after);
           void (*cpu_online)(s32 cpu);
           void (*cpu_offline)(s32 cpu);
           s32 (*init_cids)(void);
           s32 (*init)(void);
           void (*exit)(struct scx_exit_info *info);
           u32 dispatch_max_batch;
           u64 flags;
           u32 timeout_ms;
           u32 exit_dump_len;
           u64 hotplug_seq;
           u32 cid_shard_size;
           u32 rescue_bandwidth_ppt;
           u32 rescue_quantum_us;
           u64 sub_cgroup_id;
           char name[SCX_OPS_NAME_LEN];
           void *priv;
           void (*cpu_acquire)(s32 cpu, struct scx_cpu_acquire_args *args);
           void (*cpu_release)(s32 cpu, struct scx_cpu_release_args *args);
        };

πŸŽ€ Members πŸŽ€

Member-by-member tour time. Every param, every flag, every β€œthis callback may not do what you think” moment is here. πŸ’…

  • select_cpu β€” Pick the target CPU for a task which is being woken up. Params: p: task being woken up; prev_cpu: the cpu p was on before sleeping; wake_flags: SCX_WAKE_*. Decision made here isn’t final. p may be moved to any CPU while it is getting dispatched for execution later. However, as p is not on the rq at this point, getting the eventual execution CPU right here saves a small bit of overhead down the line. If an idle CPU is returned, the CPU is kicked and will try to dispatch. While an explicit custom mechanism can be added, select_cpu serves as the default way to wake up idle CPUs. p may be inserted into a DSQ directly by calling scx_bpf_dsq_insert. If so, the ops.enqueue will be skipped. Directly inserting into SCX_DSQ_LOCAL will put p in the local DSQ of the CPU returned by this operation. Note that select_cpu is never called for tasks that can only run on a single CPU or tasks with migration disabled, as they don’t have the option to select a different CPU. See select_task_rq for details. πŸ”₯
  • enqueue β€” Enqueue a task on the BPF scheduler. Params: p: task being enqueued; enq_flags: SCX_ENQ_*. p is ready to run. Insert directly into a DSQ by calling scx_bpf_dsq_insert or enqueue on the BPF scheduler. If not directly inserted, the bpf scheduler owns p and if it fails to dispatch p, the task will stall. If p was inserted into a DSQ from ops.select_cpu, this callback is skipped. πŸ¦‹
  • dequeue β€” Remove a task from the BPF scheduler. Params: p: task being dequeued; deq_flags: SCX_DEQ_*. Remove p from the BPF scheduler. This is usually called to isolate the task while updating its scheduling properties (e.g. priority). The ext core keeps track of whether the BPF side owns a given task or not and can gracefully ignore spurious dispatches from BPF side, which makes it safe to not implement this method. However, depending on the scheduling logic, this can lead to confusing behaviors β€” e.g. scheduling position not being updated across a priority change. 😭
  • dispatch β€” Dispatch tasks from the BPF scheduler and/or user DSQs. Params: cpu: CPU to dispatch tasks for; prev: previous task being switched out. Called when a CPU’s local dsq is empty. The operation should dispatch one or more tasks from the BPF scheduler into the DSQs using scx_bpf_dsq_insert and/or move from user DSQs into the local DSQ using scx_bpf_dsq_move_to_local. The maximum number of times scx_bpf_dsq_insert can be called without an intervening scx_bpf_dsq_move_to_local is specified by ops.dispatch_max_batch. See the comments on top of the two functions for more details. When not NULL, prev is an SCX task with its slice depleted. If prev is still runnable as indicated by set SCX_TASK_QUEUED in prev->scx.flags, it is not enqueued yet and will be enqueued after ops.dispatch returns. To keep executing prev, return without dispatching or moving any tasks. Also see SCX_OPS_ENQ_LAST. ⭐
  • tick β€” Periodic tick. Param: p: task running currently. This operation is called every 1/HZ seconds on CPUs which are executing an SCX task. Setting a slice of 0 for p with scx_bpf_task_set_slice will trigger an immediate dispatch cycle on the CPU. πŸ’…
  • runnable β€” A task is becoming runnable on its associated CPU. Params: p: task becoming runnable; enq_flags: SCX_ENQ_*. This and the following three functions can be used to track a task’s execution state transitions. A task becomes ->runnable on a CPU, and then goes through one or more ->running and ->stopping pairs as it runs on the CPU, and eventually becomes ->quiescent when it’s done running on the CPU. p is becoming runnable on the CPU because it’s waking up (SCX_ENQ_WAKEUP); being moved from another CPU; being restored after temporarily taken off the queue for an attribute change. This and ->enqueue are related but not coupled. This operation notifies p’s state transition and may not be followed by ->enqueue e.g. when p is being dispatched to a remote CPU, or when p is being enqueued on a CPU experiencing a hotplug event. Likewise, a task may be ->enqueue’d without being preceded by this operation e.g. after exhausting its slice. 🌈
  • running β€” A task is starting to run on its associated CPU. Param: p: task starting to run. Note that this callback may be called from a CPU other than the one the task is going to run on. This can happen when a task property is changed (i.e., affinity), since set_next_task_scx, which triggers this callback, may run on a CPU different from the task’s assigned CPU. Therefore, always use scx_bpf_task_cpu(p) to determine the target CPU the task is going to use. See ->runnable for explanation on the task state notifiers. 🌸
  • stopping β€” A task is stopping execution. Params: p: task stopping to run; runnable: is task p still runnable? Note that this callback may be called from a CPU other than the one the task was running on. This can happen when a task property is changed (i.e., affinity), since dequeue_task_scx, which triggers this callback, may run on a CPU different from the task’s assigned CPU. Therefore, always use scx_bpf_task_cpu(p) to retrieve the CPU the task was running on. See ->runnable for explanation on the task state notifiers. If !runnable, ->quiescent will be invoked after this operation returns. 🫢
  • quiescent β€” A task is becoming not runnable on its associated CPU. Params: p: task becoming not runnable; deq_flags: SCX_DEQ_*. See ->runnable for explanation on the task state notifiers. p is becoming quiescent on the CPU because it’s sleeping (SCX_DEQ_SLEEP); being moved to another CPU; being temporarily taken off the queue for an attribute change (SCX_DEQ_SCHED_CHANGE). This and ->dequeue are related but not coupled. This operation notifies p’s state transition and may not be preceded by ->dequeue e.g. when p is being dispatched to a remote CPU. πŸ¦‹
  • yield β€” Yield CPU. Params: from: yielding task; to: optional yield target task. If to is NULL, from is yielding the CPU to other runnable tasks. The BPF scheduler should ensure that other available tasks are dispatched before the yielding task. Return value is ignored in this case. If to is not-NULL, from wants to yield the CPU to to. If the bpf scheduler can implement the request, return true; otherwise, false. πŸ’«
  • core_sched_before β€” Task ordering for core-sched. Params: a: task A; b: task B. Used by core-sched to determine the ordering between two tasks. See Documentation/admin-guide/hw-vuln/core-scheduling.rst for details on core-sched. Both a and b are runnable and may or may not currently be queued on the BPF scheduler. Should return true if a should run before b. false if there’s no required ordering or b should run before a. In a scheduler hierarchy, a pair spanning two schedulers is ordered by the nearest common ancestor implementing this op, so the op may be called on tasks that the scheduler delegated to its sub-schedulers and is not scheduling anymore. See scx_prio_less. If not specified, the default is ordering them according to when they became runnable. πŸ‘‘
  • set_weight β€” Set task weight. Params: p: task to set weight for; weight: new weight [1..10000]. Update p’s weight to weight. πŸ“
  • set_cpumask β€” Set CPU affinity. Params: p: task to set CPU affinity for; cpumask: cpumask of cpus that p can run on. Update p’s CPU affinity to cpumask. 🧚
  • update_idle β€” Update the idle state of a CPU. Params: cpu: CPU to update the idle state for; idle: whether entering or exiting the idle state. πŸ’œ

So yeah β€” this op fires whenever rq's CPU goes into or leaves the idle state πŸ›Œβœ¨. But heads up bestie, by default implementing this one disables the built-in idle CPU tracking, and these helpers straight-up stop existing for you:

  • scx_bpf_select_cpu_dfl
  • scx_bpf_select_cpu_and
  • scx_bpf_test_and_clear_cpu_idle
  • scx_bpf_pick_idle_cpu

You also have to implement ops.select_cpu yourself, because the default implementation leans on scx_bpf_select_cpu_dfl 😭 (it's gone, remember?).

Wanna keep the built-in idle tracking? Slap the SCX_OPS_KEEP_BUILTIN_IDLE flag on it. Periodt πŸ’…

⚠️ Only actual transitions get reported. A CPU that's claimed with an idle pick and kicked but ends up dispatching no task just... goes back to idle with no transition. So if your scheduler is out here tracking idle CPUs on its own, you gotta restore the idle state from ops.dispatch when it returns without a next task to run. No cap, that's on you 🫢

init_task β€” initialize a task to run in a BPF scheduler ✨

  • p: task to initialize for BPF scheduling
  • args: init arguments, see the struct definition

This is the moment of truth: either we're loading a BPF scheduler, or a brand-new task is being forked πŸŽ€. Init p for BPF scheduling. This operation may block and is totally fine to use for allocations β€” and it's called exactly once for a task. Iconic, one-and-done, love that for us.

Return 0 for success, -errno for failure. If you error out while loading, it aborts loading of the BPF scheduler. If it happens during a fork, it aborts that specific fork. 🫠

exit_task β€” exit a previously-running task from the system

  • p: task to exit
  • args: exit arguments, see the struct definition

Either p is exiting or the BPF scheduler is being unloaded. Do whatever cleanup p needs. Bye bestie πŸ‘‹πŸ’œ

enable β€” enable BPF scheduling for a task

  • p: task to enable BPF scheduling for

Enables p for BPF scheduling. enable is called on p any time it enters SCX, and is always paired with a matching disable. Matching set, we love a coordinated duo 🫢

disable β€” disable BPF scheduling for a task

  • p: task to disable BPF scheduling for

p is exiting, leaving SCX, or the BPF scheduler is being unloaded. Disable BPF scheduling for p. A disable call is always matched with a prior enable call. No ghosting, no unmatched calls πŸ’…

dump β€” dump BPF scheduler state on error

  • ctx: debug dump context

Use scx_bpf_dump to generate your scheduler-specific debug dump. Spill the tea β˜•πŸ”

dump_cpu β€” dump BPF scheduler state for a CPU on error

  • ctx: debug dump context
  • cpu: CPU to generate debug dump for
  • idle: cpu is currently idle without any runnable tasks

Use scx_bpf_dump to generate a scheduler-specific debug dump for cpu. And here's a cute lil detail: if idle is true and this operation produces no output at all, cpu gets skipped for the dump. Quiet = skipped πŸ™ˆ

dump_task β€” dump BPF scheduler state for a runnable task on error

  • ctx: debug dump context
  • p: runnable task to generate debug dump for

Use scx_bpf_dump to generate a scheduler-specific debug dump for p. 🧚


cgroup_init β€” initialize a cgroup 🌸

  • cgrp: cgroup being initialized
  • args: init arguments, see the struct definition

Initialize cgrp for sched_ext β€” delivered to cgrp's sched either when the BPF scheduler is being loaded or when cgrp is created. This operation may block.

Cgroup handovers generate these ops too, iykyk: an enabling sub-scheduler receives ops.cgroup_init for every cgroup in its subtree while the previous sched receives ops.cgroup_exit β€” and disabling reverses the two πŸ”.

When the BPF scheduler is being loaded, or cgroups are being handed over, cgrp may already have been removed by userspace β€” a removed cgroup stays schedulable until its dying tasks finish their final context switches πŸ’€.

Return 0 for success, -errno for failure. An error return while loading aborts loading of the BPF scheduler. During cgroup creation, it aborts that specific cgroup creation.

cgroup_exit β€” exit a cgroup

  • cgrp: cgroup being exited

Exit cgrp for sched_ext β€” delivered to the sched whose ops.cgroup_init it pairs with, either when the BPF scheduler is being unloaded or when cgrp is destroyed. This operation may block.

For a destroyed cgrp, delivery follows the last scheduling event on it: a removed cgroup stays schedulable until its dying tasks finish their final context switches. Last one out, ghost the lights πŸ•―οΈ

cgroup_prep_move β€” prepare a task to be moved to a different cgroup

  • p: task being moved
  • from: cgroup p is being moved from
  • to: cgroup p is being moved to

Prepare p for the move from cgroup from to to. This operation may block and can be used for allocations.

The cgroup_move ops get delivered to p's sched, and only for moves that don't re-home p. A re-homing move gets reported through ops.exit_task and ops.init_task instead. Also, from and to can reference cgroups the sched never received ops.cgroup_init for β€” because the cpu controller can be coarser than the sub-scheduler topology 🀯.

Return 0 for success, -errno for failure. An error return aborts the migration. βœ‹

cgroup_move β€” commit cgroup move

  • p: task being moved
  • from: cgroup p is being moved from
  • to: cgroup p is being moved to

Commit the move. p is dequeued during this operation. Sealed, delivered, done πŸ’Œ

cgroup_cancel_move β€” cancel cgroup move

  • p: task whose cgroup move is being canceled
  • from: cgroup p was being moved from
  • to: cgroup p was being moved to

p was cgroup_prep_move'd but failed before reaching cgroup_move, so... undo the preparation. Ctrl+Z energy 😭

cgroup_set_weight β€” a cgroup's weight is being changed

  • cgrp: cgroup whose weight is being updated
  • weight: new weight [1..10000]

Update cgrp's weight to weight.

Big important vibe check: knobs of a cgroup belong to the parent, so the set_* ops get delivered to cgrp's parent's sched 😳. That sched may never have seen ops.cgroup_init for cgrp β€” at a sub-scheduler attach point, the parent sched tracks cgrp through ops.sub_attach instead.

cgroup_set_bandwidth β€” a cgroup's bandwidth is being changed

  • cgrp: cgroup whose bandwidth is being updated
  • period_us: bandwidth control period
  • quota_us: bandwidth control quota
  • burst_us: bandwidth control burst

Update cgrp's bandwidth control parameters. This comes from the cpu.max cgroup interface. This operation may block.

quota_us / period_us determines the CPU bandwidth cgrp is entitled to. For example, if period_us is 1_000_000 and quota_us is 2_500_000, cgrp is entitled to 2.5 CPUs 🍰. burst_us can be interpreted in the same fashion and specifies how much cgrp can burst temporarily. The specific control mechanism β€” and thus the interpretation of period_us and burstiness β€” is up to the BPF scheduler. Your rules, your kingdom πŸ‘‘.

Delivery follows the same rule as cgroup_set_weight (parent's sched, remember).

cgroup_set_idle β€” a cgroup's idle state is being changed

  • cgrp: cgroup whose idle state is being updated
  • idle: whether the cgroup is entering or exiting idle state

Update cgrp's idle state to idle. This callback fires when a cgroup transitions between idle and non-idle states, letting the BPF scheduler adjust its behavior accordingly. Chill mode: on/off πŸ§˜β€β™€οΈ

Delivery follows the same rule as cgroup_set_weight.


sub_attach β€” attach a sub-scheduler

  • args: argument container, see the struct definition

Return 0 to accept the sub-scheduler. -errno to reject. Simple as that, bestie β€” swipe right or swipe left πŸ’˜

sub_detach β€” detach a sub-scheduler

  • args: argument container, see the struct definition

(The un-match. See the struct definition for args.)

sub_caps_updated β€” caps on this sub-sched's shard changed

  • cmask: cids whose caps changed (cmask->base identifies the shard)
  • caps: SCX_CAP_* that changed

Invoked after grant or revoke modifies caps on a shard. There can be only one in-flight invocation per shard 🚦. cmask and caps coalesce all changes since the last delivery. Direction (set vs cleared) isn't encoded β€” so query current state with scx_bpf_sub_caps.

⚠️ It's delivered asynchronously after the change is recorded, and may run before it takes effect on any given cpu. Use it to track which caps the sub-sched holds and propagate to its own children β€” not to decide if a task can run on a cpu right now. sub_ecaps_updated reports that per cpu, once it's actually in effect. Timing is everything, fr fr ⏰

May call scx_bpf_sub_grant / scx_bpf_sub_revoke on children.

sub_ecaps_updated β€” this sub-sched's effective caps on a cid changed

  • cid: the cid whose effective caps changed
  • before: effective caps as of the last delivery
  • after: effective caps now

Invoked when this sub-sched's effective caps on cid change β€” once the change is in effect on the cpu βœ…. Runs in dispatch context with the rq lock held, and can perform all operations allowed in ops.dispatch, including inserting/moving tasks. Full dispatch privileges, no cap πŸ§’πŸ’…

cpu_online β€” a CPU became online

  • cpu: CPU which just came up

cpu just came online. Beforehand, cpu will not call ops.enqueue or ops.dispatch, nor run tasks associated with other CPUs. Fresh out the box 🌱

cpu_offline β€” a CPU is going offline

  • cpu: CPU which is going offline

cpu is going offline. Afterwards, cpu will not call ops.enqueue or ops.dispatch, nor run tasks associated with other CPUs. Going to sleep, do not disturb 😴

init_cids β€” finalize the cid layout (cid-form only)

Runs after the default cid layout is built, before caps and shards are finalized. A cid-form scheduler may call scx_bpf_cid_override here for a custom layout. Ignored for cpu-form schedulers. 🎨

init β€” initialize the BPF scheduler

(Yes. That's the whole thing. Go be the main character ✨)

exit β€” clean up after the BPF scheduler

  • info: Exit info

Heads up: ops.exit is also called on ops.init failure, which is a bit unusual 🫠. It's that way to allow rich reporting through info on how ops.init failed. Honestly? Thoughtful. We stan a diagnostic king πŸ‘‘.


dispatch_max_batch β€” max nr of tasks that dispatch can dispatch

That's it. That's the setting. Numbers, bestie πŸ”’.

flags β€” SCX_OPS_* flags

Your flag collection. Slap the SCX_OPS_* ones you want in here πŸ’…

timeout_ms β€” the max wait before a task gets scheduled

The maximum amount of time, in milliseconds, that a runnable task should be able to wait before being scheduled. The maximum timeout may not exceed the default timeout of 30 seconds ⏳.

Defaults to the maximum allowed timeout value of 30 seconds.

exit_dump_len β€” scx_exit_info.dump buffer length

If 0, the default value of 32768 is used. πŸ“¦

hotplug_seq β€” hotplug event detection sequence number

A sequence number that may be set by the scheduler to detect when a hotplug event has occurred during the loading process. If 0, no detection occurs. Otherwise, the scheduler will fail to load if the sequence number does not match scx_hotplug_seq on the enable path. πŸ”Œ

cid_shard_size β€” target number of CIDs per shard

Shards are contiguous CID ranges used as operation and locking domains for sub-scheduling 🧩. Each LLC is divided into ceil(nr_cpus / cid_shard_size) shards, then cores are distributed across them evenly. If one core has more logical CPUs than cid_shard_size, its shard will become larger than cid_shard_size. Values above SCX_CID_SHARD_MAX_CPUS are capped. 0 means use the default (24).

rescue_bandwidth_ppt β€” rescue execution bandwidth in parts per thousand

That's the description, babe 🩹

The fraction of each CPU's time that may legitimately be spent running tasks off that CPU's rescue DSQ. Bump it up and rescues get admitted + escalated faster β€” peep rescue_quantum_us for the deets, bestie πŸ’…

Only the root scheduler's value counts, fr fr. 0 = default of 20 (that's 2%). May not exceed 250 (25%). And SCX_RESCUE_DISABLE? That one just kills rescue altogether β€” after that, SCX_ENQ_RESCUE inserts get rejected exactly like any other insert that's missing the caps 😭


  • rescue_quantum_us β€” rescue execution quantum, in microseconds ⏱️

How much CPU time each individual rescue gets. Rescues run one at a time per CPU, and admissions are paced so rescue execution stays inside rescue_bandwidth_ppt β€” with the defaults that's literally one 5ms rescue every 250ms 🫢 If the queue is crowded, it round-robins on the quantum split across the waiters, floored at 1ms. A rescue that gets stuck eventually escalates to forced execution. Bigger quantum = fewer CPU interrupts, but each one drags on longer and pushes rescues further apart.

Only the root scheduler's value is used. 0 means the default (5000). Non-zero values must land within [1000, 100000]. Any value too short for the kernel to meter gets lifted silently β€” no warning, no drama, it just happens πŸ™ˆ

  • sub_cgroup_id β€” when it's >1, attach the scheduler as a sub-scheduler on the cgroup you specified πŸŽ€
  • name β€” the BPF scheduler's name, iconic as ever πŸ‘‘

Must be a non-zero valid BPF object name including only isalnum, directory, /sys/kernel/sched_ext/root/ops for the root scheduler, while the BPF scheduler is enabled.

  • cpu_acquire β€” a CPU is becoming available to the BPF scheduler.
    • cpu: the CPU being acquired by the BPF scheduler.
    • args: acquire arguments, see the struct definition.

A CPU that had previously been released from the BPF scheduler is now back under its control. Deprecated β€” use SCX_ENQ_IMMED instead πŸ’€

  • cpu_release β€” a CPU is being taken away from the BPF scheduler.
    • cpu: the CPU being released by the BPF scheduler.
    • args: release arguments, see the struct definition.

That CPU is no longer under the control of the BPF scheduler. Could be because a higher priority sched_class preempted it, though ngl there may be other reasons too. The caller should check args->reason to figure out the actual cause. Deprecated β€” use SCX_ENQ_IMMED instead πŸ’€


✨ Description ✨

A BPF scheduler can cook up a totally arbitrary scheduling policy by implementing and loading operations into this table. And plot twist πŸŒ€ β€” a userland scheduling policy can also be implemented by using the BPF scheduler as a shim layer. Slay.


🌈 SEE ALSO 🌈

Kernel file ./kernel/sched/ext/internal.h, scx_add_event(9), __scx_add_event(9), struct scx_enable_args(9), struct sched_ext_ops_cid(9), scx_arena_to_kaddr(9), scx_kaddr_to_arena(9), DEFINE_SCX_COMPAT_MARKER(9), scx_root_protected_live(9), scx_root_protected(9), scx_bypass_dsp_enabled(9), scx_ops_sanitize_err(9), scx_has_subs(9), scx_task_sched(9), scx_task_sched_rcu(9), scx_task_on_sched(9), scx_prog_sched(9), scx_parent(9)


That's the whole page, bestie β€” you ate that πŸ“ This one lives in ./kernel/sched/ext, it's the October 2026 era, and yes it's struct sched_ext_ops(9) in all her main character glory πŸ‘‘πŸ’– Go forth and schedule, periodt ✨

πŸ“¦ where this page comes from