citra-emu
/
citra-canary
Archived
1
0
Fork 0

Kernel: reimplement memory management on physical FCRAM (#4392)

* Kernel: reimplement memory management on physical FCRAM

* Kernel/Process: Unmap does not care the source memory permission

What game usually does is after mapping the memory, they reprotect the source memory as no permission to avoid modification there

* Kernel/SharedMemory: zero initialize new-allocated memory

* Process/Thread: zero new TLS entry

* Kernel: fix a bug where code segments memory usage are accumulated twice

It is added to both misc and heap (done inside HeapAlloc), which results a doubled number reported by svcGetProcessInfo. While we are on it, we just merge the three number misc, heap and linear heap usage together, as there is no where they are distinguished.

Question: is TLS page also added to this number?

* Kernel/SharedMemory: add more object info on mapping error

* Process: lower log level; SharedMemory: store phys offset

* VMManager: add helper function to retrieve backing block list for a range
This commit is contained in:
Weiyi Wang 2018-11-06 15:00:47 -05:00 committed by GitHub
parent f852f9f219
commit 2067946f59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 389 additions and 208 deletions

View File

@ -28,11 +28,9 @@ ResultCode ErrEula::ReceiveParameter(const Service::APT::MessageParameter& param
// TODO: allocated memory never released // TODO: allocated memory never released
using Kernel::MemoryPermission; using Kernel::MemoryPermission;
// Allocate a heap block of the required size for this applet.
heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
// Create a SharedMemory that directly points to this heap block. // Create a SharedMemory that directly points to this heap block.
framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet(
heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
"ErrEula Memory"); "ErrEula Memory");
// Send the response message with the newly created SharedMemory // Send the response message with the newly created SharedMemory

View File

@ -35,11 +35,9 @@ ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& p
memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info)); memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
using Kernel::MemoryPermission; using Kernel::MemoryPermission;
// Allocate a heap block of the required size for this applet.
heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
// Create a SharedMemory that directly points to this heap block. // Create a SharedMemory that directly points to this heap block.
framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet(
heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
"MiiSelector Memory"); "MiiSelector Memory");
// Send the response message with the newly created SharedMemory // Send the response message with the newly created SharedMemory

View File

@ -28,11 +28,9 @@ ResultCode Mint::ReceiveParameter(const Service::APT::MessageParameter& paramete
// TODO: allocated memory never released // TODO: allocated memory never released
using Kernel::MemoryPermission; using Kernel::MemoryPermission;
// Allocate a heap block of the required size for this applet.
heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
// Create a SharedMemory that directly points to this heap block. // Create a SharedMemory that directly points to this heap block.
framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet(
heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
"Mint Memory"); "Mint Memory");
// Send the response message with the newly created SharedMemory // Send the response message with the newly created SharedMemory

View File

@ -39,11 +39,9 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con
memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info)); memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
using Kernel::MemoryPermission; using Kernel::MemoryPermission;
// Allocate a heap block of the required size for this applet.
heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
// Create a SharedMemory that directly points to this heap block. // Create a SharedMemory that directly points to this heap block.
framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet(
heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
"SoftwareKeyboard Memory"); "SoftwareKeyboard Memory");
// Send the response message with the newly created SharedMemory // Send the response message with the newly created SharedMemory

View File

