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 static void dispatch_to_rescue(struct task_struct *p, task_ctx_t *taskc, 373 u64 enq_flags) 374 { 375 u32 cid = cmask_next_set_wrap(&taskc->cpus_allowed, 0); 376 377 if (cid >= scx_bpf_nr_cids()) { 378 scx_bpf_error("task %d has no allowed cid", p->pid); 379 return; 380 } 381 382 taskc->force_local = false; 383 __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); 384 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | cid, slice_ns, 385 enq_flags | SCX_ENQ_RESCUE); 386 } 387 388 /* first cid this node does NOT hold for fault injection, -1 if none */ 389 static s32 first_unavail_cid(void) 390 { 391 s32 nr_cids = qa.nr_cids, c; 392 393 if (nr_cids > SCX_QMAP_MAX_CPUS) { 394 scx_bpf_error("-ERANGE"); 395 return -1; 396 } 397 398 bpf_for(c, 0, nr_cids) { 399 if (!cmask_test(c, &qa.held_excl.mask) && 400 !cmask_test(c, &qa.held_shared.mask)) 401 return c; 402 } 403 return -1; 404 } 405 406 static int weight_to_idx(u32 weight) 407 { 408 /* Coarsely map the compound weight to a FIFO. */ 409 if (weight <= 25) 410 return 0; 411 else if (weight <= 50) 412 return 1; 413 else if (weight < 200) 414 return 2; 415 else if (weight < 400) 416 return 3; 417 else 418 return 4; 419 } 420 421 void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags) 422 { 423 static u32 user_cnt, kernel_cnt; 424 task_ctx_t *taskc; 425 int idx = weight_to_idx(p->scx.weight); 426 s32 cid; 427 428 if (enq_flags & SCX_ENQ_REENQ) { 429 u64 reason = p->scx.flags & SCX_TASK_REENQ_REASON_MASK; 430 431 __sync_fetch_and_add(&qa.nr_reenqueued, 1); 432 if (scx_bpf_task_cid(p) == 0) 433 __sync_fetch_and_add(&qa.nr_reenqueued_cid0, 1); 434 /* cap-loss and IMMED-handback bounces, relocated below */ 435 if (reason == SCX_TASK_REENQ_CAP) 436 __sync_fetch_and_add(&qa.nr_reenq_cap, 1); 437 else if (reason == SCX_TASK_REENQ_IMMED) 438 __sync_fetch_and_add(&qa.nr_reenq_immed, 1); 439 } 440 441 if (p->flags & PF_KTHREAD) { 442 if (stall_kernel_nth && !(++kernel_cnt % stall_kernel_nth)) 443 return; 444 } else { 445 if (stall_user_nth && !(++user_cnt % stall_user_nth)) 446 return; 447 } 448 449 if (qa.test_error_cnt && !--qa.test_error_cnt) 450 scx_bpf_error("test triggering error"); 451 452 if (!(taskc = lookup_task_ctx(p))) 453 return; 454 455 /* 456 * All enqueued tasks must have their core_sched_seq updated for correct 457 * core-sched ordering. Also, take a look at the end of qmap_dispatch(). 458 */ 459 taskc->core_sched_seq = qa.core_sched_tail_seqs[idx]++; 460 461 if (enq_flags & SCX_ENQ_BLOCKED) 462 __sync_fetch_and_add(&qa.nr_enq_blocked, 1); 463 464 /* 465 * A task of ours that can run on none of our self cids - the parent 466 * didn't grant them or we delegated them to children - would starve in 467 * SHARED/FIFO since we only pull from those on self cids. 468 * 469 * Force it onto its first allowed cid's local DSQ with SCX_ENQ_RESCUE. 470 * If we hold ENQ on that cid it runs. Otherwise the kernel diverts the 471 * task to its rescue path. IMMED would turn the insert into a legal 472 * placement on a time-shared cid and the kernel would bounce it back 473 * here instead of rescuing it. Do this before the blocked-donor fast 474 * paths, which also require an eligible self cid to make progress. 475 */ 476 if (!cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) { 477 dispatch_to_rescue(p, taskc, enq_flags); 478 return; 479 } 480 481 /* 482 * SCX_OPS_ALWAYS_ENQ_IMMED makes the local insertion below implicitly 483 * carry SCX_ENQ_IMMED. If the CPU can't run the blocked donor immediately, 484 * the core returns it through ops.enqueue() with SCX_ENQ_REENQ. Inserting 485 * it into the same local DSQ would repeat the IMMED handback until the 486 * scheduler is ejected. Move reenqueued blocked donors to the shared DSQ, 487 * which doesn't carry SCX_ENQ_IMMED, so another CPU can consume them. 488 */ 489 if ((enq_flags & (SCX_ENQ_BLOCKED | SCX_ENQ_REENQ)) == 490 (SCX_ENQ_BLOCKED | SCX_ENQ_REENQ)) { 491 taskc->force_local = false; 492 scx_bpf_dsq_insert(p, SHARED_DSQ, 0, enq_flags); 493 cid = cmask_next_and2_set_wrap(&taskc->cpus_allowed, 494 &qa.idle_cids.mask, 495 &qa.self_cids.mask, 0); 496 if (cid < scx_bpf_nr_cids()) 497 scx_bpf_kick_cid(cid, SCX_KICK_IDLE); 498 return; 499 } 500 501 /* 502 * Insert a blocked mutex donor at the head of an eligible local DSQ with 503 * a fresh slice and %SCX_ENQ_PREEMPT, requesting an immediate reschedule. 504 * The test above guarantees that cpus_allowed intersects self_cids, but 505 * the donor's current cid may have been delegated to a child. Search the 506 * intersection starting at the current cid, preserving it when qmap still 507 * holds it and wrapping to another eligible self cid otherwise. 508 * 509 * A self cid may be held exclusively with SCX_CAP_ENQ or time-shared with 510 * only SCX_CAP_ENQ_IMMED. Add needs_immed() so either kind can accept the 511 * local insertion instead of rejecting and reenqueuing the donor for a 512 * capability miss. Once selected, the core proxy-exec path can run the 513 * mutex owner using the donor's scheduling context. 514 * 515 * This policy is intentionally unfair and can strongly prioritize tasks 516 * using contended mutexes; scx_qmap is a demonstration scheduler and 517 * this behavior makes proxy-exec support easy to observe. 518 */ 519 if (enq_flags & SCX_ENQ_BLOCKED) { 520 cid = cmask_next_and_set_wrap(&taskc->cpus_allowed, 521 &qa.self_cids.mask, 522 scx_bpf_task_cid(p)); 523 if (cid >= scx_bpf_nr_cids()) { 524 /* self_cids may have changed since the intersection test */ 525 dispatch_to_rescue(p, taskc, enq_flags); 526 return; 527 } 528 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | cid, slice_ns, 529 enq_flags | needs_immed(cid) | SCX_ENQ_PREEMPT); 530 return; 531 } 532 533 /* 534 * Fault injection: deliberately dispatch one of our own tasks to a cid 535 * we don't hold. The inserts carry SCX_ENQ_RESCUE and divert to the 536 * kernel rescue path, a deterministic rescue-traffic generator. Under 537 * -B 0 the kernel cap check rejects and re-enqueues them instead, so 538 * nr_inject_attempts tracks nr_reenq_cap 1:1 and proves delivery-time 539 * enforcement. Throttled. 540 */ 541 if (qa.inject_mode == QMAP_INJ_WRONG_CID && p->nr_cpus_allowed > 1 && 542 !(enq_flags & SCX_ENQ_REENQ)) { 543 static u32 inj_cnt; 544 545 if (!(++inj_cnt % 64)) { 546 s32 bad = first_unavail_cid(); 547 548 if (bad >= 0 && cmask_test(bad, &taskc->cpus_allowed)) { 549 __sync_fetch_and_add(&qa.nr_inject_attempts, 1); 550 __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); 551 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | bad, slice_ns, 552 enq_flags | SCX_ENQ_RESCUE); 553 return; 554 } 555 } 556 } 557 558 /* 559 * IMMED stress testing: Every immed_stress_nth'th enqueue, dispatch 560 * directly to prev_cpu's local DSQ even when busy to force dsq->nr > 1 561 * and exercise the kernel IMMED reenqueue trigger paths. 562 */ 563 if (immed_stress_nth && !(enq_flags & SCX_ENQ_REENQ)) { 564 static u32 immed_stress_cnt; 565 566 if (!(++immed_stress_cnt % immed_stress_nth)) { 567 taskc->force_local = false; 568 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | scx_bpf_task_cid(p), 569 slice_ns, enq_flags); 570 return; 571 } 572 } 573 574 /* 575 * If qmap_select_cid() is telling us to or this is the last runnable 576 * task on the CPU, enqueue locally. 577 */ 578 if (taskc->force_local) { 579 taskc->force_local = false; 580 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, slice_ns, 581 enq_flags | needs_immed(scx_bpf_task_cid(p))); 582 return; 583 } 584 585 /* see lowpri_timerfn() */ 586 if (__COMPAT_has_generic_reenq() && 587 p->scx.weight < 2 && !(p->flags & PF_KTHREAD) && !(enq_flags & SCX_ENQ_REENQ)) { 588 scx_bpf_dsq_insert(p, LOWPRI_DSQ, slice_ns, enq_flags); 589 return; 590 } 591 592 /* if select_cid() wasn't called, try direct dispatch */ 593 if (!__COMPAT_is_enq_cpu_selected(enq_flags) && 594 (cid = pick_direct_dispatch_cid(p, scx_bpf_task_cid(p), taskc)) >= 0) { 595 __sync_fetch_and_add(&qa.nr_ddsp_from_enq, 1); 596 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | cid, slice_ns, 597 enq_flags | needs_immed(cid)); 598 return; 599 } 600 601 /* 602 * If the task was re-enqueued due to the CPU being preempted by a 603 * higher priority scheduling class, just re-enqueue the task directly 604 * on the global DSQ. As we want another CPU to pick it up, find and 605 * kick an idle cid. 606 */ 607 if (enq_flags & SCX_ENQ_REENQ) { 608 s32 cid; 609 610 scx_bpf_dsq_insert(p, SHARED_DSQ, 0, enq_flags); 611 cid = cmask_next_and2_set_wrap(&taskc->cpus_allowed, 612 &qa.idle_cids.mask, 613 &qa.usable_cids.mask, 0); 614 if (cid < scx_bpf_nr_cids()) 615 scx_bpf_kick_cid(cid, SCX_KICK_IDLE); 616 return; 617 } 618 619 /* Queue on the selected FIFO. */ 620 qmap_fifo_enqueue(&qa.fifos[idx], taskc); 621 622 if (highpri_boosting && p->scx.weight >= HIGHPRI_WEIGHT) { 623 taskc->highpri = true; 624 __sync_fetch_and_add(&qa.nr_highpri_queued, 1); 625 } 626 __sync_fetch_and_add(&qa.nr_enqueued, 1); 627 } 628 629 void BPF_STRUCT_OPS(qmap_dequeue, struct task_struct *p, u64 deq_flags) 630 { 631 task_ctx_t *taskc; 632 633 __sync_fetch_and_add(&qa.nr_dequeued, 1); 634 if (deq_flags & SCX_DEQ_CORE_SCHED_EXEC) 635 __sync_fetch_and_add(&qa.nr_core_sched_execed, 1); 636 637 taskc = lookup_task_ctx(p); 638 if (taskc && taskc->fifo) { 639 if (taskc->highpri) 640 __sync_fetch_and_sub(&qa.nr_highpri_queued, 1); 641 qmap_fifo_remove(taskc); 642 } 643 } 644 645 static void update_core_sched_head_seq(struct task_struct *p) 646 { 647 int idx = weight_to_idx(p->scx.weight); 648 task_ctx_t *taskc; 649 650 if ((taskc = lookup_task_ctx(p))) 651 qa.core_sched_head_seqs[idx] = taskc->core_sched_seq; 652 } 653 654 /* 655 * Send @p, stranded on the DSQ @it iterates, to the kernel rescue path. To 656 * exercise both kfuncs, move tasks with an even pid there with 657 * scx_bpf_dsq_move() and re-enqueue the others with scx_bpf_dsq_reenq_task(), 658 * which takes them through qmap_enqueue()'s rescue. The choice is per task so 659 * that a marked task isn't moved by another scan before the re-enqueue reaches 660 * it. 661 */ 662 static __always_inline void rescue_stranded(struct bpf_iter_scx_dsq *it, 663 struct task_struct *p, task_ctx_t *taskc) 664 { 665 s32 c; 666 667 if (p->pid & 1) { 668 scx_bpf_dsq_reenq_task(it, p); 669 return; 670 } 671 672 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0); 673 if (c >= 0 && c < scx_bpf_nr_cids()) { 674 __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); 675 scx_bpf_dsq_move(it, p, SCX_DSQ_LOCAL_ON | c, SCX_ENQ_RESCUE); 676 } 677 } 678 679 /* 680 * One pass over SHARED_DSQ: rescue stranded tasks and boost highpri ones. A 681 * task whose cids were lost while it was queued in the fifos would strand on 682 * SHARED_DSQ, which is consumed only on self cids it can't run on - send it to 683 * the kernel rescue path. One whose cids were lost after the highpri cull is 684 * likewise rescued out of HIGHPRI_DSQ below. 685 * 686 * To demonstrate the use of scx_bpf_dsq_move(), implement silly selective 687 * priority boosting mechanism by moving highpri tasks to HIGHPRI_DSQ and then 688 * consuming them first. This makes minor difference only when dsp_batch is 689 * larger than 1. 690 * 691 * scx_bpf_dsq_move[_vtime]() and scx_bpf_dsq_reenq_task() are allowed both from 692 * ops.dispatch() and non-rq-lock holding BPF programs. As demonstration, this 693 * function is called from qmap_dispatch() and monitor_timerfn(). 694 */ 695 static bool scan_shared_dsq(bool from_timer) 696 { 697 struct task_struct *p; 698 s32 this_cid = scx_bpf_this_cid(); 699 u32 nr_cids = scx_bpf_nr_cids(); 700 701 /* rescue strands and move highpri tasks to HIGHPRI_DSQ */ 702 bpf_for_each(scx_dsq, p, SHARED_DSQ, 0) { 703 static u64 highpri_seq; 704 task_ctx_t *taskc; 705 706 if (!(taskc = lookup_task_ctx(p))) 707 return false; 708 709 /* stranded? rescue - it can't be dispatched here either way */ 710 if (!cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) { 711 rescue_stranded(BPF_FOR_EACH_ITER, p, taskc); 712 continue; 713 } 714 715 if (taskc->highpri) { 716 /* exercise the set_*() and vtime interface too */ 717 scx_bpf_dsq_move_set_slice(BPF_FOR_EACH_ITER, slice_ns * 2); 718 scx_bpf_dsq_move_set_vtime(BPF_FOR_EACH_ITER, highpri_seq++); 719 scx_bpf_dsq_move_vtime(BPF_FOR_EACH_ITER, p, HIGHPRI_DSQ, 0); 720 } 721 } 722 723 /* 724 * Scan HIGHPRI_DSQ and dispatch until a task that can run here is 725 * found. Prefer this_cid if the task allows it; otherwise RR-scan the 726 * task's cpus_allowed starting after this_cid. 727 */ 728 bpf_for_each(scx_dsq, p, HIGHPRI_DSQ, 0) { 729 task_ctx_t *taskc; 730 bool dispatched = false; 731 s32 cid; 732 733 if (!(taskc = lookup_task_ctx(p))) 734 return false; 735 736 /* only run highpri tasks on cids this node can use right now */ 737 if (cmask_test(this_cid, &taskc->cpus_allowed) && 738 cmask_test(this_cid, &qa.usable_cids.mask)) 739 cid = this_cid; 740 else 741 cid = cmask_next_and_set_wrap(&taskc->cpus_allowed, 742 &qa.usable_cids.mask, 743 this_cid + 1); 744 if (cid >= nr_cids) { 745 /* self cids lack caps in effect yet, leave it queued */ 746 if (cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) 747 continue; 748 749 /* stranded after the cull - rescue it from here */ 750 rescue_stranded(BPF_FOR_EACH_ITER, p, taskc); 751 continue; 752 } 753 754 if (scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | cid, 755 SCX_ENQ_PREEMPT | needs_immed(cid))) { 756 if (cid == this_cid) { 757 dispatched = true; 758 __sync_fetch_and_add(&qa.nr_expedited_local, 1); 759 } else { 760 __sync_fetch_and_add(&qa.nr_expedited_remote, 1); 761 } 762 if (from_timer) 763 __sync_fetch_and_add(&qa.nr_expedited_from_timer, 1); 764 } else { 765 __sync_fetch_and_add(&qa.nr_expedited_lost, 1); 766 } 767 768 if (dispatched) 769 return true; 770 } 771 772 return false; 773 } 774 775 void BPF_STRUCT_OPS(qmap_dispatch, s32 cid, struct task_struct *prev) 776 { 777 struct task_struct *p; 778 struct cpu_ctx __arena *cpuc; 779 task_ctx_t *taskc; 780 u32 batch = dsp_batch ?: 1; 781 s32 owner, i; 782 783 if (scan_shared_dsq(false)) 784 return; 785 786 /* 787 * Sub-sched routing: a child-owned cid goes to its owner. Never run 788 * this node's own tasks on a delegated cid. Read without the guard. 789 */ 790 owner = qa.part.cid_owner[cid]; 791 if (owner == CID_SHARED) { 792 /* route to the live rr holder (0 = self, runs below) */ 793 s32 pos = qa.part.rr_pos; 794 u64 holder_cgid = (pos >= 0 && pos < MAX_PARTS) ? 795 qa.part.rr_slots[pos] : 0; 796 797 if (holder_cgid) { 798 scx_bpf_sub_dispatch(holder_cgid); 799 return; 800 } 801 } else if (owner >= 0 && owner < MAX_SUB_SCHEDS) { 802 u64 cgid = qa.sub_sched_ctxs[owner].cgroup_id; 803 804 if (cgid) { 805 if (scx_bpf_sub_dispatch(cgid)) 806 __sync_fetch_and_add(&qa.sub_sched_ctxs[owner].nr_dsps, 1); 807 return; 808 } 809 } 810 811 if (!qa.nr_highpri_queued && scx_bpf_dsq_move_to_local(SHARED_DSQ, needs_immed(cid))) 812 return; 813 814 if (dsp_inf_loop_after && qa.nr_dispatched > dsp_inf_loop_after) { 815 /* 816 * PID 2 should be kthreadd which should mostly be idle and off 817 * the scheduler. Let's keep dispatching it to force the kernel 818 * to call this function over and over again. 819 */ 820 p = bpf_task_from_pid(2); 821 if (p) { 822 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, slice_ns, 0); 823 bpf_task_release(p); 824 return; 825 } 826 } 827 828 cpuc = &qa.cpu_ctxs[scx_bpf_this_cid()]; 829 830 for (i = 0; i < 5; i++) { 831 /* Advance the dispatch cursor and pick the fifo. */ 832 if (!cpuc->dsp_cnt) { 833 cpuc->dsp_idx = (cpuc->dsp_idx + 1) % 5; 834 cpuc->dsp_cnt = 1 << cpuc->dsp_idx; 835 } 836 837 /* Dispatch or advance. */ 838 bpf_repeat(BPF_MAX_LOOPS) { 839 task_ctx_t *taskc; 840 841 taskc = qmap_fifo_pop(&qa.fifos[cpuc->dsp_idx]); 842 if (!taskc) 843 break; 844 845 p = scx_bpf_tid_to_task(taskc->tid); 846 if (!p) 847 continue; 848 849 if (taskc->highpri) 850 __sync_fetch_and_sub(&qa.nr_highpri_queued, 1); 851 852 update_core_sched_head_seq(p); 853 __sync_fetch_and_add(&qa.nr_dispatched, 1); 854 855 scx_bpf_dsq_insert(p, SHARED_DSQ, slice_ns, 0); 856 857 /* 858 * scx_qmap uses a global BPF queue that any CPU's 859 * dispatch can pop from. If this CPU popped a task that 860 * can't run here, it gets stranded on SHARED_DSQ after 861 * consume_dispatch_q() skips it. Kick the task's home 862 * CPU so it drains SHARED_DSQ. 863 * 864 * There's a race between the pop and the flush of the 865 * buffered dsq_insert: 866 * 867 * CPU 0 (dispatching) CPU 1 (home, idle) 868 * ~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~ 869 * pop from BPF queue 870 * dsq_insert(buffered) 871 * balance: 872 * SHARED_DSQ empty 873 * BPF queue empty 874 * -> goes idle 875 * flush -> on SHARED 876 * kick CPU 1 877 * wakes, drains task 878 * 879 * The kick prevents indefinite stalls but a per-CPU 880 * kthread like ksoftirqd can be briefly stranded when 881 * its home CPU enters idle with softirq pending, 882 * triggering: 883 * 884 * "NOHZ tick-stop error: local softirq work is pending, handler #N!!!" 885 * 886 * from report_idle_softirq(). The kick lands shortly 887 * after and the home CPU drains the task. This could be 888 * avoided by e.g. dispatching pinned tasks to local or 889 * global DSQs, but the current code is left as-is to 890 * document this class of issue -- other schedulers 891 * seeing similar warnings can use this as a reference. 892 */ 893 if (!cmask_test(cid, &taskc->cpus_allowed)) 894 scx_bpf_kick_cid(scx_bpf_task_cid(p), 0); 895 batch--; 896 cpuc->dsp_cnt--; 897 if (!batch || !scx_bpf_dispatch_nr_slots()) { 898 if (scan_shared_dsq(false) || 899 scx_bpf_dsq_move_to_local(SHARED_DSQ, needs_immed(cid))) 900 return; 901 goto prev; 902 } 903 if (!cpuc->dsp_cnt) 904 break; 905 } 906 907 cpuc->dsp_cnt = 0; 908 } 909 910 if (scan_shared_dsq(false)) 911 return; 912 prev: 913 /* 914 * No other tasks. @prev will keep running. Update its core_sched_seq as 915 * if the task were enqueued and dispatched immediately. 916 * 917 * No @prev to keep running means the CPU goes idle. If its claim was 918 * never used, that is not a transition and ops.update_idle() stays 919 * silent. Restore the claim here. 920 */ 921 if (prev) { 922 taskc = lookup_task_ctx(prev); 923 if (!taskc) 924 return; 925 926 taskc->core_sched_seq = 927 qa.core_sched_tail_seqs[weight_to_idx(prev->scx.weight)]++; 928 } else { 929 cmask_set(cid, &qa.idle_cids.mask); 930 } 931 } 932 933 void BPF_STRUCT_OPS(qmap_tick, struct task_struct *p) 934 { 935 struct cpu_ctx __arena *cpuc = &qa.cpu_ctxs[scx_bpf_this_cid()]; 936 int idx; 937 938 /* 939 * Use the running avg of weights to select the target cpuperf level. 940 * This is a demonstration of the cpuperf feature rather than a 941 * practical strategy to regulate CPU frequency. 942 */ 943 cpuc->avg_weight = cpuc->avg_weight * 3 / 4 + p->scx.weight / 4; 944 idx = weight_to_idx(cpuc->avg_weight); 945 cpuc->cpuperf_target = qidx_to_cpuperf_target[idx]; 946 947 scx_bpf_cidperf_set(scx_bpf_task_cid(p), cpuc->cpuperf_target); 948 } 949 950 /* 951 * The distance from the head of the queue scaled by the weight of the queue. 952 * The lower the number, the older the task and the higher the priority. 953 */ 954 static s64 task_qdist(struct task_struct *p, task_ctx_t *taskc) 955 { 956 int idx = weight_to_idx(p->scx.weight); 957 s64 qdist; 958 959 qdist = taskc->core_sched_seq - qa.core_sched_head_seqs[idx]; 960 961 /* 962 * As queue index increments, the priority doubles. The queue w/ index 3 963 * is dispatched twice more frequently than 2. Reflect the difference by 964 * scaling qdists accordingly. Note that the shift amount needs to be 965 * flipped depending on the sign to avoid flipping priority direction. 966 */ 967 if (qdist >= 0) 968 return qdist << (4 - idx); 969 else 970 return qdist << idx; 971 } 972 973 /* 974 * This is called to determine the task ordering when core-sched is picking 975 * tasks to execute on SMT siblings and should encode about the same ordering as 976 * the regular scheduling path. Use the priority-scaled distances from the head 977 * of the queues to compare the two tasks which should be consistent with the 978 * dispatch path behavior. 979 */ 980 bool BPF_STRUCT_OPS(qmap_core_sched_before, 981 struct task_struct *a, struct task_struct *b) 982 { 983 task_ctx_t *taskc_a = lookup_task_ctx(a); 984 task_ctx_t *taskc_b = lookup_task_ctx(b); 985 986 /* 987 * A task delegated to a sub-scheduler has no task_ctx here. Order such 988 * pairs by the kernel's default ordering - a running task after every 989 * waiting task, then by runnable_at. 990 */ 991 if (!taskc_a || !taskc_b) { 992 if (a->on_cpu != b->on_cpu) 993 return b->on_cpu; 994 return time_before(a->scx.runnable_at, b->scx.runnable_at); 995 } 996 997 return task_qdist(a, taskc_a) < task_qdist(b, taskc_b); 998 } 999 1000 /* 1001 * sched_switch tracepoint and cpu_release handlers are no longer needed. 1002 * With SCX_OPS_ALWAYS_ENQ_IMMED, wakeup_preempt_scx() reenqueues IMMED 1003 * tasks when a higher-priority scheduling class takes the CPU. 1004 */ 1005 1006 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init_task, struct task_struct *p, 1007 struct scx_init_task_args *args) 1008 { 1009 struct task_ctx_stor_val *v; 1010 task_ctx_t *taskc; 1011 1012 if (qa.inject_mode == QMAP_INJ_INIT_FAIL && 1013 !bpf_strncmp(p->comm, 6, "qmfail")) 1014 return -ENOMEM; 1015 1016 if (p->tgid == disallow_tgid) 1017 p->scx.disallow = true; 1018 1019 /* pop a slab entry off the free list */ 1020 if (qmap_spin_lock(&qa_task_lock)) 1021 return -EBUSY; 1022 taskc = qa.task_free_head; 1023 if (taskc) 1024 qa.task_free_head = taskc->next_free; 1025 bpf_res_spin_unlock(&qa_task_lock); 1026 if (!taskc) { 1027 scx_bpf_error("task_ctx slab exhausted (max_tasks=%u)", max_tasks); 1028 return -ENOMEM; 1029 } 1030 1031 taskc->next_free = NULL; 1032 taskc->q_next = NULL; 1033 taskc->q_prev = NULL; 1034 taskc->fifo = NULL; 1035 taskc->tid = p->scx.tid; 1036 taskc->pid = p->pid; 1037 taskc->force_local = false; 1038 taskc->highpri = false; 1039 taskc->core_sched_seq = 0; 1040 cmask_init(&taskc->cpus_allowed, 0, scx_bpf_nr_cids()); 1041 1042 v = bpf_task_storage_get(&task_ctx_stor, p, NULL, 1043 BPF_LOCAL_STORAGE_GET_F_CREATE); 1044 if (!v) { 1045 /* push back to the free list */ 1046 if (!qmap_spin_lock(&qa_task_lock)) { 1047 taskc->next_free = qa.task_free_head; 1048 qa.task_free_head = taskc; 1049 bpf_res_spin_unlock(&qa_task_lock); 1050 } 1051 return -ENOMEM; 1052 } 1053 v->taskc = taskc; 1054 return 0; 1055 } 1056 1057 void BPF_STRUCT_OPS(qmap_exit_task, struct task_struct *p, 1058 struct scx_exit_task_args *args) 1059 { 1060 struct task_ctx_stor_val *v; 1061 task_ctx_t *taskc; 1062 1063 v = bpf_task_storage_get(&task_ctx_stor, p, NULL, 0); 1064 if (!v || !v->taskc) 1065 return; 1066 taskc = v->taskc; 1067 v->taskc = NULL; 1068 1069 if (qmap_spin_lock(&qa_task_lock)) 1070 return; 1071 taskc->next_free = qa.task_free_head; 1072 qa.task_free_head = taskc; 1073 bpf_res_spin_unlock(&qa_task_lock); 1074 } 1075 1076 void BPF_STRUCT_OPS(qmap_dump, struct scx_dump_ctx *dctx) 1077 { 1078 task_ctx_t *taskc; 1079 s32 i; 1080 1081 QMAP_TOUCH_ARENA(); 1082 1083 if (suppress_dump) 1084 return; 1085 1086 /* 1087 * Walk the queue lists without locking - kfunc calls (scx_bpf_dump) 1088 * aren't in the verifier's kfunc_spin_allowed() list so we can't hold 1089 * a lock and dump. Best-effort; racing may print stale tids but the 1090 * walk is bounded by bpf_repeat() so it always terminates. 1091 */ 1092 bpf_for(i, 0, 5) { 1093 scx_bpf_dump("QMAP FIFO[%d]:", i); 1094 taskc = qa.fifos[i].head; 1095 bpf_repeat(4096) { 1096 if (!taskc) 1097 break; 1098 scx_bpf_dump(" %d:%llu", taskc->pid, taskc->tid); 1099 taskc = taskc->q_next; 1100 } 1101 scx_bpf_dump("\n"); 1102 } 1103 } 1104 1105 void BPF_STRUCT_OPS(qmap_dump_cid, struct scx_dump_ctx *dctx, s32 cid, bool idle) 1106 { 1107 struct cpu_ctx __arena *cpuc = &qa.cpu_ctxs[cid]; 1108 1109 if (suppress_dump || idle) 1110 return; 1111 1112 scx_bpf_dump("QMAP: dsp_idx=%llu dsp_cnt=%llu avg_weight=%u cpuperf_target=%u", 1113 cpuc->dsp_idx, cpuc->dsp_cnt, cpuc->avg_weight, 1114 cpuc->cpuperf_target); 1115 } 1116 1117 void BPF_STRUCT_OPS(qmap_dump_task, struct scx_dump_ctx *dctx, struct task_struct *p) 1118 { 1119 struct task_ctx_stor_val *v; 1120 task_ctx_t *taskc; 1121 1122 QMAP_TOUCH_ARENA(); 1123 1124 if (suppress_dump) 1125 return; 1126 v = bpf_task_storage_get(&task_ctx_stor, p, NULL, 0); 1127 if (!v || !v->taskc) 1128 return; 1129 taskc = v->taskc; 1130 1131 scx_bpf_dump("QMAP: force_local=%d core_sched_seq=%llu", 1132 taskc->force_local, taskc->core_sched_seq); 1133 } 1134 1135 s32 BPF_STRUCT_OPS(qmap_cpuctl_init, struct cgroup *cgrp, struct scx_cgroup_init_args *args) 1136 { 1137 QMAP_TOUCH_ARENA(); 1138 1139 if (print_msgs) 1140 bpf_printk("CGRP INIT %llu weight=%u period=%lu quota=%ld burst=%lu", 1141 cgrp->kn->id, args->weight, args->bw_period_us, 1142 args->bw_quota_us, args->bw_burst_us); 1143 1144 if (qa.inject_mode == QMAP_INJ_CGRP_INIT_FAIL) { 1145 char name[7] = {}; 1146 1147 bpf_probe_read_kernel_str(name, sizeof(name), cgrp->kn->name); 1148 if (!bpf_strncmp(name, 6, "qmfail")) 1149 return -ENOMEM; 1150 } 1151 1152 return 0; 1153 } 1154 1155 static void redistribute(void); 1156 1157 void BPF_STRUCT_OPS(qmap_cpuctl_set_weight, struct cgroup *cgrp, u32 weight) 1158 { 1159 u64 cgid = cgrp->kn->id; 1160 s32 i; 1161 1162 QMAP_TOUCH_ARENA(); 1163 1164 if (print_msgs) 1165 bpf_printk("CGRP SET %llu weight=%u", cgid, weight); 1166 1167 /* 1168 * Knobs belong to the parent, so this op carries the child subs' 1169 * attach point weights. Adjust the matching sub's share of the cid 1170 * partition. Other cgroups don't participate in the split. 1171 */ 1172 for (i = 0; i < MAX_SUB_SCHEDS; i++) { 1173 if (qa.sub_sched_ctxs[i].cgroup_id != cgid) 1174 continue; 1175 if (qa.sub_sched_ctxs[i].weight != weight) { 1176 qa.sub_sched_ctxs[i].weight = weight; 1177 redistribute(); 1178 } 1179 break; 1180 } 1181 } 1182 1183 void BPF_STRUCT_OPS(qmap_cpuctl_set_bandwidth, struct cgroup *cgrp, u64 period_us, 1184 u64 quota_us, u64 burst_us) 1185 { 1186 if (print_msgs) 1187 bpf_printk("CGRP SET %llu period=%lu quota=%ld burst=%lu", 1188 cgrp->kn->id, period_us, quota_us, burst_us); 1189 } 1190 1191 void BPF_STRUCT_OPS(qmap_cpuctl_move, struct task_struct *p, struct cgroup *from, 1192 struct cgroup *to) 1193 { 1194 if (print_msgs) 1195 bpf_printk("CGRP MOVE %d %llu -> %llu", 1196 p->pid, from->kn->id, to->kn->id); 1197 } 1198 1199 void BPF_STRUCT_OPS(qmap_update_idle, s32 cid, bool idle) 1200 { 1201 QMAP_TOUCH_ARENA(); 1202 1203 /* 1204 * The kernel delivers update_idle() for every cid this node holds 1205 * SCX_CAP_BASE on. Track every cid's idle state regardless of 1206 * delegation: the direct-dispatch pick masks idle_cids with usable_cids 1207 * at selection, so a cid already idle when it returns to self needs no 1208 * reseed here. 1209 */ 1210 if (idle) 1211 cmask_set(cid, &qa.idle_cids.mask); 1212 else 1213 cmask_clear(cid, &qa.idle_cids.mask); 1214 } 1215 1216 void BPF_STRUCT_OPS(qmap_set_cmask, struct task_struct *p, 1217 const struct scx_cmask *cmask_in) 1218 { 1219 struct scx_cmask __arena *cmask = (struct scx_cmask __arena *)(long)cmask_in; 1220 task_ctx_t *taskc; 1221 1222 taskc = lookup_task_ctx(p); 1223 if (!taskc) 1224 return; 1225 cmask_copy(&taskc->cpus_allowed, cmask); 1226 } 1227 1228 struct monitor_timer { 1229 struct bpf_timer timer; 1230 }; 1231 1232 struct { 1233 __uint(type, BPF_MAP_TYPE_ARRAY); 1234 __uint(max_entries, 1); 1235 __type(key, u32); 1236 __type(value, struct monitor_timer); 1237 } monitor_timer SEC(".maps"); 1238 1239 /* 1240 * Aggregate cidperf across the first nr_online_cids cids. Post-hotplug 1241 * the first-N-are-online invariant drifts, so some cap/cur values may 1242 * be stale. For this demo monitor that's fine; the scheduler exits on 1243 * the enable-time hotplug_seq mismatch and userspace restarts, which 1244 * rebuilds the layout. 1245 */ 1246 static void monitor_cpuperf(void) 1247 { 1248 u32 nr_online = scx_bpf_nr_online_cids(); 1249 u64 cap_sum = 0, cur_sum = 0, cur_min = SCX_CPUPERF_ONE, cur_max = 0; 1250 u64 target_sum = 0, target_min = SCX_CPUPERF_ONE, target_max = 0; 1251 s32 cid; 1252 1253 QMAP_TOUCH_ARENA(); 1254 1255 bpf_for(cid, 0, nr_online) { 1256 struct cpu_ctx __arena *cpuc = &qa.cpu_ctxs[cid]; 1257 u32 cap = scx_bpf_cidperf_cap(cid); 1258 u32 cur = scx_bpf_cidperf_cur(cid); 1259 u32 target; 1260 1261 cur_min = cur < cur_min ? cur : cur_min; 1262 cur_max = cur > cur_max ? cur : cur_max; 1263 1264 cur_sum += (u64)cur * cap / SCX_CPUPERF_ONE; 1265 cap_sum += cap; 1266 1267 target = cpuc->cpuperf_target; 1268 target_sum += target; 1269 target_min = target < target_min ? target : target_min; 1270 target_max = target > target_max ? target : target_max; 1271 } 1272 1273 if (!nr_online || !cap_sum) 1274 return; 1275 1276 qa.cpuperf_min = cur_min; 1277 qa.cpuperf_avg = cur_sum * SCX_CPUPERF_ONE / cap_sum; 1278 qa.cpuperf_max = cur_max; 1279 1280 qa.cpuperf_target_min = target_min; 1281 qa.cpuperf_target_avg = target_sum / nr_online; 1282 qa.cpuperf_target_max = target_max; 1283 } 1284 1285 /* 1286 * Dump the currently queued tasks in the shared DSQ to demonstrate the usage of 1287 * scx_bpf_dsq_nr_queued() and DSQ iterator. Raise the dispatch batch count to 1288 * see meaningful dumps in the trace pipe. 1289 */ 1290 static void dump_shared_dsq(void) 1291 { 1292 struct task_struct *p; 1293 s32 nr; 1294 1295 if (!(nr = scx_bpf_dsq_nr_queued(SHARED_DSQ))) 1296 return; 1297 1298 bpf_printk("Dumping %d tasks in SHARED_DSQ in reverse order", nr); 1299 1300 bpf_rcu_read_lock(); 1301 bpf_for_each(scx_dsq, p, SHARED_DSQ, SCX_DSQ_ITER_REV) 1302 bpf_printk("%s[%d]", p->comm, p->pid); 1303 bpf_rcu_read_unlock(); 1304 } 1305 1306 static int monitor_timerfn(void *map, int *key, struct bpf_timer *timer) 1307 { 1308 bpf_rcu_read_lock(); 1309 scan_shared_dsq(true); 1310 bpf_rcu_read_unlock(); 1311 1312 monitor_cpuperf(); 1313 1314 if (print_dsqs_and_events) { 1315 struct scx_event_stats events; 1316 1317 dump_shared_dsq(); 1318 1319 __COMPAT_scx_bpf_events(&events, sizeof(events)); 1320 1321 bpf_printk("%35s: %lld", "SCX_EV_SELECT_CPU_FALLBACK", 1322 scx_read_event(&events, SCX_EV_SELECT_CPU_FALLBACK)); 1323 bpf_printk("%35s: %lld", "SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE", 1324 scx_read_event(&events, SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE)); 1325 bpf_printk("%35s: %lld", "SCX_EV_DISPATCH_KEEP_LAST", 1326 scx_read_event(&events, SCX_EV_DISPATCH_KEEP_LAST)); 1327 bpf_printk("%35s: %lld", "SCX_EV_ENQ_SKIP_EXITING", 1328 scx_read_event(&events, SCX_EV_ENQ_SKIP_EXITING)); 1329 bpf_printk("%35s: %lld", "SCX_EV_REFILL_SLICE_DFL", 1330 scx_read_event(&events, SCX_EV_REFILL_SLICE_DFL)); 1331 bpf_printk("%35s: %lld", "SCX_EV_BYPASS_DURATION", 1332 scx_read_event(&events, SCX_EV_BYPASS_DURATION)); 1333 bpf_printk("%35s: %lld", "SCX_EV_BYPASS_DISPATCH", 1334 scx_read_event(&events, SCX_EV_BYPASS_DISPATCH)); 1335 bpf_printk("%35s: %lld", "SCX_EV_BYPASS_ACTIVATE", 1336 scx_read_event(&events, SCX_EV_BYPASS_ACTIVATE)); 1337 } 1338 1339 if (bpf_timer_start(timer, ONE_SEC_IN_NS, 0)) 1340 scx_bpf_error("failed to re-arm stats timer"); 1341 return 0; 1342 } 1343 1344 struct lowpri_timer { 1345 struct bpf_timer timer; 1346 }; 1347 1348 struct { 1349 __uint(type, BPF_MAP_TYPE_ARRAY); 1350 __uint(max_entries, 1); 1351 __type(key, u32); 1352 __type(value, struct lowpri_timer); 1353 } lowpri_timer SEC(".maps"); 1354 1355 /* 1356 * Nice 19 tasks are put into the lowpri DSQ. Every 10ms, reenq is triggered and 1357 * the tasks are transferred to SHARED_DSQ. 1358 */ 1359 static int lowpri_timerfn(void *map, int *key, struct bpf_timer *timer) 1360 { 1361 scx_bpf_dsq_reenq(LOWPRI_DSQ, 0); 1362 if (bpf_timer_start(timer, LOWPRI_INTV_NS, 0)) 1363 scx_bpf_error("failed to re-arm lowpri timer"); 1364 return 0; 1365 } 1366 1367 struct round_robin_timer { 1368 struct bpf_timer timer; 1369 }; 1370 1371 struct { 1372 __uint(type, BPF_MAP_TYPE_ARRAY); 1373 __uint(max_entries, 1); 1374 __type(key, u32); 1375 __type(value, struct round_robin_timer); 1376 } round_robin_timer SEC(".maps"); 1377 1378 enum part_pending_flags { 1379 PART_REFRESH = BIT_U64(0), 1380 PART_REDISTRIBUTE = BIT_U64(1), 1381 }; 1382 1383 /* 1384 * Partition update synchronization. qa.part can be written from concurrent 1385 * contexts. This single-runner guard admits one writer at a time without 1386 * holding a lock across the grant/revoke kfuncs. part_pending coalesces 1387 * refresh and repartition requests that arrive while it is held. 1388 * 1389 * They live in .bss, not the arena: rr_advance() runs from a bpf_timer 1390 * callback, where the verifier rejects atomic ops on arena memory. 1391 */ 1392 static u64 part_busy; 1393 static u64 part_pending; 1394 1395 static bool part_try_start(void) 1396 { 1397 /* set busy, report whether it was previously clear (we acquired it) */ 1398 return !__sync_fetch_and_or(&part_busy, 1); 1399 } 1400 1401 static void part_end(void) 1402 { 1403 __sync_fetch_and_and(&part_busy, 0); 1404 } 1405 1406 /* 1407 * compute_partition() scratch. 1408 * 1409 * The excl-held cids are handed out in cid order: position 0..nr_excl-1 over 1410 * the held cids is split into contiguous ranges, one per participant that gets 1411 * at least one excl cid. Range k is owned by cp_range_owner[k] and ends at the 1412 * cumulative position cp_range_end[k]. 1413 */ 1414 static s32 cp_range_owner[MAX_PARTS]; /* exclusive range k: its owner id ... */ 1415 static s32 cp_range_end[MAX_PARTS]; /* ... and the cumulative position it ends at */ 1416 1417 /* a participant in the partition: self or an attached child */ 1418 struct participant { 1419 s32 slot; /* child slot, or CID_SELF */ 1420 u32 weight; /* cpu.weight */ 1421 }; 1422 1423 /** 1424 * place_one - assign one excl-held cid to its owner 1425 * @cid: the excl-held cid to place 1426 * @n: its position among the excl-held cids, in [0, nr_excl) 1427 * @total_excl: how many positions are owned exclusively (the rest are shared) 1428 * 1429 * Position @n below @total_excl is owned exclusively. It falls in the range 1430 * whose cumulative end it is under, owned by cp_range_owner[]. A position at or 1431 * above @total_excl is the rounding leftover which joins the shared pool. 1432 * 1433 * A separate __noinline function to help verification. 1434 */ 1435 __noinline int place_one(s32 cid, s32 n, s32 total_excl) 1436 { 1437 s32 owner = CID_SELF, i, s; 1438 1439 if (cid < 0 || cid >= SCX_QMAP_MAX_CPUS || n < 0 || n >= SCX_QMAP_MAX_CPUS || 1440 total_excl < 0) { 1441 scx_bpf_error("-ERANGE"); 1442 return 0; 1443 } 1444 1445 if (n < total_excl) { 1446 for (i = 0; i < MAX_PARTS; i++) { 1447 if (n < cp_range_end[i]) { 1448 owner = cp_range_owner[i]; 1449 break; 1450 } 1451 } 1452 qa.part.cid_owner[cid] = owner; 1453 } else { 1454 s = n - total_excl; 1455 if (s < 0 || s >= MAX_PARTS) { 1456 scx_bpf_error("-ERANGE"); 1457 return 0; 1458 } 1459 qa.part.shared_cids[s] = cid; 1460 /* time-shared: dispatch resolves the live holder via rr_pos */ 1461 qa.part.cid_owner[cid] = CID_SHARED; 1462 } 1463 return 0; 1464 } 1465 1466 /** 1467 * compute_partition - build the cid partition from this node's held caps 1468 * 1469 * Decide each cid's owner, the shared pool and the rr rotation. __noinline to 1470 * help verification. See the comment at the top of the file. 1471 */ 1472 __noinline void compute_partition(void) 1473 { 1474 s32 nr_cids = qa.nr_cids; 1475 s32 nr_excl, total_excl = 0, nr_rr = 0; 1476 s32 sum_w, i, cid, n = 0, share, self_w; 1477 u64 cgid_snap[MAX_SUB_SCHEDS]; 1478 s32 w_snap[MAX_SUB_SCHEDS]; 1479 1480 if (nr_cids > SCX_QMAP_MAX_CPUS) { 1481 scx_bpf_error("-ERANGE"); 1482 return; 1483 } 1484 1485 /* find out the cids we hold */ 1486 scx_bpf_sub_caps(0, SCX_CAP_ENQ, &qa.held_excl.mask); 1487 scx_bpf_sub_caps(0, SCX_CAP_ENQ_IMMED, &qa.held_shared.mask); 1488 /* held only as ENQ_IMMED */ 1489 cmask_andnot(&qa.held_shared.mask, &qa.held_shared.mask, &qa.held_excl.mask); 1490 1491 qa.part.nr_shared = 0; 1492 qa.part.nr_rr = 0; 1493 qa.part.rr_pos = 0; 1494 1495 nr_excl = cmask_weight(&qa.held_excl.mask); 1496 qa.part.nr_excl = nr_excl; 1497 1498 /* no excl cid: held_shared stays self-local, the rest unheld */ 1499 if (!nr_excl) { 1500 bpf_for(cid, 0, nr_cids) { 1501 if (cmask_test(cid, &qa.held_shared.mask)) 1502 qa.part.cid_owner[cid] = CID_SELF; 1503 else 1504 qa.part.cid_owner[cid] = CID_NONE; 1505 } 1506 return; 1507 } 1508 1509 /* 1510 * Snapshot membership and weights so the sum_w and share loops agree. A 1511 * mid-compute change would otherwise wrap nr_shared negative. The self 1512 * weight is fixed at the default: a cgroup's weight is its parent's 1513 * knob, not the scheduler's own business. 1514 */ 1515 self_w = 100; 1516 bpf_for(i, 0, MAX_SUB_SCHEDS) { 1517 cgid_snap[i] = qa.sub_sched_ctxs[i].cgroup_id; 1518 w_snap[i] = cgid_snap[i] ? (qa.sub_sched_ctxs[i].weight ?: 100) : 0; 1519 } 1520 1521 /* 1522 * Participants are self plus each child. Give each a fixed range/rr 1523 * slot: self at slot 0, child i at slot i+1. 1524 * 1525 * sum_w totals every participant's weight. 1526 */ 1527 sum_w = self_w; 1528 bpf_for(i, 0, MAX_SUB_SCHEDS) { 1529 barrier_var(sum_w); 1530 sum_w += w_snap[i]; 1531 } 1532 1533 /* 1534 * Split [0, nr_excl) into one contiguous range per participant, each 1535 * the floor of its weight share. cp_range_owner[]/cp_range_end[] record 1536 * each range's owner and cumulative end, total_excl counts the 1537 * exclusive slots, and the rest (nr_excl - total_excl) are shared. 1538 * rr_slots[] lists every participant for the round-robin. 1539 */ 1540 share = (u64)nr_excl * self_w / sum_w; 1541 total_excl += share; 1542 cp_range_owner[0] = CID_SELF; 1543 cp_range_end[0] = total_excl; 1544 qa.part.rr_slots[nr_rr++] = 0; /* self holds slot 0 (cgid 0 = no grant) */ 1545 1546 bpf_for(i, 0, MAX_SUB_SCHEDS) { 1547 u64 cgid = cgid_snap[i]; 1548 s32 w = w_snap[i]; 1549 1550 barrier_var(total_excl); 1551 share = (u64)nr_excl * w / sum_w; 1552 total_excl += share; 1553 cp_range_owner[i + 1] = cgid ? i : CID_NONE; 1554 cp_range_end[i + 1] = total_excl; 1555 1556 if (cgid) { 1557 barrier_var(nr_rr); 1558 if (nr_rr < 0 || nr_rr >= MAX_PARTS) { 1559 scx_bpf_error("-ERANGE"); 1560 return; 1561 } 1562 qa.part.rr_slots[nr_rr++] = cgid; 1563 } 1564 } 1565 1566 /* assign each cid: held-excl by position, the rest self/none */ 1567 bpf_for(cid, 0, nr_cids) { 1568 if (cmask_test(cid, &qa.held_excl.mask)) { 1569 place_one(cid, n, total_excl); 1570 n++; 1571 barrier_var(n); 1572 } else if (cmask_test(cid, &qa.held_shared.mask)) { 1573 qa.part.cid_owner[cid] = CID_SELF; /* time-share, self-local */ 1574 } else { 1575 qa.part.cid_owner[cid] = CID_NONE; /* not held */ 1576 } 1577 } 1578 1579 qa.part.nr_shared = nr_excl - total_excl; 1580 qa.part.nr_rr = nr_rr; 1581 } 1582 1583 /* 1584 * Charge elapsed wall time to each cid's current owner. Runs under the 1585 * partition guard before every ownership change and from the stats flush, so 1586 * alloc_ns[] reflects the layout that was in effect. Shared-pool time is 1587 * charged to the live round-robin holder. 1588 */ 1589 static __noinline void account_alloc(void) 1590 { 1591 u64 now = bpf_ktime_get_ns(); 1592 s32 rr_owner = CID_SELF; 1593 s32 nr_cids = qa.nr_cids; 1594 u64 delta; 1595 s32 cid, i; 1596 1597 if (nr_cids < 0 || nr_cids > SCX_QMAP_MAX_CPUS) { 1598 scx_bpf_error("-ERANGE"); 1599 return; 1600 } 1601 1602 /* first call starts the clock */ 1603 if (!qa.alloc_ts) { 1604 qa.alloc_ts = now; 1605 return; 1606 } 1607 delta = now - qa.alloc_ts; 1608 qa.alloc_ts = now; 1609 qa.alloc_window_ns += delta; 1610 1611 /* resolve the live shared-pool holder to an owner id */ 1612 if (qa.part.nr_shared && qa.part.nr_rr) { 1613 u32 pos = qa.part.rr_pos; 1614 u64 cgid = pos < MAX_PARTS ? qa.part.rr_slots[pos] : 0; 1615 1616 if (cgid) { 1617 rr_owner = CID_NONE; 1618 bpf_for(i, 0, MAX_SUB_SCHEDS) 1619 if (qa.sub_sched_ctxs[i].cgroup_id == cgid) 1620 rr_owner = i; 1621 } 1622 } 1623 1624 bpf_for(cid, 0, nr_cids) { 1625 s32 owner = qa.part.cid_owner[cid]; 1626 1627 if (owner == CID_SHARED) 1628 owner = rr_owner; 1629 if (owner >= 0 && owner < MAX_SUB_SCHEDS) 1630 qa.alloc_ns[owner] += delta; 1631 else if (owner == CID_SELF) 1632 qa.self_alloc_ns += delta; 1633 } 1634 } 1635 1636 /* 1637 * usable_cids = self_cids & avail_cids. The inputs have separate writers, 1638 * apply_partition() and qmap_sub_ecaps_updated(), so the result is rebuilt in 1639 * full under the partition guard, in scratch first so that readers never see 1640 * self_cids alone. 1641 */ 1642 static void refresh_usable(void) 1643 { 1644 cmask_and(&qa.usable_scratch.mask, &qa.self_cids.mask, &qa.avail_cids.mask); 1645 cmask_copy(&qa.usable_cids.mask, &qa.usable_scratch.mask); 1646 } 1647 1648 /* 1649 * apply_partition - execute the plan compute_partition() built 1650 * 1651 * Turn the owner map into the per-child, shared and self cmasks and issue the 1652 * grant/revoke kfuncs as a delta against each child's previous grant. If no 1653 * excl cid, evict every child. 1654 */ 1655 __noinline void apply_partition(void) 1656 { 1657 s32 nr_cids = qa.nr_cids; 1658 s32 nr_shared = qa.part.nr_shared; 1659 s32 i, cid; 1660 1661 if (nr_cids < 0 || nr_cids > SCX_QMAP_MAX_CPUS || 1662 nr_shared < 0 || nr_shared > MAX_PARTS) { 1663 scx_bpf_error("-ERANGE"); 1664 return; 1665 } 1666 1667 /* no excl cpu: run own tasks on the held shares, evict children */ 1668 if (!qa.part.nr_excl) { 1669 cmask_copy(&qa.self_cids.mask, &qa.held_shared.mask); 1670 refresh_usable(); 1671 bpf_for(i, 0, MAX_SUB_SCHEDS) 1672 if (qa.sub_sched_ctxs[i].cgroup_id) 1673 scx_bpf_sub_kill(qa.sub_sched_ctxs[i].cgroup_id, 1674 "parent holds no excl cpu to distribute"); 1675 return; 1676 } 1677 1678 /* 1679 * Snapshot the old pool. The per-child revoke below clears ENQ_IMMED on 1680 * the previously-granted pool, so a cid that left the pool (now a 1681 * sibling's excl) doesn't keep a stale ENQ_IMMED on its last holder. 1682 */ 1683 cmask_copy(&qa.prev_rr_cids.mask, &qa.rr_cids.mask); 1684 1685 /* turn the owner map into the rr pool, per-child excl, and self sets */ 1686 cmask_init(&qa.rr_cids.mask, 0, nr_cids); 1687 cmask_init(&qa.self_cids.mask, 0, nr_cids); 1688 1689 /* snapshot each child's grant, then rebuild the new sets below */ 1690 bpf_for(i, 0, MAX_SUB_SCHEDS) { 1691 cmask_copy(&qa.sub_sched_ctxs[i].prev_granted.mask, 1692 &qa.sub_sched_ctxs[i].granted_cids.mask); 1693 cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, nr_cids); 1694 } 1695 1696 bpf_for(i, 0, nr_shared) 1697 cmask_set(qa.part.shared_cids[i], &qa.rr_cids.mask); 1698 bpf_for(cid, 0, nr_cids) { 1699 s32 o = qa.part.cid_owner[cid]; 1700 1701 if (cmask_test(cid, &qa.rr_cids.mask)) 1702 continue; 1703 if (o >= 0 && o < MAX_SUB_SCHEDS) 1704 cmask_set(cid, &qa.sub_sched_ctxs[o].granted_cids.mask); 1705 else if (o == CID_SELF) 1706 cmask_set(cid, &qa.self_cids.mask); 1707 } 1708 refresh_usable(); 1709 1710 /* 1711 * Apply each child's exclusive cids as a delta against its previous 1712 * grant. Separately clear the previous shared grant (ENQ_IMMED on the 1713 * old pool), covering cids still pooled and cids that left for a 1714 * sibling's excl. The current holder is granted the new pool below. 1715 */ 1716 bpf_for(i, 0, MAX_SUB_SCHEDS) { 1717 struct sub_sched_ctx __arena *ssc = &qa.sub_sched_ctxs[i]; 1718 u64 cgid = ssc->cgroup_id; 1719 1720 if (!cgid) 1721 continue; 1722 1723 cmask_andnot(&qa.to_revoke_cids.mask, &ssc->prev_granted.mask, &ssc->granted_cids.mask); 1724 cmask_andnot(&qa.to_grant_cids.mask, &ssc->granted_cids.mask, &ssc->prev_granted.mask); 1725 1726 scx_bpf_sub_revoke(cgid, SCX_CAP_ENQ_IMMED | SCX_CAP_PERF, 1727 &qa.prev_rr_cids.mask); 1728 scx_bpf_sub_revoke(cgid, SCX_CAP_ENQ | SCX_CAP_PREEMPT | 1729 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF, 1730 &qa.to_revoke_cids.mask); 1731 scx_bpf_sub_grant(cgid, SCX_CAP_ENQ | SCX_CAP_PREEMPT | 1732 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF, 1733 &qa.to_grant_cids.mask, NULL); 1734 } 1735 1736 /* the current holder of the shared pool gets ENQ_IMMED on all of it */ 1737 if (nr_shared) { 1738 s32 pos = qa.part.rr_pos; 1739 u64 holder_cgid; 1740 1741 if (pos < 0 || pos >= MAX_PARTS) { 1742 scx_bpf_error("-ERANGE"); 1743 return; 1744 } 1745 1746 holder_cgid = qa.part.rr_slots[pos]; /* 0 = self, nothing to grant */ 1747 if (holder_cgid) 1748 scx_bpf_sub_grant(holder_cgid, 1749 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF, 1750 &qa.rr_cids.mask, NULL); 1751 } 1752 } 1753 1754 /** 1755 * execute_partition - Run pending partition updates 1756 * 1757 * The rr timer is the backstop if the loop reaches its iteration limit. 1758 */ 1759 static void execute_partition(void) 1760 { 1761 u64 pending; 1762 s32 i; 1763 1764 bpf_for(i, 0, 1024) { 1765 if (!part_try_start()) 1766 break; 1767 1768 pending = __sync_fetch_and_and(&part_pending, 0); 1769 if (pending & PART_REDISTRIBUTE) { 1770 /* charge elapsed time before repartitioning */ 1771 account_alloc(); 1772 compute_partition(); 1773 apply_partition(); 1774 } else if (pending & PART_REFRESH) { 1775 refresh_usable(); 1776 } 1777 1778 /* 1779 * Requests are published before trying the guard. Releasing it 1780 * before checking pending work ensures a racing request is 1781 * either observed here or handled by a caller that acquires the 1782 * guard. 1783 */ 1784 part_end(); 1785 if (!__sync_fetch_and_or(&part_pending, 0)) 1786 break; 1787 } 1788 } 1789 1790 static void redistribute(void) 1791 { 1792 __sync_fetch_and_or(&part_pending, PART_REDISTRIBUTE); 1793 execute_partition(); 1794 } 1795 1796 /* 1797 * Owner id for a @sched value of ops.sub_cid_sched_updated(). A child is 1798 * attached before its first task runs and its tasks are re-homed before it 1799 * detaches, so a child's cgroup id always has its slot. 1800 */ 1801 static s32 cid_sched_owner(u64 sched) 1802 { 1803 s32 i; 1804 1805 if (sched == SCX_CID_SCHED_NONE) 1806 return CID_NONE; 1807 if (sched == SCX_CID_SCHED_SELF) 1808 return CID_SELF; 1809 bpf_for(i, 0, MAX_SUB_SCHEDS) 1810 if (qa.sub_sched_ctxs[i].cgroup_id == sched) 1811 return i; 1812 return CID_NONE; 1813 } 1814 1815 /* used_ns[] is summed from every cpu, hence the atomic adds */ 1816 static void cid_sched_charge(s32 cid, u64 now) 1817 { 1818 s32 owner = qa.cid_sched[cid]; 1819 u64 delta = now - qa.cid_sched_since[cid]; 1820 1821 if (owner >= 0 && owner < MAX_SUB_SCHEDS) 1822 __sync_fetch_and_add(&qa.used_ns[owner], delta); 1823 else if (owner == CID_SELF) 1824 __sync_fetch_and_add(&qa.self_used_ns, delta); 1825 qa.cid_sched_since[cid] = now; 1826 } 1827 1828 void BPF_STRUCT_OPS(qmap_sub_cid_sched_updated, s32 cid, u64 sched) 1829 { 1830 if (cid < 0 || cid >= SCX_QMAP_MAX_CPUS) 1831 return; 1832 1833 cid_sched_charge(cid, bpf_ktime_get_ns()); 1834 qa.cid_sched[cid] = cid_sched_owner(sched); 1835 } 1836 1837 /* 1838 * Snapshot the used time for the stats display: the closed intervals plus the 1839 * ones still open. The reads race the notifications on other cpus, so an 1840 * interval closing in between can be missing from one snapshot or counted in 1841 * two. The next snapshot evens it out and the display floors a negative 1842 * difference at zero. 1843 */ 1844 static void snapshot_used(void) 1845 { 1846 u64 now = bpf_ktime_get_ns(); 1847 s32 nr_cids = qa.nr_cids; 1848 s32 cid, i; 1849 1850 if (nr_cids < 0 || nr_cids > SCX_QMAP_MAX_CPUS) 1851 return; 1852 1853 bpf_for(i, 0, MAX_SUB_SCHEDS) 1854 qa.used_snap_ns[i] = qa.used_ns[i]; 1855 qa.self_used_snap_ns = qa.self_used_ns; 1856 1857 bpf_for(cid, 0, nr_cids) { 1858 s32 owner = qa.cid_sched[cid]; 1859 u64 since = qa.cid_sched_since[cid]; 1860 1861 /* restarted after @now by a notification on another cpu */ 1862 if (since > now) 1863 continue; 1864 if (owner >= 0 && owner < MAX_SUB_SCHEDS) 1865 qa.used_snap_ns[owner] += now - since; 1866 else if (owner == CID_SELF) 1867 qa.self_used_snap_ns += now - since; 1868 } 1869 } 1870 1871 /* 1872 * Userspace pokes this (PROG_RUN) to bring alloc_ns[] and the used snapshot 1873 * current before reading them for the stats display. Skipping the alloc part 1874 * when the partition guard is held is fine - alloc_ts is untouched, so the 1875 * elapsed time is charged next time. 1876 */ 1877 SEC("syscall") 1878 int flush_alloc(void *ctx) 1879 { 1880 snapshot_used(); 1881 if (part_try_start()) { 1882 account_alloc(); 1883 part_end(); 1884 execute_partition(); 1885 } 1886 return 0; 1887 } 1888 1889 /* 1890 * Hand the shared pool to the next participant in the rotation. Self's turn 1891 * just revokes the pool back to this sched. A child's turn grants it ENQ_IMMED 1892 * on the entire pool. As only excl-held cids are time-shared, a wall-clock 1893 * rotation works. Driven by the round-robin timer. 1894 */ 1895 static void rr_advance(void) 1896 { 1897 s32 nr_shared, old_pos, new_pos; 1898 u64 old_cgid, new_cgid; 1899 u32 nr_rr; /* unsigned for % */ 1900 1901 /* a redistribute holds the partition and rebuilds the pool, so skip */ 1902 if (!part_try_start()) 1903 return; 1904 1905 nr_rr = qa.part.nr_rr; 1906 nr_shared = qa.part.nr_shared; 1907 1908 if (nr_shared < 0 || nr_shared > MAX_PARTS) { 1909 scx_bpf_error("-ERANGE"); 1910 return; 1911 } 1912 1913 if (nr_shared && nr_rr >= 2) { 1914 /* close out the outgoing holder's pool time */ 1915 account_alloc(); 1916 1917 old_pos = qa.part.rr_pos; 1918 new_pos = (old_pos + 1) % nr_rr; 1919 old_cgid = qa.part.rr_slots[old_pos]; 1920 new_cgid = qa.part.rr_slots[new_pos]; 1921 qa.part.rr_pos = new_pos; 1922 1923 /* 1924 * Move the ENQ_IMMED cap to the next participant. The shared 1925 * cids stay marked CID_SHARED. qmap_dispatch() resolves the 1926 * live holder via rr_pos without the guard, so a dispatch 1927 * racing this handoff may reenqueue a task once. Harmless for a 1928 * time-share. 1929 */ 1930 if (old_cgid) 1931 scx_bpf_sub_revoke(old_cgid, 1932 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF, 1933 &qa.rr_cids.mask); 1934 if (new_cgid) 1935 scx_bpf_sub_grant(new_cgid, 1936 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF, 1937 &qa.rr_cids.mask, NULL); 1938 } 1939 1940 part_end(); 1941 1942 execute_partition(); 1943 } 1944 1945 /* advance the time-shared cid pool every round_robin_ns */ 1946 static int round_robin_timerfn(void *map, int *key, struct bpf_timer *timer) 1947 { 1948 rr_advance(); 1949 if (bpf_timer_start(timer, round_robin_ns, 0)) 1950 scx_bpf_error("failed to re-arm round-robin timer"); 1951 return 0; 1952 } 1953 1954 /* 1955 * Custom cid layout for the cid-override test. On invalid input the kfunc 1956 * scx_error()s and aborts the scheduler. 1957 */ 1958 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init_cids) 1959 { 1960 u32 nr_cpu_ids = scx_bpf_nr_cpu_ids(); 1961 1962 if (!cid_override_mode) 1963 return 0; 1964 1965 /* the arena arrays are sized SCX_QMAP_MAX_CPUS */ 1966 if (nr_cpu_ids > SCX_QMAP_MAX_CPUS) { 1967 scx_bpf_error("nr_cpu_ids=%u exceeds SCX_QMAP_MAX_CPUS=%d", 1968 nr_cpu_ids, SCX_QMAP_MAX_CPUS); 1969 return -EINVAL; 1970 } 1971 1972 scx_bpf_cid_override(qa.cid_override_cpu_to_cid, nr_cpu_ids, 1973 qa.cid_override_shard_start, cid_override_nr_shards); 1974 return 0; 1975 } 1976 1977 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init) 1978 { 1979 u8 __arena *slab; 1980 u32 nr_pages, key = 0, i; 1981 u32 nr_cids, nr_cpu_ids; 1982 struct bpf_timer *timer; 1983 s32 ret; 1984 1985 nr_cids = scx_bpf_nr_cids(); 1986 nr_cpu_ids = scx_bpf_nr_cpu_ids(); 1987 1988 if (nr_cids > SCX_QMAP_MAX_CPUS) { 1989 scx_bpf_error("nr_cids=%u exceeds SCX_QMAP_MAX_CPUS=%d", 1990 nr_cids, SCX_QMAP_MAX_CPUS); 1991 return -EINVAL; 1992 } 1993 if (nr_cpu_ids > SCX_QMAP_MAX_CPUS) { 1994 scx_bpf_error("nr_cpu_ids=%u exceeds SCX_QMAP_MAX_CPUS=%d", 1995 nr_cpu_ids, SCX_QMAP_MAX_CPUS); 1996 return -EINVAL; 1997 } 1998 1999 /* 2000 * Allocate the task_ctx slab in arena and thread the entire slab onto 2001 * the free list. max_tasks is set by userspace before load. Each entry 2002 * is TASK_CTX_STRIDE bytes - task_ctx's trailing cpus_allowed flex 2003 * array extends into the stride tail. 2004 */ 2005 if (!max_tasks) { 2006 scx_bpf_error("max_tasks must be > 0"); 2007 return -EINVAL; 2008 } 2009 2010 nr_pages = (max_tasks * TASK_CTX_STRIDE + PAGE_SIZE - 1) / PAGE_SIZE; 2011 slab = bpf_arena_alloc_pages(&arena, NULL, nr_pages, NUMA_NO_NODE, 0); 2012 if (!slab) { 2013 scx_bpf_error("failed to allocate task_ctx slab"); 2014 return -ENOMEM; 2015 } 2016 qa.task_ctxs = (task_ctx_t *)slab; 2017 2018 bpf_for(i, 0, 5) 2019 qa.fifos[i].idx = i; 2020 2021 bpf_for(i, 0, max_tasks) { 2022 task_ctx_t *cur = (task_ctx_t *)(slab + i * TASK_CTX_STRIDE); 2023 task_ctx_t *next = (i + 1 < max_tasks) ? 2024 (task_ctx_t *)(slab + (i + 1) * TASK_CTX_STRIDE) : NULL; 2025 cur->next_free = next; 2026 } 2027 qa.task_free_head = (task_ctx_t *)slab; 2028 2029 /* cache the cid count, trusted to be <= SCX_QMAP_MAX_CPUS hereafter */ 2030 qa.nr_cids = nr_cids; 2031 2032 bpf_for(i, 0, nr_cids) 2033 qa.cid_sched[i] = CID_NONE; 2034 2035 /* cmasks are embedded in qa, so they only need initializing */ 2036 cmask_init(&qa.idle_cids.mask, 0, nr_cids); 2037 cmask_init(&qa.rr_cids.mask, 0, nr_cids); 2038 cmask_init(&qa.prev_rr_cids.mask, 0, nr_cids); 2039 cmask_init(&qa.self_cids.mask, 0, nr_cids); 2040 cmask_init(&qa.avail_cids.mask, 0, nr_cids); 2041 cmask_init(&qa.usable_cids.mask, 0, nr_cids); 2042 cmask_init(&qa.to_revoke_cids.mask, 0, nr_cids); 2043 cmask_init(&qa.to_grant_cids.mask, 0, nr_cids); 2044 cmask_init(&qa.usable_scratch.mask, 0, nr_cids); 2045 cmask_init(&qa.held_excl.mask, 0, nr_cids); 2046 cmask_init(&qa.held_shared.mask, 0, nr_cids); 2047 2048 scx_bpf_sub_caps(0, SCX_CAP_ENQ, &qa.held_excl.mask); 2049 scx_bpf_sub_caps(0, SCX_CAP_ENQ_IMMED, &qa.held_shared.mask); 2050 cmask_andnot(&qa.held_shared.mask, &qa.held_shared.mask, &qa.held_excl.mask); 2051 2052 bpf_for(i, 0, MAX_SUB_SCHEDS) { 2053 cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, nr_cids); 2054 cmask_init(&qa.sub_sched_ctxs[i].prev_granted.mask, 0, nr_cids); 2055 } 2056 2057 /* 2058 * The root starts holding every cid and gets no ecaps notifications, so 2059 * its avail set is fixed here. qmap_sub_ecaps_updated() maintains the 2060 * per-cid state as effective caps settle, and redistribute() rebuilds 2061 * owner and self from held caps. A non-root node starts with nothing. 2062 */ 2063 bpf_for(i, 0, nr_cids) { 2064 if (!sub_cgroup_id) { 2065 cmask_set(i, &qa.self_cids.mask); 2066 cmask_set(i, &qa.avail_cids.mask); 2067 cmask_set(i, &qa.usable_cids.mask); 2068 qa.part.cid_owner[i] = CID_SELF; 2069 } else { 2070 qa.part.cid_owner[i] = CID_NONE; 2071 } 2072 } 2073 qa.part.nr_shared = 0; 2074 2075 ret = scx_bpf_create_dsq(SHARED_DSQ, -1); 2076 if (ret) { 2077 scx_bpf_error("failed to create DSQ %d (%d)", SHARED_DSQ, ret); 2078 return ret; 2079 } 2080 2081 ret = scx_bpf_create_dsq(HIGHPRI_DSQ, -1); 2082 if (ret) { 2083 scx_bpf_error("failed to create DSQ %d (%d)", HIGHPRI_DSQ, ret); 2084 return ret; 2085 } 2086 2087 ret = scx_bpf_create_dsq(LOWPRI_DSQ, -1); 2088 if (ret) 2089 return ret; 2090 2091 timer = bpf_map_lookup_elem(&monitor_timer, &key); 2092 if (!timer) 2093 return -ESRCH; 2094 bpf_timer_init(timer, &monitor_timer, CLOCK_MONOTONIC); 2095 bpf_timer_set_callback(timer, monitor_timerfn); 2096 ret = bpf_timer_start(timer, ONE_SEC_IN_NS, 0); 2097 if (ret) 2098 return ret; 2099 2100 if (__COMPAT_has_generic_reenq()) { 2101 /* see lowpri_timerfn() */ 2102 timer = bpf_map_lookup_elem(&lowpri_timer, &key); 2103 if (!timer) 2104 return -ESRCH; 2105 bpf_timer_init(timer, &lowpri_timer, CLOCK_MONOTONIC); 2106 bpf_timer_set_callback(timer, lowpri_timerfn); 2107 ret = bpf_timer_start(timer, LOWPRI_INTV_NS, 0); 2108 if (ret) 2109 return ret; 2110 } 2111 2112 /* sub-sched: drive the boundary-cid round-robin from a bpf timer */ 2113 timer = bpf_map_lookup_elem(&round_robin_timer, &key); 2114 if (!timer) 2115 return -ESRCH; 2116 bpf_timer_init(timer, &round_robin_timer, CLOCK_MONOTONIC); 2117 bpf_timer_set_callback(timer, round_robin_timerfn); 2118 ret = bpf_timer_start(timer, round_robin_ns, 0); 2119 if (ret) 2120 return ret; 2121 2122 return 0; 2123 } 2124 2125 void BPF_STRUCT_OPS(qmap_exit, struct scx_exit_info *ei) 2126 { 2127 UEI_RECORD(uei, ei); 2128 } 2129 2130 /* 2131 * Seed a new sub slot with the cgroup's current weight. The kernel delivers 2132 * ops.cpuctl_set_weight() only on value-changing writes, so a weight set 2133 * before the sub attached would otherwise go unnoticed. 2134 */ 2135 static u32 cgrp_cur_weight(u64 cgid) 2136 { 2137 struct cgroup_subsys_state *css; 2138 struct cgroup *cgrp; 2139 u32 weight = 100; 2140 2141 cgrp = bpf_cgroup_from_id(cgid); 2142 if (!cgrp) 2143 return weight; 2144 2145 css = BPF_CORE_READ(cgrp, subsys[cpu_cgrp_id]); 2146 if (css) { 2147 struct task_group *tg = container_of(css, struct task_group, css); 2148 u32 w = BPF_CORE_READ(tg, scx.weight); 2149 2150 if (w) 2151 weight = w; 2152 } 2153 bpf_cgroup_release(cgrp); 2154 return weight; 2155 } 2156 2157 s32 BPF_STRUCT_OPS(qmap_sub_attach, struct scx_sub_attach_args *args) 2158 { 2159 s32 i; 2160 2161 /* as long as there is at least one excl cpu, children can attach */ 2162 if (!cmask_weight(&qa.held_excl.mask)) 2163 return -ENOSPC; 2164 2165 for (i = 0; i < MAX_SUB_SCHEDS; i++) { 2166 if (qa.sub_sched_ctxs[i].cgroup_id) 2167 continue; 2168 2169 qa.sub_sched_ctxs[i].cgroup_id = args->ops->sub_cgroup_id; 2170 qa.sub_sched_ctxs[i].weight = cgrp_cur_weight(args->ops->sub_cgroup_id); 2171 qa.nr_sub_scheds++; 2172 bpf_printk("attaching sub-sched[%d] on %s", i, args->cgroup_path); 2173 redistribute(); 2174 return 0; 2175 } 2176 2177 return -ENOSPC; 2178 } 2179 2180 void BPF_STRUCT_OPS(qmap_sub_detach, struct scx_sub_detach_args *args) 2181 { 2182 u64 cgid = args->ops->sub_cgroup_id; 2183 s32 nr_cids = qa.nr_cids; 2184 s32 pos = qa.part.rr_pos; 2185 u64 holder_cgid; 2186 bool reset_pool; 2187 s32 i, cid; 2188 2189 if (nr_cids < 0 || nr_cids > SCX_QMAP_MAX_CPUS || pos < 0 || pos >= MAX_PARTS) { 2190 scx_bpf_error("-ERANGE"); 2191 return; 2192 } 2193 2194 /* 2195 * The child's caps are gone without a report, see ops.sub_detach(). 2196 * cpuperf targets persist until the next write. Reset the child's excl 2197 * cids, and the pool unless another child holds it: that child's revoke 2198 * report or its own detach resets it then. 2199 */ 2200 holder_cgid = qa.part.rr_slots[pos]; 2201 reset_pool = !holder_cgid || holder_cgid == cgid; 2202 2203 for (i = 0; i < MAX_SUB_SCHEDS; i++) { 2204 if (qa.sub_sched_ctxs[i].cgroup_id != cgid) 2205 continue; 2206 2207 bpf_arena_for(cid, 0, nr_cids) 2208 if (cmask_test(cid, &qa.sub_sched_ctxs[i].granted_cids.mask) || 2209 (reset_pool && cmask_test(cid, &qa.rr_cids.mask))) 2210 scx_bpf_cidperf_set(cid, SCX_CPUPERF_ONE); 2211 2212 qa.sub_sched_ctxs[i].cgroup_id = 0; 2213 qa.sub_sched_ctxs[i].weight = 100; 2214 cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, qa.nr_cids); 2215 qa.nr_sub_scheds--; 2216 bpf_printk("detaching sub-sched[%d] on %s", i, args->cgroup_path); 2217 redistribute(); 2218 break; 2219 } 2220 } 2221 2222 void BPF_STRUCT_OPS(qmap_sub_caps_updated, const struct scx_cmask *cmask, u64 caps) 2223 { 2224 /* our held caps changed, redistribute */ 2225 redistribute(); 2226 } 2227 2228 void BPF_STRUCT_OPS(qmap_sub_ecaps_updated, s32 cid, u64 before, u64 after) 2229 { 2230 /* 2231 * Effective caps updated. Track which cids hold shared caps so a self 2232 * task placed there enqueues IMMED, and which cids have ENQ_IMMED in 2233 * effect at all (avail, see the header comment). 2234 */ 2235 if (after & SCX_CAP_ENQ_IMMED) { 2236 qa.cid_shared[cid] = (after & SCX_CAP_ENQ) ? 0 : 1; 2237 cmask_set(cid, &qa.avail_cids.mask); 2238 } else { 2239 qa.cid_shared[cid] = 0; 2240 cmask_clear(cid, &qa.avail_cids.mask); 2241 } 2242 2243 __sync_fetch_and_or(&part_pending, PART_REFRESH); 2244 execute_partition(); 2245 } 2246 2247 void BPF_STRUCT_OPS(qmap_sub_child_ecaps_updated, u64 cgroup_id, s32 cid, u64 before, 2248 u64 after) 2249 { 2250 __sync_fetch_and_add(&qa.nr_child_ecaps, 1); 2251 2252 /* a child's last target must not outlive its PERF cap */ 2253 if ((before & ~after) & SCX_CAP_PERF) 2254 scx_bpf_cidperf_set(cid, SCX_CPUPERF_ONE); 2255 } 2256 2257 SCX_OPS_CID_DEFINE(qmap_ops, 2258 .flags = SCX_OPS_ENQ_EXITING | SCX_OPS_TID_TO_TASK, 2259 .select_cid = (void *)qmap_select_cid, 2260 .enqueue = (void *)qmap_enqueue, 2261 .dequeue = (void *)qmap_dequeue, 2262 .dispatch = (void *)qmap_dispatch, 2263 .tick = (void *)qmap_tick, 2264 .core_sched_before = (void *)qmap_core_sched_before, 2265 .set_cmask = (void *)qmap_set_cmask, 2266 .update_idle = (void *)qmap_update_idle, 2267 .init_task = (void *)qmap_init_task, 2268 .exit_task = (void *)qmap_exit_task, 2269 .dump = (void *)qmap_dump, 2270 .dump_cid = (void *)qmap_dump_cid, 2271 .dump_task = (void *)qmap_dump_task, 2272 .cpuctl_init = (void *)qmap_cpuctl_init, 2273 .cpuctl_set_weight = (void *)qmap_cpuctl_set_weight, 2274 .cpuctl_set_bandwidth = (void *)qmap_cpuctl_set_bandwidth, 2275 .cpuctl_move = (void *)qmap_cpuctl_move, 2276 .sub_attach = (void *)qmap_sub_attach, 2277 .sub_detach = (void *)qmap_sub_detach, 2278 .sub_caps_updated = (void *)qmap_sub_caps_updated, 2279 .sub_ecaps_updated = (void *)qmap_sub_ecaps_updated, 2280 .sub_cid_sched_updated = (void *)qmap_sub_cid_sched_updated, 2281 .sub_child_ecaps_updated = (void *)qmap_sub_child_ecaps_updated, 2282 .init_cids = (void *)qmap_init_cids, 2283 .init = (void *)qmap_init, 2284 .exit = (void *)qmap_exit, 2285 .timeout_ms = 5000U, 2286 .name = "qmap"); 2287