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 bpf_rcu_read_lock();
965 cmask_from_cpumask(&taskc->cpus_allowed, p->cpus_ptr);
966 bpf_rcu_read_unlock();
967
968 v = bpf_task_storage_get(&task_ctx_stor, p, NULL,
969 BPF_LOCAL_STORAGE_GET_F_CREATE);
970 if (!v) {
971 /* push back to the free list */
972 if (!qmap_spin_lock(&qa_task_lock)) {
973 taskc->next_free = qa.task_free_head;
974 qa.task_free_head = taskc;
975 bpf_res_spin_unlock(&qa_task_lock);
976 }
977 return -ENOMEM;
978 }
979 v->taskc = taskc;
980 return 0;
981 }
982
BPF_STRUCT_OPS(qmap_exit_task,struct task_struct * p,struct scx_exit_task_args * args)983 void BPF_STRUCT_OPS(qmap_exit_task, struct task_struct *p,
984 struct scx_exit_task_args *args)
985 {
986 struct task_ctx_stor_val *v;
987 task_ctx_t *taskc;
988
989 v = bpf_task_storage_get(&task_ctx_stor, p, NULL, 0);
990 if (!v || !v->taskc)
991 return;
992 taskc = v->taskc;
993 v->taskc = NULL;
994
995 if (qmap_spin_lock(&qa_task_lock))
996 return;
997 taskc->next_free = qa.task_free_head;
998 qa.task_free_head = taskc;
999 bpf_res_spin_unlock(&qa_task_lock);
1000 }
1001
BPF_STRUCT_OPS(qmap_dump,struct scx_dump_ctx * dctx)1002 void BPF_STRUCT_OPS(qmap_dump, struct scx_dump_ctx *dctx)
1003 {
1004 task_ctx_t *taskc;
1005 s32 i;
1006
1007 QMAP_TOUCH_ARENA();
1008
1009 if (suppress_dump)
1010 return;
1011
1012 /*
1013 * Walk the queue lists without locking - kfunc calls (scx_bpf_dump)
1014 * aren't in the verifier's kfunc_spin_allowed() list so we can't hold
1015 * a lock and dump. Best-effort; racing may print stale tids but the
1016 * walk is bounded by bpf_repeat() so it always terminates.
1017 */
1018 bpf_for(i, 0, 5) {
1019 scx_bpf_dump("QMAP FIFO[%d]:", i);
1020 taskc = qa.fifos[i].head;
1021 bpf_repeat(4096) {
1022 if (!taskc)
1023 break;
1024 scx_bpf_dump(" %d:%llu", taskc->pid, taskc->tid);
1025 taskc = taskc->q_next;
1026 }
1027 scx_bpf_dump("\n");
1028 }
1029 }
1030
BPF_STRUCT_OPS(qmap_dump_cid,struct scx_dump_ctx * dctx,s32 cid,bool idle)1031 void BPF_STRUCT_OPS(qmap_dump_cid, struct scx_dump_ctx *dctx, s32 cid, bool idle)
1032 {
1033 struct cpu_ctx __arena *cpuc = &qa.cpu_ctxs[cid];
1034
1035 if (suppress_dump || idle)
1036 return;
1037
1038 scx_bpf_dump("QMAP: dsp_idx=%llu dsp_cnt=%llu avg_weight=%u cpuperf_target=%u",
1039 cpuc->dsp_idx, cpuc->dsp_cnt, cpuc->avg_weight,
1040 cpuc->cpuperf_target);
1041 }
1042
BPF_STRUCT_OPS(qmap_dump_task,struct scx_dump_ctx * dctx,struct task_struct * p)1043 void BPF_STRUCT_OPS(qmap_dump_task, struct scx_dump_ctx *dctx, struct task_struct *p)
1044 {
1045 struct task_ctx_stor_val *v;
1046 task_ctx_t *taskc;
1047
1048 QMAP_TOUCH_ARENA();
1049
1050 if (suppress_dump)
1051 return;
1052 v = bpf_task_storage_get(&task_ctx_stor, p, NULL, 0);
1053 if (!v || !v->taskc)
1054 return;
1055 taskc = v->taskc;
1056
1057 scx_bpf_dump("QMAP: force_local=%d core_sched_seq=%llu",
1058 taskc->force_local, taskc->core_sched_seq);
1059 }
1060
BPF_STRUCT_OPS(qmap_cpuctl_init,struct cgroup * cgrp,struct scx_cgroup_init_args * args)1061 s32 BPF_STRUCT_OPS(qmap_cpuctl_init, struct cgroup *cgrp, struct scx_cgroup_init_args *args)
1062 {
1063 QMAP_TOUCH_ARENA();
1064
1065 if (print_msgs)
1066 bpf_printk("CGRP INIT %llu weight=%u period=%lu quota=%ld burst=%lu",
1067 cgrp->kn->id, args->weight, args->bw_period_us,
1068 args->bw_quota_us, args->bw_burst_us);
1069
1070 if (qa.inject_mode == QMAP_INJ_CGRP_INIT_FAIL) {
1071 char name[7] = {};
1072
1073 bpf_probe_read_kernel_str(name, sizeof(name), cgrp->kn->name);
1074 if (!bpf_strncmp(name, 6, "qmfail"))
1075 return -ENOMEM;
1076 }
1077
1078 return 0;
1079 }
1080
1081 static void redistribute(void);
1082
BPF_STRUCT_OPS(qmap_cpuctl_set_weight,struct cgroup * cgrp,u32 weight)1083 void BPF_STRUCT_OPS(qmap_cpuctl_set_weight, struct cgroup *cgrp, u32 weight)
1084 {
1085 u64 cgid = cgrp->kn->id;
1086 s32 i;
1087
1088 QMAP_TOUCH_ARENA();
1089
1090 if (print_msgs)
1091 bpf_printk("CGRP SET %llu weight=%u", cgid, weight);
1092
1093 /*
1094 * Knobs belong to the parent, so this op carries the child subs'
1095 * attach point weights. Adjust the matching sub's share of the cid
1096 * partition. Other cgroups don't participate in the split.
1097 */
1098 for (i = 0; i < MAX_SUB_SCHEDS; i++) {
1099 if (qa.sub_sched_ctxs[i].cgroup_id != cgid)
1100 continue;
1101 if (qa.sub_sched_ctxs[i].weight != weight) {
1102 qa.sub_sched_ctxs[i].weight = weight;
1103 redistribute();
1104 }
1105 break;
1106 }
1107 }
1108
BPF_STRUCT_OPS(qmap_cpuctl_set_bandwidth,struct cgroup * cgrp,u64 period_us,u64 quota_us,u64 burst_us)1109 void BPF_STRUCT_OPS(qmap_cpuctl_set_bandwidth, struct cgroup *cgrp, u64 period_us,
1110 u64 quota_us, u64 burst_us)
1111 {
1112 if (print_msgs)
1113 bpf_printk("CGRP SET %llu period=%lu quota=%ld burst=%lu",
1114 cgrp->kn->id, period_us, quota_us, burst_us);
1115 }
1116
BPF_STRUCT_OPS(qmap_cpuctl_move,struct task_struct * p,struct cgroup * from,struct cgroup * to)1117 void BPF_STRUCT_OPS(qmap_cpuctl_move, struct task_struct *p, struct cgroup *from,
1118 struct cgroup *to)
1119 {
1120 if (print_msgs)
1121 bpf_printk("CGRP MOVE %d %llu -> %llu",
1122 p->pid, from->kn->id, to->kn->id);
1123 }
1124
BPF_STRUCT_OPS(qmap_update_idle,s32 cid,bool idle)1125 void BPF_STRUCT_OPS(qmap_update_idle, s32 cid, bool idle)
1126 {
1127 QMAP_TOUCH_ARENA();
1128
1129 /*
1130 * The kernel delivers update_idle() for every cid this node holds
1131 * SCX_CAP_BASE on. Track every cid's idle state regardless of
1132 * delegation: the direct-dispatch pick masks idle_cids with usable_cids
1133 * at selection, so a cid already idle when it returns to self needs no
1134 * reseed here.
1135 */
1136 if (idle)
1137 cmask_set(cid, &qa.idle_cids.mask);
1138 else
1139 cmask_clear(cid, &qa.idle_cids.mask);
1140 }
1141
BPF_STRUCT_OPS(qmap_set_cmask,struct task_struct * p,const struct scx_cmask * cmask_in)1142 void BPF_STRUCT_OPS(qmap_set_cmask, struct task_struct *p,
1143 const struct scx_cmask *cmask_in)
1144 {
1145 struct scx_cmask __arena *cmask = (struct scx_cmask __arena *)(long)cmask_in;
1146 task_ctx_t *taskc;
1147
1148 taskc = lookup_task_ctx(p);
1149 if (!taskc)
1150 return;
1151 cmask_copy(&taskc->cpus_allowed, cmask);
1152 }
1153
1154 struct monitor_timer {
1155 struct bpf_timer timer;
1156 };
1157
1158 struct {
1159 __uint(type, BPF_MAP_TYPE_ARRAY);
1160 __uint(max_entries, 1);
1161 __type(key, u32);
1162 __type(value, struct monitor_timer);
1163 } monitor_timer SEC(".maps");
1164
1165 /*
1166 * Aggregate cidperf across the first nr_online_cids cids. Post-hotplug
1167 * the first-N-are-online invariant drifts, so some cap/cur values may
1168 * be stale. For this demo monitor that's fine; the scheduler exits on
1169 * the enable-time hotplug_seq mismatch and userspace restarts, which
1170 * rebuilds the layout.
1171 */
monitor_cpuperf(void)1172 static void monitor_cpuperf(void)
1173 {
1174 u32 nr_online = scx_bpf_nr_online_cids();
1175 u64 cap_sum = 0, cur_sum = 0, cur_min = SCX_CPUPERF_ONE, cur_max = 0;
1176 u64 target_sum = 0, target_min = SCX_CPUPERF_ONE, target_max = 0;
1177 s32 cid;
1178
1179 QMAP_TOUCH_ARENA();
1180
1181 bpf_for(cid, 0, nr_online) {
1182 struct cpu_ctx __arena *cpuc = &qa.cpu_ctxs[cid];
1183 u32 cap = scx_bpf_cidperf_cap(cid);
1184 u32 cur = scx_bpf_cidperf_cur(cid);
1185 u32 target;
1186
1187 cur_min = cur < cur_min ? cur : cur_min;
1188 cur_max = cur > cur_max ? cur : cur_max;
1189
1190 cur_sum += (u64)cur * cap / SCX_CPUPERF_ONE;
1191 cap_sum += cap;
1192
1193 target = cpuc->cpuperf_target;
1194 target_sum += target;
1195 target_min = target < target_min ? target : target_min;
1196 target_max = target > target_max ? target : target_max;
1197 }
1198
1199 if (!nr_online || !cap_sum)
1200 return;
1201
1202 qa.cpuperf_min = cur_min;
1203 qa.cpuperf_avg = cur_sum * SCX_CPUPERF_ONE / cap_sum;
1204 qa.cpuperf_max = cur_max;
1205
1206 qa.cpuperf_target_min = target_min;
1207 qa.cpuperf_target_avg = target_sum / nr_online;
1208 qa.cpuperf_target_max = target_max;
1209 }
1210
1211 /*
1212 * Dump the currently queued tasks in the shared DSQ to demonstrate the usage of
1213 * scx_bpf_dsq_nr_queued() and DSQ iterator. Raise the dispatch batch count to
1214 * see meaningful dumps in the trace pipe.
1215 */
dump_shared_dsq(void)1216 static void dump_shared_dsq(void)
1217 {
1218 struct task_struct *p;
1219 s32 nr;
1220
1221 if (!(nr = scx_bpf_dsq_nr_queued(SHARED_DSQ)))
1222 return;
1223
1224 bpf_printk("Dumping %d tasks in SHARED_DSQ in reverse order", nr);
1225
1226 bpf_rcu_read_lock();
1227 bpf_for_each(scx_dsq, p, SHARED_DSQ, SCX_DSQ_ITER_REV)
1228 bpf_printk("%s[%d]", p->comm, p->pid);
1229 bpf_rcu_read_unlock();
1230 }
1231
monitor_timerfn(void * map,int * key,struct bpf_timer * timer)1232 static int monitor_timerfn(void *map, int *key, struct bpf_timer *timer)
1233 {
1234 bpf_rcu_read_lock();
1235 scan_shared_dsq(true);
1236 bpf_rcu_read_unlock();
1237
1238 monitor_cpuperf();
1239
1240 if (print_dsqs_and_events) {
1241 struct scx_event_stats events;
1242
1243 dump_shared_dsq();
1244
1245 __COMPAT_scx_bpf_events(&events, sizeof(events));
1246
1247 bpf_printk("%35s: %lld", "SCX_EV_SELECT_CPU_FALLBACK",
1248 scx_read_event(&events, SCX_EV_SELECT_CPU_FALLBACK));
1249 bpf_printk("%35s: %lld", "SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE",
1250 scx_read_event(&events, SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE));
1251 bpf_printk("%35s: %lld", "SCX_EV_DISPATCH_KEEP_LAST",
1252 scx_read_event(&events, SCX_EV_DISPATCH_KEEP_LAST));
1253 bpf_printk("%35s: %lld", "SCX_EV_ENQ_SKIP_EXITING",
1254 scx_read_event(&events, SCX_EV_ENQ_SKIP_EXITING));
1255 bpf_printk("%35s: %lld", "SCX_EV_REFILL_SLICE_DFL",
1256 scx_read_event(&events, SCX_EV_REFILL_SLICE_DFL));
1257 bpf_printk("%35s: %lld", "SCX_EV_BYPASS_DURATION",
1258 scx_read_event(&events, SCX_EV_BYPASS_DURATION));
1259 bpf_printk("%35s: %lld", "SCX_EV_BYPASS_DISPATCH",
1260 scx_read_event(&events, SCX_EV_BYPASS_DISPATCH));
1261 bpf_printk("%35s: %lld", "SCX_EV_BYPASS_ACTIVATE",
1262 scx_read_event(&events, SCX_EV_BYPASS_ACTIVATE));
1263 }
1264
1265 if (bpf_timer_start(timer, ONE_SEC_IN_NS, 0))
1266 scx_bpf_error("failed to re-arm stats timer");
1267 return 0;
1268 }
1269
1270 struct lowpri_timer {
1271 struct bpf_timer timer;
1272 };
1273
1274 struct {
1275 __uint(type, BPF_MAP_TYPE_ARRAY);
1276 __uint(max_entries, 1);
1277 __type(key, u32);
1278 __type(value, struct lowpri_timer);
1279 } lowpri_timer SEC(".maps");
1280
1281 /*
1282 * Nice 19 tasks are put into the lowpri DSQ. Every 10ms, reenq is triggered and
1283 * the tasks are transferred to SHARED_DSQ.
1284 */
lowpri_timerfn(void * map,int * key,struct bpf_timer * timer)1285 static int lowpri_timerfn(void *map, int *key, struct bpf_timer *timer)
1286 {
1287 scx_bpf_dsq_reenq(LOWPRI_DSQ, 0);
1288 if (bpf_timer_start(timer, LOWPRI_INTV_NS, 0))
1289 scx_bpf_error("failed to re-arm lowpri timer");
1290 return 0;
1291 }
1292
1293 struct round_robin_timer {
1294 struct bpf_timer timer;
1295 };
1296
1297 struct {
1298 __uint(type, BPF_MAP_TYPE_ARRAY);
1299 __uint(max_entries, 1);
1300 __type(key, u32);
1301 __type(value, struct round_robin_timer);
1302 } round_robin_timer SEC(".maps");
1303
1304 enum part_pending_flags {
1305 PART_REFRESH = BIT_U64(0),
1306 PART_REDISTRIBUTE = BIT_U64(1),
1307 };
1308
1309 /*
1310 * Partition update synchronization. qa.part can be written from concurrent
1311 * contexts. This single-runner guard admits one writer at a time without
1312 * holding a lock across the grant/revoke kfuncs. part_pending coalesces
1313 * refresh and repartition requests that arrive while it is held.
1314 *
1315 * They live in .bss, not the arena: rr_advance() runs from a bpf_timer
1316 * callback, where the verifier rejects atomic ops on arena memory.
1317 */
1318 static u64 part_busy;
1319 static u64 part_pending;
1320
part_try_start(void)1321 static bool part_try_start(void)
1322 {
1323 /* set busy, report whether it was previously clear (we acquired it) */
1324 return !__sync_fetch_and_or(&part_busy, 1);
1325 }
1326
part_end(void)1327 static void part_end(void)
1328 {
1329 __sync_fetch_and_and(&part_busy, 0);
1330 }
1331
1332 /*
1333 * compute_partition() scratch.
1334 *
1335 * The excl-held cids are handed out in cid order: position 0..nr_excl-1 over
1336 * the held cids is split into contiguous ranges, one per participant that gets
1337 * at least one excl cid. Range k is owned by cp_range_owner[k] and ends at the
1338 * cumulative position cp_range_end[k].
1339 */
1340 static s32 cp_range_owner[MAX_PARTS]; /* exclusive range k: its owner id ... */
1341 static s32 cp_range_end[MAX_PARTS]; /* ... and the cumulative position it ends at */
1342
1343 /* a participant in the partition: self or an attached child */
1344 struct participant {
1345 s32 slot; /* child slot, or CID_SELF */
1346 u32 weight; /* cpu.weight */
1347 };
1348
1349 /**
1350 * place_one - assign one excl-held cid to its owner
1351 * @cid: the excl-held cid to place
1352 * @n: its position among the excl-held cids, in [0, nr_excl)
1353 * @total_excl: how many positions are owned exclusively (the rest are shared)
1354 *
1355 * Position @n below @total_excl is owned exclusively. It falls in the range
1356 * whose cumulative end it is under, owned by cp_range_owner[]. A position at or
1357 * above @total_excl is the rounding leftover which joins the shared pool.
1358 *
1359 * A separate __noinline function to help verification.
1360 */
place_one(s32 cid,s32 n,s32 total_excl)1361 __noinline int place_one(s32 cid, s32 n, s32 total_excl)
1362 {
1363 s32 owner = CID_SELF, i, s;
1364
1365 if (cid < 0 || cid >= SCX_QMAP_MAX_CPUS || n < 0 || n >= SCX_QMAP_MAX_CPUS ||
1366 total_excl < 0) {
1367 scx_bpf_error("-ERANGE");
1368 return 0;
1369 }
1370
1371 if (n < total_excl) {
1372 for (i = 0; i < MAX_PARTS; i++) {
1373 if (n < cp_range_end[i]) {
1374 owner = cp_range_owner[i];
1375 break;
1376 }
1377 }
1378 qa.part.cid_owner[cid] = owner;
1379 } else {
1380 s = n - total_excl;
1381 if (s < 0 || s >= MAX_PARTS) {
1382 scx_bpf_error("-ERANGE");
1383 return 0;
1384 }
1385 qa.part.shared_cids[s] = cid;
1386 /* time-shared: dispatch resolves the live holder via rr_pos */
1387 qa.part.cid_owner[cid] = CID_SHARED;
1388 }
1389 return 0;
1390 }
1391
1392 /**
1393 * compute_partition - build the cid partition from this node's held caps
1394 *
1395 * Decide each cid's owner, the shared pool and the rr rotation. __noinline to
1396 * help verification. See the comment at the top of the file.
1397 */
compute_partition(void)1398 __noinline void compute_partition(void)
1399 {
1400 s32 nr_cids = qa.nr_cids;
1401 s32 nr_excl, total_excl = 0, nr_rr = 0;
1402 s32 sum_w, i, cid, n = 0, share, self_w;
1403 u64 cgid_snap[MAX_SUB_SCHEDS];
1404 s32 w_snap[MAX_SUB_SCHEDS];
1405
1406 if (nr_cids > SCX_QMAP_MAX_CPUS) {
1407 scx_bpf_error("-ERANGE");
1408 return;
1409 }
1410
1411 /* find out the cids we hold */
1412 scx_bpf_sub_caps(0, SCX_CAP_ENQ, &qa.held_excl.mask);
1413 scx_bpf_sub_caps(0, SCX_CAP_ENQ_IMMED, &qa.held_shared.mask);
1414 cmask_andnot(&qa.held_shared.mask, &qa.held_excl.mask); /* held only as ENQ_IMMED */
1415
1416 qa.part.nr_shared = 0;
1417 qa.part.nr_rr = 0;
1418 qa.part.rr_pos = 0;
1419
1420 nr_excl = cmask_weight(&qa.held_excl.mask);
1421 qa.part.nr_excl = nr_excl;
1422
1423 /* no excl cid: held_shared stays self-local, the rest unheld */
1424 if (!nr_excl) {
1425 bpf_for(cid, 0, nr_cids) {
1426 if (cmask_test(cid, &qa.held_shared.mask))
1427 qa.part.cid_owner[cid] = CID_SELF;
1428 else
1429 qa.part.cid_owner[cid] = CID_NONE;
1430 }
1431 return;
1432 }
1433
1434 /*
1435 * Snapshot membership and weights so the sum_w and share loops agree. A
1436 * mid-compute change would otherwise wrap nr_shared negative. The self
1437 * weight is fixed at the default: a cgroup's weight is its parent's
1438 * knob, not the scheduler's own business.
1439 */
1440 self_w = 100;
1441 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1442 cgid_snap[i] = qa.sub_sched_ctxs[i].cgroup_id;
1443 w_snap[i] = cgid_snap[i] ? (qa.sub_sched_ctxs[i].weight ?: 100) : 0;
1444 }
1445
1446 /*
1447 * Participants are self plus each child. Give each a fixed range/rr
1448 * slot: self at slot 0, child i at slot i+1.
1449 *
1450 * sum_w totals every participant's weight.
1451 */
1452 sum_w = self_w;
1453 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1454 barrier_var(sum_w);
1455 sum_w += w_snap[i];
1456 }
1457
1458 /*
1459 * Split [0, nr_excl) into one contiguous range per participant, each
1460 * the floor of its weight share. cp_range_owner[]/cp_range_end[] record
1461 * each range's owner and cumulative end, total_excl counts the
1462 * exclusive slots, and the rest (nr_excl - total_excl) are shared.
1463 * rr_slots[] lists every participant for the round-robin.
1464 */
1465 share = (u64)nr_excl * self_w / sum_w;
1466 total_excl += share;
1467 cp_range_owner[0] = CID_SELF;
1468 cp_range_end[0] = total_excl;
1469 qa.part.rr_slots[nr_rr++] = 0; /* self holds slot 0 (cgid 0 = no grant) */
1470
1471 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1472 u64 cgid = cgid_snap[i];
1473 s32 w = w_snap[i];
1474
1475 barrier_var(total_excl);
1476 share = (u64)nr_excl * w / sum_w;
1477 total_excl += share;
1478 cp_range_owner[i + 1] = cgid ? i : CID_NONE;
1479 cp_range_end[i + 1] = total_excl;
1480
1481 if (cgid) {
1482 barrier_var(nr_rr);
1483 if (nr_rr < 0 || nr_rr >= MAX_PARTS) {
1484 scx_bpf_error("-ERANGE");
1485 return;
1486 }
1487 qa.part.rr_slots[nr_rr++] = cgid;
1488 }
1489 }
1490
1491 /* assign each cid: held-excl by position, the rest self/none */
1492 bpf_for(cid, 0, nr_cids) {
1493 if (cmask_test(cid, &qa.held_excl.mask)) {
1494 place_one(cid, n, total_excl);
1495 n++;
1496 barrier_var(n);
1497 } else if (cmask_test(cid, &qa.held_shared.mask)) {
1498 qa.part.cid_owner[cid] = CID_SELF; /* time-share, self-local */
1499 } else {
1500 qa.part.cid_owner[cid] = CID_NONE; /* not held */
1501 }
1502 }
1503
1504 qa.part.nr_shared = nr_excl - total_excl;
1505 qa.part.nr_rr = nr_rr;
1506 }
1507
1508 /*
1509 * Charge elapsed wall time to each cid's current owner. Runs under the
1510 * partition guard before every ownership change and from the stats flush, so
1511 * alloc_ns[] reflects the layout that was in effect. Shared-pool time is
1512 * charged to the live round-robin holder.
1513 */
account_alloc(void)1514 static __noinline void account_alloc(void)
1515 {
1516 u64 now = bpf_ktime_get_ns();
1517 s32 rr_owner = CID_SELF;
1518 s32 nr_cids = qa.nr_cids;
1519 u64 delta;
1520 s32 cid, i;
1521
1522 if (nr_cids < 0 || nr_cids > SCX_QMAP_MAX_CPUS) {
1523 scx_bpf_error("-ERANGE");
1524 return;
1525 }
1526
1527 /* first call starts the clock */
1528 if (!qa.alloc_ts) {
1529 qa.alloc_ts = now;
1530 return;
1531 }
1532 delta = now - qa.alloc_ts;
1533 qa.alloc_ts = now;
1534 qa.alloc_window_ns += delta;
1535
1536 /* resolve the live shared-pool holder to an owner id */
1537 if (qa.part.nr_shared && qa.part.nr_rr) {
1538 u32 pos = qa.part.rr_pos;
1539 u64 cgid = pos < MAX_PARTS ? qa.part.rr_slots[pos] : 0;
1540
1541 if (cgid) {
1542 rr_owner = CID_NONE;
1543 bpf_for(i, 0, MAX_SUB_SCHEDS)
1544 if (qa.sub_sched_ctxs[i].cgroup_id == cgid)
1545 rr_owner = i;
1546 }
1547 }
1548
1549 bpf_for(cid, 0, nr_cids) {
1550 s32 owner = qa.part.cid_owner[cid];
1551
1552 if (owner == CID_SHARED)
1553 owner = rr_owner;
1554 if (owner >= 0 && owner < MAX_SUB_SCHEDS)
1555 qa.alloc_ns[owner] += delta;
1556 else if (owner == CID_SELF)
1557 qa.self_alloc_ns += delta;
1558 }
1559 }
1560
1561 /*
1562 * usable_cids = self_cids & avail_cids. The inputs have separate writers,
1563 * apply_partition() and qmap_sub_ecaps_updated(), so the result is rebuilt in
1564 * full under the partition guard, in scratch first so that readers never see
1565 * self_cids alone.
1566 */
refresh_usable(void)1567 static void refresh_usable(void)
1568 {
1569 cmask_copy(&qa.usable_scratch.mask, &qa.self_cids.mask);
1570 cmask_and(&qa.usable_scratch.mask, &qa.avail_cids.mask);
1571 cmask_copy(&qa.usable_cids.mask, &qa.usable_scratch.mask);
1572 }
1573
1574 /*
1575 * apply_partition - execute the plan compute_partition() built
1576 *
1577 * Turn the owner map into the per-child, shared and self cmasks and issue the
1578 * grant/revoke kfuncs as a delta against each child's previous grant. If no
1579 * excl cid, evict every child.
1580 */
apply_partition(void)1581 __noinline void apply_partition(void)
1582 {
1583 s32 nr_cids = qa.nr_cids;
1584 s32 nr_shared = qa.part.nr_shared;
1585 s32 i, cid;
1586
1587 if (nr_cids < 0 || nr_cids > SCX_QMAP_MAX_CPUS ||
1588 nr_shared < 0 || nr_shared > MAX_PARTS) {
1589 scx_bpf_error("-ERANGE");
1590 return;
1591 }
1592
1593 /* no excl cpu: run own tasks on the held shares, evict children */
1594 if (!qa.part.nr_excl) {
1595 cmask_copy(&qa.self_cids.mask, &qa.held_shared.mask);
1596 refresh_usable();
1597 bpf_for(i, 0, MAX_SUB_SCHEDS)
1598 if (qa.sub_sched_ctxs[i].cgroup_id)
1599 scx_bpf_sub_kill(qa.sub_sched_ctxs[i].cgroup_id,
1600 "parent holds no excl cpu to distribute");
1601 return;
1602 }
1603
1604 /*
1605 * Snapshot the old pool. The per-child revoke below clears ENQ_IMMED on
1606 * the previously-granted pool, so a cid that left the pool (now a
1607 * sibling's excl) doesn't keep a stale ENQ_IMMED on its last holder.
1608 */
1609 cmask_copy(&qa.prev_rr_cids.mask, &qa.rr_cids.mask);
1610
1611 /* turn the owner map into the rr pool, per-child excl, and self sets */
1612 cmask_init(&qa.rr_cids.mask, 0, nr_cids);
1613 cmask_init(&qa.self_cids.mask, 0, nr_cids);
1614
1615 /* snapshot each child's grant, then rebuild the new sets below */
1616 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1617 cmask_copy(&qa.sub_sched_ctxs[i].prev_granted.mask,
1618 &qa.sub_sched_ctxs[i].granted_cids.mask);
1619 cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, nr_cids);
1620 }
1621
1622 bpf_for(i, 0, nr_shared)
1623 cmask_set(qa.part.shared_cids[i], &qa.rr_cids.mask);
1624 bpf_for(cid, 0, nr_cids) {
1625 s32 o = qa.part.cid_owner[cid];
1626
1627 if (cmask_test(cid, &qa.rr_cids.mask))
1628 continue;
1629 if (o >= 0 && o < MAX_SUB_SCHEDS)
1630 cmask_set(cid, &qa.sub_sched_ctxs[o].granted_cids.mask);
1631 else if (o == CID_SELF)
1632 cmask_set(cid, &qa.self_cids.mask);
1633 }
1634 refresh_usable();
1635
1636 /*
1637 * Apply each child's exclusive cids as a delta against its previous
1638 * grant. Separately clear the previous shared grant (ENQ_IMMED on the
1639 * old pool), covering cids still pooled and cids that left for a
1640 * sibling's excl. The current holder is granted the new pool below.
1641 */
1642 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1643 struct sub_sched_ctx __arena *ssc = &qa.sub_sched_ctxs[i];
1644 u64 cgid = ssc->cgroup_id;
1645
1646 if (!cgid)
1647 continue;
1648
1649 cmask_copy(&qa.to_revoke_cids.mask, &ssc->prev_granted.mask);
1650 cmask_andnot(&qa.to_revoke_cids.mask, &ssc->granted_cids.mask);
1651 cmask_copy(&qa.to_grant_cids.mask, &ssc->granted_cids.mask);
1652 cmask_andnot(&qa.to_grant_cids.mask, &ssc->prev_granted.mask);
1653
1654 scx_bpf_sub_revoke(cgid, SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1655 &qa.prev_rr_cids.mask);
1656 scx_bpf_sub_revoke(cgid, SCX_CAP_ENQ | SCX_CAP_PREEMPT |
1657 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1658 &qa.to_revoke_cids.mask);
1659 scx_bpf_sub_grant(cgid, SCX_CAP_ENQ | SCX_CAP_PREEMPT |
1660 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1661 &qa.to_grant_cids.mask, NULL);
1662 }
1663
1664 /* the current holder of the shared pool gets ENQ_IMMED on all of it */
1665 if (nr_shared) {
1666 s32 pos = qa.part.rr_pos;
1667 u64 holder_cgid;
1668
1669 if (pos < 0 || pos >= MAX_PARTS) {
1670 scx_bpf_error("-ERANGE");
1671 return;
1672 }
1673
1674 holder_cgid = qa.part.rr_slots[pos]; /* 0 = self, nothing to grant */
1675 if (holder_cgid)
1676 scx_bpf_sub_grant(holder_cgid,
1677 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1678 &qa.rr_cids.mask, NULL);
1679 }
1680 }
1681
1682 /**
1683 * execute_partition - Run pending partition updates
1684 *
1685 * The rr timer is the backstop if the loop reaches its iteration limit.
1686 */
execute_partition(void)1687 static void execute_partition(void)
1688 {
1689 u64 pending;
1690 s32 i;
1691
1692 bpf_for(i, 0, 1024) {
1693 if (!part_try_start())
1694 break;
1695
1696 pending = __sync_fetch_and_and(&part_pending, 0);
1697 if (pending & PART_REDISTRIBUTE) {
1698 /* charge elapsed time before repartitioning */
1699 account_alloc();
1700 compute_partition();
1701 apply_partition();
1702 } else if (pending & PART_REFRESH) {
1703 refresh_usable();
1704 }
1705
1706 /*
1707 * Requests are published before trying the guard. Releasing it
1708 * before checking pending work ensures a racing request is
1709 * either observed here or handled by a caller that acquires the
1710 * guard.
1711 */
1712 part_end();
1713 if (!__sync_fetch_and_or(&part_pending, 0))
1714 break;
1715 }
1716 }
1717
redistribute(void)1718 static void redistribute(void)
1719 {
1720 __sync_fetch_and_or(&part_pending, PART_REDISTRIBUTE);
1721 execute_partition();
1722 }
1723
1724 /*
1725 * Userspace pokes this (PROG_RUN) to bring alloc_ns[] current before reading
1726 * it for the stats display. Skipping when the partition guard is held is
1727 * fine - alloc_ts is untouched, so the elapsed time is charged next time.
1728 */
1729 SEC("syscall")
flush_alloc(void * ctx)1730 int flush_alloc(void *ctx)
1731 {
1732 if (part_try_start()) {
1733 account_alloc();
1734 part_end();
1735 execute_partition();
1736 }
1737 return 0;
1738 }
1739
1740 /*
1741 * Hand the shared pool to the next participant in the rotation. Self's turn
1742 * just revokes the pool back to this sched. A child's turn grants it ENQ_IMMED
1743 * on the entire pool. As only excl-held cids are time-shared, a wall-clock
1744 * rotation works. Driven by the round-robin timer.
1745 */
rr_advance(void)1746 static void rr_advance(void)
1747 {
1748 s32 nr_shared, old_pos, new_pos;
1749 u64 old_cgid, new_cgid;
1750 u32 nr_rr; /* unsigned for % */
1751
1752 /* a redistribute holds the partition and rebuilds the pool, so skip */
1753 if (!part_try_start())
1754 return;
1755
1756 nr_rr = qa.part.nr_rr;
1757 nr_shared = qa.part.nr_shared;
1758
1759 if (nr_shared < 0 || nr_shared > MAX_PARTS) {
1760 scx_bpf_error("-ERANGE");
1761 return;
1762 }
1763
1764 if (nr_shared && nr_rr >= 2) {
1765 /* close out the outgoing holder's pool time */
1766 account_alloc();
1767
1768 old_pos = qa.part.rr_pos;
1769 new_pos = (old_pos + 1) % nr_rr;
1770 old_cgid = qa.part.rr_slots[old_pos];
1771 new_cgid = qa.part.rr_slots[new_pos];
1772 qa.part.rr_pos = new_pos;
1773
1774 /*
1775 * Move the ENQ_IMMED cap to the next participant. The shared
1776 * cids stay marked CID_SHARED. qmap_dispatch() resolves the
1777 * live holder via rr_pos without the guard, so a dispatch
1778 * racing this handoff may reenqueue a task once. Harmless for a
1779 * time-share.
1780 */
1781 if (old_cgid)
1782 scx_bpf_sub_revoke(old_cgid,
1783 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1784 &qa.rr_cids.mask);
1785 if (new_cgid)
1786 scx_bpf_sub_grant(new_cgid,
1787 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1788 &qa.rr_cids.mask, NULL);
1789 }
1790
1791 part_end();
1792
1793 execute_partition();
1794 }
1795
1796 /* advance the time-shared cid pool every round_robin_ns */
round_robin_timerfn(void * map,int * key,struct bpf_timer * timer)1797 static int round_robin_timerfn(void *map, int *key, struct bpf_timer *timer)
1798 {
1799 rr_advance();
1800 if (bpf_timer_start(timer, round_robin_ns, 0))
1801 scx_bpf_error("failed to re-arm round-robin timer");
1802 return 0;
1803 }
1804
1805 /*
1806 * Custom cid layout for the cid-override test. On invalid input the kfunc
1807 * scx_error()s and aborts the scheduler.
1808 */
BPF_STRUCT_OPS_SLEEPABLE(qmap_init_cids)1809 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init_cids)
1810 {
1811 u32 nr_cpu_ids = scx_bpf_nr_cpu_ids();
1812
1813 if (!cid_override_mode)
1814 return 0;
1815
1816 /* the arena arrays are sized SCX_QMAP_MAX_CPUS */
1817 if (nr_cpu_ids > SCX_QMAP_MAX_CPUS) {
1818 scx_bpf_error("nr_cpu_ids=%u exceeds SCX_QMAP_MAX_CPUS=%d",
1819 nr_cpu_ids, SCX_QMAP_MAX_CPUS);
1820 return -EINVAL;
1821 }
1822
1823 scx_bpf_cid_override(qa.cid_override_cpu_to_cid, nr_cpu_ids,
1824 qa.cid_override_shard_start, cid_override_nr_shards);
1825 return 0;
1826 }
1827
BPF_STRUCT_OPS_SLEEPABLE(qmap_init)1828 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init)
1829 {
1830 u8 __arena *slab;
1831 u32 nr_pages, key = 0, i;
1832 u32 nr_cids, nr_cpu_ids;
1833 struct bpf_timer *timer;
1834 s32 ret;
1835
1836 nr_cids = scx_bpf_nr_cids();
1837 nr_cpu_ids = scx_bpf_nr_cpu_ids();
1838
1839 if (nr_cids > SCX_QMAP_MAX_CPUS) {
1840 scx_bpf_error("nr_cids=%u exceeds SCX_QMAP_MAX_CPUS=%d",
1841 nr_cids, SCX_QMAP_MAX_CPUS);
1842 return -EINVAL;
1843 }
1844 if (nr_cpu_ids > SCX_QMAP_MAX_CPUS) {
1845 scx_bpf_error("nr_cpu_ids=%u exceeds SCX_QMAP_MAX_CPUS=%d",
1846 nr_cpu_ids, SCX_QMAP_MAX_CPUS);
1847 return -EINVAL;
1848 }
1849
1850 /*
1851 * Allocate the task_ctx slab in arena and thread the entire slab onto
1852 * the free list. max_tasks is set by userspace before load. Each entry
1853 * is TASK_CTX_STRIDE bytes - task_ctx's trailing cpus_allowed flex
1854 * array extends into the stride tail.
1855 */
1856 if (!max_tasks) {
1857 scx_bpf_error("max_tasks must be > 0");
1858 return -EINVAL;
1859 }
1860
1861 nr_pages = (max_tasks * TASK_CTX_STRIDE + PAGE_SIZE - 1) / PAGE_SIZE;
1862 slab = bpf_arena_alloc_pages(&arena, NULL, nr_pages, NUMA_NO_NODE, 0);
1863 if (!slab) {
1864 scx_bpf_error("failed to allocate task_ctx slab");
1865 return -ENOMEM;
1866 }
1867 qa.task_ctxs = (task_ctx_t *)slab;
1868
1869 bpf_for(i, 0, 5)
1870 qa.fifos[i].idx = i;
1871
1872 bpf_for(i, 0, max_tasks) {
1873 task_ctx_t *cur = (task_ctx_t *)(slab + i * TASK_CTX_STRIDE);
1874 task_ctx_t *next = (i + 1 < max_tasks) ?
1875 (task_ctx_t *)(slab + (i + 1) * TASK_CTX_STRIDE) : NULL;
1876 cur->next_free = next;
1877 }
1878 qa.task_free_head = (task_ctx_t *)slab;
1879
1880 /* cache the cid count, trusted to be <= SCX_QMAP_MAX_CPUS hereafter */
1881 qa.nr_cids = nr_cids;
1882
1883 /* cmasks are embedded in qa, so they only need initializing */
1884 cmask_init(&qa.idle_cids.mask, 0, nr_cids);
1885 cmask_init(&qa.rr_cids.mask, 0, nr_cids);
1886 cmask_init(&qa.prev_rr_cids.mask, 0, nr_cids);
1887 cmask_init(&qa.self_cids.mask, 0, nr_cids);
1888 cmask_init(&qa.avail_cids.mask, 0, nr_cids);
1889 cmask_init(&qa.usable_cids.mask, 0, nr_cids);
1890 cmask_init(&qa.to_revoke_cids.mask, 0, nr_cids);
1891 cmask_init(&qa.to_grant_cids.mask, 0, nr_cids);
1892 cmask_init(&qa.usable_scratch.mask, 0, nr_cids);
1893 cmask_init(&qa.held_excl.mask, 0, nr_cids);
1894 cmask_init(&qa.held_shared.mask, 0, nr_cids);
1895
1896 scx_bpf_sub_caps(0, SCX_CAP_ENQ, &qa.held_excl.mask);
1897 scx_bpf_sub_caps(0, SCX_CAP_ENQ_IMMED, &qa.held_shared.mask);
1898 cmask_andnot(&qa.held_shared.mask, &qa.held_excl.mask);
1899
1900 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1901 cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, nr_cids);
1902 cmask_init(&qa.sub_sched_ctxs[i].prev_granted.mask, 0, nr_cids);
1903 }
1904
1905 /*
1906 * The root starts holding every cid and gets no ecaps notifications, so
1907 * its avail set is fixed here. qmap_sub_ecaps_updated() maintains the
1908 * per-cid state as effective caps settle, and redistribute() rebuilds
1909 * owner and self from held caps. A non-root node starts with nothing.
1910 */
1911 bpf_for(i, 0, nr_cids) {
1912 if (!sub_cgroup_id) {
1913 cmask_set(i, &qa.self_cids.mask);
1914 cmask_set(i, &qa.avail_cids.mask);
1915 cmask_set(i, &qa.usable_cids.mask);
1916 qa.part.cid_owner[i] = CID_SELF;
1917 } else {
1918 qa.part.cid_owner[i] = CID_NONE;
1919 }
1920 }
1921 qa.part.nr_shared = 0;
1922
1923 ret = scx_bpf_create_dsq(SHARED_DSQ, -1);
1924 if (ret) {
1925 scx_bpf_error("failed to create DSQ %d (%d)", SHARED_DSQ, ret);
1926 return ret;
1927 }
1928
1929 ret = scx_bpf_create_dsq(HIGHPRI_DSQ, -1);
1930 if (ret) {
1931 scx_bpf_error("failed to create DSQ %d (%d)", HIGHPRI_DSQ, ret);
1932 return ret;
1933 }
1934
1935 ret = scx_bpf_create_dsq(LOWPRI_DSQ, -1);
1936 if (ret)
1937 return ret;
1938
1939 timer = bpf_map_lookup_elem(&monitor_timer, &key);
1940 if (!timer)
1941 return -ESRCH;
1942 bpf_timer_init(timer, &monitor_timer, CLOCK_MONOTONIC);
1943 bpf_timer_set_callback(timer, monitor_timerfn);
1944 ret = bpf_timer_start(timer, ONE_SEC_IN_NS, 0);
1945 if (ret)
1946 return ret;
1947
1948 if (__COMPAT_has_generic_reenq()) {
1949 /* see lowpri_timerfn() */
1950 timer = bpf_map_lookup_elem(&lowpri_timer, &key);
1951 if (!timer)
1952 return -ESRCH;
1953 bpf_timer_init(timer, &lowpri_timer, CLOCK_MONOTONIC);
1954 bpf_timer_set_callback(timer, lowpri_timerfn);
1955 ret = bpf_timer_start(timer, LOWPRI_INTV_NS, 0);
1956 if (ret)
1957 return ret;
1958 }
1959
1960 /* sub-sched: drive the boundary-cid round-robin from a bpf timer */
1961 timer = bpf_map_lookup_elem(&round_robin_timer, &key);
1962 if (!timer)
1963 return -ESRCH;
1964 bpf_timer_init(timer, &round_robin_timer, CLOCK_MONOTONIC);
1965 bpf_timer_set_callback(timer, round_robin_timerfn);
1966 ret = bpf_timer_start(timer, round_robin_ns, 0);
1967 if (ret)
1968 return ret;
1969
1970 return 0;
1971 }
1972
BPF_STRUCT_OPS(qmap_exit,struct scx_exit_info * ei)1973 void BPF_STRUCT_OPS(qmap_exit, struct scx_exit_info *ei)
1974 {
1975 UEI_RECORD(uei, ei);
1976 }
1977
1978 /*
1979 * Seed a new sub slot with the cgroup's current weight. The kernel delivers
1980 * ops.cpuctl_set_weight() only on value-changing writes, so a weight set
1981 * before the sub attached would otherwise go unnoticed.
1982 */
cgrp_cur_weight(u64 cgid)1983 static u32 cgrp_cur_weight(u64 cgid)
1984 {
1985 struct cgroup_subsys_state *css;
1986 struct cgroup *cgrp;
1987 u32 weight = 100;
1988
1989 cgrp = bpf_cgroup_from_id(cgid);
1990 if (!cgrp)
1991 return weight;
1992
1993 css = BPF_CORE_READ(cgrp, subsys[cpu_cgrp_id]);
1994 if (css) {
1995 struct task_group *tg = container_of(css, struct task_group, css);
1996 u32 w = BPF_CORE_READ(tg, scx.weight);
1997
1998 if (w)
1999 weight = w;
2000 }
2001 bpf_cgroup_release(cgrp);
2002 return weight;
2003 }
2004
BPF_STRUCT_OPS(qmap_sub_attach,struct scx_sub_attach_args * args)2005 s32 BPF_STRUCT_OPS(qmap_sub_attach, struct scx_sub_attach_args *args)
2006 {
2007 s32 i;
2008
2009 /* as long as there is at least one excl cpu, children can attach */
2010 if (!cmask_weight(&qa.held_excl.mask))
2011 return -ENOSPC;
2012
2013 for (i = 0; i < MAX_SUB_SCHEDS; i++) {
2014 if (qa.sub_sched_ctxs[i].cgroup_id)
2015 continue;
2016
2017 qa.sub_sched_ctxs[i].cgroup_id = args->ops->sub_cgroup_id;
2018 qa.sub_sched_ctxs[i].weight = cgrp_cur_weight(args->ops->sub_cgroup_id);
2019 qa.nr_sub_scheds++;
2020 bpf_printk("attaching sub-sched[%d] on %s", i, args->cgroup_path);
2021 redistribute();
2022 return 0;
2023 }
2024
2025 return -ENOSPC;
2026 }
2027
BPF_STRUCT_OPS(qmap_sub_detach,struct scx_sub_detach_args * args)2028 void BPF_STRUCT_OPS(qmap_sub_detach, struct scx_sub_detach_args *args)
2029 {
2030 s32 i;
2031
2032 for (i = 0; i < MAX_SUB_SCHEDS; i++) {
2033 if (qa.sub_sched_ctxs[i].cgroup_id != args->ops->sub_cgroup_id)
2034 continue;
2035
2036 qa.sub_sched_ctxs[i].cgroup_id = 0;
2037 qa.sub_sched_ctxs[i].weight = 100;
2038 cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, qa.nr_cids);
2039 qa.nr_sub_scheds--;
2040 bpf_printk("detaching sub-sched[%d] on %s", i, args->cgroup_path);
2041 redistribute();
2042 break;
2043 }
2044 }
2045
BPF_STRUCT_OPS(qmap_sub_caps_updated,const struct scx_cmask * cmask,u64 caps)2046 void BPF_STRUCT_OPS(qmap_sub_caps_updated, const struct scx_cmask *cmask, u64 caps)
2047 {
2048 /* our held caps changed, redistribute */
2049 redistribute();
2050 }
2051
BPF_STRUCT_OPS(qmap_sub_ecaps_updated,s32 cid,u64 before,u64 after)2052 void BPF_STRUCT_OPS(qmap_sub_ecaps_updated, s32 cid, u64 before, u64 after)
2053 {
2054 /*
2055 * Effective caps updated. Track which cids hold shared caps so a self
2056 * task placed there enqueues IMMED, and which cids have ENQ_IMMED in
2057 * effect at all (avail, see the header comment).
2058 */
2059 if (after & SCX_CAP_ENQ_IMMED) {
2060 qa.cid_shared[cid] = (after & SCX_CAP_ENQ) ? 0 : 1;
2061 cmask_set(cid, &qa.avail_cids.mask);
2062 } else {
2063 qa.cid_shared[cid] = 0;
2064 cmask_clear(cid, &qa.avail_cids.mask);
2065 }
2066
2067 __sync_fetch_and_or(&part_pending, PART_REFRESH);
2068 execute_partition();
2069 }
2070
2071 SCX_OPS_CID_DEFINE(qmap_ops,
2072 .flags = SCX_OPS_ENQ_EXITING | SCX_OPS_TID_TO_TASK,
2073 .select_cid = (void *)qmap_select_cid,
2074 .enqueue = (void *)qmap_enqueue,
2075 .dequeue = (void *)qmap_dequeue,
2076 .dispatch = (void *)qmap_dispatch,
2077 .tick = (void *)qmap_tick,
2078 .core_sched_before = (void *)qmap_core_sched_before,
2079 .set_cmask = (void *)qmap_set_cmask,
2080 .update_idle = (void *)qmap_update_idle,
2081 .init_task = (void *)qmap_init_task,
2082 .exit_task = (void *)qmap_exit_task,
2083 .dump = (void *)qmap_dump,
2084 .dump_cid = (void *)qmap_dump_cid,
2085 .dump_task = (void *)qmap_dump_task,
2086 .cpuctl_init = (void *)qmap_cpuctl_init,
2087 .cpuctl_set_weight = (void *)qmap_cpuctl_set_weight,
2088 .cpuctl_set_bandwidth = (void *)qmap_cpuctl_set_bandwidth,
2089 .cpuctl_move = (void *)qmap_cpuctl_move,
2090 .sub_attach = (void *)qmap_sub_attach,
2091 .sub_detach = (void *)qmap_sub_detach,
2092 .sub_caps_updated = (void *)qmap_sub_caps_updated,
2093 .sub_ecaps_updated = (void *)qmap_sub_ecaps_updated,
2094 .init_cids = (void *)qmap_init_cids,
2095 .init = (void *)qmap_init,
2096 .exit = (void *)qmap_exit,
2097 .timeout_ms = 5000U,
2098 .name = "qmap");
2099