@ -67,6 +67,10 @@ constexpr ResultCode ERR_MISALIGNED_SIZE(ErrorDescription::MisalignedSize, Error
constexpr ResultCode ERR_OUT_OF_MEMORY(ErrorDescription::OutOfMemory, ErrorModule::Kernel, constexpr ResultCode ERR_OUT_OF_MEMORY(ErrorDescription::OutOfMemory, ErrorModule::Kernel,
ErrorSummary::OutOfResource, ErrorSummary::OutOfResource,
ErrorLevel::Permanent); // 0xD86007F3 ErrorLevel::Permanent); // 0xD86007F3
/// Returned when out of heap or linear heap memory when allocating
constexpr ResultCode ERR_OUT_OF_HEAP_MEMORY(ErrorDescription::OutOfMemory, ErrorModule::OS,
ErrorSummary::OutOfResource,
ErrorLevel::Status); // 0xC860180A
constexpr ResultCode ERR_NOT_IMPLEMENTED(ErrorDescription::NotImplemented, ErrorModule::OS, constexpr ResultCode ERR_NOT_IMPLEMENTED(ErrorDescription::NotImplemented, ErrorModule::OS,
ErrorSummary::InvalidArgument, ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E01BF4 ErrorLevel::Usage); // 0xE0E01BF4

View File

@ -179,7 +179,6 @@ public:
/** /**
* Creates a shared memory object from a block of memory managed by an HLE applet. * Creates a shared memory object from a block of memory managed by an HLE applet.
* @param heap_block Heap block of the HLE applet.
* @param offset The offset into the heap block that the SharedMemory will map. * @param offset The offset into the heap block that the SharedMemory will map.
* @param size Size of the memory block. Must be page-aligned. * @param size Size of the memory block. Must be page-aligned.
* @param permissions Permission restrictions applied to the process which created the block. * @param permissions Permission restrictions applied to the process which created the block.
@ -187,8 +186,7 @@ public:
* block. * block.
* @param name Optional object name, used for debugging purposes. * @param name Optional object name, used for debugging purposes.
*/ */
SharedPtr<SharedMemory> CreateSharedMemoryForApplet(std::shared_ptr<std::vector<u8>> heap_block, SharedPtr<SharedMemory> CreateSharedMemoryForApplet(u32 offset, u32 size,
u32 offset, u32 size,
MemoryPermission permissions, MemoryPermission permissions,
MemoryPermission other_permissions, MemoryPermission other_permissions,
std::string name = "Unknown Applet"); std::string name = "Unknown Applet");

View File

@ -50,13 +50,7 @@ void KernelSystem::MemoryInit(u32 mem_type) {
// the sizes specified in the memory_region_sizes table. // the sizes specified in the memory_region_sizes table.
VAddr base = 0; VAddr base = 0;
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
memory_regions[i].base = base; memory_regions[i].Reset(base, memory_region_sizes[mem_type][i]);
memory_regions[i].size = memory_region_sizes[mem_type][i];
memory_regions[i].used = 0;
memory_regions[i].linear_heap_memory = std::make_shared<std::vector<u8>>();
// Reserve enough space for this region of FCRAM.
// We do not want this block of memory to be relocated when allocating from it.
memory_regions[i].linear_heap_memory->reserve(memory_regions[i].size);
base += memory_regions[i].size; base += memory_regions[i].size;
} }
@ -164,4 +158,75 @@ void KernelSystem::MapSharedPages(VMManager& address_space) {
address_space.Reprotect(shared_page_vma, VMAPermission::Read); address_space.Reprotect(shared_page_vma, VMAPermission::Read);
} }
void MemoryRegionInfo::Reset(u32 base, u32 size) {
this->base = base;
this->size = size;
used = 0;
free_blocks.clear();
// mark the entire region as free
free_blocks.insert(Interval::right_open(base, base + size));
}
MemoryRegionInfo::IntervalSet MemoryRegionInfo::HeapAllocate(u32 size) {
IntervalSet result;
u32 rest = size;
// Try allocating from the higher address
for (auto iter = free_blocks.rbegin(); iter != free_blocks.rend(); ++iter) {
ASSERT(iter->bounds() == boost::icl::interval_bounds::right_open());
if (iter->upper() - iter->lower() >= rest) {
// Requested size is fulfilled with this block
result += Interval(iter->upper() - rest, iter->upper());
rest = 0;
break;
}
result += *iter;
rest -= iter->upper() - iter->lower();
}
if (rest != 0) {
// There is no enough free space
return {};
}
free_blocks -= result;
used += size;
return result;
}
bool MemoryRegionInfo::LinearAllocate(u32 offset, u32 size) {
Interval interval(offset, offset + size);
if (!boost::icl::contains(free_blocks, interval)) {
// The requested range is already allocated
return false;
}
free_blocks -= interval;
used += size;
return true;
}
std::optional<u32> MemoryRegionInfo::LinearAllocate(u32 size) {
// Find the first sufficient continuous block from the lower address
for (const auto& interval : free_blocks) {
ASSERT(interval.bounds() == boost::icl::interval_bounds::right_open());
if (interval.upper() - interval.lower() >= size) {
Interval allocated(interval.lower(), interval.lower() + size);
free_blocks -= allocated;
used += size;
return allocated.lower();
}
}
// No sufficient block found
return {};
}
void MemoryRegionInfo::Free(u32 offset, u32 size) {
Interval interval(offset, offset + size);
ASSERT(!boost::icl::intersects(free_blocks, interval)); // must be allocated blocks
free_blocks += interval;
used -= size;
}
} // namespace Kernel } // namespace Kernel

View File

@ -4,8 +4,8 @@
#pragma once #pragma once
#include <memory> #include <optional>
#include <vector> #include <boost/icl/interval_set.hpp>
#include "common/common_types.h" #include "common/common_types.h"
namespace Kernel { namespace Kernel {
@ -18,7 +18,48 @@ struct MemoryRegionInfo {
u32 size; u32 size;
u32 used; u32 used;
std::shared_ptr<std::vector<u8>> linear_heap_memory; // The domain of the interval_set are offsets from start of FCRAM
using IntervalSet = boost::icl::interval_set<u32>;
using Interval = IntervalSet::interval_type;
IntervalSet free_blocks;
/**
* Reset the allocator state
* @param base The base offset the beginning of FCRAM.
* @param size The region size this allocator manages
*/
void Reset(u32 base, u32 size);
/**
* Allocates memory from the heap.
* @param size The size of memory to allocate.
* @returns The set of blocks that make up the allocation request. Empty set if there is no
* enough space.
*/
IntervalSet HeapAllocate(u32 size);
/**
* Allocates memory from the linear heap with specific address and size.
* @param offset the address offset to the beginning of FCRAM.
* @param size size of the memory to allocate.
* @returns true if the allocation is successful. false if the requested region is not free.
*/
bool LinearAllocate(u32 offset, u32 size);
/**
* Allocates memory from the linear heap with only size specified.
* @param size size of the memory to allocate.
* @returns the address offset to the beginning of FCRAM; null if there is no enough space
*/
std::optional<u32> LinearAllocate(u32 size);
/**
* Frees one segment of memory. The memory must have been allocated as heap or linear heap.
* @param offset the region address offset to the beginning of FCRAM.
* @param size the size of the region to free.
*/
void Free(u32 offset, u32 size);
}; };
void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping); void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping);

View File

