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