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