@ -119,13 +119,9 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions,
MemoryState memory_state) { MemoryState memory_state) {
auto vma = vm_manager HeapAllocate(segment.addr, segment.size, permissions, memory_state, true);
.MapMemoryBlock(segment.addr, codeset->memory, segment.offset, segment.size, Memory::WriteBlock(*this, segment.addr, codeset->memory->data() + segment.offset,
memory_state) segment.size);
.Unwrap();
vm_manager.Reprotect(vma, permissions);
misc_memory_used += segment.size;
memory_region->used += segment.size;
}; };
// Map CodeSet segments // Map CodeSet segments
@ -134,13 +130,8 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
MapSegment(codeset->DataSegment(), VMAPermission::ReadWrite, MemoryState::Private); MapSegment(codeset->DataSegment(), VMAPermission::ReadWrite, MemoryState::Private);
// Allocate and map stack // Allocate and map stack
vm_manager HeapAllocate(Memory::HEAP_VADDR_END - stack_size, stack_size, VMAPermission::ReadWrite,
.MapMemoryBlock(Memory::HEAP_VADDR_END - stack_size, MemoryState::Locked, true);
std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size,
MemoryState::Locked)
.Unwrap();
misc_memory_used += stack_size;
memory_region->used += stack_size;
// Map special address mappings // Map special address mappings
kernel.MapSharedPages(vm_manager); kernel.MapSharedPages(vm_manager);
@ -168,44 +159,55 @@ VAddr Process::GetLinearHeapLimit() const {
return GetLinearHeapBase() + memory_region->size; return GetLinearHeapBase() + memory_region->size;
} }
ResultVal<VAddr> Process::HeapAllocate(VAddr target, u32 size, VMAPermission perms) { ResultVal<VAddr> Process::HeapAllocate(VAddr target, u32 size, VMAPermission perms,
MemoryState memory_state, bool skip_range_check) {
LOG_DEBUG(Kernel, "Allocate heap target={:08X}, size={:08X}", target, size);
if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END || if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END ||
target + size < target) { target + size < target) {
if (!skip_range_check) {
LOG_ERROR(Kernel, "Invalid heap address");
return ERR_INVALID_ADDRESS; return ERR_INVALID_ADDRESS;
} }
if (heap_memory == nullptr) {
// Initialize heap
heap_memory = std::make_shared<std::vector<u8>>();
heap_start = heap_end = target;
} }
// If necessary, expand backing vector to cover new heap extents. auto vma = vm_manager.FindVMA(target);
if (target < heap_start) { if (vma->second.type != VMAType::Free || vma->second.base + vma->second.size < target + size) {
heap_memory->insert(begin(*heap_memory), heap_start - target, 0); LOG_ERROR(Kernel, "Trying to allocate already allocated memory");
heap_start = target; return ERR_INVALID_ADDRESS_STATE;
vm_manager.RefreshMemoryBlockMappings(heap_memory.get());
} }
if (target + size > heap_end) {
heap_memory->insert(end(*heap_memory), (target + size) - heap_end, 0); auto allocated_fcram = memory_region->HeapAllocate(size);
heap_end = target + size; if (allocated_fcram.empty()) {
vm_manager.RefreshMemoryBlockMappings(heap_memory.get()); LOG_ERROR(Kernel, "Not enough space");
return ERR_OUT_OF_HEAP_MEMORY;
} }
ASSERT(heap_end - heap_start == heap_memory->size());
CASCADE_RESULT(auto vma, vm_manager.MapMemoryBlock(target, heap_memory, target - heap_start, // Maps heap block by block
size, MemoryState::Private)); VAddr interval_target = target;
vm_manager.Reprotect(vma, perms); for (const auto& interval : allocated_fcram) {
u32 interval_size = interval.upper() - interval.lower();
LOG_DEBUG(Kernel, "Allocated FCRAM region lower={:08X}, upper={:08X}", interval.lower(),
interval.upper());
std::fill(Memory::fcram.begin() + interval.lower(),
Memory::fcram.begin() + interval.upper(), 0);
auto vma = vm_manager.MapBackingMemory(
interval_target, Memory::fcram.data() + interval.lower(), interval_size, memory_state);
ASSERT(vma.Succeeded());
vm_manager.Reprotect(vma.Unwrap(), perms);
interval_target += interval_size;
}
heap_used += size; memory_used += size;
memory_region->used += size; resource_limit->current_commit += size;
return MakeResult<VAddr>(heap_end - size); return MakeResult<VAddr>(target);
} }
ResultCode Process::HeapFree(VAddr target, u32 size) { ResultCode Process::HeapFree(VAddr target, u32 size) {
LOG_DEBUG(Kernel, "Free heap target={:08X}, size={:08X}", target, size);
if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END || if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END ||
target + size < target) { target + size < target) {
LOG_ERROR(Kernel, "Invalid heap address");
return ERR_INVALID_ADDRESS; return ERR_INVALID_ADDRESS;
} }
@ -213,59 +215,72 @@ ResultCode Process::HeapFree(VAddr target, u32 size) {
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
ResultCode result = vm_manager.UnmapRange(target, size); // Free heaps block by block
if (result.IsError()) CASCADE_RESULT(auto backing_blocks, vm_manager.GetBackingBlocksForRange(target, size));
return result; for (const auto [backing_memory, block_size] : backing_blocks) {
memory_region->Free(Memory::GetFCRAMOffset(backing_memory), block_size);
}
heap_used -= size; ResultCode result = vm_manager.UnmapRange(target, size);
memory_region->used -= size; ASSERT(result.IsSuccess());
memory_used -= size;
resource_limit->current_commit -= size;
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
ResultVal<VAddr> Process::LinearAllocate(VAddr target, u32 size, VMAPermission perms) { ResultVal<VAddr> Process::LinearAllocate(VAddr target, u32 size, VMAPermission perms) {
auto& linheap_memory = memory_region->linear_heap_memory; LOG_DEBUG(Kernel, "Allocate linear heap target={:08X}, size={:08X}", target, size);
u32 physical_offset;
VAddr heap_end = GetLinearHeapBase() + (u32)linheap_memory->size();
// Games and homebrew only ever seem to pass 0 here (which lets the kernel decide the address),
// but explicit addresses are also accepted and respected.
if (target == 0) { if (target == 0) {
target = heap_end; auto offset = memory_region->LinearAllocate(size);
if (!offset) {
LOG_ERROR(Kernel, "Not enough space");
return ERR_OUT_OF_HEAP_MEMORY;
} }
physical_offset = *offset;
if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() || target > heap_end || target = physical_offset + GetLinearHeapAreaAddress();
} else {
if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() ||
target + size < target) { target + size < target) {
LOG_ERROR(Kernel, "Invalid linear heap address");
return ERR_INVALID_ADDRESS; return ERR_INVALID_ADDRESS;
} }
// Expansion of the linear heap is only allowed if you do an allocation immediately at its // Kernel would crash/return error when target doesn't meet some requirement.
// end. It's possible to free gaps in the middle of the heap and then reallocate them later, // It seems that target is required to follow immediately after the allocated linear heap,
// but expansions are only allowed at the end. // or cover the entire hole if there is any.
if (target == heap_end) { // Right now we just ignore these checks because they are still unclear. Further more,
linheap_memory->insert(linheap_memory->end(), size, 0); // games and homebrew only ever seem to pass target = 0 here (which lets the kernel decide
vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); // the address), so this not important.
physical_offset = target - GetLinearHeapAreaAddress(); // relative to FCRAM
if (!memory_region->LinearAllocate(physical_offset, size)) {
LOG_ERROR(Kernel, "Trying to allocate already allocated memory");
return ERR_INVALID_ADDRESS_STATE;
}
} }
// TODO(yuriks): As is, this lets processes map memory allocated by other processes from the u8* backing_memory = Memory::fcram.data() + physical_offset;
// same region. It is unknown if or how the 3DS kernel checks against this.
std::size_t offset = target - GetLinearHeapBase();
CASCADE_RESULT(auto vma, vm_manager.MapMemoryBlock(target, linheap_memory, offset, size,
MemoryState::Continuous));
vm_manager.Reprotect(vma, perms);
linear_heap_used += size; std::fill(backing_memory, backing_memory + size, 0);
memory_region->used += size; auto vma = vm_manager.MapBackingMemory(target, backing_memory, size, MemoryState::Continuous);
ASSERT(vma.Succeeded());
vm_manager.Reprotect(vma.Unwrap(), perms);
memory_used += size;
resource_limit->current_commit += size;
LOG_DEBUG(Kernel, "Allocated at target={:08X}", target);
return MakeResult<VAddr>(target); return MakeResult<VAddr>(target);
} }
ResultCode Process::LinearFree(VAddr target, u32 size) { ResultCode Process::LinearFree(VAddr target, u32 size) {
auto& linheap_memory = memory_region->linear_heap_memory; LOG_DEBUG(Kernel, "Free linear heap target={:08X}, size={:08X}", target, size);
if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() || if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() ||
target + size < target) { target + size < target) {
LOG_ERROR(Kernel, "Invalid linear heap address");
return ERR_INVALID_ADDRESS; return ERR_INVALID_ADDRESS;
} }
@ -273,30 +288,73 @@ ResultCode Process::LinearFree(VAddr target, u32 size) {
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
VAddr heap_end = GetLinearHeapBase() + (u32)linheap_memory->size(); ResultCode result = vm_manager.UnmapRange(target, size);
if (target + size > heap_end) { if (result.IsError()) {
LOG_ERROR(Kernel, "Trying to free already freed memory");
return result;
}
memory_used -= size;
resource_limit->current_commit -= size;
u32 physical_offset = target - GetLinearHeapAreaAddress(); // relative to FCRAM
memory_region->Free(physical_offset, size);
return RESULT_SUCCESS;
}
ResultCode Process::Map(VAddr target, VAddr source, u32 size, VMAPermission perms) {
LOG_DEBUG(Kernel, "Map memory target={:08X}, source={:08X}, size={:08X}, perms={:08X}", target,
source, size, static_cast<u8>(perms));
if (source < Memory::HEAP_VADDR || source + size > Memory::HEAP_VADDR_END ||
source + size < source) {
LOG_ERROR(Kernel, "Invalid source address");
return ERR_INVALID_ADDRESS;
}
// TODO(wwylele): check target address range. Is it also restricted to heap region?
auto vma = vm_manager.FindVMA(target);
if (vma->second.type != VMAType::Free || vma->second.base + vma->second.size < target + size) {
LOG_ERROR(Kernel, "Trying to map to already allocated memory");
return ERR_INVALID_ADDRESS_STATE; return ERR_INVALID_ADDRESS_STATE;
} }
ResultCode result = vm_manager.UnmapRange(target, size); // Mark source region as Aliased
if (result.IsError()) CASCADE_CODE(vm_manager.ChangeMemoryState(source, size, MemoryState::Private,
return result; VMAPermission::ReadWrite, MemoryState::Aliased,
VMAPermission::ReadWrite));
linear_heap_used -= size; CASCADE_RESULT(auto backing_blocks, vm_manager.GetBackingBlocksForRange(source, size));
memory_region->used -= size; VAddr interval_target = target;
for (const auto [backing_memory, block_size] : backing_blocks) {
auto target_vma = vm_manager.MapBackingMemory(interval_target, backing_memory, block_size,
MemoryState::Alias);
interval_target += block_size;
}
if (target + size == heap_end) { return RESULT_SUCCESS;
// End of linear heap has been freed, so check what's the last allocated block in it and }
// reduce the size. ResultCode Process::Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms) {
auto vma = vm_manager.FindVMA(target); LOG_DEBUG(Kernel, "Unmap memory target={:08X}, source={:08X}, size={:08X}, perms={:08X}",
ASSERT(vma != vm_manager.vma_map.end()); target, source, size, static_cast<u8>(perms));
ASSERT(vma->second.type == VMAType::Free); if (source < Memory::HEAP_VADDR || source + size > Memory::HEAP_VADDR_END ||
VAddr new_end = vma->second.base; source + size < source) {
if (new_end >= GetLinearHeapBase()) { LOG_ERROR(Kernel, "Invalid source address");
linheap_memory->resize(new_end - GetLinearHeapBase()); return ERR_INVALID_ADDRESS;
}
} }
// TODO(wwylele): check target address range. Is it also restricted to heap region?
// TODO(wwylele): check that the source and the target are actually a pair created by Map
// Should return error 0xD8E007F5 in this case
CASCADE_CODE(vm_manager.UnmapRange(target, size));
// Change back source region state. Note that the permission is reprotected according to param
CASCADE_CODE(vm_manager.ChangeMemoryState(source, size, MemoryState::Aliased,
VMAPermission::None, MemoryState::Private, perms));
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }

