mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu.git
				synced 2025-05-12 00:45:25 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									a70ed9c8ae
								
							
						
					
					
						commit
						46572d027d
					
				| @ -18,13 +18,13 @@ namespace Kernel { | |||||||
| 
 | 
 | ||||||
| /// Returns the number of threads that are waiting for a mutex, and the highest priority one among
 | /// Returns the number of threads that are waiting for a mutex, and the highest priority one among
 | ||||||
| /// those.
 | /// those.
 | ||||||
| static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(VAddr mutex_addr) { | static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread( | ||||||
|     auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList(); |     SharedPtr<Thread> current_thread, VAddr mutex_addr) { | ||||||
| 
 | 
 | ||||||
|     SharedPtr<Thread> highest_priority_thread; |     SharedPtr<Thread> highest_priority_thread; | ||||||
|     u32 num_waiters = 0; |     u32 num_waiters = 0; | ||||||
| 
 | 
 | ||||||
|     for (auto& thread : thread_list) { |     for (auto& thread : current_thread->wait_mutex_threads) { | ||||||
|         if (thread->mutex_wait_address != mutex_addr) |         if (thread->mutex_wait_address != mutex_addr) | ||||||
|             continue; |             continue; | ||||||
| 
 | 
 | ||||||
| @ -40,6 +40,21 @@ static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(VA | |||||||
|     return {highest_priority_thread, num_waiters}; |     return {highest_priority_thread, num_waiters}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
 | ||||||
|  | static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread, | ||||||
|  |                                    SharedPtr<Thread> new_owner) { | ||||||
|  |     auto threads = current_thread->wait_mutex_threads; | ||||||
|  |     for (auto& thread : threads) { | ||||||
|  |         if (thread->mutex_wait_address != mutex_addr) | ||||||
|  |             continue; | ||||||
|  | 
 | ||||||
|  |         ASSERT(thread->lock_owner == current_thread); | ||||||
|  |         current_thread->RemoveMutexWaiter(thread); | ||||||
|  |         if (new_owner != thread) | ||||||
|  |             new_owner->AddMutexWaiter(thread); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle, | ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle, | ||||||
|                              Handle requesting_thread_handle) { |                              Handle requesting_thread_handle) { | ||||||
|     // The mutex address must be 4-byte aligned
 |     // The mutex address must be 4-byte aligned
 | ||||||
| @ -65,11 +80,14 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle, | |||||||
|         return ERR_INVALID_HANDLE; |         return ERR_INVALID_HANDLE; | ||||||
| 
 | 
 | ||||||
|     // Wait until the mutex is released
 |     // Wait until the mutex is released
 | ||||||
|     requesting_thread->mutex_wait_address = address; |     GetCurrentThread()->mutex_wait_address = address; | ||||||
|     requesting_thread->wait_handle = requesting_thread_handle; |     GetCurrentThread()->wait_handle = requesting_thread_handle; | ||||||
| 
 | 
 | ||||||
|     requesting_thread->status = THREADSTATUS_WAIT_MUTEX; |     GetCurrentThread()->status = THREADSTATUS_WAIT_MUTEX; | ||||||
|     requesting_thread->wakeup_callback = nullptr; |     GetCurrentThread()->wakeup_callback = nullptr; | ||||||
|  | 
 | ||||||
|  |     // Update the lock holder thread's priority to prevent priority inversion.
 | ||||||
|  |     holding_thread->AddMutexWaiter(GetCurrentThread()); | ||||||
| 
 | 
 | ||||||
|     Core::System::GetInstance().PrepareReschedule(); |     Core::System::GetInstance().PrepareReschedule(); | ||||||
| 
 | 
 | ||||||
| @ -82,14 +100,18 @@ ResultCode Mutex::Release(VAddr address) { | |||||||
|         return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress); |         return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(address); |     auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address); | ||||||
| 
 | 
 | ||||||
|     // There are no more threads waiting for the mutex, release it completely.
 |     // There are no more threads waiting for the mutex, release it completely.
 | ||||||
|     if (thread == nullptr) { |     if (thread == nullptr) { | ||||||
|  |         ASSERT(GetCurrentThread()->wait_mutex_threads.empty()); | ||||||
|         Memory::Write32(address, 0); |         Memory::Write32(address, 0); | ||||||
|         return RESULT_SUCCESS; |         return RESULT_SUCCESS; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Transfer the ownership of the mutex from the previous owner to the new one.
 | ||||||
|  |     TransferMutexOwnership(address, GetCurrentThread(), thread); | ||||||
|  | 
 | ||||||
|     u32 mutex_value = thread->wait_handle; |     u32 mutex_value = thread->wait_handle; | ||||||
| 
 | 
 | ||||||
|     if (num_waiters >= 2) { |     if (num_waiters >= 2) { | ||||||
| @ -103,6 +125,7 @@ ResultCode Mutex::Release(VAddr address) { | |||||||
|     ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX); |     ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX); | ||||||
|     thread->ResumeFromWait(); |     thread->ResumeFromWait(); | ||||||
| 
 | 
 | ||||||
|  |     thread->lock_owner = nullptr; | ||||||
|     thread->condvar_wait_address = 0; |     thread->condvar_wait_address = 0; | ||||||
|     thread->mutex_wait_address = 0; |     thread->mutex_wait_address = 0; | ||||||
|     thread->wait_handle = 0; |     thread->wait_handle = 0; | ||||||
|  | |||||||
| @ -621,6 +621,8 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var | |||||||
| 
 | 
 | ||||||
|     current_thread->WakeAfterDelay(nano_seconds); |     current_thread->WakeAfterDelay(nano_seconds); | ||||||
| 
 | 
 | ||||||
|  |     // Note: Deliberately don't attempt to inherit the lock owner's priority.
 | ||||||
|  | 
 | ||||||
|     Core::System::GetInstance().PrepareReschedule(); |     Core::System::GetInstance().PrepareReschedule(); | ||||||
|     return RESULT_SUCCESS; |     return RESULT_SUCCESS; | ||||||
| } | } | ||||||
| @ -651,6 +653,11 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target | |||||||
|             ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX); |             ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX); | ||||||
|             thread->ResumeFromWait(); |             thread->ResumeFromWait(); | ||||||
| 
 | 
 | ||||||
|  |             auto lock_owner = thread->lock_owner; | ||||||
|  |             if (lock_owner) | ||||||
|  |                 lock_owner->RemoveMutexWaiter(thread); | ||||||
|  | 
 | ||||||
|  |             thread->lock_owner = nullptr; | ||||||
|             thread->mutex_wait_address = 0; |             thread->mutex_wait_address = 0; | ||||||
|             thread->condvar_wait_address = 0; |             thread->condvar_wait_address = 0; | ||||||
|             thread->wait_handle = 0; |             thread->wait_handle = 0; | ||||||
| @ -666,6 +673,8 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target | |||||||
|             // Signal that the mutex now has a waiting thread.
 |             // Signal that the mutex now has a waiting thread.
 | ||||||
|             Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag); |             Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag); | ||||||
| 
 | 
 | ||||||
|  |             owner->AddMutexWaiter(thread); | ||||||
|  | 
 | ||||||
|             Core::System::GetInstance().PrepareReschedule(); |             Core::System::GetInstance().PrepareReschedule(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -129,6 +129,11 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { | |||||||
|         thread->mutex_wait_address = 0; |         thread->mutex_wait_address = 0; | ||||||
|         thread->condvar_wait_address = 0; |         thread->condvar_wait_address = 0; | ||||||
|         thread->wait_handle = 0; |         thread->wait_handle = 0; | ||||||
|  | 
 | ||||||
|  |         auto lock_owner = thread->lock_owner; | ||||||
|  |         // Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
 | ||||||
|  |         // and don't have a lock owner.
 | ||||||
|  |         ASSERT(lock_owner == nullptr); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (resume) |     if (resume) | ||||||
| @ -325,8 +330,8 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, | |||||||
| void Thread::SetPriority(u32 priority) { | void Thread::SetPriority(u32 priority) { | ||||||
|     ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, |     ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, | ||||||
|                "Invalid priority value."); |                "Invalid priority value."); | ||||||
|     Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority); |     nominal_priority = priority; | ||||||
|     nominal_priority = current_priority = priority; |     UpdatePriority(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Thread::BoostPriority(u32 priority) { | void Thread::BoostPriority(u32 priority) { | ||||||
| @ -376,6 +381,38 @@ VAddr Thread::GetCommandBufferAddress() const { | |||||||
|     return GetTLSAddress() + CommandHeaderOffset; |     return GetTLSAddress() + CommandHeaderOffset; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void Thread::AddMutexWaiter(SharedPtr<Thread> thread) { | ||||||
|  |     thread->lock_owner = this; | ||||||
|  |     wait_mutex_threads.emplace_back(std::move(thread)); | ||||||
|  |     UpdatePriority(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) { | ||||||
|  |     boost::remove_erase(wait_mutex_threads, thread); | ||||||
|  |     thread->lock_owner = nullptr; | ||||||
|  |     UpdatePriority(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Thread::UpdatePriority() { | ||||||
|  |     // Find the highest priority among all the threads that are waiting for this thread's lock
 | ||||||
|  |     u32 new_priority = nominal_priority; | ||||||
|  |     for (const auto& thread : wait_mutex_threads) { | ||||||
|  |         if (thread->nominal_priority < new_priority) | ||||||
|  |             new_priority = thread->nominal_priority; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (new_priority == current_priority) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     Core::System::GetInstance().Scheduler().SetThreadPriority(this, new_priority); | ||||||
|  | 
 | ||||||
|  |     current_priority = new_priority; | ||||||
|  | 
 | ||||||
|  |     // Recursively update the priority of the thread that depends on the priority of this one.
 | ||||||
|  |     if (lock_owner) | ||||||
|  |         lock_owner->UpdatePriority(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  | |||||||
| @ -109,6 +109,15 @@ public: | |||||||
|      */ |      */ | ||||||
|     void BoostPriority(u32 priority); |     void BoostPriority(u32 priority); | ||||||
| 
 | 
 | ||||||
|  |     /// Adds a thread to the list of threads that are waiting for a lock held by this thread.
 | ||||||
|  |     void AddMutexWaiter(SharedPtr<Thread> thread); | ||||||
|  | 
 | ||||||
|  |     /// Removes a thread from the list of threads that are waiting for a lock held by this thread.
 | ||||||
|  |     void RemoveMutexWaiter(SharedPtr<Thread> thread); | ||||||
|  | 
 | ||||||
|  |     /// Recalculates the current priority taking into account priority inheritance.
 | ||||||
|  |     void UpdatePriority(); | ||||||
|  | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Gets the thread's thread ID |      * Gets the thread's thread ID | ||||||
|      * @return The thread's ID |      * @return The thread's ID | ||||||
| @ -205,6 +214,12 @@ public: | |||||||
|     // passed to WaitSynchronization1/N.
 |     // passed to WaitSynchronization1/N.
 | ||||||
|     std::vector<SharedPtr<WaitObject>> wait_objects; |     std::vector<SharedPtr<WaitObject>> wait_objects; | ||||||
| 
 | 
 | ||||||
|  |     /// List of threads that are waiting for a mutex that is held by this thread.
 | ||||||
|  |     std::vector<SharedPtr<Thread>> wait_mutex_threads; | ||||||
|  | 
 | ||||||
|  |     /// Thread that owns the lock that this thread is waiting for.
 | ||||||
|  |     SharedPtr<Thread> lock_owner; | ||||||
|  | 
 | ||||||
|     // If waiting on a ConditionVariable, this is the ConditionVariable  address
 |     // If waiting on a ConditionVariable, this is the ConditionVariable  address
 | ||||||
|     VAddr condvar_wait_address; |     VAddr condvar_wait_address; | ||||||
|     VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address
 |     VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Subv
						Subv