core: hle: kernel: Updated implementation of svcSetHeapSize.

- Updates our svcSetHeapSize with latest HOS, furthermore allowing heap size to properly be extended/shrunk.
- Validated with tests https://github.com/Atmosphere-NX/Atmosphere/blob/master/tests/TestSvc/source/test_set_heap_size.cpp.
This commit is contained in:
bunnei 2021-12-28 00:18:41 -08:00
parent 9a0648ff0a
commit 091463a429
6 changed files with 138 additions and 80 deletions

View file

@ -120,7 +120,7 @@ static_assert(static_cast<u32>(KMemoryState::CodeOut) == 0x00402015);
enum class KMemoryPermission : u8 { enum class KMemoryPermission : u8 {
None = 0, None = 0,
Mask = static_cast<u8>(~None), All = static_cast<u8>(~None),
Read = 1 << 0, Read = 1 << 0,
Write = 1 << 1, Write = 1 << 1,

View file

@ -264,9 +264,9 @@ ResultCode KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_
ASSERT(heap_last < stack_start || stack_last < heap_start); ASSERT(heap_last < stack_start || stack_last < heap_start);
ASSERT(heap_last < kmap_start || kmap_last < heap_start); ASSERT(heap_last < kmap_start || kmap_last < heap_start);
current_heap_addr = heap_region_start; current_heap_end = heap_region_start;
heap_capacity = 0; max_heap_size = 0;
physical_memory_usage = 0; mapped_physical_memory_size = 0;
memory_pool = pool; memory_pool = pool;
page_table_impl.Resize(address_space_width, PageBits); page_table_impl.Resize(address_space_width, PageBits);
@ -306,7 +306,7 @@ ResultCode KPageTable::MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std:
KMemoryState state{}; KMemoryState state{};
KMemoryPermission perm{}; KMemoryPermission perm{};
CASCADE_CODE(CheckMemoryState(&state, &perm, nullptr, src_addr, size, KMemoryState::All, CASCADE_CODE(CheckMemoryState(&state, &perm, nullptr, src_addr, size, KMemoryState::All,
KMemoryState::Normal, KMemoryPermission::Mask, KMemoryState::Normal, KMemoryPermission::All,
KMemoryPermission::ReadAndWrite, KMemoryAttribute::Mask, KMemoryPermission::ReadAndWrite, KMemoryAttribute::Mask,
KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped));
@ -465,7 +465,7 @@ ResultCode KPageTable::MapPhysicalMemory(VAddr addr, std::size_t size) {
MapPhysicalMemory(page_linked_list, addr, end_addr); MapPhysicalMemory(page_linked_list, addr, end_addr);
physical_memory_usage += remaining_size; mapped_physical_memory_size += remaining_size;
const std::size_t num_pages{size / PageSize}; const std::size_t num_pages{size / PageSize};
block_manager->Update(addr, num_pages, KMemoryState::Free, KMemoryPermission::None, block_manager->Update(addr, num_pages, KMemoryState::Free, KMemoryPermission::None,
@ -507,7 +507,7 @@ ResultCode KPageTable::UnmapPhysicalMemory(VAddr addr, std::size_t size) {
auto process{system.Kernel().CurrentProcess()}; auto process{system.Kernel().CurrentProcess()};
process->GetResourceLimit()->Release(LimitableResource::PhysicalMemory, mapped_size); process->GetResourceLimit()->Release(LimitableResource::PhysicalMemory, mapped_size);
physical_memory_usage -= mapped_size; mapped_physical_memory_size -= mapped_size;
return ResultSuccess; return ResultSuccess;
} }
@ -554,7 +554,7 @@ ResultCode KPageTable::Map(VAddr dst_addr, VAddr src_addr, std::size_t size) {
KMemoryState src_state{}; KMemoryState src_state{};
CASCADE_CODE(CheckMemoryState( CASCADE_CODE(CheckMemoryState(
&src_state, nullptr, nullptr, src_addr, size, KMemoryState::FlagCanAlias, &src_state, nullptr, nullptr, src_addr, size, KMemoryState::FlagCanAlias,
KMemoryState::FlagCanAlias, KMemoryPermission::Mask, KMemoryPermission::ReadAndWrite, KMemoryState::FlagCanAlias, KMemoryPermission::All, KMemoryPermission::ReadAndWrite,
KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped));
if (IsRegionMapped(dst_addr, size)) { if (IsRegionMapped(dst_addr, size)) {
@ -593,7 +593,7 @@ ResultCode KPageTable::Unmap(VAddr dst_addr, VAddr src_addr, std::size_t size) {
KMemoryState src_state{}; KMemoryState src_state{};
CASCADE_CODE(CheckMemoryState( CASCADE_CODE(CheckMemoryState(
&src_state, nullptr, nullptr, src_addr, size, KMemoryState::FlagCanAlias, &src_state, nullptr, nullptr, src_addr, size, KMemoryState::FlagCanAlias,
KMemoryState::FlagCanAlias, KMemoryPermission::Mask, KMemoryPermission::None, KMemoryState::FlagCanAlias, KMemoryPermission::All, KMemoryPermission::None,
KMemoryAttribute::Mask, KMemoryAttribute::Locked, KMemoryAttribute::IpcAndDeviceMapped)); KMemoryAttribute::Mask, KMemoryAttribute::Locked, KMemoryAttribute::IpcAndDeviceMapped));
KMemoryPermission dst_perm{}; KMemoryPermission dst_perm{};
@ -784,7 +784,7 @@ ResultCode KPageTable::ReserveTransferMemory(VAddr addr, std::size_t size, KMemo
CASCADE_CODE(CheckMemoryState( CASCADE_CODE(CheckMemoryState(
&state, nullptr, &attribute, addr, size, &state, nullptr, &attribute, addr, size,
KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted,
KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, KMemoryPermission::Mask, KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, KMemoryPermission::All,
KMemoryPermission::ReadAndWrite, KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryPermission::ReadAndWrite, KMemoryAttribute::Mask, KMemoryAttribute::None,
KMemoryAttribute::IpcAndDeviceMapped)); KMemoryAttribute::IpcAndDeviceMapped));
@ -859,61 +859,125 @@ ResultCode KPageTable::SetMemoryAttribute(VAddr addr, std::size_t size, KMemoryA
return ResultSuccess; return ResultSuccess;
} }
ResultCode KPageTable::SetHeapCapacity(std::size_t new_heap_capacity) { ResultCode KPageTable::SetMaxHeapSize(std::size_t size) {
// Lock the table.
std::lock_guard lock{page_table_lock}; std::lock_guard lock{page_table_lock};
heap_capacity = new_heap_capacity;
// Only process page tables are allowed to set heap size.
ASSERT(!this->IsKernel());
max_heap_size = size;
return ResultSuccess; return ResultSuccess;
} }
ResultVal<VAddr> KPageTable::SetHeapSize(std::size_t size) { ResultCode KPageTable::SetHeapSize(VAddr* out, std::size_t size) {
// Try to perform a reduction in heap, instead of an extension.
VAddr cur_address{};
std::size_t allocation_size{};
{
// Lock the table.
std::lock_guard lk(page_table_lock);
if (size > heap_region_end - heap_region_start) { // Validate that setting heap size is possible at all.
return ResultOutOfMemory; R_UNLESS(!is_kernel, ResultOutOfMemory);
R_UNLESS(size <= static_cast<std::size_t>(heap_region_end - heap_region_start),
ResultOutOfMemory);
R_UNLESS(size <= max_heap_size, ResultOutOfMemory);
if (size < GetHeapSize()) {
// The size being requested is less than the current size, so we need to free the end of
// the heap.
// Validate memory state.
std::size_t num_allocator_blocks;
R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks),
heap_region_start + size, GetHeapSize() - size,
KMemoryState::All, KMemoryState::Normal,
KMemoryPermission::All, KMemoryPermission::ReadAndWrite,
KMemoryAttribute::All, KMemoryAttribute::None));
// Unmap the end of the heap.
const auto num_pages = (GetHeapSize() - size) / PageSize;
R_TRY(Operate(heap_region_start + size, num_pages, KMemoryPermission::None,
OperationType::Unmap));
// Release the memory from the resource limit.
system.Kernel().CurrentProcess()->GetResourceLimit()->Release(
LimitableResource::PhysicalMemory, num_pages * PageSize);
// Apply the memory block update.
block_manager->Update(heap_region_start + size, num_pages, KMemoryState::Free,
KMemoryPermission::None, KMemoryAttribute::None);
// Update the current heap end.
current_heap_end = heap_region_start + size;
// Set the output.
*out = heap_region_start;
return ResultSuccess;
} else if (size == GetHeapSize()) {
// The size requested is exactly the current size.
*out = heap_region_start;
return ResultSuccess;
} else {
// We have to allocate memory. Determine how much to allocate and where while the table
// is locked.
cur_address = current_heap_end;
allocation_size = size - GetHeapSize();
}
} }
const u64 previous_heap_size{GetHeapSize()}; // Reserve memory for the heap extension.
KScopedResourceReservation memory_reservation(
system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory,
allocation_size);
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
UNIMPLEMENTED_IF_MSG(previous_heap_size > size, "Heap shrink is unimplemented"); // Allocate pages for the heap extension.
KPageLinkedList page_linked_list;
R_TRY(system.Kernel().MemoryManager().Allocate(page_linked_list, allocation_size / PageSize,
memory_pool));
// Increase the heap size // Map the pages.
{ {
std::lock_guard lock{page_table_lock}; // Lock the table.
std::lock_guard lk(page_table_lock);
const u64 delta{size - previous_heap_size}; // Ensure that the heap hasn't changed since we began executing.
ASSERT(cur_address == current_heap_end);
// Reserve memory for the heap extension. // Check the memory state.
KScopedResourceReservation memory_reservation( std::size_t num_allocator_blocks{};
system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory, R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), current_heap_end,
delta); allocation_size, KMemoryState::All, KMemoryState::Free,
KMemoryPermission::None, KMemoryPermission::None,
KMemoryAttribute::None, KMemoryAttribute::None));
if (!memory_reservation.Succeeded()) { // Map the pages.
LOG_ERROR(Kernel, "Could not reserve heap extension of size {:X} bytes", delta); const auto num_pages = allocation_size / PageSize;
return ResultLimitReached; R_TRY(Operate(current_heap_end, num_pages, page_linked_list, OperationType::MapGroup));
// Clear all the newly allocated pages.
for (std::size_t cur_page = 0; cur_page < num_pages; ++cur_page) {
std::memset(system.Memory().GetPointer(current_heap_end + (cur_page * PageSize)), 0,
PageSize);
} }
KPageLinkedList page_linked_list; // We succeeded, so commit our memory reservation.
const std::size_t num_pages{delta / PageSize};
CASCADE_CODE(
system.Kernel().MemoryManager().Allocate(page_linked_list, num_pages, memory_pool));
if (IsRegionMapped(current_heap_addr, delta)) {
return ResultInvalidCurrentMemory;
}
CASCADE_CODE(
Operate(current_heap_addr, num_pages, page_linked_list, OperationType::MapGroup));
// Succeeded in allocation, commit the resource reservation
memory_reservation.Commit(); memory_reservation.Commit();
block_manager->Update(current_heap_addr, num_pages, KMemoryState::Normal, // Apply the memory block update.
KMemoryPermission::ReadAndWrite); block_manager->Update(current_heap_end, num_pages, KMemoryState::Normal,
KMemoryPermission::ReadAndWrite, KMemoryAttribute::None);
current_heap_addr = heap_region_start + size; // Update the current heap end.
current_heap_end = heap_region_start + size;
// Set the output.
*out = heap_region_start;
return ResultSuccess;
} }
return heap_region_start;
} }
ResultVal<VAddr> KPageTable::AllocateAndMapMemory(std::size_t needed_num_pages, std::size_t align, ResultVal<VAddr> KPageTable::AllocateAndMapMemory(std::size_t needed_num_pages, std::size_t align,
@ -1005,7 +1069,7 @@ ResultCode KPageTable::LockForCodeMemory(VAddr addr, std::size_t size) {
if (const ResultCode result{CheckMemoryState( if (const ResultCode result{CheckMemoryState(
nullptr, &old_perm, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, nullptr, &old_perm, nullptr, addr, size, KMemoryState::FlagCanCodeMemory,
KMemoryState::FlagCanCodeMemory, KMemoryPermission::Mask, KMemoryState::FlagCanCodeMemory, KMemoryPermission::All,
KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None)}; KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None)};
result.IsError()) { result.IsError()) {
return result; return result;
@ -1058,9 +1122,8 @@ ResultCode KPageTable::InitializeMemoryLayout(VAddr start, VAddr end) {
bool KPageTable::IsRegionMapped(VAddr address, u64 size) { bool KPageTable::IsRegionMapped(VAddr address, u64 size) {
return CheckMemoryState(address, size, KMemoryState::All, KMemoryState::Free, return CheckMemoryState(address, size, KMemoryState::All, KMemoryState::Free,
KMemoryPermission::Mask, KMemoryPermission::None, KMemoryPermission::All, KMemoryPermission::None, KMemoryAttribute::Mask,
KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)
KMemoryAttribute::IpcAndDeviceMapped)
.IsError(); .IsError();
} }

View file

@ -50,8 +50,8 @@ public:
ResultCode SetMemoryPermission(VAddr addr, std::size_t size, Svc::MemoryPermission perm); ResultCode SetMemoryPermission(VAddr addr, std::size_t size, Svc::MemoryPermission perm);
ResultCode SetMemoryAttribute(VAddr addr, std::size_t size, KMemoryAttribute mask, ResultCode SetMemoryAttribute(VAddr addr, std::size_t size, KMemoryAttribute mask,
KMemoryAttribute value); KMemoryAttribute value);
ResultCode SetHeapCapacity(std::size_t new_heap_capacity); ResultCode SetMaxHeapSize(std::size_t size);
ResultVal<VAddr> SetHeapSize(std::size_t size); ResultCode SetHeapSize(VAddr* out, std::size_t size);
ResultVal<VAddr> AllocateAndMapMemory(std::size_t needed_num_pages, std::size_t align, ResultVal<VAddr> AllocateAndMapMemory(std::size_t needed_num_pages, std::size_t align,
bool is_map_only, VAddr region_start, bool is_map_only, VAddr region_start,
std::size_t region_num_pages, KMemoryState state, std::size_t region_num_pages, KMemoryState state,
@ -183,14 +183,15 @@ public:
constexpr VAddr GetAliasCodeRegionSize() const { constexpr VAddr GetAliasCodeRegionSize() const {
return alias_code_region_end - alias_code_region_start; return alias_code_region_end - alias_code_region_start;
} }
size_t GetNormalMemorySize() {
std::lock_guard lk(page_table_lock);
return GetHeapSize() + mapped_physical_memory_size;
}
constexpr std::size_t GetAddressSpaceWidth() const { constexpr std::size_t GetAddressSpaceWidth() const {
return address_space_width; return address_space_width;
} }
constexpr std::size_t GetHeapSize() { constexpr std::size_t GetHeapSize() const {
return current_heap_addr - heap_region_start; return current_heap_end - heap_region_start;
}
constexpr std::size_t GetTotalHeapSize() {
return GetHeapSize() + physical_memory_usage;
} }
constexpr bool IsInsideAddressSpace(VAddr address, std::size_t size) const { constexpr bool IsInsideAddressSpace(VAddr address, std::size_t size) const {
return address_space_start <= address && address + size - 1 <= address_space_end - 1; return address_space_start <= address && address + size - 1 <= address_space_end - 1;
@ -270,10 +271,8 @@ private:
VAddr code_region_end{}; VAddr code_region_end{};
VAddr alias_code_region_start{}; VAddr alias_code_region_start{};
VAddr alias_code_region_end{}; VAddr alias_code_region_end{};
VAddr current_heap_addr{};
std::size_t heap_capacity{}; std::size_t mapped_physical_memory_size{};
std::size_t physical_memory_usage{};
std::size_t max_heap_size{}; std::size_t max_heap_size{};
std::size_t max_physical_memory_size{}; std::size_t max_physical_memory_size{};
std::size_t address_space_width{}; std::size_t address_space_width{};

View file

@ -172,7 +172,7 @@ void KProcess::DecrementThreadCount() {
u64 KProcess::GetTotalPhysicalMemoryAvailable() const { u64 KProcess::GetTotalPhysicalMemoryAvailable() const {
const u64 capacity{resource_limit->GetFreeValue(LimitableResource::PhysicalMemory) + const u64 capacity{resource_limit->GetFreeValue(LimitableResource::PhysicalMemory) +
page_table->GetTotalHeapSize() + GetSystemResourceSize() + image_size + page_table->GetNormalMemorySize() + GetSystemResourceSize() + image_size +
main_thread_stack_size}; main_thread_stack_size};
if (const auto pool_size = kernel.MemoryManager().GetSize(KMemoryManager::Pool::Application); if (const auto pool_size = kernel.MemoryManager().GetSize(KMemoryManager::Pool::Application);
capacity != pool_size) { capacity != pool_size) {
@ -189,7 +189,7 @@ u64 KProcess::GetTotalPhysicalMemoryAvailableWithoutSystemResource() const {
} }
u64 KProcess::GetTotalPhysicalMemoryUsed() const { u64 KProcess::GetTotalPhysicalMemoryUsed() const {
return image_size + main_thread_stack_size + page_table->GetTotalHeapSize() + return image_size + main_thread_stack_size + page_table->GetNormalMemorySize() +
GetSystemResourceSize(); GetSystemResourceSize();
} }
@ -410,8 +410,8 @@ void KProcess::Run(s32 main_thread_priority, u64 stack_size) {
resource_limit->Reserve(LimitableResource::Threads, 1); resource_limit->Reserve(LimitableResource::Threads, 1);
resource_limit->Reserve(LimitableResource::PhysicalMemory, main_thread_stack_size); resource_limit->Reserve(LimitableResource::PhysicalMemory, main_thread_stack_size);
const std::size_t heap_capacity{memory_usage_capacity - main_thread_stack_size - image_size}; const std::size_t heap_capacity{memory_usage_capacity - (main_thread_stack_size + image_size)};
ASSERT(!page_table->SetHeapCapacity(heap_capacity).IsError()); ASSERT(!page_table->SetMaxHeapSize(heap_capacity).IsError());
ChangeStatus(ProcessStatus::Running); ChangeStatus(ProcessStatus::Running);

View file

@ -135,24 +135,15 @@ enum class ResourceLimitValueType {
} // Anonymous namespace } // Anonymous namespace
/// Set the process heap to a given Size. It can both extend and shrink the heap. /// Set the process heap to a given Size. It can both extend and shrink the heap.
static ResultCode SetHeapSize(Core::System& system, VAddr* heap_addr, u64 heap_size) { static ResultCode SetHeapSize(Core::System& system, VAddr* out_address, u64 size) {
LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", heap_size); LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", size);
// Size must be a multiple of 0x200000 (2MB) and be equal to or less than 8GB. // Validate size.
if ((heap_size % 0x200000) != 0) { R_UNLESS(Common::IsAligned(size, HeapSizeAlignment), ResultInvalidSize);
LOG_ERROR(Kernel_SVC, "The heap size is not a multiple of 2MB, heap_size=0x{:016X}", R_UNLESS(size < MainMemorySizeMax, ResultInvalidSize);
heap_size);
return ResultInvalidSize;
}
if (heap_size >= 0x200000000) { // Set the heap size.
LOG_ERROR(Kernel_SVC, "The heap size is not less than 8GB, heap_size=0x{:016X}", heap_size); R_TRY(system.Kernel().CurrentProcess()->PageTable().SetHeapSize(out_address, size));
return ResultInvalidSize;
}
auto& page_table{system.Kernel().CurrentProcess()->PageTable()};
CASCADE_RESULT(*heap_addr, page_table.SetHeapSize(heap_size));
return ResultSuccess; return ResultSuccess;
} }

View file

@ -5,6 +5,7 @@
#pragma once #pragma once
#include "common/common_types.h" #include "common/common_types.h"
#include "common/literals.h"
namespace Kernel { namespace Kernel {
using Handle = u32; using Handle = u32;
@ -12,9 +13,13 @@ using Handle = u32;
namespace Kernel::Svc { namespace Kernel::Svc {
using namespace Common::Literals;
constexpr s32 ArgumentHandleCountMax = 0x40; constexpr s32 ArgumentHandleCountMax = 0x40;
constexpr u32 HandleWaitMask{1u << 30}; constexpr u32 HandleWaitMask{1u << 30};
constexpr inline std::size_t HeapSizeAlignment = 2_MiB;
constexpr inline Handle InvalidHandle = Handle(0); constexpr inline Handle InvalidHandle = Handle(0);
enum PseudoHandle : Handle { enum PseudoHandle : Handle {