View File

@ -165,15 +165,7 @@ public:
VMManager vm_manager; VMManager vm_manager;
// Memory used to back the allocations in the regular heap. A single vector is used to cover u32 memory_used = 0;
// the entire virtual address space extents that bound the allocations, including any holes.
// This makes deallocation and reallocation of holes fast and keeps process memory contiguous
// in the emulator address space, allowing Memory::GetPointer to be reasonably safe.
std::shared_ptr<std::vector<u8>> heap_memory;
// The left/right bounds of the address space covered by heap_memory.
VAddr heap_start = 0, heap_end = 0;
u32 heap_used = 0, linear_heap_used = 0, misc_memory_used = 0;
MemoryRegionInfo* memory_region = nullptr; MemoryRegionInfo* memory_region = nullptr;
@ -188,12 +180,17 @@ public:
VAddr GetLinearHeapBase() const; VAddr GetLinearHeapBase() const;
VAddr GetLinearHeapLimit() const; VAddr GetLinearHeapLimit() const;
ResultVal<VAddr> HeapAllocate(VAddr target, u32 size, VMAPermission perms); ResultVal<VAddr> HeapAllocate(VAddr target, u32 size, VMAPermission perms,
MemoryState memory_state = MemoryState::Private,
bool skip_range_check = false);
ResultCode HeapFree(VAddr target, u32 size); ResultCode HeapFree(VAddr target, u32 size);
ResultVal<VAddr> LinearAllocate(VAddr target, u32 size, VMAPermission perms); ResultVal<VAddr> LinearAllocate(VAddr target, u32 size, VMAPermission perms);
ResultCode LinearFree(VAddr target, u32 size); ResultCode LinearFree(VAddr target, u32 size);
ResultCode Map(VAddr target, VAddr source, u32 size, VMAPermission perms);
ResultCode Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms);
private: private:
explicit Process(Kernel::KernelSystem& kernel); explicit Process(Kernel::KernelSystem& kernel);
~Process() override; ~Process() override;

