core: hle: kernel: k_process: Implement thread local storage accurately.

This commit is contained in:
bunnei 2022-03-11 17:14:17 -08:00
parent 3210bc2767
commit 813b2ef253
3 changed files with 99 additions and 111 deletions

View file

@ -70,58 +70,6 @@ void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority
} }
} // Anonymous namespace } // Anonymous namespace
// Represents a page used for thread-local storage.
//
// Each TLS page contains slots that may be used by processes and threads.
// Every process and thread is created with a slot in some arbitrary page
// (whichever page happens to have an available slot).
class TLSPage {
public:
static constexpr std::size_t num_slot_entries =
Core::Memory::PAGE_SIZE / Core::Memory::TLS_ENTRY_SIZE;
explicit TLSPage(VAddr address) : base_address{address} {}
bool HasAvailableSlots() const {
return !is_slot_used.all();
}
VAddr GetBaseAddress() const {
return base_address;
}
std::optional<VAddr> ReserveSlot() {
for (std::size_t i = 0; i < is_slot_used.size(); i++) {
if (is_slot_used[i]) {
continue;
}
is_slot_used[i] = true;
return base_address + (i * Core::Memory::TLS_ENTRY_SIZE);
}
return std::nullopt;
}
void ReleaseSlot(VAddr address) {
// Ensure that all given addresses are consistent with how TLS pages
// are intended to be used when releasing slots.
ASSERT(IsWithinPage(address));
ASSERT((address % Core::Memory::TLS_ENTRY_SIZE) == 0);
const std::size_t index = (address - base_address) / Core::Memory::TLS_ENTRY_SIZE;
is_slot_used[index] = false;
}
private:
bool IsWithinPage(VAddr address) const {
return base_address <= address && address < base_address + Core::Memory::PAGE_SIZE;
}
VAddr base_address;
std::bitset<num_slot_entries> is_slot_used;
};
ResultCode KProcess::Initialize(KProcess* process, Core::System& system, std::string process_name, ResultCode KProcess::Initialize(KProcess* process, Core::System& system, std::string process_name,
ProcessType type, KResourceLimit* res_limit) { ProcessType type, KResourceLimit* res_limit) {
auto& kernel = system.Kernel(); auto& kernel = system.Kernel();
@ -404,7 +352,7 @@ ResultCode KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata,
} }
// Create TLS region // Create TLS region
tls_region_address = CreateTLSRegion(); R_TRY(this->CreateThreadLocalRegion(std::addressof(tls_region_address)));
memory_reservation.Commit(); memory_reservation.Commit();
return handle_table.Initialize(capabilities.GetHandleTableSize()); return handle_table.Initialize(capabilities.GetHandleTableSize());
@ -444,7 +392,7 @@ void KProcess::PrepareForTermination() {
stop_threads(kernel.System().GlobalSchedulerContext().GetThreadList()); stop_threads(kernel.System().GlobalSchedulerContext().GetThreadList());
FreeTLSRegion(tls_region_address); this->DeleteThreadLocalRegion(tls_region_address);
tls_region_address = 0; tls_region_address = 0;
if (resource_limit) { if (resource_limit) {
@ -487,63 +435,103 @@ void KProcess::Finalize() {
KAutoObjectWithSlabHeapAndContainer<KProcess, KWorkerTask>::Finalize(); KAutoObjectWithSlabHeapAndContainer<KProcess, KWorkerTask>::Finalize();
} }
/** ResultCode KProcess::CreateThreadLocalRegion(VAddr* out) {
* Attempts to find a TLS page that contains a free slot for KThreadLocalPage* tlp = nullptr;
* use by a thread. VAddr tlr = 0;
*
* @returns If a page with an available slot is found, then an iterator // See if we can get a region from a partially used TLP.
* pointing to the page is returned. Otherwise the end iterator {
* is returned instead. KScopedSchedulerLock sl{kernel};
*/
static auto FindTLSPageWithAvailableSlots(std::vector<TLSPage>& tls_pages) { if (auto it = partially_used_tlp_tree.begin(); it != partially_used_tlp_tree.end()) {
return std::find_if(tls_pages.begin(), tls_pages.end(), tlr = it->Reserve();
[](const auto& page) { return page.HasAvailableSlots(); }); ASSERT(tlr != 0);
if (it->IsAllUsed()) {
tlp = std::addressof(*it);
partially_used_tlp_tree.erase(it);
fully_used_tlp_tree.insert(*tlp);
} }
VAddr KProcess::CreateTLSRegion() { *out = tlr;
KScopedSchedulerLock lock(kernel); return ResultSuccess;
if (auto tls_page_iter{FindTLSPageWithAvailableSlots(tls_pages)}; }
tls_page_iter != tls_pages.cend()) {
return *tls_page_iter->ReserveSlot();
} }
Page* const tls_page_ptr{kernel.GetUserSlabHeapPages().Allocate()}; // Allocate a new page.
ASSERT(tls_page_ptr); tlp = KThreadLocalPage::Allocate(kernel);
R_UNLESS(tlp != nullptr, ResultOutOfMemory);
auto tlp_guard = SCOPE_GUARD({ KThreadLocalPage::Free(kernel, tlp); });
const VAddr start{page_table->GetKernelMapRegionStart()}; // Initialize the new page.
const VAddr size{page_table->GetKernelMapRegionEnd() - start}; R_TRY(tlp->Initialize(kernel, this));
const PAddr tls_map_addr{kernel.System().DeviceMemory().GetPhysicalAddr(tls_page_ptr)};
const VAddr tls_page_addr{page_table
->AllocateAndMapMemory(1, PageSize, true, start, size / PageSize,
KMemoryState::ThreadLocal,
KMemoryPermission::UserReadWrite,
tls_map_addr)
.ValueOr(0)};
ASSERT(tls_page_addr); // Reserve a TLR.
tlr = tlp->Reserve();
ASSERT(tlr != 0);
std::memset(tls_page_ptr, 0, PageSize); // Insert into our tree.
tls_pages.emplace_back(tls_page_addr); {
KScopedSchedulerLock sl{kernel};
const auto reserve_result{tls_pages.back().ReserveSlot()}; if (tlp->IsAllUsed()) {
ASSERT(reserve_result.has_value()); fully_used_tlp_tree.insert(*tlp);
} else {
return *reserve_result; partially_used_tlp_tree.insert(*tlp);
}
} }
void KProcess::FreeTLSRegion(VAddr tls_address) { // We succeeded!
KScopedSchedulerLock lock(kernel); tlp_guard.Cancel();
const VAddr aligned_address = Common::AlignDown(tls_address, Core::Memory::PAGE_SIZE); *out = tlr;
auto iter = return ResultSuccess;
std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) { }
return page.GetBaseAddress() == aligned_address;
});
// Something has gone very wrong if we're freeing a region ResultCode KProcess::DeleteThreadLocalRegion(VAddr addr) {
// with no actual page available. KThreadLocalPage* page_to_free = nullptr;
ASSERT(iter != tls_pages.cend());
iter->ReleaseSlot(tls_address); // Release the region.
{
KScopedSchedulerLock sl{kernel};
// Try to find the page in the partially used list.
auto it = partially_used_tlp_tree.find_key(Common::AlignDown(addr, PageSize));
if (it == partially_used_tlp_tree.end()) {
// If we don't find it, it has to be in the fully used list.
it = fully_used_tlp_tree.find_key(Common::AlignDown(addr, PageSize));
R_UNLESS(it != fully_used_tlp_tree.end(), ResultInvalidAddress);
// Release the region.
it->Release(addr);
// Move the page out of the fully used list.
KThreadLocalPage* tlp = std::addressof(*it);
fully_used_tlp_tree.erase(it);
if (tlp->IsAllFree()) {
page_to_free = tlp;
} else {
partially_used_tlp_tree.insert(*tlp);
}
} else {
// Release the region.
it->Release(addr);
// Handle the all-free case.
KThreadLocalPage* tlp = std::addressof(*it);
if (tlp->IsAllFree()) {
partially_used_tlp_tree.erase(it);
page_to_free = tlp;
}
}
}
// If we should free the page it was in, do so.
if (page_to_free != nullptr) {
page_to_free->Finalize();
KThreadLocalPage::Free(kernel, page_to_free);
}
return ResultSuccess;
} }
void KProcess::LoadModule(CodeSet code_set, VAddr base_addr) { void KProcess::LoadModule(CodeSet code_set, VAddr base_addr) {

View file

@ -15,6 +15,7 @@
#include "core/hle/kernel/k_condition_variable.h" #include "core/hle/kernel/k_condition_variable.h"
#include "core/hle/kernel/k_handle_table.h" #include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_synchronization_object.h" #include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/kernel/k_thread_local_page.h"
#include "core/hle/kernel/k_worker_task.h" #include "core/hle/kernel/k_worker_task.h"
#include "core/hle/kernel/process_capability.h" #include "core/hle/kernel/process_capability.h"
#include "core/hle/kernel/slab_helpers.h" #include "core/hle/kernel/slab_helpers.h"
@ -362,10 +363,10 @@ public:
// Thread-local storage management // Thread-local storage management
// Marks the next available region as used and returns the address of the slot. // Marks the next available region as used and returns the address of the slot.
[[nodiscard]] VAddr CreateTLSRegion(); [[nodiscard]] ResultCode CreateThreadLocalRegion(VAddr* out);
// Frees a used TLS slot identified by the given address // Frees a used TLS slot identified by the given address
void FreeTLSRegion(VAddr tls_address); ResultCode DeleteThreadLocalRegion(VAddr addr);
private: private:
void PinThread(s32 core_id, KThread* thread) { void PinThread(s32 core_id, KThread* thread) {
@ -413,13 +414,6 @@ private:
/// The ideal CPU core for this process, threads are scheduled on this core by default. /// The ideal CPU core for this process, threads are scheduled on this core by default.
u8 ideal_core = 0; u8 ideal_core = 0;
/// The Thread Local Storage area is allocated as processes create threads,
/// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part
/// holds the TLS for a specific thread. This vector contains which parts are in use for each
/// page as a bitmask.
/// This vector will grow as more pages are allocated for new threads.
std::vector<TLSPage> tls_pages;
/// Contains the parsed process capability descriptors. /// Contains the parsed process capability descriptors.
ProcessCapabilities capabilities; ProcessCapabilities capabilities;
@ -482,6 +476,12 @@ private:
KThread* exception_thread{}; KThread* exception_thread{};
KLightLock state_lock; KLightLock state_lock;
using TLPTree =
Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>;
using TLPIterator = TLPTree::iterator;
TLPTree fully_used_tlp_tree;
TLPTree partially_used_tlp_tree;
}; };
} // namespace Kernel } // namespace Kernel

View file

@ -210,7 +210,7 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
if (owner != nullptr) { if (owner != nullptr) {
// Setup the TLS, if needed. // Setup the TLS, if needed.
if (type == ThreadType::User) { if (type == ThreadType::User) {
tls_address = owner->CreateTLSRegion(); R_TRY(owner->CreateThreadLocalRegion(std::addressof(tls_address)));
} }
parent = owner; parent = owner;
@ -305,7 +305,7 @@ void KThread::Finalize() {
// If the thread has a local region, delete it. // If the thread has a local region, delete it.
if (tls_address != 0) { if (tls_address != 0) {
parent->FreeTLSRegion(tls_address); ASSERT(parent->DeleteThreadLocalRegion(tls_address).IsSuccess());
} }
// Release any waiters. // Release any waiters.