diff options
| author | 2018-04-20 20:15:16 -0500 | |
|---|---|---|
| committer | 2018-04-23 11:23:44 -0500 | |
| commit | 46572d027dc9620ed2b2a50277e6afd2a115ab81 (patch) | |
| tree | 72562a37575252e8f4c0160a3067b415027fdf4b /src/core/hle/kernel | |
| parent | Kernel: Use 0x2C as default main thread priority for homebrew and lone NRO/NSOs (diff) | |
| download | yuzu-46572d027dc9620ed2b2a50277e6afd2a115ab81.tar.gz yuzu-46572d027dc9620ed2b2a50277e6afd2a115ab81.tar.xz yuzu-46572d027dc9620ed2b2a50277e6afd2a115ab81.zip | |
Kernel: Implemented mutex priority inheritance.
Verified with a hwtest and implemented based on reverse engineering.
Thread A's priority will get bumped to the highest priority among all the threads that are waiting for a mutex that A holds.
Once A releases the mutex and ownership is transferred to B, A's priority will return to normal and B's priority will be bumped.
Diffstat (limited to 'src/core/hle/kernel')
| -rw-r--r-- | src/core/hle/kernel/mutex.cpp | 39 | ||||
| -rw-r--r-- | src/core/hle/kernel/svc.cpp | 9 | ||||
| -rw-r--r-- | src/core/hle/kernel/thread.cpp | 41 | ||||
| -rw-r--r-- | src/core/hle/kernel/thread.h | 15 |
4 files changed, 94 insertions, 10 deletions
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 5cc0bd266..63733ad79 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp | |||
| @@ -18,13 +18,13 @@ namespace Kernel { | |||
| 18 | 18 | ||
| 19 | /// Returns the number of threads that are waiting for a mutex, and the highest priority one among | 19 | /// Returns the number of threads that are waiting for a mutex, and the highest priority one among |
| 20 | /// those. | 20 | /// those. |
| 21 | static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(VAddr mutex_addr) { | 21 | static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread( |
| 22 | auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList(); | 22 | SharedPtr<Thread> current_thread, VAddr mutex_addr) { |
| 23 | 23 | ||
| 24 | SharedPtr<Thread> highest_priority_thread; | 24 | SharedPtr<Thread> highest_priority_thread; |
| 25 | u32 num_waiters = 0; | 25 | u32 num_waiters = 0; |
| 26 | 26 | ||
| 27 | for (auto& thread : thread_list) { | 27 | for (auto& thread : current_thread->wait_mutex_threads) { |
| 28 | if (thread->mutex_wait_address != mutex_addr) | 28 | if (thread->mutex_wait_address != mutex_addr) |
| 29 | continue; | 29 | continue; |
| 30 | 30 | ||
| @@ -40,6 +40,21 @@ static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(VA | |||
| 40 | return {highest_priority_thread, num_waiters}; | 40 | return {highest_priority_thread, num_waiters}; |
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | /// Update the mutex owner field of all threads waiting on the mutex to point to the new owner. | ||
| 44 | static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread, | ||
| 45 | SharedPtr<Thread> new_owner) { | ||
| 46 | auto threads = current_thread->wait_mutex_threads; | ||
| 47 | for (auto& thread : threads) { | ||
| 48 | if (thread->mutex_wait_address != mutex_addr) | ||
| 49 | continue; | ||
| 50 | |||
| 51 | ASSERT(thread->lock_owner == current_thread); | ||
| 52 | current_thread->RemoveMutexWaiter(thread); | ||
| 53 | if (new_owner != thread) | ||
| 54 | new_owner->AddMutexWaiter(thread); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 43 | ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle, | 58 | ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle, |
| 44 | Handle requesting_thread_handle) { | 59 | Handle requesting_thread_handle) { |
| 45 | // The mutex address must be 4-byte aligned | 60 | // The mutex address must be 4-byte aligned |
| @@ -65,11 +80,14 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle, | |||
| 65 | return ERR_INVALID_HANDLE; | 80 | return ERR_INVALID_HANDLE; |
| 66 | 81 | ||
| 67 | // Wait until the mutex is released | 82 | // Wait until the mutex is released |
| 68 | requesting_thread->mutex_wait_address = address; | 83 | GetCurrentThread()->mutex_wait_address = address; |
| 69 | requesting_thread->wait_handle = requesting_thread_handle; | 84 | GetCurrentThread()->wait_handle = requesting_thread_handle; |
| 70 | 85 | ||
| 71 | requesting_thread->status = THREADSTATUS_WAIT_MUTEX; | 86 | GetCurrentThread()->status = THREADSTATUS_WAIT_MUTEX; |
| 72 | requesting_thread->wakeup_callback = nullptr; | 87 | GetCurrentThread()->wakeup_callback = nullptr; |
| 88 | |||
| 89 | // Update the lock holder thread's priority to prevent priority inversion. | ||
| 90 | holding_thread->AddMutexWaiter(GetCurrentThread()); | ||
| 73 | 91 | ||
| 74 | Core::System::GetInstance().PrepareReschedule(); | 92 | Core::System::GetInstance().PrepareReschedule(); |
| 75 | 93 | ||
| @@ -82,14 +100,18 @@ ResultCode Mutex::Release(VAddr address) { | |||
| 82 | return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress); | 100 | return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress); |
| 83 | } | 101 | } |
| 84 | 102 | ||
| 85 | auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(address); | 103 | auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address); |
| 86 | 104 | ||
| 87 | // There are no more threads waiting for the mutex, release it completely. | 105 | // There are no more threads waiting for the mutex, release it completely. |
| 88 | if (thread == nullptr) { | 106 | if (thread == nullptr) { |
| 107 | ASSERT(GetCurrentThread()->wait_mutex_threads.empty()); | ||
| 89 | Memory::Write32(address, 0); | 108 | Memory::Write32(address, 0); |
| 90 | return RESULT_SUCCESS; | 109 | return RESULT_SUCCESS; |
| 91 | } | 110 | } |
| 92 | 111 | ||
| 112 | // Transfer the ownership of the mutex from the previous owner to the new one. | ||
| 113 | TransferMutexOwnership(address, GetCurrentThread(), thread); | ||
| 114 | |||
| 93 | u32 mutex_value = thread->wait_handle; | 115 | u32 mutex_value = thread->wait_handle; |
| 94 | 116 | ||
| 95 | if (num_waiters >= 2) { | 117 | if (num_waiters >= 2) { |
| @@ -103,6 +125,7 @@ ResultCode Mutex::Release(VAddr address) { | |||
| 103 | ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX); | 125 | ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX); |
| 104 | thread->ResumeFromWait(); | 126 | thread->ResumeFromWait(); |
| 105 | 127 | ||
| 128 | thread->lock_owner = nullptr; | ||
| 106 | thread->condvar_wait_address = 0; | 129 | thread->condvar_wait_address = 0; |
| 107 | thread->mutex_wait_address = 0; | 130 | thread->mutex_wait_address = 0; |
| 108 | thread->wait_handle = 0; | 131 | thread->wait_handle = 0; |
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 082c36caf..a3015cf7a 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp | |||
| @@ -621,6 +621,8 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var | |||
| 621 | 621 | ||
| 622 | current_thread->WakeAfterDelay(nano_seconds); | 622 | current_thread->WakeAfterDelay(nano_seconds); |
| 623 | 623 | ||
| 624 | // Note: Deliberately don't attempt to inherit the lock owner's priority. | ||
| 625 | |||
| 624 | Core::System::GetInstance().PrepareReschedule(); | 626 | Core::System::GetInstance().PrepareReschedule(); |
| 625 | return RESULT_SUCCESS; | 627 | return RESULT_SUCCESS; |
| 626 | } | 628 | } |
| @@ -651,6 +653,11 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target | |||
| 651 | ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX); | 653 | ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX); |
| 652 | thread->ResumeFromWait(); | 654 | thread->ResumeFromWait(); |
| 653 | 655 | ||
| 656 | auto lock_owner = thread->lock_owner; | ||
| 657 | if (lock_owner) | ||
| 658 | lock_owner->RemoveMutexWaiter(thread); | ||
| 659 | |||
| 660 | thread->lock_owner = nullptr; | ||
| 654 | thread->mutex_wait_address = 0; | 661 | thread->mutex_wait_address = 0; |
| 655 | thread->condvar_wait_address = 0; | 662 | thread->condvar_wait_address = 0; |
| 656 | thread->wait_handle = 0; | 663 | thread->wait_handle = 0; |
| @@ -666,6 +673,8 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target | |||
| 666 | // Signal that the mutex now has a waiting thread. | 673 | // Signal that the mutex now has a waiting thread. |
| 667 | Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag); | 674 | Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag); |
| 668 | 675 | ||
| 676 | owner->AddMutexWaiter(thread); | ||
| 677 | |||
| 669 | Core::System::GetInstance().PrepareReschedule(); | 678 | Core::System::GetInstance().PrepareReschedule(); |
| 670 | } | 679 | } |
| 671 | 680 | ||
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 16d9b9e36..36222d45f 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp | |||
| @@ -129,6 +129,11 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { | |||
| 129 | thread->mutex_wait_address = 0; | 129 | thread->mutex_wait_address = 0; |
| 130 | thread->condvar_wait_address = 0; | 130 | thread->condvar_wait_address = 0; |
| 131 | thread->wait_handle = 0; | 131 | thread->wait_handle = 0; |
| 132 | |||
| 133 | auto lock_owner = thread->lock_owner; | ||
| 134 | // Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance | ||
| 135 | // and don't have a lock owner. | ||
| 136 | ASSERT(lock_owner == nullptr); | ||
| 132 | } | 137 | } |
| 133 | 138 | ||
| 134 | if (resume) | 139 | if (resume) |
| @@ -325,8 +330,8 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, | |||
| 325 | void Thread::SetPriority(u32 priority) { | 330 | void Thread::SetPriority(u32 priority) { |
| 326 | ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, | 331 | ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, |
| 327 | "Invalid priority value."); | 332 | "Invalid priority value."); |
| 328 | Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority); | 333 | nominal_priority = priority; |
| 329 | nominal_priority = current_priority = priority; | 334 | UpdatePriority(); |
| 330 | } | 335 | } |
| 331 | 336 | ||
| 332 | void Thread::BoostPriority(u32 priority) { | 337 | void Thread::BoostPriority(u32 priority) { |
| @@ -376,6 +381,38 @@ VAddr Thread::GetCommandBufferAddress() const { | |||
| 376 | return GetTLSAddress() + CommandHeaderOffset; | 381 | return GetTLSAddress() + CommandHeaderOffset; |
| 377 | } | 382 | } |
| 378 | 383 | ||
| 384 | void Thread::AddMutexWaiter(SharedPtr<Thread> thread) { | ||
| 385 | thread->lock_owner = this; | ||
| 386 | wait_mutex_threads.emplace_back(std::move(thread)); | ||
| 387 | UpdatePriority(); | ||
| 388 | } | ||
| 389 | |||
| 390 | void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) { | ||
| 391 | boost::remove_erase(wait_mutex_threads, thread); | ||
| 392 | thread->lock_owner = nullptr; | ||
| 393 | UpdatePriority(); | ||
| 394 | } | ||
| 395 | |||
| 396 | void Thread::UpdatePriority() { | ||
| 397 | // Find the highest priority among all the threads that are waiting for this thread's lock | ||
| 398 | u32 new_priority = nominal_priority; | ||
| 399 | for (const auto& thread : wait_mutex_threads) { | ||
| 400 | if (thread->nominal_priority < new_priority) | ||
| 401 | new_priority = thread->nominal_priority; | ||
| 402 | } | ||
| 403 | |||
| 404 | if (new_priority == current_priority) | ||
| 405 | return; | ||
| 406 | |||
| 407 | Core::System::GetInstance().Scheduler().SetThreadPriority(this, new_priority); | ||
| 408 | |||
| 409 | current_priority = new_priority; | ||
| 410 | |||
| 411 | // Recursively update the priority of the thread that depends on the priority of this one. | ||
| 412 | if (lock_owner) | ||
| 413 | lock_owner->UpdatePriority(); | ||
| 414 | } | ||
| 415 | |||
| 379 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 416 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 380 | 417 | ||
| 381 | /** | 418 | /** |
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 0e0eae28d..e0a3c0934 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h | |||
| @@ -109,6 +109,15 @@ public: | |||
| 109 | */ | 109 | */ |
| 110 | void BoostPriority(u32 priority); | 110 | void BoostPriority(u32 priority); |
| 111 | 111 | ||
| 112 | /// Adds a thread to the list of threads that are waiting for a lock held by this thread. | ||
| 113 | void AddMutexWaiter(SharedPtr<Thread> thread); | ||
| 114 | |||
| 115 | /// Removes a thread from the list of threads that are waiting for a lock held by this thread. | ||
| 116 | void RemoveMutexWaiter(SharedPtr<Thread> thread); | ||
| 117 | |||
| 118 | /// Recalculates the current priority taking into account priority inheritance. | ||
| 119 | void UpdatePriority(); | ||
| 120 | |||
| 112 | /** | 121 | /** |
| 113 | * Gets the thread's thread ID | 122 | * Gets the thread's thread ID |
| 114 | * @return The thread's ID | 123 | * @return The thread's ID |
| @@ -205,6 +214,12 @@ public: | |||
| 205 | // passed to WaitSynchronization1/N. | 214 | // passed to WaitSynchronization1/N. |
| 206 | std::vector<SharedPtr<WaitObject>> wait_objects; | 215 | std::vector<SharedPtr<WaitObject>> wait_objects; |
| 207 | 216 | ||
| 217 | /// List of threads that are waiting for a mutex that is held by this thread. | ||
| 218 | std::vector<SharedPtr<Thread>> wait_mutex_threads; | ||
| 219 | |||
| 220 | /// Thread that owns the lock that this thread is waiting for. | ||
| 221 | SharedPtr<Thread> lock_owner; | ||
| 222 | |||
| 208 | // If waiting on a ConditionVariable, this is the ConditionVariable address | 223 | // If waiting on a ConditionVariable, this is the ConditionVariable address |
| 209 | VAddr condvar_wait_address; | 224 | VAddr condvar_wait_address; |
| 210 | VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address | 225 | VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address |