View File

@ -11,8 +11,13 @@
namespace Kernel { namespace Kernel {
SharedMemory::SharedMemory(KernelSystem& kernel) : Object(kernel) {} SharedMemory::SharedMemory(KernelSystem& kernel) : Object(kernel), kernel(kernel) {}
SharedMemory::~SharedMemory() {} SharedMemory::~SharedMemory() {
for (const auto& interval : holding_memory) {
kernel.GetMemoryRegion(MemoryRegion::SYSTEM)
->Free(interval.lower(), interval.upper() - interval.lower());
}
}
SharedPtr<SharedMemory> KernelSystem::CreateSharedMemory(Process* owner_process, u32 size, SharedPtr<SharedMemory> KernelSystem::CreateSharedMemory(Process* owner_process, u32 size,
MemoryPermission permissions, MemoryPermission permissions,
@ -31,44 +36,26 @@ SharedPtr<SharedMemory> KernelSystem::CreateSharedMemory(Process* owner_process,
// We need to allocate a block from the Linear Heap ourselves. // We need to allocate a block from the Linear Heap ourselves.
// We'll manually allocate some memory from the linear heap in the specified region. // We'll manually allocate some memory from the linear heap in the specified region.
MemoryRegionInfo* memory_region = GetMemoryRegion(region); MemoryRegionInfo* memory_region = GetMemoryRegion(region);
auto& linheap_memory = memory_region->linear_heap_memory; auto offset = memory_region->LinearAllocate(size);
ASSERT_MSG(linheap_memory->size() + size <= memory_region->size, ASSERT_MSG(offset, "Not enough space in region to allocate shared memory!");
"Not enough space in region to allocate shared memory!");
shared_memory->backing_block = linheap_memory; std::fill(Memory::fcram.data() + *offset, Memory::fcram.data() + *offset + size, 0);
shared_memory->backing_block_offset = linheap_memory->size(); shared_memory->backing_blocks = {{Memory::fcram.data() + *offset, size}};
// Allocate some memory from the end of the linear heap for this region. shared_memory->holding_memory += MemoryRegionInfo::Interval(*offset, *offset + size);
linheap_memory->insert(linheap_memory->end(), size, 0); shared_memory->linear_heap_phys_offset = *offset;
memory_region->used += size;
shared_memory->linear_heap_phys_address =
Memory::FCRAM_PADDR + memory_region->base +
static_cast<PAddr>(shared_memory->backing_block_offset);
// Increase the amount of used linear heap memory for the owner process. // Increase the amount of used linear heap memory for the owner process.
if (shared_memory->owner_process != nullptr) { if (shared_memory->owner_process != nullptr) {
shared_memory->owner_process->linear_heap_used += size; shared_memory->owner_process->memory_used += size;
}
// Refresh the address mappings for the current process.
if (current_process != nullptr) {
current_process->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get());
} }
} else { } else {
auto& vm_manager = shared_memory->owner_process->vm_manager; auto& vm_manager = shared_memory->owner_process->vm_manager;
// The memory is already available and mapped in the owner process. // The memory is already available and mapped in the owner process.
auto vma = vm_manager.FindVMA(address);
ASSERT_MSG(vma != vm_manager.vma_map.end(), "Invalid memory address");
ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address");
// The returned VMA might be a bigger one encompassing the desired address. auto backing_blocks = vm_manager.GetBackingBlocksForRange(address, size);
auto vma_offset = address - vma->first; ASSERT_MSG(backing_blocks.Succeeded(), "Trying to share freed memory");
ASSERT_MSG(vma_offset + size <= vma->second.size, shared_memory->backing_blocks = std::move(backing_blocks).Unwrap();
"Shared memory exceeds bounds of mapped block");
shared_memory->backing_block = vma->second.backing_block;
shared_memory->backing_block_offset = vma->second.offset + vma_offset;
} }
shared_memory->base_address = address; shared_memory->base_address = address;
@ -76,17 +63,26 @@ SharedPtr<SharedMemory> KernelSystem::CreateSharedMemory(Process* owner_process,
} }
SharedPtr<SharedMemory> KernelSystem::CreateSharedMemoryForApplet( SharedPtr<SharedMemory> KernelSystem::CreateSharedMemoryForApplet(
std::shared_ptr<std::vector<u8>> heap_block, u32 offset, u32 size, MemoryPermission permissions, u32 offset, u32 size, MemoryPermission permissions, MemoryPermission other_permissions,
MemoryPermission other_permissions, std::string name) { std::string name) {
SharedPtr<SharedMemory> shared_memory(new SharedMemory(*this)); SharedPtr<SharedMemory> shared_memory(new SharedMemory(*this));
// Allocate memory in heap
MemoryRegionInfo* memory_region = GetMemoryRegion(MemoryRegion::SYSTEM);
auto backing_blocks = memory_region->HeapAllocate(size);
ASSERT_MSG(!backing_blocks.empty(), "Not enough space in region to allocate shared memory!");
shared_memory->holding_memory = backing_blocks;
shared_memory->owner_process = nullptr; shared_memory->owner_process = nullptr;
shared_memory->name = std::move(name); shared_memory->name = std::move(name);
shared_memory->size = size; shared_memory->size = size;
shared_memory->permissions = permissions; shared_memory->permissions = permissions;
shared_memory->other_permissions = other_permissions; shared_memory->other_permissions = other_permissions;
shared_memory->backing_block = heap_block; for (const auto& interval : backing_blocks) {
shared_memory->backing_block_offset = offset; shared_memory->backing_blocks.push_back(
{Memory::fcram.data() + interval.lower(), interval.upper() - interval.lower()});
std::fill(Memory::fcram.data() + interval.lower(), Memory::fcram.data() + interval.upper(),
0);
}
shared_memory->base_address = Memory::HEAP_VADDR + offset; shared_memory->base_address = Memory::HEAP_VADDR + offset;
return shared_memory; return shared_memory;
@ -146,24 +142,29 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
if (base_address == 0 && target_address == 0) { if (base_address == 0 && target_address == 0) {
// Calculate the address at which to map the memory block. // Calculate the address at which to map the memory block.
auto maybe_vaddr = Memory::PhysicalToVirtualAddress(linear_heap_phys_address); target_address = linear_heap_phys_offset + target_process->GetLinearHeapAreaAddress();
ASSERT(maybe_vaddr); }
target_address = *maybe_vaddr;
auto vma = target_process->vm_manager.FindVMA(target_address);
if (vma->second.type != VMAType::Free ||
vma->second.base + vma->second.size < target_address + size) {
LOG_ERROR(Kernel,
"cannot map id={}, address=0x{:08X} name={}, mapping to already allocated memory",
GetObjectId(), address, name);
return ERR_INVALID_ADDRESS_STATE;
} }
// Map the memory block into the target process // Map the memory block into the target process
auto result = target_process->vm_manager.MapMemoryBlock( VAddr interval_target = target_address;
target_address, backing_block, backing_block_offset, size, MemoryState::Shared); for (const auto& interval : backing_blocks) {
if (result.Failed()) { auto vma = target_process->vm_manager.MapBackingMemory(
LOG_ERROR( interval_target, interval.first, interval.second, MemoryState::Shared);
Kernel, ASSERT(vma.Succeeded());
"cannot map id={}, target_address=0x{:08X} name={}, error mapping to virtual memory", target_process->vm_manager.Reprotect(vma.Unwrap(), ConvertPermissions(permissions));
GetObjectId(), target_address, name); interval_target += interval.second;
return result.Code();
} }
return target_process->vm_manager.ReprotectRange(target_address, size, return RESULT_SUCCESS;
ConvertPermissions(permissions));
} }
ResultCode SharedMemory::Unmap(Process* target_process, VAddr address) { ResultCode SharedMemory::Unmap(Process* target_process, VAddr address) {
@ -179,7 +180,10 @@ VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) {
}; };
u8* SharedMemory::GetPointer(u32 offset) { u8* SharedMemory::GetPointer(u32 offset) {
return backing_block->data() + backing_block_offset + offset; if (backing_blocks.size() != 1) {
LOG_WARNING(Kernel, "Unsafe GetPointer on discontinuous SharedMemory");
}
return backing_blocks[0].first + offset;
} }
} // namespace Kernel } // namespace Kernel

