| 1 | |
| 2 | |
| 3 | * Verify that ops.dequeue() is called when a task leaves the BPF scheduler's
|
| 4 | * custody by being moved to the local DSQ of a CPU other than the one whose
|
| 5 | * rq it's on (move_remote_task_to_local_dsq()).
|
| 6 | *
|
| 7 | * ops.enqueue() puts every task into custody and ops.dispatch() moves it to
|
| 8 | * the dispatching CPU's local DSQ, so most moves cross CPUs. With
|
| 9 | * @test_use_move_to_local, tasks are queued on a user DSQ and consumed with
|
| 10 | * scx_bpf_dsq_move_to_local(). Otherwise, they are queued in a BPF queue and
|
| 11 | * dispatched with SCX_DSQ_LOCAL_ON.
|
| 12 | *
|
| 13 | * Copyright (c) 2026 Google LLC.
|
| 14 |
|
| 15 |
|
| 16 | #include <scx/common.bpf.h>
|
| 17 |
|
| 18 | #define SHARED_DSQ 0
|
| 19 | #define MAX_DISPATCH_POPS 8
|
| 20 |
|
| 21 | char _license[] SEC("license") = "GPL";
|
| 22 |
|
| 23 | UEI_DEFINE(uei);
|
| 24 |
|
| 25 | struct {
|
| 26 | __uint(type, BPF_MAP_TYPE_QUEUE);
|
| 27 | __uint(max_entries, 32768);
|
| 28 | __type(value, s32);
|
| 29 | } global_queue SEC(".maps");
|
| 30 |
|
| 31 | enum task_state {
|
| 32 | TASK_NONE = 0,
|
| 33 | TASK_ENQUEUED,
|
| 34 | TASK_DISPATCHED,
|
| 35 | };
|
| 36 |
|
| 37 | struct task_ctx {
|
| 38 | enum task_state state;
|
| 39 | s32 enq_cpu;
|
| 40 | u64 enqueue_seq;
|
| 41 | };
|
| 42 |
|
| 43 | struct {
|
| 44 | __uint(type, BPF_MAP_TYPE_TASK_STORAGE);
|
| 45 | __uint(map_flags, BPF_F_NO_PREALLOC);
|
| 46 | __type(key, int);
|
| 47 | __type(value, struct task_ctx);
|
| 48 | } task_ctx_stor SEC(".maps");
|
| 49 |
|
| 50 |
|
| 51 | struct task_struct___core_sched {
|
| 52 | unsigned long core_cookie;
|
| 53 | } __attribute__((preserve_access_index));
|
| 54 |
|
| 55 | bool test_use_move_to_local;
|
| 56 |
|
| 57 | u64 enqueue_cnt, dequeue_cnt, dispatch_dequeue_cnt, change_dequeue_cnt;
|
| 58 | u64 remote_dispatch_cnt, remote_running_cnt, missed_dequeue_cnt;
|
| 59 | u64 core_sched_exec_dequeue_cnt;
|
| 60 |
|
| 61 | static struct task_ctx *lookup_task_ctx(struct task_struct *p)
|
| 62 | {
|
| 63 | return bpf_task_storage_get(&task_ctx_stor, p, 0, 0);
|
| 64 | }
|
| 65 |
|
| 66 | static bool task_has_core_cookie(struct task_struct *p)
|
| 67 | {
|
| 68 | struct task_struct___core_sched *t = (void *)p;
|
| 69 |
|
| 70 | if (!bpf_core_field_exists(t->core_cookie))
|
| 71 | return false;
|
| 72 | return BPF_CORE_READ(t, core_cookie);
|
| 73 | }
|
| 74 |
|
| 75 | s32 BPF_STRUCT_OPS(dequeue_remote_select_cpu, struct task_struct *p,
|
| 76 | s32 prev_cpu, u64 wake_flags)
|
| 77 | {
|
| 78 |
|
| 79 | return prev_cpu;
|
| 80 | }
|
| 81 |
|
| 82 | void BPF_STRUCT_OPS(dequeue_remote_enqueue, struct task_struct *p, u64 enq_flags)
|
| 83 | {
|
| 84 | struct task_ctx *tctx;
|
| 85 | s32 pid = p->pid;
|
| 86 |
|
| 87 | tctx = lookup_task_ctx(p);
|
| 88 | if (!tctx) {
|
| 89 | scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags);
|
| 90 | return;
|
| 91 | }
|
| 92 |
|
| 93 |
|
| 94 | if (tctx->state == TASK_ENQUEUED)
|
| 95 | scx_bpf_error("%d (%s): enqueue while in ENQUEUED state seq=%llu",
|
| 96 | p->pid, p->comm, tctx->enqueue_seq);
|
| 97 |
|
| 98 | |
| 99 | * Mark @p as enqueued before making it visible to ops.dispatch() on
|
| 100 | * other CPUs, which skips queue entries of tasks not in ENQUEUED
|
| 101 | * state as stale.
|
| 102 |
|
| 103 | tctx->state = TASK_ENQUEUED;
|
| 104 | tctx->enq_cpu = scx_bpf_task_cpu(p);
|
| 105 | tctx->enqueue_seq++;
|
| 106 |
|
| 107 | if (test_use_move_to_local) {
|
| 108 | scx_bpf_dsq_insert(p, SHARED_DSQ, SCX_SLICE_DFL, enq_flags);
|
| 109 | } else if (bpf_map_push_elem(&global_queue, &pid, 0)) {
|
| 110 | scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags);
|
| 111 | tctx->state = TASK_DISPATCHED;
|
| 112 | tctx->enq_cpu = -1;
|
| 113 | goto out;
|
| 114 | }
|
| 115 |
|
| 116 | __sync_fetch_and_add(&enqueue_cnt, 1);
|
| 117 | out:
|
| 118 | scx_bpf_kick_cpu(scx_bpf_task_cpu(p), SCX_KICK_IDLE);
|
| 119 | }
|
| 120 |
|
| 121 | void BPF_STRUCT_OPS(dequeue_remote_dequeue, struct task_struct *p, u64 deq_flags)
|
| 122 | {
|
| 123 | struct task_ctx *tctx;
|
| 124 |
|
| 125 | __sync_fetch_and_add(&dequeue_cnt, 1);
|
| 126 |
|
| 127 | tctx = lookup_task_ctx(p);
|
| 128 | if (!tctx)
|
| 129 | return;
|
| 130 |
|
| 131 | |
| 132 | * Only core scheduling can pick a task straight out of custody, and
|
| 133 | * only if the task has a core cookie. Otherwise, the custody exit was
|
| 134 | * missed when @p was inserted into a local DSQ and got deferred until
|
| 135 | * @p was picked.
|
| 136 |
|
| 137 | if ((deq_flags & SCX_DEQ_CORE_SCHED_EXEC) && !task_has_core_cookie(p)) {
|
| 138 | __sync_fetch_and_add(&core_sched_exec_dequeue_cnt, 1);
|
| 139 | scx_bpf_error("%d (%s): late ops.dequeue() with SCX_DEQ_CORE_SCHED_EXEC (enq_cpu=%d cpu=%d seq=%llu)",
|
| 140 | p->pid, p->comm, tctx->enq_cpu,
|
| 141 | scx_bpf_task_cpu(p), tctx->enqueue_seq);
|
| 142 | }
|
| 143 |
|
| 144 |
|
| 145 | if (tctx->state != TASK_ENQUEUED)
|
| 146 | scx_bpf_error("%d (%s): dequeue outside custody deq_flags=0x%llx state=%d seq=%llu",
|
| 147 | p->pid, p->comm, deq_flags, tctx->state,
|
| 148 | tctx->enqueue_seq);
|
| 149 |
|
| 150 | if (deq_flags & SCX_DEQ_SCHED_CHANGE) {
|
| 151 | __sync_fetch_and_add(&change_dequeue_cnt, 1);
|
| 152 | tctx->state = TASK_NONE;
|
| 153 | } else {
|
| 154 | __sync_fetch_and_add(&dispatch_dequeue_cnt, 1);
|
| 155 | tctx->state = TASK_DISPATCHED;
|
| 156 | }
|
| 157 | }
|
| 158 |
|
| 159 | void BPF_STRUCT_OPS(dequeue_remote_dispatch, s32 cpu, struct task_struct *prev)
|
| 160 | {
|
| 161 | struct task_ctx *tctx;
|
| 162 | struct task_struct *p;
|
| 163 | s32 pid;
|
| 164 | int i;
|
| 165 |
|
| 166 | if (test_use_move_to_local) {
|
| 167 | scx_bpf_dsq_move_to_local(SHARED_DSQ, 0);
|
| 168 | return;
|
| 169 | }
|
| 170 |
|
| 171 |
|
| 172 | bpf_for(i, 0, MAX_DISPATCH_POPS) {
|
| 173 | if (bpf_map_pop_elem(&global_queue, &pid))
|
| 174 | return;
|
| 175 |
|
| 176 | p = bpf_task_from_pid(pid);
|
| 177 | if (!p)
|
| 178 | continue;
|
| 179 |
|
| 180 | |
| 181 | * Entries are stale if @p left custody through a property
|
| 182 | * change dequeue or was dispatched from a duplicate entry.
|
| 183 |
|
| 184 | tctx = lookup_task_ctx(p);
|
| 185 | if (!tctx || tctx->state != TASK_ENQUEUED) {
|
| 186 | bpf_task_release(p);
|
| 187 | continue;
|
| 188 | }
|
| 189 |
|
| 190 | if (bpf_cpumask_test_cpu(cpu, p->cpus_ptr)) {
|
| 191 | if (scx_bpf_task_cpu(p) != cpu)
|
| 192 | __sync_fetch_and_add(&remote_dispatch_cnt, 1);
|
| 193 | scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | cpu,
|
| 194 | SCX_SLICE_DFL, 0);
|
| 195 | } else {
|
| 196 | scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, 0);
|
| 197 | }
|
| 198 |
|
| 199 | bpf_task_release(p);
|
| 200 | return;
|
| 201 | }
|
| 202 |
|
| 203 |
|
| 204 | if (!bpf_map_peek_elem(&global_queue, &pid))
|
| 205 | scx_bpf_kick_cpu(cpu, SCX_KICK_IDLE);
|
| 206 | }
|
| 207 |
|
| 208 | void BPF_STRUCT_OPS(dequeue_remote_running, struct task_struct *p)
|
| 209 | {
|
| 210 | struct task_ctx *tctx;
|
| 211 |
|
| 212 | tctx = lookup_task_ctx(p);
|
| 213 | if (!tctx)
|
| 214 | return;
|
| 215 |
|
| 216 |
|
| 217 | if (tctx->state == TASK_ENQUEUED) {
|
| 218 | __sync_fetch_and_add(&missed_dequeue_cnt, 1);
|
| 219 | scx_bpf_error("%d (%s): running without ops.dequeue() (enq_cpu=%d cpu=%d seq=%llu)",
|
| 220 | p->pid, p->comm, tctx->enq_cpu,
|
| 221 | scx_bpf_task_cpu(p), tctx->enqueue_seq);
|
| 222 | return;
|
| 223 | }
|
| 224 |
|
| 225 | if (tctx->enq_cpu >= 0 && tctx->enq_cpu != scx_bpf_task_cpu(p))
|
| 226 | __sync_fetch_and_add(&remote_running_cnt, 1);
|
| 227 | tctx->enq_cpu = -1;
|
| 228 | }
|
| 229 |
|
| 230 | s32 BPF_STRUCT_OPS(dequeue_remote_init_task, struct task_struct *p,
|
| 231 | struct scx_init_task_args *args)
|
| 232 | {
|
| 233 | struct task_ctx *tctx;
|
| 234 |
|
| 235 | tctx = bpf_task_storage_get(&task_ctx_stor, p, 0,
|
| 236 | BPF_LOCAL_STORAGE_GET_F_CREATE);
|
| 237 | if (!tctx)
|
| 238 | return -ENOMEM;
|
| 239 |
|
| 240 |
|
| 241 | tctx->state = TASK_NONE;
|
| 242 | tctx->enq_cpu = -1;
|
| 243 | tctx->enqueue_seq = 0;
|
| 244 |
|
| 245 | return 0;
|
| 246 | }
|
| 247 |
|
| 248 | s32 BPF_STRUCT_OPS_SLEEPABLE(dequeue_remote_init)
|
| 249 | {
|
| 250 | return scx_bpf_create_dsq(SHARED_DSQ, -1);
|
| 251 | }
|
| 252 |
|
| 253 | void BPF_STRUCT_OPS(dequeue_remote_exit, struct scx_exit_info *ei)
|
| 254 | {
|
| 255 | UEI_RECORD(uei, ei);
|
| 256 | }
|
| 257 |
|
| 258 | SEC(".struct_ops.link")
|
| 259 | struct sched_ext_ops dequeue_remote_ops = {
|
| 260 | .select_cpu = (void *)dequeue_remote_select_cpu,
|
| 261 | .enqueue = (void *)dequeue_remote_enqueue,
|
| 262 | .dequeue = (void *)dequeue_remote_dequeue,
|
| 263 | .dispatch = (void *)dequeue_remote_dispatch,
|
| 264 | .running = (void *)dequeue_remote_running,
|
| 265 | .init_task = (void *)dequeue_remote_init_task,
|
| 266 | .init = (void *)dequeue_remote_init,
|
| 267 | .exit = (void *)dequeue_remote_exit,
|
| 268 | .flags = SCX_OPS_ENQ_LAST,
|
| 269 | .name = "dequeue_remote",
|
| 270 | };
|