1 /* SPDX-License-Identifier: GPL-2.0 */
2 /*
3 * scx_qmap: a demonstration and testing scheduler for sched_ext features.
4 *
5 * A simple scheduler that exercises a broad set of sched_ext features. Unlikely
6 * to be useful for real workloads. It demonstrates:
7 *
8 * - BPF-side queueing using TIDs.
9 * - BPF arena for scheduler state.
10 * - Core-sched support.
11 * - Hierarchical sub-scheduling: delegating cpus to child cgroup schedulers.
12 *
13 * Base design: Five FIFOs (arena-backed doubly-linked lists through per-task
14 * context). A task is assigned to a FIFO by its compound weight. Each cpu
15 * round-robins the FIFOs, dispatching more from higher ones.
16 *
17 * Sub-scheduling: Any qmap sched can delegate cpus to its own child cgroup
18 * schedulers and keep the rest for its tasks. Terminology:
19 *
20 * excl - A cpu the delegatee owns wholly (ENQ_IMMED|ENQ|PREEMPT).
21 * shared - A cpu delegated as ENQ_IMMED only. Time-shared.
22 * held_excl / held_shared - What this node was handed by its parent.
23 * held-excl cpus are re-delegatable. A held-shared cpu is a
24 * time-share that stays self-local.
25 * self - The excl cpus the node kept for itself, plus all of held_shared.
26 * owner - Who holds a cid - a child slot, CID_SELF, or CID_NONE.
27 * avail - Cpus whose caps are in effect, per ops.sub_ecaps_updated().
28 * usable - self AND avail. Placement decisions use this: self is the
29 * delegation split and can run ahead of what the cpus honor.
30 *
31 * The scheduler splits its held-excl cpus among self and the children in
32 * proportion to each node's cpu.weight, handing each the floor of its share as
33 * excl cpus. The leftover from rounding forms a shared pool the round-robin
34 * timer hands around. With no excl cpu to delegate, the node evicts its
35 * children.
36 *
37 * This policy is a demonstration only, not a practical one. The split
38 * considers only direct children and is not work-conserving. It only exists to
39 * drive sub-sched primitives with as simple logic as possible.
40 *
41 * Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
42 * Copyright (c) 2022 Tejun Heo <tj@kernel.org>
43 * Copyright (c) 2022 David Vernet <dvernet@meta.com>
44 */
45 #include <scx/common.bpf.h>
46
47 #include "scx_qmap.h"
48
49 enum consts {
50 ONE_SEC_IN_NS = 1000000000,
51 ONE_MSEC_IN_NS = 1000000,
52 LOWPRI_INTV_NS = 10 * ONE_MSEC_IN_NS,
53 SHARED_DSQ = 0,
54 HIGHPRI_DSQ = 1,
55 LOWPRI_DSQ = 2,
56 HIGHPRI_WEIGHT = 8668, /* this is what -20 maps to */
57 };
58
59 char _license[] SEC("license") = "GPL";
60
61 const volatile u64 slice_ns;
62 const volatile u32 stall_user_nth;
63 const volatile u32 stall_kernel_nth;
64 const volatile u32 dsp_inf_loop_after;
65 const volatile u32 dsp_batch;
66 const volatile bool highpri_boosting;
67 const volatile bool print_dsqs_and_events;
68 const volatile bool print_msgs;
69 const volatile u64 sub_cgroup_id;
70 const volatile s32 disallow_tgid;
71 const volatile bool suppress_dump;
72 const volatile u32 immed_stress_nth;
73 const volatile u32 max_tasks;
74
75 /* sub-sched: period for handing the round-robin cid pool to the next child */
76 const volatile u64 round_robin_ns;
77
78 /*
79 * Optional cid-override test harness. When cid_override_mode is non-zero,
80 * qmap_init_cids() calls scx_bpf_cid_override() with the caller-supplied arrays
81 * to exercise the kfunc's acceptance and error paths. See enum
82 * qmap_cid_override for the modes.
83 */
84 const volatile u32 cid_override_mode;
85 const volatile u32 cid_override_nr_shards;
86
87 UEI_DEFINE(uei);
88
89 /*
90 * All scheduler state - per-cpu context, stats counters, core-sched sequence
91 * numbers, sub-sched cgroup ids - lives in this single BPF arena map. Userspace
92 * reaches it via skel->arena->qa.
93 */
94 struct {
95 __uint(type, BPF_MAP_TYPE_ARENA);
96 __uint(map_flags, BPF_F_MMAPABLE);
97 __uint(max_entries, 1 << 16); /* upper bound in pages */
98 #if defined(__TARGET_ARCH_arm64) || defined(__aarch64__)
99 __ulong(map_extra, 0x1ull << 32); /* user/BPF mmap base */
100 #else
101 __ulong(map_extra, 0x1ull << 44);
102 #endif
103 } arena SEC(".maps");
104
105 struct qmap_arena __arena_global qa;
106
107 /* ensure that BPF and userspace are seeing the same size for qmap_cmask */
108 _Static_assert(QMAP_CMASK_WORDS == CMASK_NR_WORDS(SCX_QMAP_MAX_CPUS),
109 "QMAP_CMASK_WORDS must equal CMASK_NR_WORDS(SCX_QMAP_MAX_CPUS)");
110 _Static_assert(sizeof(struct qmap_cmask) ==
111 struct_size_t(struct scx_cmask, bits, QMAP_CMASK_WORDS),
112 "qmap_cmask must be exactly sized to back a full scx_cmask");
113
114 /* Per-queue locks. Each in its own .data section as bpf_res_spin_lock requires. */
115 __hidden struct bpf_res_spin_lock qa_q_lock0 SEC(".data.qa_q_lock0");
116 __hidden struct bpf_res_spin_lock qa_q_lock1 SEC(".data.qa_q_lock1");
117 __hidden struct bpf_res_spin_lock qa_q_lock2 SEC(".data.qa_q_lock2");
118 __hidden struct bpf_res_spin_lock qa_q_lock3 SEC(".data.qa_q_lock3");
119 __hidden struct bpf_res_spin_lock qa_q_lock4 SEC(".data.qa_q_lock4");
120
qa_q_lock(s32 qid)121 static struct bpf_res_spin_lock *qa_q_lock(s32 qid)
122 {
123 switch (qid) {
124 case 0: return &qa_q_lock0;
125 case 1: return &qa_q_lock1;
126 case 2: return &qa_q_lock2;
127 case 3: return &qa_q_lock3;
128 case 4: return &qa_q_lock4;
129 default: return NULL;
130 }
131 }
132
133 /*
134 * If enabled, CPU performance target is set according to the queue index
135 * according to the following table.
136 */
137 static const u32 qidx_to_cpuperf_target[] = {
138 [0] = SCX_CPUPERF_ONE * 0 / 4,
139 [1] = SCX_CPUPERF_ONE * 1 / 4,
140 [2] = SCX_CPUPERF_ONE * 2 / 4,
141 [3] = SCX_CPUPERF_ONE * 3 / 4,
142 [4] = SCX_CPUPERF_ONE * 4 / 4,
143 };
144
145 /*
146 * Per-queue sequence numbers to implement core-sched ordering.
147 *
148 * Tail seq is assigned to each queued task and incremented. Head seq tracks the
149 * sequence number of the latest dispatched task. The distance between the a
150 * task's seq and the associated queue's head seq is called the queue distance
151 * and used when comparing two tasks for ordering. See qmap_core_sched_before().
152 */
153
154 /*
155 * Per-task scheduling context. Allocated from the qa.task_ctxs[] slab in
156 * arena. While the task is alive the entry is referenced from task_ctx_stor;
157 * while it's free the entry sits on the free list singly-linked through
158 * @next_free.
159 *
160 * When the task is queued on one of the five priority FIFOs, @q_idx is the
161 * queue index and @q_next/@q_prev link it in the queue's doubly-linked list.
162 * @q_idx is -1 when the task isn't on any queue.
163 */
164 struct task_ctx {
165 struct task_ctx __arena *next_free; /* only valid on free list */
166 struct task_ctx __arena *q_next; /* queue link, NULL if tail */
167 struct task_ctx __arena *q_prev; /* queue link, NULL if head */
168 struct qmap_fifo __arena *fifo; /* queue we're on, NULL if not queued */
169 u64 tid;
170 s32 pid; /* for dump only */
171 bool force_local; /* Dispatch directly to local_dsq */
172 bool highpri;
173 u64 core_sched_seq;
174 struct scx_cmask cpus_allowed; /* per-task affinity in cid space */
175 };
176
177 /*
178 * Slab stride for task_ctx. cpus_allowed's flex array bits[] overlaps the
179 * tail bytes appended per entry; struct_size() gives the actual per-entry
180 * footprint.
181 */
182 #define TASK_CTX_STRIDE \
183 struct_size_t(struct task_ctx, cpus_allowed.bits, \
184 CMASK_NR_WORDS(SCX_QMAP_MAX_CPUS))
185
186 /* All task_ctx pointers are arena pointers. */
187 typedef struct task_ctx __arena task_ctx_t;
188
189 /* Holds an arena pointer to the task's slab entry. */
190 struct task_ctx_stor_val {
191 task_ctx_t *taskc;
192 };
193
194 struct {
195 __uint(type, BPF_MAP_TYPE_TASK_STORAGE);
196 __uint(map_flags, BPF_F_NO_PREALLOC);
197 __type(key, int);
198 __type(value, struct task_ctx_stor_val);
199 } task_ctx_stor SEC(".maps");
200
201 /* Protects the task_ctx slab free list. */
202 __hidden struct bpf_res_spin_lock qa_task_lock SEC(".data.qa_task_lock");
203
qmap_spin_lock(struct bpf_res_spin_lock * lock)204 static int qmap_spin_lock(struct bpf_res_spin_lock *lock)
205 {
206 if (bpf_res_spin_lock(lock)) {
207 scx_bpf_error("res_spin_lock failed");
208 return -EBUSY;
209 }
210 return 0;
211 }
212
213 /*
214 * Try prev_cid, then scan cpus_allowed AND idle_cids AND usable_cids
215 * round-robin from prev_cid + 1. Atomic claim retries on race; bounded by
216 * IDLE_PICK_RETRIES to keep the verifier's insn budget in check.
217 */
218 #define IDLE_PICK_RETRIES 16
219
pick_direct_dispatch_cid(struct task_struct * p,s32 prev_cid,task_ctx_t * taskc)220 static s32 pick_direct_dispatch_cid(struct task_struct *p, s32 prev_cid,
221 task_ctx_t *taskc)
222 {
223 u32 nr_cids = scx_bpf_nr_cids();
224 s32 cid;
225 u32 i;
226
227 if (cmask_test(prev_cid, &qa.usable_cids.mask) &&
228 cmask_test_and_clear(prev_cid, &qa.idle_cids.mask))
229 return prev_cid;
230
231 cid = prev_cid;
232 bpf_for(i, 0, IDLE_PICK_RETRIES) {
233 cid = cmask_next_and2_set_wrap(&taskc->cpus_allowed,
234 &qa.idle_cids.mask,
235 &qa.usable_cids.mask, cid + 1);
236 barrier_var(cid);
237 if (cid >= nr_cids)
238 return -1;
239 if (cmask_test_and_clear(cid, &qa.idle_cids.mask))
240 return cid;
241 }
242 return -1;
243 }
244
245 /*
246 * Force a reference to the arena map. The verifier associates an arena with
247 * a program by finding an LD_IMM64 instruction that loads the arena's BPF
248 * map; programs that only use arena pointers returned from task-local
249 * storage (like qmap_select_cpu) never reference @arena directly. Without
250 * this, the verifier rejects addr_space_cast with "addr_space_cast insn
251 * can only be used in a program that has an associated arena".
252 */
253 #define QMAP_TOUCH_ARENA() do { asm volatile("" :: "r"(&arena)); } while (0)
254
lookup_task_ctx(struct task_struct * p)255 static task_ctx_t *lookup_task_ctx(struct task_struct *p)
256 {
257 struct task_ctx_stor_val *v;
258
259 QMAP_TOUCH_ARENA();
260
261 v = bpf_task_storage_get(&task_ctx_stor, p, 0, 0);
262 if (!v || !v->taskc)
263 return NULL;
264 return v->taskc;
265 }
266
267 /* Append @taskc to the tail of @fifo. Must not already be queued. */
qmap_fifo_enqueue(struct qmap_fifo __arena * fifo,task_ctx_t * taskc)268 static void qmap_fifo_enqueue(struct qmap_fifo __arena *fifo, task_ctx_t *taskc)
269 {
270 struct bpf_res_spin_lock *lock = qa_q_lock(fifo->idx);
271
272 if (!lock || qmap_spin_lock(lock))
273 return;
274 taskc->fifo = fifo;
275 taskc->q_next = NULL;
276 taskc->q_prev = fifo->tail;
277 if (fifo->tail)
278 fifo->tail->q_next = taskc;
279 else
280 fifo->head = taskc;
281 fifo->tail = taskc;
282 bpf_res_spin_unlock(lock);
283 }
284
285 /* Pop the head of @fifo. Returns NULL if empty. */
qmap_fifo_pop(struct qmap_fifo __arena * fifo)286 static task_ctx_t *qmap_fifo_pop(struct qmap_fifo __arena *fifo)
287 {
288 struct bpf_res_spin_lock *lock = qa_q_lock(fifo->idx);
289 task_ctx_t *taskc;
290
291 if (!lock || qmap_spin_lock(lock))
292 return NULL;
293 taskc = fifo->head;
294 if (taskc) {
295 fifo->head = taskc->q_next;
296 if (taskc->q_next)
297 taskc->q_next->q_prev = NULL;
298 else
299 fifo->tail = NULL;
300 taskc->q_next = NULL;
301 taskc->q_prev = NULL;
302 taskc->fifo = NULL;
303 }
304 bpf_res_spin_unlock(lock);
305 return taskc;
306 }
307
308 /* Remove @taskc from its fifo. No-op if not queued. */
qmap_fifo_remove(task_ctx_t * taskc)309 static void qmap_fifo_remove(task_ctx_t *taskc)
310 {
311 struct qmap_fifo __arena *fifo = taskc->fifo;
312 struct bpf_res_spin_lock *lock;
313
314 if (!fifo)
315 return;
316
317 lock = qa_q_lock(fifo->idx);
318 if (!lock || qmap_spin_lock(lock))
319 return;
320
321 /* Re-check under lock — a concurrent pop may have cleared fifo. */
322 if (taskc->fifo != fifo) {
323 bpf_res_spin_unlock(lock);
324 return;
325 }
326
327 if (taskc->q_next)
328 taskc->q_next->q_prev = taskc->q_prev;
329 else
330 fifo->tail = taskc->q_prev;
331 if (taskc->q_prev)
332 taskc->q_prev->q_next = taskc->q_next;
333 else
334 fifo->head = taskc->q_next;
335 taskc->q_next = NULL;
336 taskc->q_prev = NULL;
337 taskc->fifo = NULL;
338 bpf_res_spin_unlock(lock);
339 }
340
BPF_STRUCT_OPS(qmap_select_cid,struct task_struct * p,s32 prev_cid,u64 wake_flags)341 s32 BPF_STRUCT_OPS(qmap_select_cid, struct task_struct *p,
342 s32 prev_cid, u64 wake_flags)
343 {
344 task_ctx_t *taskc;
345 s32 cid;
346
347 if (!(taskc = lookup_task_ctx(p)))
348 return prev_cid;
349
350 if (p->scx.weight < 2 && !(p->flags & PF_KTHREAD))
351 return prev_cid;
352
353 cid = pick_direct_dispatch_cid(p, prev_cid, taskc);
354
355 if (cid >= 0) {
356 taskc->force_local = true;
357 return cid;
358 } else {
359 return prev_cid;
360 }
361 }
362
363 /*
364 * A received time-shared cid is held ENQ_IMMED-only, so inserts meant to run
365 * there must set SCX_ENQ_IMMED.
366 */
needs_immed(s32 cid)367 static u64 needs_immed(s32 cid)
368 {
369 return qa.cid_shared[cid] ? SCX_ENQ_IMMED : 0;
370 }
371
372 /* first cid this node does NOT hold for fault injection, -1 if none */
first_unavail_cid(void)373 static s32 first_unavail_cid(void)
374 {
375 s32 nr_cids = qa.nr_cids, c;
376
377 if (nr_cids > SCX_QMAP_MAX_CPUS) {
378 scx_bpf_error("-ERANGE");
379 return -1;
380 }
381
382 bpf_for(c, 0, nr_cids) {
383 if (!cmask_test(c, &qa.held_excl.mask) &&
384 !cmask_test(c, &qa.held_shared.mask))
385 return c;
386 }
387 return -1;
388 }
389
weight_to_idx(u32 weight)390 static int weight_to_idx(u32 weight)
391 {
392 /* Coarsely map the compound weight to a FIFO. */
393 if (weight <= 25)
394 return 0;
395 else if (weight <= 50)
396 return 1;
397 else if (weight < 200)
398 return 2;
399 else if (weight < 400)
400 return 3;
401 else
402 return 4;
403 }
404
BPF_STRUCT_OPS(qmap_enqueue,struct task_struct * p,u64 enq_flags)405 void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags)
406 {
407 static u32 user_cnt, kernel_cnt;
408 task_ctx_t *taskc;
409 int idx = weight_to_idx(p->scx.weight);
410 s32 cid;
411
412 if (enq_flags & SCX_ENQ_REENQ) {
413 u64 reason = p->scx.flags & SCX_TASK_REENQ_REASON_MASK;
414
415 __sync_fetch_and_add(&qa.nr_reenqueued, 1);
416 if (scx_bpf_task_cid(p) == 0)
417 __sync_fetch_and_add(&qa.nr_reenqueued_cid0, 1);
418 /* cap-loss and IMMED-handback bounces, relocated below */
419 if (reason == SCX_TASK_REENQ_CAP)
420 __sync_fetch_and_add(&qa.nr_reenq_cap, 1);
421 else if (reason == SCX_TASK_REENQ_IMMED)
422 __sync_fetch_and_add(&qa.nr_reenq_immed, 1);
423 }
424
425 if (p->flags & PF_KTHREAD) {
426 if (stall_kernel_nth && !(++kernel_cnt % stall_kernel_nth))
427 return;
428 } else {
429 if (stall_user_nth && !(++user_cnt % stall_user_nth))
430 return;
431 }
432
433 if (qa.test_error_cnt && !--qa.test_error_cnt)
434 scx_bpf_error("test triggering error");
435
436 if (!(taskc = lookup_task_ctx(p)))
437 return;
438
439 /*
440 * All enqueued tasks must have their core_sched_seq updated for correct
441 * core-sched ordering. Also, take a look at the end of qmap_dispatch().
442 */
443 taskc->core_sched_seq = qa.core_sched_tail_seqs[idx]++;
444
445 /*
446 * A task of ours that can run on none of our self cids - the parent
447 * didn't grant them or we delegated them to children - would starve in
448 * SHARED/FIFO since we only pull from those on self cids.
449 *
450 * Force it onto its first allowed cid's local DSQ with SCX_ENQ_RESCUE.
451 * If we hold ENQ on that cid it runs. Otherwise the kernel diverts the
452 * task to its rescue path. IMMED would turn the insert into a legal
453 * placement on a time-shared cid and the kernel would bounce it back
454 * here instead of rescuing it.
455 */
456 if (!cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) {
457 s32 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0);
458
459 if (c >= 0 && c < scx_bpf_nr_cids()) {
460 taskc->force_local = false;
461 __sync_fetch_and_add(&qa.nr_rescue_dsp, 1);
462 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | c, slice_ns,
463 enq_flags | SCX_ENQ_RESCUE);
464 return;
465 }
466 }
467
468 /*
469 * Fault injection: deliberately dispatch one of our own tasks to a cid
470 * we don't hold. The inserts carry SCX_ENQ_RESCUE and divert to the
471 * kernel rescue path, a deterministic rescue-traffic generator. Under
472 * -B 0 the kernel cap check rejects and re-enqueues them instead, so
473 * nr_inject_attempts tracks nr_reenq_cap 1:1 and proves delivery-time
474 * enforcement. Throttled.
475 */
476 if (qa.inject_mode == QMAP_INJ_WRONG_CID && p->nr_cpus_allowed > 1 &&
477 !(enq_flags & SCX_ENQ_REENQ)) {
478 static u32 inj_cnt;
479
480 if (!(++inj_cnt % 64)) {
481 s32 bad = first_unavail_cid();
482
483 if (bad >= 0 && cmask_test(bad, &taskc->cpus_allowed)) {
484 __sync_fetch_and_add(&qa.nr_inject_attempts, 1);
485 __sync_fetch_and_add(&qa.nr_rescue_dsp, 1);
486 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | bad, slice_ns,
487 enq_flags | SCX_ENQ_RESCUE);
488 return;
489 }
490 }
491 }
492
493 /*
494 * IMMED stress testing: Every immed_stress_nth'th enqueue, dispatch
495 * directly to prev_cpu's local DSQ even when busy to force dsq->nr > 1
496 * and exercise the kernel IMMED reenqueue trigger paths.
497 */
498 if (immed_stress_nth && !(enq_flags & SCX_ENQ_REENQ)) {
499 static u32 immed_stress_cnt;
500
501 if (!(++immed_stress_cnt % immed_stress_nth)) {
502 taskc->force_local = false;
503 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | scx_bpf_task_cid(p),
504 slice_ns, enq_flags);
505 return;
506 }
507 }
508
509 /*
510 * If qmap_select_cid() is telling us to or this is the last runnable
511 * task on the CPU, enqueue locally.
512 */
513 if (taskc->force_local) {
514 taskc->force_local = false;
515 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, slice_ns,
516 enq_flags | needs_immed(scx_bpf_task_cid(p)));
517 return;
518 }
519
520 /* see lowpri_timerfn() */
521 if (__COMPAT_has_generic_reenq() &&
522 p->scx.weight < 2 && !(p->flags & PF_KTHREAD) && !(enq_flags & SCX_ENQ_REENQ)) {
523 scx_bpf_dsq_insert(p, LOWPRI_DSQ, slice_ns, enq_flags);
524 return;
525 }
526
527 /* if select_cid() wasn't called, try direct dispatch */
528 if (!__COMPAT_is_enq_cpu_selected(enq_flags) &&
529 (cid = pick_direct_dispatch_cid(p, scx_bpf_task_cid(p), taskc)) >= 0) {
530 __sync_fetch_and_add(&qa.nr_ddsp_from_enq, 1);
531 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | cid, slice_ns,
532 enq_flags | needs_immed(cid));
533 return;
534 }
535
536 /*
537 * If the task was re-enqueued due to the CPU being preempted by a
538 * higher priority scheduling class, just re-enqueue the task directly
539 * on the global DSQ. As we want another CPU to pick it up, find and
540 * kick an idle cid.
541 */
542 if (enq_flags & SCX_ENQ_REENQ) {
543 s32 cid;
544
545 scx_bpf_dsq_insert(p, SHARED_DSQ, 0, enq_flags);
546 cid = cmask_next_and2_set_wrap(&taskc->cpus_allowed,
547 &qa.idle_cids.mask,
548 &qa.usable_cids.mask, 0);
549 if (cid < scx_bpf_nr_cids())
550 scx_bpf_kick_cid(cid, SCX_KICK_IDLE);
551 return;
552 }
553
554 /* Queue on the selected FIFO. */
555 qmap_fifo_enqueue(&qa.fifos[idx], taskc);
556
557 if (highpri_boosting && p->scx.weight >= HIGHPRI_WEIGHT) {
558 taskc->highpri = true;
559 __sync_fetch_and_add(&qa.nr_highpri_queued, 1);
560 }
561 __sync_fetch_and_add(&qa.nr_enqueued, 1);
562 }
563
BPF_STRUCT_OPS(qmap_dequeue,struct task_struct * p,u64 deq_flags)564 void BPF_STRUCT_OPS(qmap_dequeue, struct task_struct *p, u64 deq_flags)
565 {
566 task_ctx_t *taskc;
567
568 __sync_fetch_and_add(&qa.nr_dequeued, 1);
569 if (deq_flags & SCX_DEQ_CORE_SCHED_EXEC)
570 __sync_fetch_and_add(&qa.nr_core_sched_execed, 1);
571
572 taskc = lookup_task_ctx(p);
573 if (taskc && taskc->fifo) {
574 if (taskc->highpri)
575 __sync_fetch_and_sub(&qa.nr_highpri_queued, 1);
576 qmap_fifo_remove(taskc);
577 }
578 }
579
update_core_sched_head_seq(struct task_struct * p)580 static void update_core_sched_head_seq(struct task_struct *p)
581 {
582 int idx = weight_to_idx(p->scx.weight);
583 task_ctx_t *taskc;
584
585 if ((taskc = lookup_task_ctx(p)))
586 qa.core_sched_head_seqs[idx] = taskc->core_sched_seq;
587 }
588
589 /*
590 * One pass over SHARED_DSQ: rescue stranded tasks and boost highpri ones. A
591 * task whose cids were lost while it was queued in the fifos would strand on
592 * SHARED_DSQ, which is consumed only on self cids it can't run on - move it to
593 * the kernel rescue path. One whose cids were lost after the highpri cull is
594 * likewise rescued out of HIGHPRI_DSQ below.
595 *
596 * To demonstrate the use of scx_bpf_dsq_move(), implement silly selective
597 * priority boosting mechanism by moving highpri tasks to HIGHPRI_DSQ and then
598 * consuming them first. This makes minor difference only when dsp_batch is
599 * larger than 1.
600 *
601 * scx_bpf_dsq_move[_vtime]() are allowed both from ops.dispatch() and
602 * non-rq-lock holding BPF programs. As demonstration, this function is called
603 * from qmap_dispatch() and monitor_timerfn().
604 */
scan_shared_dsq(bool from_timer)605 static bool scan_shared_dsq(bool from_timer)
606 {
607 struct task_struct *p;
608 s32 this_cid = scx_bpf_this_cid();
609 u32 nr_cids = scx_bpf_nr_cids();
610
611 /* rescue strands and move highpri tasks to HIGHPRI_DSQ */
612 bpf_for_each(scx_dsq, p, SHARED_DSQ, 0) {
613 static u64 highpri_seq;
614 task_ctx_t *taskc;
615 s32 c;
616
617 if (!(taskc = lookup_task_ctx(p)))
618 return false;
619
620 /* stranded? rescue - it can't be dispatched here either way */
621 if (!cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) {
622 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0);
623 if (c >= 0 && c < scx_bpf_nr_cids()) {
624 __sync_fetch_and_add(&qa.nr_rescue_dsp, 1);
625 scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c,
626 SCX_ENQ_RESCUE);
627 }
628 continue;
629 }
630
631 if (taskc->highpri) {
632 /* exercise the set_*() and vtime interface too */
633 scx_bpf_dsq_move_set_slice(BPF_FOR_EACH_ITER, slice_ns * 2);
634 scx_bpf_dsq_move_set_vtime(BPF_FOR_EACH_ITER, highpri_seq++);
635 scx_bpf_dsq_move_vtime(BPF_FOR_EACH_ITER, p, HIGHPRI_DSQ, 0);
636 }
637 }
638
639 /*
640 * Scan HIGHPRI_DSQ and dispatch until a task that can run here is
641 * found. Prefer this_cid if the task allows it; otherwise RR-scan the
642 * task's cpus_allowed starting after this_cid.
643 */
644 bpf_for_each(scx_dsq, p, HIGHPRI_DSQ, 0) {
645 task_ctx_t *taskc;
646 bool dispatched = false;
647 s32 cid;
648
649 if (!(taskc = lookup_task_ctx(p)))
650 return false;
651
652 /* only run highpri tasks on cids this node can use right now */
653 if (cmask_test(this_cid, &taskc->cpus_allowed) &&
654 cmask_test(this_cid, &qa.usable_cids.mask))
655 cid = this_cid;
656 else
657 cid = cmask_next_and_set_wrap(&taskc->cpus_allowed,
658 &qa.usable_cids.mask,
659 this_cid + 1);
660 if (cid >= nr_cids) {
661 s32 c;
662
663 /* self cids lack caps in effect yet, leave it queued */
664 if (cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask))
665 continue;
666
667 /* stranded after the cull - rescue it from here */
668 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0);
669 if (c >= 0 && c < nr_cids) {
670 __sync_fetch_and_add(&qa.nr_rescue_dsp, 1);
671 scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c,
672 SCX_ENQ_RESCUE);
673 }
674 continue;
675 }
676
677 if (scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | cid,
678 SCX_ENQ_PREEMPT | needs_immed(cid))) {
679 if (cid == this_cid) {
680 dispatched = true;
681 __sync_fetch_and_add(&qa.nr_expedited_local, 1);
682 } else {
683 __sync_fetch_and_add(&qa.nr_expedited_remote, 1);
684 }
685 if (from_timer)
686 __sync_fetch_and_add(&qa.nr_expedited_from_timer, 1);
687 } else {
688 __sync_fetch_and_add(&qa.nr_expedited_lost, 1);
689 }
690
691 if (dispatched)
692 return true;
693 }
694
695 return false;
696 }
697
BPF_STRUCT_OPS(qmap_dispatch,s32 cid,struct task_struct * prev)698 void BPF_STRUCT_OPS(qmap_dispatch, s32 cid, struct task_struct *prev)
699 {
700 struct task_struct *p;
701 struct cpu_ctx __arena *cpuc;
702 task_ctx_t *taskc;
703 u32 batch = dsp_batch ?: 1;
704 s32 owner, i;
705
706 if (scan_shared_dsq(false))
707 return;
708
709 /*
710 * Sub-sched routing: a child-owned cid goes to its owner. Never run
711 * this node's own tasks on a delegated cid. Read without the guard.
712 */
713 owner = qa.part.cid_owner[cid];
714 if (owner == CID_SHARED) {
715 /* route to the live rr holder (0 = self, runs below) */
716 s32 pos = qa.part.rr_pos;
717 u64 holder_cgid = (pos >= 0 && pos < MAX_PARTS) ?
718 qa.part.rr_slots[pos] : 0;
719
720 if (holder_cgid) {
721 scx_bpf_sub_dispatch(holder_cgid);
722 return;
723 }
724 } else if (owner >= 0 && owner < MAX_SUB_SCHEDS) {
725 u64 cgid = qa.sub_sched_ctxs[owner].cgroup_id;
726
727 if (cgid) {
728 if (scx_bpf_sub_dispatch(cgid))
729 __sync_fetch_and_add(&qa.sub_sched_ctxs[owner].nr_dsps, 1);
730 return;
731 }
732 }
733
734 if (!qa.nr_highpri_queued && scx_bpf_dsq_move_to_local(SHARED_DSQ, needs_immed(cid)))
735 return;
736
737 if (dsp_inf_loop_after && qa.nr_dispatched > dsp_inf_loop_after) {
738 /*
739 * PID 2 should be kthreadd which should mostly be idle and off
740 * the scheduler. Let's keep dispatching it to force the kernel
741 * to call this function over and over again.
742 */
743 p = bpf_task_from_pid(2);
744 if (p) {
745 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, slice_ns, 0);
746 bpf_task_release(p);
747 return;
748 }
749 }
750
751 cpuc = &qa.cpu_ctxs[scx_bpf_this_cid()];
752
753 for (i = 0; i < 5; i++) {
754 /* Advance the dispatch cursor and pick the fifo. */
755 if (!cpuc->dsp_cnt) {
756 cpuc->dsp_idx = (cpuc->dsp_idx + 1) % 5;
757 cpuc->dsp_cnt = 1 << cpuc->dsp_idx;
758 }
759
760 /* Dispatch or advance. */
761 bpf_repeat(BPF_MAX_LOOPS) {
762 task_ctx_t *taskc;
763
764 taskc = qmap_fifo_pop(&qa.fifos[cpuc->dsp_idx]);
765 if (!taskc)
766 break;
767
768 p = scx_bpf_tid_to_task(taskc->tid);
769 if (!p)
770 continue;
771
772 if (taskc->highpri)
773 __sync_fetch_and_sub(&qa.nr_highpri_queued, 1);
774
775 update_core_sched_head_seq(p);
776 __sync_fetch_and_add(&qa.nr_dispatched, 1);
777
778 scx_bpf_dsq_insert(p, SHARED_DSQ, slice_ns, 0);
779
780 /*
781 * scx_qmap uses a global BPF queue that any CPU's
782 * dispatch can pop from. If this CPU popped a task that
783 * can't run here, it gets stranded on SHARED_DSQ after
784 * consume_dispatch_q() skips it. Kick the task's home
785 * CPU so it drains SHARED_DSQ.
786 *
787 * There's a race between the pop and the flush of the
788 * buffered dsq_insert:
789 *
790 * CPU 0 (dispatching) CPU 1 (home, idle)
791 * ~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
792 * pop from BPF queue
793 * dsq_insert(buffered)
794 * balance:
795 * SHARED_DSQ empty
796 * BPF queue empty
797 * -> goes idle
798 * flush -> on SHARED
799 * kick CPU 1
800 * wakes, drains task
801 *
802 * The kick prevents indefinite stalls but a per-CPU
803 * kthread like ksoftirqd can be briefly stranded when
804 * its home CPU enters idle with softirq pending,
805 * triggering:
806 *
807 * "NOHZ tick-stop error: local softirq work is pending, handler #N!!!"
808 *
809 * from report_idle_softirq(). The kick lands shortly
810 * after and the home CPU drains the task. This could be
811 * avoided by e.g. dispatching pinned tasks to local or
812 * global DSQs, but the current code is left as-is to
813 * document this class of issue -- other schedulers
814 * seeing similar warnings can use this as a reference.
815 */
816 if (!cmask_test(cid, &taskc->cpus_allowed))
817 scx_bpf_kick_cid(scx_bpf_task_cid(p), 0);
818 batch--;
819 cpuc->dsp_cnt--;
820 if (!batch || !scx_bpf_dispatch_nr_slots()) {
821 if (scan_shared_dsq(false) ||
822 scx_bpf_dsq_move_to_local(SHARED_DSQ, needs_immed(cid)))
823 return;
824 goto prev;
825 }
826 if (!cpuc->dsp_cnt)
827 break;
828 }
829
830 cpuc->dsp_cnt = 0;
831 }
832
833 if (scan_shared_dsq(false))
834 return;
835 prev:
836 /*
837 * No other tasks. @prev will keep running. Update its core_sched_seq as
838 * if the task were enqueued and dispatched immediately.
839 *
840 * No @prev to keep running means the CPU goes idle. If its claim was
841 * never used, that is not a transition and ops.update_idle() stays
842 * silent. Restore the claim here.
843 */
844 if (prev) {
845 taskc = lookup_task_ctx(prev);
846 if (!taskc)
847 return;
848
849 taskc->core_sched_seq =
850 qa.core_sched_tail_seqs[weight_to_idx(prev->scx.weight)]++;
851 } else {
852 cmask_set(cid, &qa.idle_cids.mask);
853 }
854 }
855
BPF_STRUCT_OPS(qmap_tick,struct task_struct * p)856 void BPF_STRUCT_OPS(qmap_tick, struct task_struct *p)
857 {
858 struct cpu_ctx __arena *cpuc = &qa.cpu_ctxs[scx_bpf_this_cid()];
859 int idx;
860
861 /*
862 * Use the running avg of weights to select the target cpuperf level.
863 * This is a demonstration of the cpuperf feature rather than a
864 * practical strategy to regulate CPU frequency.
865 */
866 cpuc->avg_weight = cpuc->avg_weight * 3 / 4 + p->scx.weight / 4;
867 idx = weight_to_idx(cpuc->avg_weight);
868 cpuc->cpuperf_target = qidx_to_cpuperf_target[idx];
869
870 scx_bpf_cidperf_set(scx_bpf_task_cid(p), cpuc->cpuperf_target);
871 }
872
873 /*
874 * The distance from the head of the queue scaled by the weight of the queue.
875 * The lower the number, the older the task and the higher the priority.
876 */
task_qdist(struct task_struct * p,task_ctx_t * taskc)877 static s64 task_qdist(struct task_struct *p, task_ctx_t *taskc)
878 {
879 int idx = weight_to_idx(p->scx.weight);
880 s64 qdist;
881
882 qdist = taskc->core_sched_seq - qa.core_sched_head_seqs[idx];
883
884 /*
885 * As queue index increments, the priority doubles. The queue w/ index 3
886 * is dispatched twice more frequently than 2. Reflect the difference by
887 * scaling qdists accordingly. Note that the shift amount needs to be
888 * flipped depending on the sign to avoid flipping priority direction.
889 */
890 if (qdist >= 0)
891 return qdist << (4 - idx);
892 else
893 return qdist << idx;
894 }
895
896 /*
897 * This is called to determine the task ordering when core-sched is picking
898 * tasks to execute on SMT siblings and should encode about the same ordering as
899 * the regular scheduling path. Use the priority-scaled distances from the head
900 * of the queues to compare the two tasks which should be consistent with the
901 * dispatch path behavior.
902 */
BPF_STRUCT_OPS(qmap_core_sched_before,struct task_struct * a,struct task_struct * b)903 bool BPF_STRUCT_OPS(qmap_core_sched_before,
904 struct task_struct *a, struct task_struct *b)
905 {
906 task_ctx_t *taskc_a = lookup_task_ctx(a);
907 task_ctx_t *taskc_b = lookup_task_ctx(b);
908
909 /*
910 * A task delegated to a sub-scheduler has no task_ctx here. Order such
911 * pairs by the kernel's default ordering - a running task after every
912 * waiting task, then by runnable_at.
913 */
914 if (!taskc_a || !taskc_b) {
915 if (a->on_cpu != b->on_cpu)
916 return b->on_cpu;
917 return time_before(a->scx.runnable_at, b->scx.runnable_at);
918 }
919
920 return task_qdist(a, taskc_a) < task_qdist(b, taskc_b);
921 }
922
923 /*
924 * sched_switch tracepoint and cpu_release handlers are no longer needed.
925 * With SCX_OPS_ALWAYS_ENQ_IMMED, wakeup_preempt_scx() reenqueues IMMED
926 * tasks when a higher-priority scheduling class takes the CPU.
927 */
928
BPF_STRUCT_OPS_SLEEPABLE(qmap_init_task,struct task_struct * p,struct scx_init_task_args * args)929 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init_task, struct task_struct *p,
930 struct scx_init_task_args *args)
931 {
932 struct task_ctx_stor_val *v;
933 task_ctx_t *taskc;
934
935 if (qa.inject_mode == QMAP_INJ_INIT_FAIL &&
936 !bpf_strncmp(p->comm, 6, "qmfail"))
937 return -ENOMEM;
938
939 if (p->tgid == disallow_tgid)
940 p->scx.disallow = true;
941
942 /* pop a slab entry off the free list */
943 if (qmap_spin_lock(&qa_task_lock))
944 return -EBUSY;
945 taskc = qa.task_free_head;
946 if (taskc)
947 qa.task_free_head = taskc->next_free;
948 bpf_res_spin_unlock(&qa_task_lock);
949 if (!taskc) {
950 scx_bpf_error("task_ctx slab exhausted (max_tasks=%u)", max_tasks);
951 return -ENOMEM;
952 }
953
954 taskc->next_free = NULL;
955 taskc->q_next = NULL;
956 taskc->q_prev = NULL;
957 taskc->fifo = NULL;
958 taskc->tid = p->scx.tid;
959 taskc->pid = p->pid;
960 taskc->force_local = false;
961 taskc->highpri = false;
962 taskc->core_sched_seq = 0;
963 cmask_init(&taskc->cpus_allowed, 0, scx_bpf_nr_cids());
964
965 v = bpf_task_storage_get(&task_ctx_stor, p, NULL,
966 BPF_LOCAL_STORAGE_GET_F_CREATE);
967 if (!v) {
968 /* push back to the free list */
969 if (!qmap_spin_lock(&qa_task_lock)) {
970 taskc->next_free = qa.task_free_head;
971 qa.task_free_head = taskc;
972 bpf_res_spin_unlock(&qa_task_lock);
973 }
974 return -ENOMEM;
975 }
976 v->taskc = taskc;
977 return 0;
978 }
979
BPF_STRUCT_OPS(qmap_exit_task,struct task_struct * p,struct scx_exit_task_args * args)980 void BPF_STRUCT_OPS(qmap_exit_task, struct task_struct *p,
981 struct scx_exit_task_args *args)
982 {
983 struct task_ctx_stor_val *v;
984 task_ctx_t *taskc;
985
986 v = bpf_task_storage_get(&task_ctx_stor, p, NULL, 0);
987 if (!v || !v->taskc)
988 return;
989 taskc = v->taskc;
990 v->taskc = NULL;
991
992 if (qmap_spin_lock(&qa_task_lock))
993 return;
994 taskc->next_free = qa.task_free_head;
995 qa.task_free_head = taskc;
996 bpf_res_spin_unlock(&qa_task_lock);
997 }
998
BPF_STRUCT_OPS(qmap_dump,struct scx_dump_ctx * dctx)999 void BPF_STRUCT_OPS(qmap_dump, struct scx_dump_ctx *dctx)
1000 {
1001 task_ctx_t *taskc;
1002 s32 i;
1003
1004 QMAP_TOUCH_ARENA();
1005
1006 if (suppress_dump)
1007 return;
1008
1009 /*
1010 * Walk the queue lists without locking - kfunc calls (scx_bpf_dump)
1011 * aren't in the verifier's kfunc_spin_allowed() list so we can't hold
1012 * a lock and dump. Best-effort; racing may print stale tids but the
1013 * walk is bounded by bpf_repeat() so it always terminates.
1014 */
1015 bpf_for(i, 0, 5) {
1016 scx_bpf_dump("QMAP FIFO[%d]:", i);
1017 taskc = qa.fifos[i].head;
1018 bpf_repeat(4096) {
1019 if (!taskc)
1020 break;
1021 scx_bpf_dump(" %d:%llu", taskc->pid, taskc->tid);
1022 taskc = taskc->q_next;
1023 }
1024 scx_bpf_dump("\n");
1025 }
1026 }
1027
BPF_STRUCT_OPS(qmap_dump_cid,struct scx_dump_ctx * dctx,s32 cid,bool idle)1028 void BPF_STRUCT_OPS(qmap_dump_cid, struct scx_dump_ctx *dctx, s32 cid, bool idle)
1029 {
1030 struct cpu_ctx __arena *cpuc = &qa.cpu_ctxs[cid];
1031
1032 if (suppress_dump || idle)
1033 return;
1034
1035 scx_bpf_dump("QMAP: dsp_idx=%llu dsp_cnt=%llu avg_weight=%u cpuperf_target=%u",
1036 cpuc->dsp_idx, cpuc->dsp_cnt, cpuc->avg_weight,
1037 cpuc->cpuperf_target);
1038 }
1039
BPF_STRUCT_OPS(qmap_dump_task,struct scx_dump_ctx * dctx,struct task_struct * p)1040 void BPF_STRUCT_OPS(qmap_dump_task, struct scx_dump_ctx *dctx, struct task_struct *p)
1041 {
1042 struct task_ctx_stor_val *v;
1043 task_ctx_t *taskc;
1044
1045 QMAP_TOUCH_ARENA();
1046
1047 if (suppress_dump)
1048 return;
1049 v = bpf_task_storage_get(&task_ctx_stor, p, NULL, 0);
1050 if (!v || !v->taskc)
1051 return;
1052 taskc = v->taskc;
1053
1054 scx_bpf_dump("QMAP: force_local=%d core_sched_seq=%llu",
1055 taskc->force_local, taskc->core_sched_seq);
1056 }
1057
BPF_STRUCT_OPS(qmap_cpuctl_init,struct cgroup * cgrp,struct scx_cgroup_init_args * args)1058 s32 BPF_STRUCT_OPS(qmap_cpuctl_init, struct cgroup *cgrp, struct scx_cgroup_init_args *args)
1059 {
1060 QMAP_TOUCH_ARENA();
1061
1062 if (print_msgs)
1063 bpf_printk("CGRP INIT %llu weight=%u period=%lu quota=%ld burst=%lu",
1064 cgrp->kn->id, args->weight, args->bw_period_us,
1065 args->bw_quota_us, args->bw_burst_us);
1066
1067 if (qa.inject_mode == QMAP_INJ_CGRP_INIT_FAIL) {
1068 char name[7] = {};
1069
1070 bpf_probe_read_kernel_str(name, sizeof(name), cgrp->kn->name);
1071 if (!bpf_strncmp(name, 6, "qmfail"))
1072 return -ENOMEM;
1073 }
1074
1075 return 0;
1076 }
1077
1078 static void redistribute(void);
1079
BPF_STRUCT_OPS(qmap_cpuctl_set_weight,struct cgroup * cgrp,u32 weight)1080 void BPF_STRUCT_OPS(qmap_cpuctl_set_weight, struct cgroup *cgrp, u32 weight)
1081 {
1082 u64 cgid = cgrp->kn->id;
1083 s32 i;
1084
1085 QMAP_TOUCH_ARENA();
1086
1087 if (print_msgs)
1088 bpf_printk("CGRP SET %llu weight=%u", cgid, weight);
1089
1090 /*
1091 * Knobs belong to the parent, so this op carries the child subs'
1092 * attach point weights. Adjust the matching sub's share of the cid
1093 * partition. Other cgroups don't participate in the split.
1094 */
1095 for (i = 0; i < MAX_SUB_SCHEDS; i++) {
1096 if (qa.sub_sched_ctxs[i].cgroup_id != cgid)
1097 continue;
1098 if (qa.sub_sched_ctxs[i].weight != weight) {
1099 qa.sub_sched_ctxs[i].weight = weight;
1100 redistribute();
1101 }
1102 break;
1103 }
1104 }
1105
BPF_STRUCT_OPS(qmap_cpuctl_set_bandwidth,struct cgroup * cgrp,u64 period_us,u64 quota_us,u64 burst_us)1106 void BPF_STRUCT_OPS(qmap_cpuctl_set_bandwidth, struct cgroup *cgrp, u64 period_us,
1107 u64 quota_us, u64 burst_us)
1108 {
1109 if (print_msgs)
1110 bpf_printk("CGRP SET %llu period=%lu quota=%ld burst=%lu",
1111 cgrp->kn->id, period_us, quota_us, burst_us);
1112 }
1113
BPF_STRUCT_OPS(qmap_cpuctl_move,struct task_struct * p,struct cgroup * from,struct cgroup * to)1114 void BPF_STRUCT_OPS(qmap_cpuctl_move, struct task_struct *p, struct cgroup *from,
1115 struct cgroup *to)
1116 {
1117 if (print_msgs)
1118 bpf_printk("CGRP MOVE %d %llu -> %llu",
1119 p->pid, from->kn->id, to->kn->id);
1120 }
1121
BPF_STRUCT_OPS(qmap_update_idle,s32 cid,bool idle)1122 void BPF_STRUCT_OPS(qmap_update_idle, s32 cid, bool idle)
1123 {
1124 QMAP_TOUCH_ARENA();
1125
1126 /*
1127 * The kernel delivers update_idle() for every cid this node holds
1128 * SCX_CAP_BASE on. Track every cid's idle state regardless of
1129 * delegation: the direct-dispatch pick masks idle_cids with usable_cids
1130 * at selection, so a cid already idle when it returns to self needs no
1131 * reseed here.
1132 */
1133 if (idle)
1134 cmask_set(cid, &qa.idle_cids.mask);
1135 else
1136 cmask_clear(cid, &qa.idle_cids.mask);
1137 }
1138
BPF_STRUCT_OPS(qmap_set_cmask,struct task_struct * p,const struct scx_cmask * cmask_in)1139 void BPF_STRUCT_OPS(qmap_set_cmask, struct task_struct *p,
1140 const struct scx_cmask *cmask_in)
1141 {
1142 struct scx_cmask __arena *cmask = (struct scx_cmask __arena *)(long)cmask_in;
1143 task_ctx_t *taskc;
1144
1145 taskc = lookup_task_ctx(p);
1146 if (!taskc)
1147 return;
1148 cmask_copy(&taskc->cpus_allowed, cmask);
1149 }
1150
1151 struct monitor_timer {
1152 struct bpf_timer timer;
1153 };
1154
1155 struct {
1156 __uint(type, BPF_MAP_TYPE_ARRAY);
1157 __uint(max_entries, 1);
1158 __type(key, u32);
1159 __type(value, struct monitor_timer);
1160 } monitor_timer SEC(".maps");
1161
1162 /*
1163 * Aggregate cidperf across the first nr_online_cids cids. Post-hotplug
1164 * the first-N-are-online invariant drifts, so some cap/cur values may
1165 * be stale. For this demo monitor that's fine; the scheduler exits on
1166 * the enable-time hotplug_seq mismatch and userspace restarts, which
1167 * rebuilds the layout.
1168 */
monitor_cpuperf(void)1169 static void monitor_cpuperf(void)
1170 {
1171 u32 nr_online = scx_bpf_nr_online_cids();
1172 u64 cap_sum = 0, cur_sum = 0, cur_min = SCX_CPUPERF_ONE, cur_max = 0;
1173 u64 target_sum = 0, target_min = SCX_CPUPERF_ONE, target_max = 0;
1174 s32 cid;
1175
1176 QMAP_TOUCH_ARENA();
1177
1178 bpf_for(cid, 0, nr_online) {
1179 struct cpu_ctx __arena *cpuc = &qa.cpu_ctxs[cid];
1180 u32 cap = scx_bpf_cidperf_cap(cid);
1181 u32 cur = scx_bpf_cidperf_cur(cid);
1182 u32 target;
1183
1184 cur_min = cur < cur_min ? cur : cur_min;
1185 cur_max = cur > cur_max ? cur : cur_max;
1186
1187 cur_sum += (u64)cur * cap / SCX_CPUPERF_ONE;
1188 cap_sum += cap;
1189
1190 target = cpuc->cpuperf_target;
1191 target_sum += target;
1192 target_min = target < target_min ? target : target_min;
1193 target_max = target > target_max ? target : target_max;
1194 }
1195
1196 if (!nr_online || !cap_sum)
1197 return;
1198
1199 qa.cpuperf_min = cur_min;
1200 qa.cpuperf_avg = cur_sum * SCX_CPUPERF_ONE / cap_sum;
1201 qa.cpuperf_max = cur_max;
1202
1203 qa.cpuperf_target_min = target_min;
1204 qa.cpuperf_target_avg = target_sum / nr_online;
1205 qa.cpuperf_target_max = target_max;
1206 }
1207
1208 /*
1209 * Dump the currently queued tasks in the shared DSQ to demonstrate the usage of
1210 * scx_bpf_dsq_nr_queued() and DSQ iterator. Raise the dispatch batch count to
1211 * see meaningful dumps in the trace pipe.
1212 */
dump_shared_dsq(void)1213 static void dump_shared_dsq(void)
1214 {
1215 struct task_struct *p;
1216 s32 nr;
1217
1218 if (!(nr = scx_bpf_dsq_nr_queued(SHARED_DSQ)))
1219 return;
1220
1221 bpf_printk("Dumping %d tasks in SHARED_DSQ in reverse order", nr);
1222
1223 bpf_rcu_read_lock();
1224 bpf_for_each(scx_dsq, p, SHARED_DSQ, SCX_DSQ_ITER_REV)
1225 bpf_printk("%s[%d]", p->comm, p->pid);
1226 bpf_rcu_read_unlock();
1227 }
1228
monitor_timerfn(void * map,int * key,struct bpf_timer * timer)1229 static int monitor_timerfn(void *map, int *key, struct bpf_timer *timer)
1230 {
1231 bpf_rcu_read_lock();
1232 scan_shared_dsq(true);
1233 bpf_rcu_read_unlock();
1234
1235 monitor_cpuperf();
1236
1237 if (print_dsqs_and_events) {
1238 struct scx_event_stats events;
1239
1240 dump_shared_dsq();
1241
1242 __COMPAT_scx_bpf_events(&events, sizeof(events));
1243
1244 bpf_printk("%35s: %lld", "SCX_EV_SELECT_CPU_FALLBACK",
1245 scx_read_event(&events, SCX_EV_SELECT_CPU_FALLBACK));
1246 bpf_printk("%35s: %lld", "SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE",
1247 scx_read_event(&events, SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE));
1248 bpf_printk("%35s: %lld", "SCX_EV_DISPATCH_KEEP_LAST",
1249 scx_read_event(&events, SCX_EV_DISPATCH_KEEP_LAST));
1250 bpf_printk("%35s: %lld", "SCX_EV_ENQ_SKIP_EXITING",
1251 scx_read_event(&events, SCX_EV_ENQ_SKIP_EXITING));
1252 bpf_printk("%35s: %lld", "SCX_EV_REFILL_SLICE_DFL",
1253 scx_read_event(&events, SCX_EV_REFILL_SLICE_DFL));
1254 bpf_printk("%35s: %lld", "SCX_EV_BYPASS_DURATION",
1255 scx_read_event(&events, SCX_EV_BYPASS_DURATION));
1256 bpf_printk("%35s: %lld", "SCX_EV_BYPASS_DISPATCH",
1257 scx_read_event(&events, SCX_EV_BYPASS_DISPATCH));
1258 bpf_printk("%35s: %lld", "SCX_EV_BYPASS_ACTIVATE",
1259 scx_read_event(&events, SCX_EV_BYPASS_ACTIVATE));
1260 }
1261
1262 if (bpf_timer_start(timer, ONE_SEC_IN_NS, 0))
1263 scx_bpf_error("failed to re-arm stats timer");
1264 return 0;
1265 }
1266
1267 struct lowpri_timer {
1268 struct bpf_timer timer;
1269 };
1270
1271 struct {
1272 __uint(type, BPF_MAP_TYPE_ARRAY);
1273 __uint(max_entries, 1);
1274 __type(key, u32);
1275 __type(value, struct lowpri_timer);
1276 } lowpri_timer SEC(".maps");
1277
1278 /*
1279 * Nice 19 tasks are put into the lowpri DSQ. Every 10ms, reenq is triggered and
1280 * the tasks are transferred to SHARED_DSQ.
1281 */
lowpri_timerfn(void * map,int * key,struct bpf_timer * timer)1282 static int lowpri_timerfn(void *map, int *key, struct bpf_timer *timer)
1283 {
1284 scx_bpf_dsq_reenq(LOWPRI_DSQ, 0);
1285 if (bpf_timer_start(timer, LOWPRI_INTV_NS, 0))
1286 scx_bpf_error("failed to re-arm lowpri timer");
1287 return 0;
1288 }
1289
1290 struct round_robin_timer {
1291 struct bpf_timer timer;
1292 };
1293
1294 struct {
1295 __uint(type, BPF_MAP_TYPE_ARRAY);
1296 __uint(max_entries, 1);
1297 __type(key, u32);
1298 __type(value, struct round_robin_timer);
1299 } round_robin_timer SEC(".maps");
1300
1301 enum part_pending_flags {
1302 PART_REFRESH = BIT_U64(0),
1303 PART_REDISTRIBUTE = BIT_U64(1),
1304 };
1305
1306 /*
1307 * Partition update synchronization. qa.part can be written from concurrent
1308 * contexts. This single-runner guard admits one writer at a time without
1309 * holding a lock across the grant/revoke kfuncs. part_pending coalesces
1310 * refresh and repartition requests that arrive while it is held.
1311 *
1312 * They live in .bss, not the arena: rr_advance() runs from a bpf_timer
1313 * callback, where the verifier rejects atomic ops on arena memory.
1314 */
1315 static u64 part_busy;
1316 static u64 part_pending;
1317
part_try_start(void)1318 static bool part_try_start(void)
1319 {
1320 /* set busy, report whether it was previously clear (we acquired it) */
1321 return !__sync_fetch_and_or(&part_busy, 1);
1322 }
1323
part_end(void)1324 static void part_end(void)
1325 {
1326 __sync_fetch_and_and(&part_busy, 0);
1327 }
1328
1329 /*
1330 * compute_partition() scratch.
1331 *
1332 * The excl-held cids are handed out in cid order: position 0..nr_excl-1 over
1333 * the held cids is split into contiguous ranges, one per participant that gets
1334 * at least one excl cid. Range k is owned by cp_range_owner[k] and ends at the
1335 * cumulative position cp_range_end[k].
1336 */
1337 static s32 cp_range_owner[MAX_PARTS]; /* exclusive range k: its owner id ... */
1338 static s32 cp_range_end[MAX_PARTS]; /* ... and the cumulative position it ends at */
1339
1340 /* a participant in the partition: self or an attached child */
1341 struct participant {
1342 s32 slot; /* child slot, or CID_SELF */
1343 u32 weight; /* cpu.weight */
1344 };
1345
1346 /**
1347 * place_one - assign one excl-held cid to its owner
1348 * @cid: the excl-held cid to place
1349 * @n: its position among the excl-held cids, in [0, nr_excl)
1350 * @total_excl: how many positions are owned exclusively (the rest are shared)
1351 *
1352 * Position @n below @total_excl is owned exclusively. It falls in the range
1353 * whose cumulative end it is under, owned by cp_range_owner[]. A position at or
1354 * above @total_excl is the rounding leftover which joins the shared pool.
1355 *
1356 * A separate __noinline function to help verification.
1357 */
place_one(s32 cid,s32 n,s32 total_excl)1358 __noinline int place_one(s32 cid, s32 n, s32 total_excl)
1359 {
1360 s32 owner = CID_SELF, i, s;
1361
1362 if (cid < 0 || cid >= SCX_QMAP_MAX_CPUS || n < 0 || n >= SCX_QMAP_MAX_CPUS ||
1363 total_excl < 0) {
1364 scx_bpf_error("-ERANGE");
1365 return 0;
1366 }
1367
1368 if (n < total_excl) {
1369 for (i = 0; i < MAX_PARTS; i++) {
1370 if (n < cp_range_end[i]) {
1371 owner = cp_range_owner[i];
1372 break;
1373 }
1374 }
1375 qa.part.cid_owner[cid] = owner;
1376 } else {
1377 s = n - total_excl;
1378 if (s < 0 || s >= MAX_PARTS) {
1379 scx_bpf_error("-ERANGE");
1380 return 0;
1381 }
1382 qa.part.shared_cids[s] = cid;
1383 /* time-shared: dispatch resolves the live holder via rr_pos */
1384 qa.part.cid_owner[cid] = CID_SHARED;
1385 }
1386 return 0;
1387 }
1388
1389 /**
1390 * compute_partition - build the cid partition from this node's held caps
1391 *
1392 * Decide each cid's owner, the shared pool and the rr rotation. __noinline to
1393 * help verification. See the comment at the top of the file.
1394 */
compute_partition(void)1395 __noinline void compute_partition(void)
1396 {
1397 s32 nr_cids = qa.nr_cids;
1398 s32 nr_excl, total_excl = 0, nr_rr = 0;
1399 s32 sum_w, i, cid, n = 0, share, self_w;
1400 u64 cgid_snap[MAX_SUB_SCHEDS];
1401 s32 w_snap[MAX_SUB_SCHEDS];
1402
1403 if (nr_cids > SCX_QMAP_MAX_CPUS) {
1404 scx_bpf_error("-ERANGE");
1405 return;
1406 }
1407
1408 /* find out the cids we hold */
1409 scx_bpf_sub_caps(0, SCX_CAP_ENQ, &qa.held_excl.mask);
1410 scx_bpf_sub_caps(0, SCX_CAP_ENQ_IMMED, &qa.held_shared.mask);
1411 cmask_andnot(&qa.held_shared.mask, &qa.held_excl.mask); /* held only as ENQ_IMMED */
1412
1413 qa.part.nr_shared = 0;
1414 qa.part.nr_rr = 0;
1415 qa.part.rr_pos = 0;
1416
1417 nr_excl = cmask_weight(&qa.held_excl.mask);
1418 qa.part.nr_excl = nr_excl;
1419
1420 /* no excl cid: held_shared stays self-local, the rest unheld */
1421 if (!nr_excl) {
1422 bpf_for(cid, 0, nr_cids) {
1423 if (cmask_test(cid, &qa.held_shared.mask))
1424 qa.part.cid_owner[cid] = CID_SELF;
1425 else
1426 qa.part.cid_owner[cid] = CID_NONE;
1427 }
1428 return;
1429 }
1430
1431 /*
1432 * Snapshot membership and weights so the sum_w and share loops agree. A
1433 * mid-compute change would otherwise wrap nr_shared negative. The self
1434 * weight is fixed at the default: a cgroup's weight is its parent's
1435 * knob, not the scheduler's own business.
1436 */
1437 self_w = 100;
1438 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1439 cgid_snap[i] = qa.sub_sched_ctxs[i].cgroup_id;
1440 w_snap[i] = cgid_snap[i] ? (qa.sub_sched_ctxs[i].weight ?: 100) : 0;
1441 }
1442
1443 /*
1444 * Participants are self plus each child. Give each a fixed range/rr
1445 * slot: self at slot 0, child i at slot i+1.
1446 *
1447 * sum_w totals every participant's weight.
1448 */
1449 sum_w = self_w;
1450 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1451 barrier_var(sum_w);
1452 sum_w += w_snap[i];
1453 }
1454
1455 /*
1456 * Split [0, nr_excl) into one contiguous range per participant, each
1457 * the floor of its weight share. cp_range_owner[]/cp_range_end[] record
1458 * each range's owner and cumulative end, total_excl counts the
1459 * exclusive slots, and the rest (nr_excl - total_excl) are shared.
1460 * rr_slots[] lists every participant for the round-robin.
1461 */
1462 share = (u64)nr_excl * self_w / sum_w;
1463 total_excl += share;
1464 cp_range_owner[0] = CID_SELF;
1465 cp_range_end[0] = total_excl;
1466 qa.part.rr_slots[nr_rr++] = 0; /* self holds slot 0 (cgid 0 = no grant) */
1467
1468 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1469 u64 cgid = cgid_snap[i];
1470 s32 w = w_snap[i];
1471
1472 barrier_var(total_excl);
1473 share = (u64)nr_excl * w / sum_w;
1474 total_excl += share;
1475 cp_range_owner[i + 1] = cgid ? i : CID_NONE;
1476 cp_range_end[i + 1] = total_excl;
1477
1478 if (cgid) {
1479 barrier_var(nr_rr);
1480 if (nr_rr < 0 || nr_rr >= MAX_PARTS) {
1481 scx_bpf_error("-ERANGE");
1482 return;
1483 }
1484 qa.part.rr_slots[nr_rr++] = cgid;
1485 }
1486 }
1487
1488 /* assign each cid: held-excl by position, the rest self/none */
1489 bpf_for(cid, 0, nr_cids) {
1490 if (cmask_test(cid, &qa.held_excl.mask)) {
1491 place_one(cid, n, total_excl);
1492 n++;
1493 barrier_var(n);
1494 } else if (cmask_test(cid, &qa.held_shared.mask)) {
1495 qa.part.cid_owner[cid] = CID_SELF; /* time-share, self-local */
1496 } else {
1497 qa.part.cid_owner[cid] = CID_NONE; /* not held */
1498 }
1499 }
1500
1501 qa.part.nr_shared = nr_excl - total_excl;
1502 qa.part.nr_rr = nr_rr;
1503 }
1504
1505 /*
1506 * Charge elapsed wall time to each cid's current owner. Runs under the
1507 * partition guard before every ownership change and from the stats flush, so
1508 * alloc_ns[] reflects the layout that was in effect. Shared-pool time is
1509 * charged to the live round-robin holder.
1510 */
account_alloc(void)1511 static __noinline void account_alloc(void)
1512 {
1513 u64 now = bpf_ktime_get_ns();
1514 s32 rr_owner = CID_SELF;
1515 s32 nr_cids = qa.nr_cids;
1516 u64 delta;
1517 s32 cid, i;
1518
1519 if (nr_cids < 0 || nr_cids > SCX_QMAP_MAX_CPUS) {
1520 scx_bpf_error("-ERANGE");
1521 return;
1522 }
1523
1524 /* first call starts the clock */
1525 if (!qa.alloc_ts) {
1526 qa.alloc_ts = now;
1527 return;
1528 }
1529 delta = now - qa.alloc_ts;
1530 qa.alloc_ts = now;
1531 qa.alloc_window_ns += delta;
1532
1533 /* resolve the live shared-pool holder to an owner id */
1534 if (qa.part.nr_shared && qa.part.nr_rr) {
1535 u32 pos = qa.part.rr_pos;
1536 u64 cgid = pos < MAX_PARTS ? qa.part.rr_slots[pos] : 0;
1537
1538 if (cgid) {
1539 rr_owner = CID_NONE;
1540 bpf_for(i, 0, MAX_SUB_SCHEDS)
1541 if (qa.sub_sched_ctxs[i].cgroup_id == cgid)
1542 rr_owner = i;
1543 }
1544 }
1545
1546 bpf_for(cid, 0, nr_cids) {
1547 s32 owner = qa.part.cid_owner[cid];
1548
1549 if (owner == CID_SHARED)
1550 owner = rr_owner;
1551 if (owner >= 0 && owner < MAX_SUB_SCHEDS)
1552 qa.alloc_ns[owner] += delta;
1553 else if (owner == CID_SELF)
1554 qa.self_alloc_ns += delta;
1555 }
1556 }
1557
1558 /*
1559 * usable_cids = self_cids & avail_cids. The inputs have separate writers,
1560 * apply_partition() and qmap_sub_ecaps_updated(), so the result is rebuilt in
1561 * full under the partition guard, in scratch first so that readers never see
1562 * self_cids alone.
1563 */
refresh_usable(void)1564 static void refresh_usable(void)
1565 {
1566 cmask_copy(&qa.usable_scratch.mask, &qa.self_cids.mask);
1567 cmask_and(&qa.usable_scratch.mask, &qa.avail_cids.mask);
1568 cmask_copy(&qa.usable_cids.mask, &qa.usable_scratch.mask);
1569 }
1570
1571 /*
1572 * apply_partition - execute the plan compute_partition() built
1573 *
1574 * Turn the owner map into the per-child, shared and self cmasks and issue the
1575 * grant/revoke kfuncs as a delta against each child's previous grant. If no
1576 * excl cid, evict every child.
1577 */
apply_partition(void)1578 __noinline void apply_partition(void)
1579 {
1580 s32 nr_cids = qa.nr_cids;
1581 s32 nr_shared = qa.part.nr_shared;
1582 s32 i, cid;
1583
1584 if (nr_cids < 0 || nr_cids > SCX_QMAP_MAX_CPUS ||
1585 nr_shared < 0 || nr_shared > MAX_PARTS) {
1586 scx_bpf_error("-ERANGE");
1587 return;
1588 }
1589
1590 /* no excl cpu: run own tasks on the held shares, evict children */
1591 if (!qa.part.nr_excl) {
1592 cmask_copy(&qa.self_cids.mask, &qa.held_shared.mask);
1593 refresh_usable();
1594 bpf_for(i, 0, MAX_SUB_SCHEDS)
1595 if (qa.sub_sched_ctxs[i].cgroup_id)
1596 scx_bpf_sub_kill(qa.sub_sched_ctxs[i].cgroup_id,
1597 "parent holds no excl cpu to distribute");
1598 return;
1599 }
1600
1601 /*
1602 * Snapshot the old pool. The per-child revoke below clears ENQ_IMMED on
1603 * the previously-granted pool, so a cid that left the pool (now a
1604 * sibling's excl) doesn't keep a stale ENQ_IMMED on its last holder.
1605 */
1606 cmask_copy(&qa.prev_rr_cids.mask, &qa.rr_cids.mask);
1607
1608 /* turn the owner map into the rr pool, per-child excl, and self sets */
1609 cmask_init(&qa.rr_cids.mask, 0, nr_cids);
1610 cmask_init(&qa.self_cids.mask, 0, nr_cids);
1611
1612 /* snapshot each child's grant, then rebuild the new sets below */
1613 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1614 cmask_copy(&qa.sub_sched_ctxs[i].prev_granted.mask,
1615 &qa.sub_sched_ctxs[i].granted_cids.mask);
1616 cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, nr_cids);
1617 }
1618
1619 bpf_for(i, 0, nr_shared)
1620 cmask_set(qa.part.shared_cids[i], &qa.rr_cids.mask);
1621 bpf_for(cid, 0, nr_cids) {
1622 s32 o = qa.part.cid_owner[cid];
1623
1624 if (cmask_test(cid, &qa.rr_cids.mask))
1625 continue;
1626 if (o >= 0 && o < MAX_SUB_SCHEDS)
1627 cmask_set(cid, &qa.sub_sched_ctxs[o].granted_cids.mask);
1628 else if (o == CID_SELF)
1629 cmask_set(cid, &qa.self_cids.mask);
1630 }
1631 refresh_usable();
1632
1633 /*
1634 * Apply each child's exclusive cids as a delta against its previous
1635 * grant. Separately clear the previous shared grant (ENQ_IMMED on the
1636 * old pool), covering cids still pooled and cids that left for a
1637 * sibling's excl. The current holder is granted the new pool below.
1638 */
1639 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1640 struct sub_sched_ctx __arena *ssc = &qa.sub_sched_ctxs[i];
1641 u64 cgid = ssc->cgroup_id;
1642
1643 if (!cgid)
1644 continue;
1645
1646 cmask_copy(&qa.to_revoke_cids.mask, &ssc->prev_granted.mask);
1647 cmask_andnot(&qa.to_revoke_cids.mask, &ssc->granted_cids.mask);
1648 cmask_copy(&qa.to_grant_cids.mask, &ssc->granted_cids.mask);
1649 cmask_andnot(&qa.to_grant_cids.mask, &ssc->prev_granted.mask);
1650
1651 scx_bpf_sub_revoke(cgid, SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1652 &qa.prev_rr_cids.mask);
1653 scx_bpf_sub_revoke(cgid, SCX_CAP_ENQ | SCX_CAP_PREEMPT |
1654 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1655 &qa.to_revoke_cids.mask);
1656 scx_bpf_sub_grant(cgid, SCX_CAP_ENQ | SCX_CAP_PREEMPT |
1657 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1658 &qa.to_grant_cids.mask, NULL);
1659 }
1660
1661 /* the current holder of the shared pool gets ENQ_IMMED on all of it */
1662 if (nr_shared) {
1663 s32 pos = qa.part.rr_pos;
1664 u64 holder_cgid;
1665
1666 if (pos < 0 || pos >= MAX_PARTS) {
1667 scx_bpf_error("-ERANGE");
1668 return;
1669 }
1670
1671 holder_cgid = qa.part.rr_slots[pos]; /* 0 = self, nothing to grant */
1672 if (holder_cgid)
1673 scx_bpf_sub_grant(holder_cgid,
1674 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1675 &qa.rr_cids.mask, NULL);
1676 }
1677 }
1678
1679 /**
1680 * execute_partition - Run pending partition updates
1681 *
1682 * The rr timer is the backstop if the loop reaches its iteration limit.
1683 */
execute_partition(void)1684 static void execute_partition(void)
1685 {
1686 u64 pending;
1687 s32 i;
1688
1689 bpf_for(i, 0, 1024) {
1690 if (!part_try_start())
1691 break;
1692
1693 pending = __sync_fetch_and_and(&part_pending, 0);
1694 if (pending & PART_REDISTRIBUTE) {
1695 /* charge elapsed time before repartitioning */
1696 account_alloc();
1697 compute_partition();
1698 apply_partition();
1699 } else if (pending & PART_REFRESH) {
1700 refresh_usable();
1701 }
1702
1703 /*
1704 * Requests are published before trying the guard. Releasing it
1705 * before checking pending work ensures a racing request is
1706 * either observed here or handled by a caller that acquires the
1707 * guard.
1708 */
1709 part_end();
1710 if (!__sync_fetch_and_or(&part_pending, 0))
1711 break;
1712 }
1713 }
1714
redistribute(void)1715 static void redistribute(void)
1716 {
1717 __sync_fetch_and_or(&part_pending, PART_REDISTRIBUTE);
1718 execute_partition();
1719 }
1720
1721 /*
1722 * Userspace pokes this (PROG_RUN) to bring alloc_ns[] current before reading
1723 * it for the stats display. Skipping when the partition guard is held is
1724 * fine - alloc_ts is untouched, so the elapsed time is charged next time.
1725 */
1726 SEC("syscall")
flush_alloc(void * ctx)1727 int flush_alloc(void *ctx)
1728 {
1729 if (part_try_start()) {
1730 account_alloc();
1731 part_end();
1732 execute_partition();
1733 }
1734 return 0;
1735 }
1736
1737 /*
1738 * Hand the shared pool to the next participant in the rotation. Self's turn
1739 * just revokes the pool back to this sched. A child's turn grants it ENQ_IMMED
1740 * on the entire pool. As only excl-held cids are time-shared, a wall-clock
1741 * rotation works. Driven by the round-robin timer.
1742 */
rr_advance(void)1743 static void rr_advance(void)
1744 {
1745 s32 nr_shared, old_pos, new_pos;
1746 u64 old_cgid, new_cgid;
1747 u32 nr_rr; /* unsigned for % */
1748
1749 /* a redistribute holds the partition and rebuilds the pool, so skip */
1750 if (!part_try_start())
1751 return;
1752
1753 nr_rr = qa.part.nr_rr;
1754 nr_shared = qa.part.nr_shared;
1755
1756 if (nr_shared < 0 || nr_shared > MAX_PARTS) {
1757 scx_bpf_error("-ERANGE");
1758 return;
1759 }
1760
1761 if (nr_shared && nr_rr >= 2) {
1762 /* close out the outgoing holder's pool time */
1763 account_alloc();
1764
1765 old_pos = qa.part.rr_pos;
1766 new_pos = (old_pos + 1) % nr_rr;
1767 old_cgid = qa.part.rr_slots[old_pos];
1768 new_cgid = qa.part.rr_slots[new_pos];
1769 qa.part.rr_pos = new_pos;
1770
1771 /*
1772 * Move the ENQ_IMMED cap to the next participant. The shared
1773 * cids stay marked CID_SHARED. qmap_dispatch() resolves the
1774 * live holder via rr_pos without the guard, so a dispatch
1775 * racing this handoff may reenqueue a task once. Harmless for a
1776 * time-share.
1777 */
1778 if (old_cgid)
1779 scx_bpf_sub_revoke(old_cgid,
1780 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1781 &qa.rr_cids.mask);
1782 if (new_cgid)
1783 scx_bpf_sub_grant(new_cgid,
1784 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1785 &qa.rr_cids.mask, NULL);
1786 }
1787
1788 part_end();
1789
1790 execute_partition();
1791 }
1792
1793 /* advance the time-shared cid pool every round_robin_ns */
round_robin_timerfn(void * map,int * key,struct bpf_timer * timer)1794 static int round_robin_timerfn(void *map, int *key, struct bpf_timer *timer)
1795 {
1796 rr_advance();
1797 if (bpf_timer_start(timer, round_robin_ns, 0))
1798 scx_bpf_error("failed to re-arm round-robin timer");
1799 return 0;
1800 }
1801
1802 /*
1803 * Custom cid layout for the cid-override test. On invalid input the kfunc
1804 * scx_error()s and aborts the scheduler.
1805 */
BPF_STRUCT_OPS_SLEEPABLE(qmap_init_cids)1806 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init_cids)
1807 {
1808 u32 nr_cpu_ids = scx_bpf_nr_cpu_ids();
1809
1810 if (!cid_override_mode)
1811 return 0;
1812
1813 /* the arena arrays are sized SCX_QMAP_MAX_CPUS */
1814 if (nr_cpu_ids > SCX_QMAP_MAX_CPUS) {
1815 scx_bpf_error("nr_cpu_ids=%u exceeds SCX_QMAP_MAX_CPUS=%d",
1816 nr_cpu_ids, SCX_QMAP_MAX_CPUS);
1817 return -EINVAL;
1818 }
1819
1820 scx_bpf_cid_override(qa.cid_override_cpu_to_cid, nr_cpu_ids,
1821 qa.cid_override_shard_start, cid_override_nr_shards);
1822 return 0;
1823 }
1824
BPF_STRUCT_OPS_SLEEPABLE(qmap_init)1825 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init)
1826 {
1827 u8 __arena *slab;
1828 u32 nr_pages, key = 0, i;
1829 u32 nr_cids, nr_cpu_ids;
1830 struct bpf_timer *timer;
1831 s32 ret;
1832
1833 nr_cids = scx_bpf_nr_cids();
1834 nr_cpu_ids = scx_bpf_nr_cpu_ids();
1835
1836 if (nr_cids > SCX_QMAP_MAX_CPUS) {
1837 scx_bpf_error("nr_cids=%u exceeds SCX_QMAP_MAX_CPUS=%d",
1838 nr_cids, SCX_QMAP_MAX_CPUS);
1839 return -EINVAL;
1840 }
1841 if (nr_cpu_ids > SCX_QMAP_MAX_CPUS) {
1842 scx_bpf_error("nr_cpu_ids=%u exceeds SCX_QMAP_MAX_CPUS=%d",
1843 nr_cpu_ids, SCX_QMAP_MAX_CPUS);
1844 return -EINVAL;
1845 }
1846
1847 /*
1848 * Allocate the task_ctx slab in arena and thread the entire slab onto
1849 * the free list. max_tasks is set by userspace before load. Each entry
1850 * is TASK_CTX_STRIDE bytes - task_ctx's trailing cpus_allowed flex
1851 * array extends into the stride tail.
1852 */
1853 if (!max_tasks) {
1854 scx_bpf_error("max_tasks must be > 0");
1855 return -EINVAL;
1856 }
1857
1858 nr_pages = (max_tasks * TASK_CTX_STRIDE + PAGE_SIZE - 1) / PAGE_SIZE;
1859 slab = bpf_arena_alloc_pages(&arena, NULL, nr_pages, NUMA_NO_NODE, 0);
1860 if (!slab) {
1861 scx_bpf_error("failed to allocate task_ctx slab");
1862 return -ENOMEM;
1863 }
1864 qa.task_ctxs = (task_ctx_t *)slab;
1865
1866 bpf_for(i, 0, 5)
1867 qa.fifos[i].idx = i;
1868
1869 bpf_for(i, 0, max_tasks) {
1870 task_ctx_t *cur = (task_ctx_t *)(slab + i * TASK_CTX_STRIDE);
1871 task_ctx_t *next = (i + 1 < max_tasks) ?
1872 (task_ctx_t *)(slab + (i + 1) * TASK_CTX_STRIDE) : NULL;
1873 cur->next_free = next;
1874 }
1875 qa.task_free_head = (task_ctx_t *)slab;
1876
1877 /* cache the cid count, trusted to be <= SCX_QMAP_MAX_CPUS hereafter */
1878 qa.nr_cids = nr_cids;
1879
1880 /* cmasks are embedded in qa, so they only need initializing */
1881 cmask_init(&qa.idle_cids.mask, 0, nr_cids);
1882 cmask_init(&qa.rr_cids.mask, 0, nr_cids);
1883 cmask_init(&qa.prev_rr_cids.mask, 0, nr_cids);
1884 cmask_init(&qa.self_cids.mask, 0, nr_cids);
1885 cmask_init(&qa.avail_cids.mask, 0, nr_cids);
1886 cmask_init(&qa.usable_cids.mask, 0, nr_cids);
1887 cmask_init(&qa.to_revoke_cids.mask, 0, nr_cids);
1888 cmask_init(&qa.to_grant_cids.mask, 0, nr_cids);
1889 cmask_init(&qa.usable_scratch.mask, 0, nr_cids);
1890 cmask_init(&qa.held_excl.mask, 0, nr_cids);
1891 cmask_init(&qa.held_shared.mask, 0, nr_cids);
1892
1893 scx_bpf_sub_caps(0, SCX_CAP_ENQ, &qa.held_excl.mask);
1894 scx_bpf_sub_caps(0, SCX_CAP_ENQ_IMMED, &qa.held_shared.mask);
1895 cmask_andnot(&qa.held_shared.mask, &qa.held_excl.mask);
1896
1897 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1898 cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, nr_cids);
1899 cmask_init(&qa.sub_sched_ctxs[i].prev_granted.mask, 0, nr_cids);
1900 }
1901
1902 /*
1903 * The root starts holding every cid and gets no ecaps notifications, so
1904 * its avail set is fixed here. qmap_sub_ecaps_updated() maintains the
1905 * per-cid state as effective caps settle, and redistribute() rebuilds
1906 * owner and self from held caps. A non-root node starts with nothing.
1907 */
1908 bpf_for(i, 0, nr_cids) {
1909 if (!sub_cgroup_id) {
1910 cmask_set(i, &qa.self_cids.mask);
1911 cmask_set(i, &qa.avail_cids.mask);
1912 cmask_set(i, &qa.usable_cids.mask);
1913 qa.part.cid_owner[i] = CID_SELF;
1914 } else {
1915 qa.part.cid_owner[i] = CID_NONE;
1916 }
1917 }
1918 qa.part.nr_shared = 0;
1919
1920 ret = scx_bpf_create_dsq(SHARED_DSQ, -1);
1921 if (ret) {
1922 scx_bpf_error("failed to create DSQ %d (%d)", SHARED_DSQ, ret);
1923 return ret;
1924 }
1925
1926 ret = scx_bpf_create_dsq(HIGHPRI_DSQ, -1);
1927 if (ret) {
1928 scx_bpf_error("failed to create DSQ %d (%d)", HIGHPRI_DSQ, ret);
1929 return ret;
1930 }
1931
1932 ret = scx_bpf_create_dsq(LOWPRI_DSQ, -1);
1933 if (ret)
1934 return ret;
1935
1936 timer = bpf_map_lookup_elem(&monitor_timer, &key);
1937 if (!timer)
1938 return -ESRCH;
1939 bpf_timer_init(timer, &monitor_timer, CLOCK_MONOTONIC);
1940 bpf_timer_set_callback(timer, monitor_timerfn);
1941 ret = bpf_timer_start(timer, ONE_SEC_IN_NS, 0);
1942 if (ret)
1943 return ret;
1944
1945 if (__COMPAT_has_generic_reenq()) {
1946 /* see lowpri_timerfn() */
1947 timer = bpf_map_lookup_elem(&lowpri_timer, &key);
1948 if (!timer)
1949 return -ESRCH;
1950 bpf_timer_init(timer, &lowpri_timer, CLOCK_MONOTONIC);
1951 bpf_timer_set_callback(timer, lowpri_timerfn);
1952 ret = bpf_timer_start(timer, LOWPRI_INTV_NS, 0);
1953 if (ret)
1954 return ret;
1955 }
1956
1957 /* sub-sched: drive the boundary-cid round-robin from a bpf timer */
1958 timer = bpf_map_lookup_elem(&round_robin_timer, &key);
1959 if (!timer)
1960 return -ESRCH;
1961 bpf_timer_init(timer, &round_robin_timer, CLOCK_MONOTONIC);
1962 bpf_timer_set_callback(timer, round_robin_timerfn);
1963 ret = bpf_timer_start(timer, round_robin_ns, 0);
1964 if (ret)
1965 return ret;
1966
1967 return 0;
1968 }
1969
BPF_STRUCT_OPS(qmap_exit,struct scx_exit_info * ei)1970 void BPF_STRUCT_OPS(qmap_exit, struct scx_exit_info *ei)
1971 {
1972 UEI_RECORD(uei, ei);
1973 }
1974
1975 /*
1976 * Seed a new sub slot with the cgroup's current weight. The kernel delivers
1977 * ops.cpuctl_set_weight() only on value-changing writes, so a weight set
1978 * before the sub attached would otherwise go unnoticed.
1979 */
cgrp_cur_weight(u64 cgid)1980 static u32 cgrp_cur_weight(u64 cgid)
1981 {
1982 struct cgroup_subsys_state *css;
1983 struct cgroup *cgrp;
1984 u32 weight = 100;
1985
1986 cgrp = bpf_cgroup_from_id(cgid);
1987 if (!cgrp)
1988 return weight;
1989
1990 css = BPF_CORE_READ(cgrp, subsys[cpu_cgrp_id]);
1991 if (css) {
1992 struct task_group *tg = container_of(css, struct task_group, css);
1993 u32 w = BPF_CORE_READ(tg, scx.weight);
1994
1995 if (w)
1996 weight = w;
1997 }
1998 bpf_cgroup_release(cgrp);
1999 return weight;
2000 }
2001
BPF_STRUCT_OPS(qmap_sub_attach,struct scx_sub_attach_args * args)2002 s32 BPF_STRUCT_OPS(qmap_sub_attach, struct scx_sub_attach_args *args)
2003 {
2004 s32 i;
2005
2006 /* as long as there is at least one excl cpu, children can attach */
2007 if (!cmask_weight(&qa.held_excl.mask))
2008 return -ENOSPC;
2009
2010 for (i = 0; i < MAX_SUB_SCHEDS; i++) {
2011 if (qa.sub_sched_ctxs[i].cgroup_id)
2012 continue;
2013
2014 qa.sub_sched_ctxs[i].cgroup_id = args->ops->sub_cgroup_id;
2015 qa.sub_sched_ctxs[i].weight = cgrp_cur_weight(args->ops->sub_cgroup_id);
2016 qa.nr_sub_scheds++;
2017 bpf_printk("attaching sub-sched[%d] on %s", i, args->cgroup_path);
2018 redistribute();
2019 return 0;
2020 }
2021
2022 return -ENOSPC;
2023 }
2024
BPF_STRUCT_OPS(qmap_sub_detach,struct scx_sub_detach_args * args)2025 void BPF_STRUCT_OPS(qmap_sub_detach, struct scx_sub_detach_args *args)
2026 {
2027 s32 i;
2028
2029 for (i = 0; i < MAX_SUB_SCHEDS; i++) {
2030 if (qa.sub_sched_ctxs[i].cgroup_id != args->ops->sub_cgroup_id)
2031 continue;
2032
2033 qa.sub_sched_ctxs[i].cgroup_id = 0;
2034 qa.sub_sched_ctxs[i].weight = 100;
2035 cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, qa.nr_cids);
2036 qa.nr_sub_scheds--;
2037 bpf_printk("detaching sub-sched[%d] on %s", i, args->cgroup_path);
2038 redistribute();
2039 break;
2040 }
2041 }
2042
BPF_STRUCT_OPS(qmap_sub_caps_updated,const struct scx_cmask * cmask,u64 caps)2043 void BPF_STRUCT_OPS(qmap_sub_caps_updated, const struct scx_cmask *cmask, u64 caps)
2044 {
2045 /* our held caps changed, redistribute */
2046 redistribute();
2047 }
2048
BPF_STRUCT_OPS(qmap_sub_ecaps_updated,s32 cid,u64 before,u64 after)2049 void BPF_STRUCT_OPS(qmap_sub_ecaps_updated, s32 cid, u64 before, u64 after)
2050 {
2051 /*
2052 * Effective caps updated. Track which cids hold shared caps so a self
2053 * task placed there enqueues IMMED, and which cids have ENQ_IMMED in
2054 * effect at all (avail, see the header comment).
2055 */
2056 if (after & SCX_CAP_ENQ_IMMED) {
2057 qa.cid_shared[cid] = (after & SCX_CAP_ENQ) ? 0 : 1;
2058 cmask_set(cid, &qa.avail_cids.mask);
2059 } else {
2060 qa.cid_shared[cid] = 0;
2061 cmask_clear(cid, &qa.avail_cids.mask);
2062 }
2063
2064 __sync_fetch_and_or(&part_pending, PART_REFRESH);
2065 execute_partition();
2066 }
2067
2068 SCX_OPS_CID_DEFINE(qmap_ops,
2069 .flags = SCX_OPS_ENQ_EXITING | SCX_OPS_TID_TO_TASK,
2070 .select_cid = (void *)qmap_select_cid,
2071 .enqueue = (void *)qmap_enqueue,
2072 .dequeue = (void *)qmap_dequeue,
2073 .dispatch = (void *)qmap_dispatch,
2074 .tick = (void *)qmap_tick,
2075 .core_sched_before = (void *)qmap_core_sched_before,
2076 .set_cmask = (void *)qmap_set_cmask,
2077 .update_idle = (void *)qmap_update_idle,
2078 .init_task = (void *)qmap_init_task,
2079 .exit_task = (void *)qmap_exit_task,
2080 .dump = (void *)qmap_dump,
2081 .dump_cid = (void *)qmap_dump_cid,
2082 .dump_task = (void *)qmap_dump_task,
2083 .cpuctl_init = (void *)qmap_cpuctl_init,
2084 .cpuctl_set_weight = (void *)qmap_cpuctl_set_weight,
2085 .cpuctl_set_bandwidth = (void *)qmap_cpuctl_set_bandwidth,
2086 .cpuctl_move = (void *)qmap_cpuctl_move,
2087 .sub_attach = (void *)qmap_sub_attach,
2088 .sub_detach = (void *)qmap_sub_detach,
2089 .sub_caps_updated = (void *)qmap_sub_caps_updated,
2090 .sub_ecaps_updated = (void *)qmap_sub_ecaps_updated,
2091 .init_cids = (void *)qmap_init_cids,
2092 .init = (void *)qmap_init,
2093 .exit = (void *)qmap_exit,
2094 .timeout_ms = 5000U,
2095 .name = "qmap");
2096