View File

@ -61,13 +61,11 @@ public:
Process* owner_process; Process* owner_process;
/// Address of shared memory block in the owner process if specified. /// Address of shared memory block in the owner process if specified.
VAddr base_address; VAddr base_address;
/// Physical address of the shared memory block in the linear heap if no address was specified /// Offset in FCRAM of the shared memory block in the linear heap if no address was specified
/// during creation. /// during creation.
PAddr linear_heap_phys_address; PAddr linear_heap_phys_offset;
/// Backing memory for this shared memory block. /// Backing memory for this shared memory block.
std::shared_ptr<std::vector<u8>> backing_block; std::vector<std::pair<u8*, u32>> backing_blocks;
/// Offset into the backing block for this shared memory.
std::size_t backing_block_offset;
/// Size of the memory block. Page-aligned. /// Size of the memory block. Page-aligned.
u32 size; u32 size;
/// Permission restrictions applied to the process which created the block. /// Permission restrictions applied to the process which created the block.
@ -77,11 +75,14 @@ public:
/// Name of shared memory object. /// Name of shared memory object.
std::string name; std::string name;
MemoryRegionInfo::IntervalSet holding_memory;
private: private:
explicit SharedMemory(KernelSystem& kernel); explicit SharedMemory(KernelSystem& kernel);
~SharedMemory() override; ~SharedMemory() override;
friend class KernelSystem; friend class KernelSystem;
KernelSystem& kernel;
}; };
} // namespace Kernel } // namespace Kernel

