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 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 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 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 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. */ 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. */ 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. */ 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 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 */ 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 */ 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 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 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 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 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 */ 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 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 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 */ 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 */ 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 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 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 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 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 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 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 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 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 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 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 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 */ 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 */ 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 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 */ 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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") 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 */ 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 */ 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 */ 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 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 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 */ 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 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 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 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 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