| 1 | #include "internal.h" |
| 2 | #include <thread/apc.h> |
| 3 | |
| 4 | /* file implements `thread_migrate`. |
| 5 | * |
| 6 | * NOTE: when we acquire two `struct scheduler` locks, |
| 7 | * we ALWAYS ALWAYS acquire the lock for the LOWER |
| 8 | * memory address first, same for unlock, |
| 9 | * and we ALWAYS acquire thread |
| 10 | * locks AFTER any scheduler locks! */ |
| 11 | |
| 12 | /* |
| 13 | * # Small Idea: Thread being_moved locking |
| 14 | * |
| 15 | * ## Credits: gummi |
| 16 | * |
| 17 | * ## Context: This flag is used to ensure that everyone reads an accurate |
| 18 | * `last_ran` field on a thread. Basically, READY threads can be |
| 19 | * arbitrarily migrated at any time if it is possible. All |
| 20 | * migrations must occur whilst both scheduler locks are held. |
| 21 | * |
| 22 | * Threads also have to have their core's scheduler lock |
| 23 | * acquired when the being_moved is being locked. |
| 24 | * |
| 25 | * ## Problem: We need to guarantee that this lock is used appropriately |
| 26 | * alongside thread flags to make sure that an invalid |
| 27 | * last_ran field is NEVER read. This must be done |
| 28 | * because if an invalid last_ran field is read, we will |
| 29 | * lock the WRONG scheduler and can cause catastrophic races. |
| 30 | * |
| 31 | * ## Strategy: Upon any thread migration within the scheduler, we spin |
| 32 | * on the lock and try to set it. Upon any read of the |
| 33 | * thread's last_ran field, we spin on the lock and wait |
| 34 | * for it to become free but never set it. Prior to any |
| 35 | * spin_raw on the lock from outside the scheduler, |
| 36 | * we ALWAYS set the thread as NO_STEAL so that it cannot |
| 37 | * possibly be migrated after the lock is available. |
| 38 | * |
| 39 | * Every time we read the flags to see if the thread can |
| 40 | * be migrated, we must hold the lock prior to reading the |
| 41 | * flags, and release the lock if we are able to migrate |
| 42 | * the thread and only after we set last_ran. |
| 43 | * |
| 44 | * ## Changelog: |
| 45 | * 12/4/2025 - gummi - Created Idea |
| 46 | * |
| 47 | * |
| 48 | */ |
| 49 | |
| 50 | void thread_post_migrate(struct thread *t, size_t old_cpu, size_t new_cpu) { |
| 51 | /* assert that both scheduler/runqueue locks are held, |
| 52 | * migrate relevant things */ |
| 53 | SPINLOCK_ASSERT_HELD(&global.schedulers[old_cpu]->lock); |
| 54 | SPINLOCK_ASSERT_HELD(&global.schedulers[new_cpu]->lock); |
| 55 | climb_post_migrate_hook(t, old_cpu, new_cpu); |
| 56 | } |
| 57 | |
| 58 | void thread_migrate(struct thread *t, size_t dest_core) { |
| 59 | /* first acquire both the lock of the thread's scheduler |
| 60 | * and the destination core's scheduler */ |
| 61 | |
| 62 | enum irql sirql, dirql, tirql; |
| 63 | |
| 64 | struct scheduler *src; |
| 65 | struct scheduler *dst = global.schedulers[dest_core]; |
| 66 | |
| 67 | thread_lock_thread_and_rq(t, other_rq: dst, out_thread_rq: &src, irq_first: &sirql, irq_second: &dirql); |
| 68 | |
| 69 | if (src == dst) { |
| 70 | thread_unlock_thread_and_rq(thread_rq: src, other_rq: dst, irq_first: sirql, irq_second: dirql); |
| 71 | return; |
| 72 | } |
| 73 | |
| 74 | /* now that we have acquired both scheduler locks, |
| 75 | * we have control over the thread and can |
| 76 | * unmark it as NO_STEAL */ |
| 77 | |
| 78 | bool ok; |
| 79 | tirql = thread_acquire(t, success: &ok); |
| 80 | if (!ok) { |
| 81 | goto end; |
| 82 | } |
| 83 | |
| 84 | /* bro cannot be migrated */ |
| 85 | if (!scheduler_can_take_thread(core: dest_core, target: t)) { |
| 86 | goto out; |
| 87 | } |
| 88 | |
| 89 | /* finally, we can do something here. */ |
| 90 | if (thread_get_state(t) == THREAD_STATE_RUNNING || |
| 91 | !(thread_get_flags(t) & THREAD_FLAG_YIELDED)) { |
| 92 | thread_set_migration_target(t, new: dest_core); |
| 93 | scheduler_force_resched(sched: dst); |
| 94 | } else if (thread_get_state(t) == THREAD_STATE_READY) { |
| 95 | scheduler_remove_thread(sched: src, t, /* lock_held = */ true); |
| 96 | scheduler_add_thread(sched: dst, thread: t, /* lock_held = */ true); |
| 97 | } |
| 98 | |
| 99 | thread_set_runqueue(t, s: dst); |
| 100 | |
| 101 | out: |
| 102 | |
| 103 | thread_release(t, irql: tirql); |
| 104 | |
| 105 | end: |
| 106 | thread_unlock_thread_and_rq(thread_rq: src, other_rq: dst, irq_first: sirql, irq_second: dirql); |
| 107 | } |
| 108 | |