View File

@ -114,16 +114,12 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add
} }
case MEMOP_MAP: { case MEMOP_MAP: {
// TODO: This is just a hack to avoid regressions until memory aliasing is implemented CASCADE_CODE(process.Map(addr0, addr1, size, vma_permissions));
CASCADE_RESULT(*out_addr, process.HeapAllocate(addr0, size, vma_permissions));
break; break;
} }
case MEMOP_UNMAP: { case MEMOP_UNMAP: {
// TODO: This is just a hack to avoid regressions until memory aliasing is implemented CASCADE_CODE(process.Unmap(addr0, addr1, size, vma_permissions));
ResultCode result = process.HeapFree(addr0, size);
if (result.IsError())
return result;
break; break;
} }
@ -1289,7 +1285,7 @@ static ResultCode GetProcessInfo(s64* out, Handle process_handle, u32 type) {
case 2: case 2:
// TODO(yuriks): Type 0 returns a slightly higher number than type 2, but I'm not sure // TODO(yuriks): Type 0 returns a slightly higher number than type 2, but I'm not sure
// what's the difference between them. // what's the difference between them.
*out = process->heap_used + process->linear_heap_used + process->misc_memory_used; *out = process->memory_used;
if (*out % Memory::PAGE_SIZE != 0) { if (*out % Memory::PAGE_SIZE != 0) {
LOG_ERROR(Kernel_SVC, "called, memory size not page-aligned"); LOG_ERROR(Kernel_SVC, "called, memory size not page-aligned");
return ERR_MISALIGNED_SIZE; return ERR_MISALIGNED_SIZE;

View File

@ -333,32 +333,27 @@ ResultVal<SharedPtr<Thread>> KernelSystem::CreateThread(std::string name, VAddr
// There are no already-allocated pages with free slots, lets allocate a new one. // There are no already-allocated pages with free slots, lets allocate a new one.
// TLS pages are allocated from the BASE region in the linear heap. // TLS pages are allocated from the BASE region in the linear heap.
MemoryRegionInfo* memory_region = GetMemoryRegion(MemoryRegion::BASE); MemoryRegionInfo* memory_region = GetMemoryRegion(MemoryRegion::BASE);
auto& linheap_memory = memory_region->linear_heap_memory;
if (linheap_memory->size() + Memory::PAGE_SIZE > memory_region->size) { // Allocate some memory from the end of the linear heap for this region.
auto offset = memory_region->LinearAllocate(Memory::PAGE_SIZE);
if (!offset) {
LOG_ERROR(Kernel_SVC, LOG_ERROR(Kernel_SVC,
"Not enough space in region to allocate a new TLS page for thread"); "Not enough space in region to allocate a new TLS page for thread");
return ERR_OUT_OF_MEMORY; return ERR_OUT_OF_MEMORY;
} }
owner_process.memory_used += Memory::PAGE_SIZE;
std::size_t offset = linheap_memory->size();
// Allocate some memory from the end of the linear heap for this region.
linheap_memory->insert(linheap_memory->end(), Memory::PAGE_SIZE, 0);
memory_region->used += Memory::PAGE_SIZE;
owner_process.linear_heap_used += Memory::PAGE_SIZE;
tls_slots.emplace_back(0); // The page is completely available at the start tls_slots.emplace_back(0); // The page is completely available at the start
available_page = tls_slots.size() - 1; available_page = tls_slots.size() - 1;
available_slot = 0; // Use the first slot in the new page available_slot = 0; // Use the first slot in the new page
auto& vm_manager = owner_process.vm_manager; auto& vm_manager = owner_process.vm_manager;
vm_manager.RefreshMemoryBlockMappings(linheap_memory.get());
// Map the page to the current process' address space. // Map the page to the current process' address space.
// TODO(Subv): Find the correct MemoryState for this region. // TODO(Subv): Find the correct MemoryState for this region.
vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE, vm_manager.MapBackingMemory(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE,
linheap_memory, offset, Memory::PAGE_SIZE, MemoryState::Private); Memory::fcram.data() + *offset, Memory::PAGE_SIZE,
MemoryState::Private);
} }
// Mark the slot as used // Mark the slot as used
@ -366,6 +361,8 @@ ResultVal<SharedPtr<Thread>> KernelSystem::CreateThread(std::string name, VAddr
thread->tls_address = Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE + thread->tls_address = Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE +
available_slot * Memory::TLS_ENTRY_SIZE; available_slot * Memory::TLS_ENTRY_SIZE;
Memory::ZeroBlock(owner_process, thread->tls_address, Memory::TLS_ENTRY_SIZE);
// TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
// to initialize the context // to initialize the context
ResetThreadContext(thread->context, stack_top, entry_point, arg); ResetThreadContext(thread->context, stack_top, entry_point, arg);

View File

@ -408,4 +408,25 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
break; break;
} }
} }
ResultVal<std::vector<std::pair<u8*, u32>>> VMManager::GetBackingBlocksForRange(VAddr address,
u32 size) {
std::vector<std::pair<u8*, u32>> backing_blocks;
VAddr interval_target = address;
while (interval_target != address + size) {
auto vma = FindVMA(interval_target);
if (vma->second.type != VMAType::BackingMemory) {
LOG_ERROR(Kernel, "Trying to use already freed memory");
return ERR_INVALID_ADDRESS_STATE;
}
VAddr interval_end = std::min(address + size, vma->second.base + vma->second.size);
u32 interval_size = interval_end - interval_target;
u8* backing_memory = vma->second.backing_memory + (interval_target - vma->second.base);
backing_blocks.push_back({backing_memory, interval_size});
interval_target += interval_size;
}
return MakeResult(backing_blocks);
}
} // namespace Kernel } // namespace Kernel

