diff options
Diffstat (limited to 'src/core/hle/kernel/scheduler.h')
| -rw-r--r-- | src/core/hle/kernel/scheduler.h | 247 |
1 files changed, 146 insertions, 101 deletions
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h index b29bf7be8..fcae28e0a 100644 --- a/src/core/hle/kernel/scheduler.h +++ b/src/core/hle/kernel/scheduler.h | |||
| @@ -20,124 +20,172 @@ namespace Kernel { | |||
| 20 | 20 | ||
| 21 | class Process; | 21 | class Process; |
| 22 | 22 | ||
| 23 | class Scheduler final { | 23 | class GlobalScheduler final { |
| 24 | public: | 24 | public: |
| 25 | explicit Scheduler(Core::System& system, Core::ARM_Interface& cpu_core); | 25 | static constexpr u32 NUM_CPU_CORES = 4; |
| 26 | ~Scheduler(); | ||
| 27 | 26 | ||
| 28 | /// Returns whether there are any threads that are ready to run. | 27 | explicit GlobalScheduler(Core::System& system); |
| 29 | bool HaveReadyThreads() const; | 28 | ~GlobalScheduler(); |
| 29 | /// Adds a new thread to the scheduler | ||
| 30 | void AddThread(SharedPtr<Thread> thread); | ||
| 30 | 31 | ||
| 31 | /// Reschedules to the next available thread (call after current thread is suspended) | 32 | /// Removes a thread from the scheduler |
| 32 | void Reschedule(); | 33 | void RemoveThread(const Thread* thread); |
| 33 | 34 | ||
| 34 | /// Gets the current running thread | 35 | /// Returns a list of all threads managed by the scheduler |
| 35 | Thread* GetCurrentThread() const; | 36 | const std::vector<SharedPtr<Thread>>& GetThreadList() const { |
| 37 | return thread_list; | ||
| 38 | } | ||
| 36 | 39 | ||
| 37 | /// Gets the timestamp for the last context switch in ticks. | 40 | // Add a thread to the suggested queue of a cpu core. Suggested threads may be |
| 38 | u64 GetLastContextSwitchTicks() const; | 41 | // picked if no thread is scheduled to run on the core. |
| 42 | void Suggest(u32 priority, u32 core, Thread* thread); | ||
| 39 | 43 | ||
| 40 | /// Adds a new thread to the scheduler | 44 | // Remove a thread to the suggested queue of a cpu core. Suggested threads may be |
| 41 | void AddThread(SharedPtr<Thread> thread); | 45 | // picked if no thread is scheduled to run on the core. |
| 46 | void Unsuggest(u32 priority, u32 core, Thread* thread); | ||
| 42 | 47 | ||
| 43 | /// Removes a thread from the scheduler | 48 | // Add a thread to the scheduling queue of a cpu core. The thread is added at the |
| 44 | void RemoveThread(Thread* thread); | 49 | // back the queue in its priority level |
| 50 | void Schedule(u32 priority, u32 core, Thread* thread); | ||
| 45 | 51 | ||
| 46 | /// Schedules a thread that has become "ready" | 52 | // Add a thread to the scheduling queue of a cpu core. The thread is added at the |
| 47 | void ScheduleThread(Thread* thread, u32 priority); | 53 | // front the queue in its priority level |
| 54 | void SchedulePrepend(u32 priority, u32 core, Thread* thread); | ||
| 48 | 55 | ||
| 49 | /// Unschedules a thread that was already scheduled | 56 | // Reschedule an already scheduled thread based on a new priority |
| 50 | void UnscheduleThread(Thread* thread, u32 priority); | 57 | void Reschedule(u32 priority, u32 core, Thread* thread); |
| 51 | 58 | ||
| 52 | /// Sets the priority of a thread in the scheduler | 59 | // Unschedule a thread. |
| 53 | void SetThreadPriority(Thread* thread, u32 priority); | 60 | void Unschedule(u32 priority, u32 core, Thread* thread); |
| 54 | 61 | ||
| 55 | /// Gets the next suggested thread for load balancing | 62 | // Transfers a thread into an specific core. If the destination_core is -1 |
| 56 | Thread* GetNextSuggestedThread(u32 core, u32 minimum_priority) const; | 63 | // it will be unscheduled from its source code and added into its suggested |
| 64 | // queue. | ||
| 65 | void TransferToCore(u32 priority, s32 destination_core, Thread* thread); | ||
| 57 | 66 | ||
| 58 | /** | 67 | /* |
| 59 | * YieldWithoutLoadBalancing -- analogous to normal yield on a system | 68 | * UnloadThread selects a core and forces it to unload its current thread's context |
| 60 | * Moves the thread to the end of the ready queue for its priority, and then reschedules the | ||
| 61 | * system to the new head of the queue. | ||
| 62 | * | ||
| 63 | * Example (Single Core -- but can be extrapolated to multi): | ||
| 64 | * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC (->exec order->) | ||
| 65 | * Currently Running: ThreadR | ||
| 66 | * | ||
| 67 | * ThreadR calls YieldWithoutLoadBalancing | ||
| 68 | * | ||
| 69 | * ThreadR is moved to the end of ready_queue[prio=0]: | ||
| 70 | * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC, ThreadR (->exec order->) | ||
| 71 | * Currently Running: Nothing | ||
| 72 | * | ||
| 73 | * System is rescheduled (ThreadA is popped off of queue): | ||
| 74 | * ready_queue[prio=0]: ThreadB, ThreadC, ThreadR (->exec order->) | ||
| 75 | * Currently Running: ThreadA | ||
| 76 | * | ||
| 77 | * If the queue is empty at time of call, no yielding occurs. This does not cross between cores | ||
| 78 | * or priorities at all. | ||
| 79 | */ | 69 | */ |
| 80 | void YieldWithoutLoadBalancing(Thread* thread); | 70 | void UnloadThread(s32 core); |
| 71 | |||
| 72 | /* | ||
| 73 | * SelectThread takes care of selecting the new scheduled thread. | ||
| 74 | * It does it in 3 steps: | ||
| 75 | * - First a thread is selected from the top of the priority queue. If no thread | ||
| 76 | * is obtained then we move to step two, else we are done. | ||
| 77 | * - Second we try to get a suggested thread that's not assigned to any core or | ||
| 78 | * that is not the top thread in that core. | ||
| 79 | * - Third is no suggested thread is found, we do a second pass and pick a running | ||
| 80 | * thread in another core and swap it with its current thread. | ||
| 81 | */ | ||
| 82 | void SelectThread(u32 core); | ||
| 81 | 83 | ||
| 82 | /** | 84 | bool HaveReadyThreads(u32 core_id) const { |
| 83 | * YieldWithLoadBalancing -- yield but with better selection of the new running thread | 85 | return !scheduled_queue[core_id].empty(); |
| 84 | * Moves the current thread to the end of the ready queue for its priority, then selects a | 86 | } |
| 85 | * 'suggested thread' (a thread on a different core that could run on this core) from the | 87 | |
| 86 | * scheduler, changes its core, and reschedules the current core to that thread. | 88 | /* |
| 87 | * | 89 | * YieldThread takes a thread and moves it to the back of the it's priority list |
| 88 | * Example (Dual Core -- can be extrapolated to Quad Core, this is just normal yield if it were | 90 | * This operation can be redundant and no scheduling is changed if marked as so. |
| 89 | * single core): | ||
| 90 | * ready_queue[core=0][prio=0]: ThreadA, ThreadB (affinities not pictured as irrelevant | ||
| 91 | * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only] | ||
| 92 | * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1 | ||
| 93 | * | ||
| 94 | * ThreadQ calls YieldWithLoadBalancing | ||
| 95 | * | ||
| 96 | * ThreadQ is moved to the end of ready_queue[core=0][prio=0]: | ||
| 97 | * ready_queue[core=0][prio=0]: ThreadA, ThreadB | ||
| 98 | * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only] | ||
| 99 | * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1 | ||
| 100 | * | ||
| 101 | * A list of suggested threads for each core is compiled | ||
| 102 | * Suggested Threads: {ThreadC on Core 1} | ||
| 103 | * If this were quad core (as the switch is), there could be between 0 and 3 threads in this | ||
| 104 | * list. If there are more than one, the thread is selected by highest prio. | ||
| 105 | * | ||
| 106 | * ThreadC is core changed to Core 0: | ||
| 107 | * ready_queue[core=0][prio=0]: ThreadC, ThreadA, ThreadB, ThreadQ | ||
| 108 | * ready_queue[core=1][prio=0]: ThreadD | ||
| 109 | * Currently Running: None on Core 0 || ThreadP on Core 1 | ||
| 110 | * | ||
| 111 | * System is rescheduled (ThreadC is popped off of queue): | ||
| 112 | * ready_queue[core=0][prio=0]: ThreadA, ThreadB, ThreadQ | ||
| 113 | * ready_queue[core=1][prio=0]: ThreadD | ||
| 114 | * Currently Running: ThreadC on Core 0 || ThreadP on Core 1 | ||
| 115 | * | ||
| 116 | * If no suggested threads can be found this will behave just as normal yield. If there are | ||
| 117 | * multiple candidates for the suggested thread on a core, the highest prio is taken. | ||
| 118 | */ | 91 | */ |
| 119 | void YieldWithLoadBalancing(Thread* thread); | 92 | bool YieldThread(Thread* thread); |
| 120 | 93 | ||
| 121 | /// Currently unknown -- asserts as unimplemented on call | 94 | /* |
| 122 | void YieldAndWaitForLoadBalancing(Thread* thread); | 95 | * YieldThreadAndBalanceLoad takes a thread and moves it to the back of the it's priority list. |
| 96 | * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or | ||
| 97 | * a better priority than the next thread in the core. | ||
| 98 | * This operation can be redundant and no scheduling is changed if marked as so. | ||
| 99 | */ | ||
| 100 | bool YieldThreadAndBalanceLoad(Thread* thread); | ||
| 123 | 101 | ||
| 124 | /// Returns a list of all threads managed by the scheduler | 102 | /* |
| 125 | const std::vector<SharedPtr<Thread>>& GetThreadList() const { | 103 | * YieldThreadAndWaitForLoadBalancing takes a thread and moves it out of the scheduling queue |
| 126 | return thread_list; | 104 | * and into the suggested queue. If no thread can be squeduled afterwards in that core, |
| 105 | * a suggested thread is obtained instead. | ||
| 106 | * This operation can be redundant and no scheduling is changed if marked as so. | ||
| 107 | */ | ||
| 108 | bool YieldThreadAndWaitForLoadBalancing(Thread* thread); | ||
| 109 | |||
| 110 | /* | ||
| 111 | * PreemptThreads this operation rotates the scheduling queues of threads at | ||
| 112 | * a preemption priority and then does some core rebalancing. Preemption priorities | ||
| 113 | * can be found in the array 'preemption_priorities'. This operation happens | ||
| 114 | * every 10ms. | ||
| 115 | */ | ||
| 116 | void PreemptThreads(); | ||
| 117 | |||
| 118 | u32 CpuCoresCount() const { | ||
| 119 | return NUM_CPU_CORES; | ||
| 120 | } | ||
| 121 | |||
| 122 | void SetReselectionPending() { | ||
| 123 | is_reselection_pending.store(true, std::memory_order_release); | ||
| 127 | } | 124 | } |
| 128 | 125 | ||
| 126 | bool IsReselectionPending() const { | ||
| 127 | return is_reselection_pending.load(std::memory_order_acquire); | ||
| 128 | } | ||
| 129 | |||
| 130 | void Shutdown(); | ||
| 131 | |||
| 129 | private: | 132 | private: |
| 130 | /** | 133 | bool AskForReselectionOrMarkRedundant(Thread* current_thread, Thread* winner); |
| 131 | * Pops and returns the next thread from the thread queue | 134 | |
| 132 | * @return A pointer to the next ready thread | 135 | static constexpr u32 min_regular_priority = 2; |
| 133 | */ | 136 | std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, NUM_CPU_CORES> scheduled_queue; |
| 134 | Thread* PopNextReadyThread(); | 137 | std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, NUM_CPU_CORES> suggested_queue; |
| 138 | std::atomic<bool> is_reselection_pending; | ||
| 139 | |||
| 140 | // `preemption_priorities` are the priority levels at which the global scheduler | ||
| 141 | // preempts threads every 10 ms. They are ordered from Core 0 to Core 3 | ||
| 142 | std::array<u32, NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62}; | ||
| 143 | |||
| 144 | /// Lists all thread ids that aren't deleted/etc. | ||
| 145 | std::vector<SharedPtr<Thread>> thread_list; | ||
| 146 | Core::System& system; | ||
| 147 | }; | ||
| 148 | |||
| 149 | class Scheduler final { | ||
| 150 | public: | ||
| 151 | explicit Scheduler(Core::System& system, Core::ARM_Interface& cpu_core, u32 core_id); | ||
| 152 | ~Scheduler(); | ||
| 153 | |||
| 154 | /// Returns whether there are any threads that are ready to run. | ||
| 155 | bool HaveReadyThreads() const; | ||
| 135 | 156 | ||
| 157 | /// Reschedules to the next available thread (call after current thread is suspended) | ||
| 158 | void TryDoContextSwitch(); | ||
| 159 | |||
| 160 | /// Unloads currently running thread | ||
| 161 | void UnloadThread(); | ||
| 162 | |||
| 163 | /// Select the threads in top of the scheduling multilist. | ||
| 164 | void SelectThreads(); | ||
| 165 | |||
| 166 | /// Gets the current running thread | ||
| 167 | Thread* GetCurrentThread() const; | ||
| 168 | |||
| 169 | /// Gets the currently selected thread from the top of the multilevel queue | ||
| 170 | Thread* GetSelectedThread() const; | ||
| 171 | |||
| 172 | /// Gets the timestamp for the last context switch in ticks. | ||
| 173 | u64 GetLastContextSwitchTicks() const; | ||
| 174 | |||
| 175 | bool ContextSwitchPending() const { | ||
| 176 | return is_context_switch_pending; | ||
| 177 | } | ||
| 178 | |||
| 179 | /// Shutdowns the scheduler. | ||
| 180 | void Shutdown(); | ||
| 181 | |||
| 182 | private: | ||
| 183 | friend class GlobalScheduler; | ||
| 136 | /** | 184 | /** |
| 137 | * Switches the CPU's active thread context to that of the specified thread | 185 | * Switches the CPU's active thread context to that of the specified thread |
| 138 | * @param new_thread The thread to switch to | 186 | * @param new_thread The thread to switch to |
| 139 | */ | 187 | */ |
| 140 | void SwitchContext(Thread* new_thread); | 188 | void SwitchContext(); |
| 141 | 189 | ||
| 142 | /** | 190 | /** |
| 143 | * Called on every context switch to update the internal timestamp | 191 | * Called on every context switch to update the internal timestamp |
| @@ -152,19 +200,16 @@ private: | |||
| 152 | */ | 200 | */ |
| 153 | void UpdateLastContextSwitchTime(Thread* thread, Process* process); | 201 | void UpdateLastContextSwitchTime(Thread* thread, Process* process); |
| 154 | 202 | ||
| 155 | /// Lists all thread ids that aren't deleted/etc. | ||
| 156 | std::vector<SharedPtr<Thread>> thread_list; | ||
| 157 | |||
| 158 | /// Lists only ready thread ids. | ||
| 159 | Common::MultiLevelQueue<Thread*, THREADPRIO_LOWEST + 1> ready_queue; | ||
| 160 | |||
| 161 | SharedPtr<Thread> current_thread = nullptr; | 203 | SharedPtr<Thread> current_thread = nullptr; |
| 204 | SharedPtr<Thread> selected_thread = nullptr; | ||
| 162 | 205 | ||
| 206 | Core::System& system; | ||
| 163 | Core::ARM_Interface& cpu_core; | 207 | Core::ARM_Interface& cpu_core; |
| 164 | u64 last_context_switch_time = 0; | 208 | u64 last_context_switch_time = 0; |
| 209 | u64 idle_selection_count = 0; | ||
| 210 | const u32 core_id; | ||
| 165 | 211 | ||
| 166 | Core::System& system; | 212 | bool is_context_switch_pending = false; |
| 167 | static std::mutex scheduler_mutex; | ||
| 168 | }; | 213 | }; |
| 169 | 214 | ||
| 170 | } // namespace Kernel | 215 | } // namespace Kernel |