1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst 4 * 5 * Sub-scheduler hierarchy support. 6 * 7 * A sub-scheduler is an scx_sched attached to a cgroup subtree under another 8 * scx_sched. This file holds the sub-scheduler implementation: the scheduler 9 * tree walk, capability delegation, per-shard cap state and its sync, and the 10 * sub-scheduler enable/disable paths. The core dispatch/enqueue machinery it 11 * builds on lives in ext.c. 12 * 13 * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. 14 * Copyright (c) 2026 Tejun Heo <tj@kernel.org> 15 */ 16 #include <linux/rhashtable.h> 17 #include "internal.h" 18 #include "cid.h" 19 #include "arena.h" 20 #include "sub.h" 21 22 #ifdef CONFIG_EXT_SUB_SCHED 23 24 /** 25 * scx_next_descendant_pre - find the next descendant for pre-order walk 26 * @pos: the current position (%NULL to initiate traversal) 27 * @root: sched whose descendants to walk 28 * 29 * To be used by scx_for_each_descendant_pre(). Find the next descendant to 30 * visit for pre-order traversal of @root's descendants. @root is included in 31 * the iteration and the first node to be visited. 32 */ 33 struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root) 34 { 35 struct scx_sched *next; 36 37 lockdep_assert(lockdep_is_held(&scx_enable_mutex) || 38 lockdep_is_held(&scx_sched_lock)); 39 40 /* if first iteration, visit @root */ 41 if (!pos) 42 return root; 43 44 /* visit the first child if exists */ 45 next = list_first_entry_or_null(&pos->children, struct scx_sched, sibling); 46 if (next) 47 return next; 48 49 /* no child, visit my or the closest ancestor's next sibling */ 50 while (pos != root) { 51 if (!list_is_last(&pos->sibling, &scx_parent(pos)->children)) 52 return list_next_entry(pos, sibling); 53 pos = scx_parent(pos); 54 } 55 56 return NULL; 57 } 58 59 static struct scx_sched *scx_find_sub_sched(u64 cgroup_id) 60 { 61 return rhashtable_lookup(&scx_sched_hash, &cgroup_id, 62 scx_sched_hash_params); 63 } 64 65 void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch) 66 { 67 rcu_assign_pointer(p->scx.sched, sch); 68 } 69 70 struct cgroup *sch_cgroup(struct scx_sched *sch) 71 { 72 return sch->cgrp; 73 } 74 75 /* for each descendant of @cgrp including self, set ->scx_sched to @sch */ 76 void set_cgroup_sched(struct cgroup *cgrp, struct scx_sched *sch) 77 { 78 struct cgroup *pos; 79 struct cgroup_subsys_state *css; 80 81 cgroup_for_each_live_descendant_pre(pos, css, cgrp) 82 rcu_assign_pointer(pos->scx_sched, sch); 83 } 84 85 static DECLARE_WAIT_QUEUE_HEAD(scx_unlink_waitq); 86 87 void drain_descendants(struct scx_sched *sch) 88 { 89 /* 90 * Child scheds that finished the critical part of disabling will take 91 * themselves off @sch->children. Wait for it to drain. As propagation 92 * is recursive, empty @sch->children means that all proper descendant 93 * scheds reached unlinking stage. 94 */ 95 wait_event(scx_unlink_waitq, list_empty(&sch->children)); 96 } 97 98 static void scx_fail_parent(struct scx_sched *sch, 99 struct task_struct *failed, s32 fail_code) 100 { 101 struct scx_sched *parent = scx_parent(sch); 102 struct scx_task_iter sti; 103 struct task_struct *p; 104 105 scx_error(parent, "ops.init_task() failed (%d) for %s[%d] while disabling a sub-scheduler", 106 fail_code, failed->comm, failed->pid); 107 108 /* 109 * Once $parent is bypassed, it's safe to put SCX_TASK_NONE tasks into 110 * it. This may cause downstream failures on the BPF side but $parent is 111 * dying anyway. 112 */ 113 scx_bypass(parent, true); 114 115 scx_task_iter_start(&sti, sch->cgrp); 116 while ((p = scx_task_iter_next_locked(&sti))) { 117 if (scx_task_on_sched(parent, p)) 118 continue; 119 120 scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) { 121 scx_disable_and_exit_task(sch, p); 122 scx_set_task_sched(p, parent); 123 } 124 } 125 scx_task_iter_stop(&sti); 126 } 127 128 void scx_sub_disable(struct scx_sched *sch) 129 { 130 struct scx_sched *parent = scx_parent(sch); 131 struct scx_task_iter sti; 132 struct task_struct *p; 133 int ret; 134 135 /* 136 * Guarantee forward progress and wait for descendants to be disabled. 137 * To limit disruptions, $parent is not bypassed. Tasks are fully 138 * prepped and then inserted back into $parent. 139 */ 140 scx_bypass(sch, true); 141 drain_descendants(sch); 142 143 /* 144 * Here, every runnable task is guaranteed to make forward progress and 145 * we can safely use blocking synchronization constructs. Actually 146 * disable ops. 147 */ 148 mutex_lock(&scx_enable_mutex); 149 percpu_down_write(&scx_fork_rwsem); 150 scx_cgroup_lock(); 151 152 set_cgroup_sched(sch_cgroup(sch), parent); 153 154 scx_task_iter_start(&sti, sch->cgrp); 155 while ((p = scx_task_iter_next_locked(&sti))) { 156 struct rq *rq; 157 struct rq_flags rf; 158 159 /* filter out duplicate visits */ 160 if (scx_task_on_sched(parent, p)) 161 continue; 162 163 /* 164 * By the time control reaches here, all descendant schedulers 165 * should already have been disabled. 166 */ 167 WARN_ON_ONCE(!scx_task_on_sched(sch, p)); 168 169 /* 170 * @p is pinned by the iter: css_task_iter_next() takes a 171 * reference and holds it until the next iter_next() call, so 172 * @p->usage is guaranteed > 0. 173 */ 174 get_task_struct(p); 175 176 scx_task_iter_unlock(&sti); 177 178 /* 179 * $p is READY or ENABLED on @sch. Initialize for $parent, 180 * disable and exit from @sch, and then switch over to $parent. 181 * 182 * If a task fails to initialize for $parent, the only available 183 * action is disabling $parent too. While this allows disabling 184 * of a child sched to cause the parent scheduler to fail, the 185 * failure can only originate from ops.init_task() of the 186 * parent. A child can't directly affect the parent through its 187 * own failures. 188 */ 189 ret = __scx_init_task(parent, p, false); 190 if (ret) { 191 scx_fail_parent(sch, p, ret); 192 put_task_struct(p); 193 break; 194 } 195 196 rq = task_rq_lock(p, &rf); 197 198 if (scx_get_task_state(p) == SCX_TASK_DEAD) { 199 /* 200 * sched_ext_dead() raced us between __scx_init_task() 201 * and this rq lock and ran exit_task() on @sch (the 202 * sched @p was on at that point), not on $parent. 203 * $parent's just-completed init is owed an exit_task() 204 * and we issue it here. 205 */ 206 scx_sub_init_cancel_task(parent, p); 207 task_rq_unlock(rq, p, &rf); 208 put_task_struct(p); 209 continue; 210 } 211 212 scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) { 213 /* 214 * $p is initialized for $parent and still attached to 215 * @sch. Disable and exit for @sch, switch over to 216 * $parent, override the state to READY to account for 217 * $p having already been initialized, and then enable. 218 */ 219 scx_disable_and_exit_task(sch, p); 220 scx_set_task_state(p, SCX_TASK_INIT_BEGIN); 221 scx_set_task_state(p, SCX_TASK_INIT); 222 scx_set_task_sched(p, parent); 223 scx_set_task_state(p, SCX_TASK_READY); 224 scx_enable_task(parent, p); 225 } 226 227 task_rq_unlock(rq, p, &rf); 228 put_task_struct(p); 229 } 230 scx_task_iter_stop(&sti); 231 232 scx_disable_dump(sch); 233 234 scx_cgroup_unlock(); 235 percpu_up_write(&scx_fork_rwsem); 236 237 /* 238 * All tasks are moved off of @sch but there may still be on-going 239 * operations (e.g. ops.select_cpu()). Drain them by flushing RCU. Use 240 * the expedited version as ancestors may be waiting in bypass mode. 241 * Also, tell the parent that there is no need to keep running bypass 242 * DSQs for us. 243 */ 244 synchronize_rcu_expedited(); 245 scx_disable_bypass_dsp(sch); 246 247 scx_unlink_sched(sch); 248 249 mutex_unlock(&scx_enable_mutex); 250 251 /* 252 * @sch is now unlinked from the parent's children list. Notify and call 253 * ops.sub_detach/exit(). Note that ops.sub_detach/exit() must be called 254 * after unlinking and releasing all locks. See scx_claim_exit(). 255 */ 256 wake_up_all(&scx_unlink_waitq); 257 258 if (parent->ops.sub_detach && sch->sub_attached) { 259 struct scx_sub_detach_args sub_detach_args = { 260 .ops = &sch->ops, 261 .cgroup_path = sch->cgrp_path, 262 }; 263 SCX_CALL_OP(parent, sub_detach, NULL, 264 &sub_detach_args); 265 } 266 267 scx_log_sched_disable(sch); 268 269 if (sch->ops.exit) 270 SCX_CALL_OP(sch, exit, NULL, sch->exit_info); 271 if (sch->sub_kset) 272 kobject_del(&sch->sub_kset->kobj); 273 kobject_del(&sch->kobj); 274 } 275 276 /* verify that a scheduler can be attached to @cgrp and return the parent */ 277 static struct scx_sched *find_parent_sched(struct cgroup *cgrp) 278 { 279 struct scx_sched *parent = cgrp->scx_sched; 280 struct scx_sched *pos; 281 282 lockdep_assert_held(&scx_sched_lock); 283 284 /* can't attach twice to the same cgroup */ 285 if (parent->cgrp == cgrp) 286 return ERR_PTR(-EBUSY); 287 288 /* does $parent allow sub-scheds? */ 289 if (!parent->ops.sub_attach) 290 return ERR_PTR(-EOPNOTSUPP); 291 292 /* can't insert between $parent and its exiting children */ 293 list_for_each_entry(pos, &parent->children, sibling) 294 if (cgroup_is_descendant(pos->cgrp, cgrp)) 295 return ERR_PTR(-EBUSY); 296 297 return parent; 298 } 299 300 static bool assert_task_ready_or_enabled(struct task_struct *p) 301 { 302 u32 state = scx_get_task_state(p); 303 304 switch (state) { 305 case SCX_TASK_READY: 306 case SCX_TASK_ENABLED: 307 return true; 308 default: 309 WARN_ONCE(true, "sched_ext: Invalid task state %d for %s[%d] during enabling sub sched", 310 state, p->comm, p->pid); 311 return false; 312 } 313 } 314 315 void scx_sub_enable_workfn(struct kthread_work *work) 316 { 317 struct scx_enable_cmd *cmd = container_of(work, struct scx_enable_cmd, work); 318 struct sched_ext_ops *ops = cmd->ops; 319 struct cgroup *cgrp; 320 struct scx_sched *parent, *sch; 321 struct scx_task_iter sti; 322 struct task_struct *p; 323 s32 i, ret; 324 325 mutex_lock(&scx_enable_mutex); 326 327 if (!scx_enabled()) { 328 ret = -ENODEV; 329 goto out_unlock; 330 } 331 332 /* See scx_root_enable_workfn() for the @ops->priv check. */ 333 if (rcu_access_pointer(ops->priv)) { 334 ret = -EBUSY; 335 goto out_unlock; 336 } 337 338 cgrp = cgroup_get_from_id(ops->sub_cgroup_id); 339 if (IS_ERR(cgrp)) { 340 ret = PTR_ERR(cgrp); 341 goto out_unlock; 342 } 343 344 raw_spin_lock_irq(&scx_sched_lock); 345 parent = find_parent_sched(cgrp); 346 if (IS_ERR(parent)) { 347 raw_spin_unlock_irq(&scx_sched_lock); 348 ret = PTR_ERR(parent); 349 goto out_put_cgrp; 350 } 351 kobject_get(&parent->kobj); 352 raw_spin_unlock_irq(&scx_sched_lock); 353 354 /* scx_alloc_and_add_sched() consumes @cgrp whether it succeeds or not */ 355 sch = scx_alloc_and_add_sched(cmd, cgrp, parent); 356 kobject_put(&parent->kobj); 357 if (IS_ERR(sch)) { 358 ret = PTR_ERR(sch); 359 goto out_unlock; 360 } 361 362 ret = scx_link_sched(sch); 363 if (ret) 364 goto err_disable; 365 366 if (sch->level >= SCX_SUB_MAX_DEPTH) { 367 scx_error(sch, "max nesting depth %d violated", 368 SCX_SUB_MAX_DEPTH); 369 goto err_disable; 370 } 371 372 if (sch->ops.init) { 373 ret = SCX_CALL_OP_RET(sch, init, NULL); 374 if (ret) { 375 ret = scx_ops_sanitize_err(sch, "init", ret); 376 scx_error(sch, "ops.init() failed (%d)", ret); 377 goto err_disable; 378 } 379 sch->exit_info->flags |= SCX_EFLAG_INITIALIZED; 380 } 381 382 ret = scx_arena_pool_init(sch); 383 if (ret) 384 goto err_disable; 385 386 ret = scx_set_cmask_scratch_alloc(sch); 387 if (ret) 388 goto err_disable; 389 390 if (scx_validate_ops(sch, ops)) 391 goto err_disable; 392 393 struct scx_sub_attach_args sub_attach_args = { 394 .ops = &sch->ops, 395 .cgroup_path = sch->cgrp_path, 396 }; 397 398 ret = SCX_CALL_OP_RET(parent, sub_attach, NULL, 399 &sub_attach_args); 400 if (ret) { 401 ret = scx_ops_sanitize_err(sch, "sub_attach", ret); 402 scx_error(sch, "parent rejected (%d)", ret); 403 goto err_disable; 404 } 405 sch->sub_attached = true; 406 407 scx_bypass(sch, true); 408 409 for (i = SCX_OPI_BEGIN; i < SCX_OPI_END; i++) 410 if (((void (**)(void))ops)[i]) 411 set_bit(i, sch->has_op); 412 413 percpu_down_write(&scx_fork_rwsem); 414 scx_cgroup_lock(); 415 416 /* 417 * Set cgroup->scx_sched's and check CSS_ONLINE. Either we see 418 * !CSS_ONLINE or scx_cgroup_lifetime_notify() sees and shoots us down. 419 */ 420 set_cgroup_sched(sch_cgroup(sch), sch); 421 if (!(cgrp->self.flags & CSS_ONLINE)) { 422 scx_error(sch, "cgroup is not online"); 423 goto err_unlock_and_disable; 424 } 425 426 /* 427 * Initialize tasks for the new child $sch without exiting them for 428 * $parent so that the tasks can always be reverted back to $parent 429 * sched on child init failure. 430 */ 431 WARN_ON_ONCE(scx_enabling_sub_sched); 432 scx_enabling_sub_sched = sch; 433 434 scx_task_iter_start(&sti, sch->cgrp); 435 while ((p = scx_task_iter_next_locked(&sti))) { 436 struct rq *rq; 437 struct rq_flags rf; 438 439 /* 440 * Task iteration may visit the same task twice when racing 441 * against exiting. Use %SCX_TASK_SUB_INIT to mark tasks which 442 * finished __scx_init_task() and skip if set. 443 * 444 * A task may exit and get freed between __scx_init_task() 445 * completion and scx_enable_task(). In such cases, 446 * scx_disable_and_exit_task() must exit the task for both the 447 * parent and child scheds. 448 */ 449 if (p->scx.flags & SCX_TASK_SUB_INIT) 450 continue; 451 452 /* @p is pinned by the iter; see scx_sub_disable() */ 453 get_task_struct(p); 454 455 if (!assert_task_ready_or_enabled(p)) { 456 ret = -EINVAL; 457 goto abort; 458 } 459 460 scx_task_iter_unlock(&sti); 461 462 /* 463 * As $p is still on $parent, it can't be transitioned to INIT. 464 * Let's worry about task state later. Use __scx_init_task(). 465 */ 466 ret = __scx_init_task(sch, p, false); 467 if (ret) 468 goto abort; 469 470 rq = task_rq_lock(p, &rf); 471 472 if (scx_get_task_state(p) == SCX_TASK_DEAD) { 473 /* 474 * sched_ext_dead() raced us between __scx_init_task() 475 * and this rq lock and ran exit_task() on $parent (the 476 * sched @p was on at that point), not on @sch. @sch's 477 * just-completed init is owed an exit_task() and we 478 * issue it here. 479 */ 480 scx_sub_init_cancel_task(sch, p); 481 task_rq_unlock(rq, p, &rf); 482 put_task_struct(p); 483 continue; 484 } 485 486 p->scx.flags |= SCX_TASK_SUB_INIT; 487 task_rq_unlock(rq, p, &rf); 488 489 put_task_struct(p); 490 } 491 scx_task_iter_stop(&sti); 492 493 /* 494 * All tasks are prepped. Disable/exit tasks for $parent and enable for 495 * the new @sch. 496 */ 497 scx_task_iter_start(&sti, sch->cgrp); 498 while ((p = scx_task_iter_next_locked(&sti))) { 499 /* 500 * Use clearing of %SCX_TASK_SUB_INIT to detect and skip 501 * duplicate iterations. 502 */ 503 if (!(p->scx.flags & SCX_TASK_SUB_INIT)) 504 continue; 505 506 scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) { 507 /* 508 * $p must be either READY or ENABLED. If ENABLED, 509 * __scx_disabled_and_exit_task() first disables and 510 * makes it READY. However, after exiting $p, it will 511 * leave $p as READY. 512 */ 513 assert_task_ready_or_enabled(p); 514 __scx_disable_and_exit_task(parent, p); 515 516 /* 517 * $p is now only initialized for @sch and READY, which 518 * is what we want. Assign it to @sch and enable. 519 */ 520 scx_set_task_sched(p, sch); 521 scx_enable_task(sch, p); 522 523 p->scx.flags &= ~SCX_TASK_SUB_INIT; 524 } 525 } 526 scx_task_iter_stop(&sti); 527 528 scx_enabling_sub_sched = NULL; 529 530 scx_cgroup_unlock(); 531 percpu_up_write(&scx_fork_rwsem); 532 533 scx_bypass(sch, false); 534 535 pr_info("sched_ext: BPF sub-scheduler \"%s\" enabled\n", sch->ops.name); 536 kobject_uevent(&sch->kobj, KOBJ_ADD); 537 ret = 0; 538 goto out_unlock; 539 540 out_put_cgrp: 541 cgroup_put(cgrp); 542 out_unlock: 543 mutex_unlock(&scx_enable_mutex); 544 cmd->ret = ret; 545 return; 546 547 abort: 548 put_task_struct(p); 549 scx_task_iter_stop(&sti); 550 551 /* 552 * Undo __scx_init_task() for tasks we marked. scx_enable_task() never 553 * ran for @sch on them, so calling scx_disable_task() here would invoke 554 * ops.disable() without a matching ops.enable(). scx_enabling_sub_sched 555 * must stay set until SUB_INIT is cleared from every marked task - 556 * scx_disable_and_exit_task() reads it when a task exits concurrently. 557 */ 558 scx_task_iter_start(&sti, sch->cgrp); 559 while ((p = scx_task_iter_next_locked(&sti))) { 560 if (p->scx.flags & SCX_TASK_SUB_INIT) { 561 scx_sub_init_cancel_task(sch, p); 562 p->scx.flags &= ~SCX_TASK_SUB_INIT; 563 } 564 } 565 scx_task_iter_stop(&sti); 566 scx_enabling_sub_sched = NULL; 567 err_unlock_and_disable: 568 /* we'll soon enter disable path, keep bypass on */ 569 scx_cgroup_unlock(); 570 percpu_up_write(&scx_fork_rwsem); 571 err_disable: 572 mutex_unlock(&scx_enable_mutex); 573 /* 574 * Some enable failures only return an errno (e.g. -ENOMEM from an 575 * allocation) without calling scx_error(). Record it so 576 * scx_flush_disable_work() runs the disable and ops.exit() fires. 577 */ 578 scx_error(sch, "scx_sub_enable() failed (%d)", ret); 579 scx_flush_disable_work(sch); 580 cmd->ret = 0; 581 } 582 583 static s32 scx_cgroup_lifetime_notify(struct notifier_block *nb, 584 unsigned long action, void *data) 585 { 586 struct cgroup *cgrp = data; 587 struct cgroup *parent = cgroup_parent(cgrp); 588 589 if (!cgroup_on_dfl(cgrp)) 590 return NOTIFY_OK; 591 592 switch (action) { 593 case CGROUP_LIFETIME_ONLINE: 594 /* inherit ->scx_sched from $parent */ 595 if (parent) 596 rcu_assign_pointer(cgrp->scx_sched, parent->scx_sched); 597 break; 598 case CGROUP_LIFETIME_OFFLINE: 599 /* if there is a sched attached, shoot it down */ 600 if (cgrp->scx_sched && cgrp->scx_sched->cgrp == cgrp) 601 scx_exit(cgrp->scx_sched, SCX_EXIT_UNREG_KERN, 602 SCX_ECODE_RSN_CGROUP_OFFLINE, 603 "cgroup %llu going offline", cgroup_id(cgrp)); 604 break; 605 } 606 607 return NOTIFY_OK; 608 } 609 610 static struct notifier_block scx_cgroup_lifetime_nb = { 611 .notifier_call = scx_cgroup_lifetime_notify, 612 }; 613 614 static s32 __init scx_cgroup_lifetime_notifier_init(void) 615 { 616 return blocking_notifier_chain_register(&cgroup_lifetime_notifier, 617 &scx_cgroup_lifetime_nb); 618 } 619 core_initcall(scx_cgroup_lifetime_notifier_init); 620 621 void scx_pstack_recursion_on_dispatch(struct bpf_prog *prog) 622 { 623 struct scx_sched *sch; 624 625 guard(rcu)(); 626 sch = scx_prog_sched(prog->aux); 627 if (unlikely(!sch)) 628 return; 629 630 scx_error(sch, "dispatch recursion detected"); 631 } 632 633 __bpf_kfunc_start_defs(); 634 635 /** 636 * scx_bpf_sub_dispatch - Trigger dispatching on a child scheduler 637 * @cgroup_id: cgroup ID of the child scheduler to dispatch 638 * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs 639 * 640 * Allows a parent scheduler to trigger dispatching on one of its direct 641 * child schedulers. The child scheduler runs its dispatch operation to 642 * move tasks from dispatch queues to the local runqueue. 643 * 644 * Returns: true on success, false if cgroup_id is invalid, not a direct 645 * child, or caller lacks dispatch permission. 646 */ 647 __bpf_kfunc bool scx_bpf_sub_dispatch(u64 cgroup_id, const struct bpf_prog_aux *aux) 648 { 649 struct rq *this_rq = this_rq(); 650 struct scx_sched *parent, *child; 651 652 guard(rcu)(); 653 parent = scx_prog_sched(aux); 654 if (unlikely(!parent)) 655 return false; 656 657 child = scx_find_sub_sched(cgroup_id); 658 659 if (unlikely(!child)) 660 return false; 661 662 if (unlikely(scx_parent(child) != parent)) { 663 scx_error(parent, "trying to dispatch a distant sub-sched on cgroup %llu", 664 cgroup_id); 665 return false; 666 } 667 668 return scx_dispatch_sched(child, this_rq, this_rq->scx.sub_dispatch_prev, 669 true); 670 } 671 672 __bpf_kfunc_end_defs(); 673 674 #endif /* CONFIG_EXT_SUB_SCHED */ 675