View File

@ -6,6 +6,7 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <utility>
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hle/result.h" #include "core/hle/result.h"
@ -213,6 +214,9 @@ public:
/// Dumps the address space layout to the log, for debugging /// Dumps the address space layout to the log, for debugging
void LogLayout(Log::Level log_level) const; void LogLayout(Log::Level log_level) const;
/// Gets a list of backing memory blocks for the specified range
ResultVal<std::vector<std::pair<u8*, u32>>> GetBackingBlocksForRange(VAddr address, u32 size);
/// Each VMManager has its own page table, which is set as the main one when the owning process /// Each VMManager has its own page table, which is set as the main one when the owning process
/// is scheduled. /// is scheduled.
Memory::PageTable page_table; Memory::PageTable page_table;

View File

@ -207,8 +207,8 @@ void Module::Interface::GetSharedFont(Kernel::HLERequestContext& ctx) {
// The shared font has to be relocated to the new address before being passed to the // The shared font has to be relocated to the new address before being passed to the
// application. // application.
auto maybe_vaddr = auto maybe_vaddr = Memory::PhysicalToVirtualAddress(
Memory::PhysicalToVirtualAddress(apt->shared_font_mem->linear_heap_phys_address); apt->shared_font_mem->linear_heap_phys_offset + Memory::FCRAM_PADDR);
ASSERT(maybe_vaddr); ASSERT(maybe_vaddr);
VAddr target_address = *maybe_vaddr; VAddr target_address = *maybe_vaddr;
if (!apt->shared_font_relocated) { if (!apt->shared_font_relocated) {

View File

@ -23,6 +23,7 @@ namespace Memory {
static std::array<u8, Memory::VRAM_SIZE> vram; static std::array<u8, Memory::VRAM_SIZE> vram;
static std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram; static std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram;
std::array<u8, Memory::FCRAM_N3DS_SIZE> fcram;
static PageTable* current_page_table = nullptr; static PageTable* current_page_table = nullptr;
@ -305,15 +306,7 @@ u8* GetPhysicalPointer(PAddr address) {
target_pointer = Core::DSP().GetDspMemory().data() + offset_into_region; target_pointer = Core::DSP().GetDspMemory().data() + offset_into_region;
break; break;
case FCRAM_PADDR: case FCRAM_PADDR:
for (const auto& region : Core::System::GetInstance().Kernel().memory_regions) { target_pointer = fcram.data() + offset_into_region;
if (offset_into_region >= region.base &&
offset_into_region < region.base + region.size) {
target_pointer =
region.linear_heap_memory->data() + offset_into_region - region.base;
break;
}
}
ASSERT_MSG(target_pointer != nullptr, "Invalid FCRAM address");
break; break;
case N3DS_EXTRA_RAM_PADDR: case N3DS_EXTRA_RAM_PADDR:
target_pointer = n3ds_extra_ram.data() + offset_into_region; target_pointer = n3ds_extra_ram.data() + offset_into_region;
@ -846,4 +839,9 @@ std::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) {
return {}; return {};
} }
u32 GetFCRAMOffset(u8* pointer) {
ASSERT(pointer >= fcram.data() && pointer < fcram.data() + fcram.size());
return pointer - fcram.data();
}
} // namespace Memory } // namespace Memory

View File

@ -176,6 +176,8 @@ enum : VAddr {
NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE, NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE,
}; };
extern std::array<u8, Memory::FCRAM_N3DS_SIZE> fcram;
/// Currently active page table /// Currently active page table
void SetCurrentPageTable(PageTable* page_table); void SetCurrentPageTable(PageTable* page_table);
PageTable* GetCurrentPageTable(); PageTable* GetCurrentPageTable();
@ -271,4 +273,7 @@ enum class FlushMode {
*/ */
void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode); void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode);
/// Gets offset in FCRAM from a pointer inside FCRAM range
u32 GetFCRAMOffset(u8* pointer);
} // namespace Memory } // namespace Memory