diff --git a/app/src/main/cpp/skyline/gpu/buffer.cpp b/app/src/main/cpp/skyline/gpu/buffer.cpp index 2cf93d29..b108b86f 100644 --- a/app/src/main/cpp/skyline/gpu/buffer.cpp +++ b/app/src/main/cpp/skyline/gpu/buffer.cpp @@ -387,7 +387,7 @@ namespace skyline::gpu { SynchronizeHost(true); // Will transition the Buffer to Clean dirtyState = DirtyState::GpuDirty; - gpu.state.nce->PageOutRegions(*trapHandle); // All data can be paged out from the guest as the guest mirror won't be used + gpu.state.process->memory.FreeMemory(mirror); // All data can be paged out from the guest as the guest mirror won't be used BlockAllCpuBackingWrites(); AdvanceSequence(); // The GPU will modify buffer contents so advance to the next sequence diff --git a/app/src/main/cpp/skyline/gpu/texture/texture.cpp b/app/src/main/cpp/skyline/gpu/texture/texture.cpp index a2ed57c4..a5a2de04 100644 --- a/app/src/main/cpp/skyline/gpu/texture/texture.cpp +++ b/app/src/main/cpp/skyline/gpu/texture/texture.cpp @@ -728,7 +728,7 @@ namespace skyline::gpu { // If a texture is Clean then we can just transition it to being GPU dirty and retrap it dirtyState = DirtyState::GpuDirty; gpu.state.nce->TrapRegions(*trapHandle, false); - gpu.state.nce->PageOutRegions(*trapHandle); + gpu.state.process->memory.FreeMemory(mirror); return; } else if (dirtyState != DirtyState::CpuDirty) { return; // If the texture has not been modified on the CPU, there is no need to synchronize it @@ -756,7 +756,7 @@ namespace skyline::gpu { std::scoped_lock lock{stateMutex}; if (dirtyState != DirtyState::CpuDirty && gpuDirty) - gpu.state.nce->PageOutRegions(*trapHandle); // All data can be paged out from the guest as the guest mirror won't be used + gpu.state.process->memory.FreeMemory(mirror); // All data can be paged out from the guest as the guest mirror won't be used } } @@ -771,7 +771,7 @@ namespace skyline::gpu { if (gpuDirty && dirtyState == DirtyState::Clean) { dirtyState = DirtyState::GpuDirty; gpu.state.nce->TrapRegions(*trapHandle, false); - gpu.state.nce->PageOutRegions(*trapHandle); + gpu.state.process->memory.FreeMemory(mirror); return; } else if (dirtyState != DirtyState::CpuDirty) { return; @@ -793,7 +793,7 @@ namespace skyline::gpu { std::scoped_lock lock{stateMutex}; if (dirtyState != DirtyState::CpuDirty && gpuDirty) - gpu.state.nce->PageOutRegions(*trapHandle); // All data can be paged out from the guest as the guest mirror won't be used + gpu.state.process->memory.FreeMemory(mirror); // All data can be paged out from the guest as the guest mirror won't be used } } diff --git a/app/src/main/cpp/skyline/kernel/memory.cpp b/app/src/main/cpp/skyline/kernel/memory.cpp index 9b14050e..919e93ae 100644 --- a/app/src/main/cpp/skyline/kernel/memory.cpp +++ b/app/src/main/cpp/skyline/kernel/memory.cpp @@ -17,7 +17,7 @@ namespace skyline::kernel { constexpr size_t RegionAlignment{1ULL << 21}; //!< The minimum alignment of a HOS memory region constexpr size_t CodeRegionSize{4ULL * 1024 * 1024 * 1024}; //!< The assumed maximum size of the code region (4GiB) - static std::pair, FileDescriptor> AllocateMappedRange(size_t minSize, size_t align, size_t minAddress, size_t maxAddress, bool findLargest) { + static span AllocateMappedRange(size_t minSize, size_t align, size_t minAddress, size_t maxAddress, bool findLargest) { span region{}; size_t size{minSize}; @@ -47,18 +47,11 @@ namespace skyline::kernel { if (!region.valid()) throw exception("Allocation failed"); - FileDescriptor memoryFd{static_cast(syscall(__NR_memfd_create, "HOS-AS", MFD_CLOEXEC))}; // We need to use memfd directly as ASharedMemory doesn't always use it while we depend on it for FreeMemory (using FALLOC_FL_PUNCH_HOLE) to work - if (memoryFd == -1) - throw exception("Failed to create memfd for guest address space: {}", strerror(errno)); - - if (ftruncate(memoryFd, static_cast(size)) == -1) - throw exception("Failed to resize memfd for guest address space: {}", strerror(errno)); - - auto result{mmap(reinterpret_cast(region.data()), size, PROT_WRITE, MAP_FIXED | MAP_SHARED, memoryFd, 0)}; + auto result{mmap(reinterpret_cast(region.data()), size, PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_SHARED, -1, 0)}; if (result == MAP_FAILED) throw exception("Failed to mmap guest address space: {}", strerror(errno)); - return {region, memoryFd}; + return region; } void MemoryManager::InitializeVmm(memory::AddressSpaceType type) { @@ -88,7 +81,7 @@ namespace skyline::kernel { // Qualcomm KGSL (Kernel Graphic Support Layer/Kernel GPU driver) maps below 35-bits, reserving it causes KGSL to go OOM static constexpr size_t KgslReservedRegionSize{1ULL << 35}; if (type != memory::AddressSpaceType::AddressSpace36Bit) { - std::tie(base, memoryFd) = AllocateMappedRange(baseSize, RegionAlignment, KgslReservedRegionSize, addressSpace.size(), false); + base = AllocateMappedRange(baseSize, RegionAlignment, KgslReservedRegionSize, addressSpace.size(), false); chunks = { ChunkDescriptor{ @@ -110,8 +103,8 @@ namespace skyline::kernel { code = base; } else { - std::tie(base, memoryFd) = AllocateMappedRange(baseSize, 1ULL << 36, KgslReservedRegionSize, addressSpace.size(), false); - std::tie(codeBase36Bit, code36BitFd) = AllocateMappedRange(0x32000000, RegionAlignment, 0xC000000, 0x78000000ULL + reinterpret_cast(addressSpace.data()), true); + base = AllocateMappedRange(baseSize, 1ULL << 36, KgslReservedRegionSize, addressSpace.size(), false); + codeBase36Bit = AllocateMappedRange(0x32000000, RegionAlignment, 0xC000000, 0x78000000ULL + reinterpret_cast(addressSpace.data()), true); chunks = { ChunkDescriptor{ @@ -191,10 +184,12 @@ namespace skyline::kernel { if (!util::IsPageAligned(offset) || !util::IsPageAligned(mapping.size())) throw exception("Mapping is not aligned to a page: 0x{:X}-0x{:X} (0x{:X})", mapping.data(), mapping.end().base(), offset); - auto mirror{mmap(nullptr, mapping.size(), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, memoryFd, static_cast(offset))}; + auto mirror{mremap(mapping.data(), 0, mapping.size(), MREMAP_MAYMOVE)}; if (mirror == MAP_FAILED) throw exception("Failed to create mirror mapping at 0x{:X}-0x{:X} (0x{:X}): {}", mapping.data(), mapping.end().base(), offset, strerror(errno)); + mprotect(mirror, mapping.size(), PROT_READ | PROT_WRITE | PROT_EXEC); + return span{reinterpret_cast(mirror), mapping.size()}; } @@ -216,10 +211,12 @@ namespace skyline::kernel { if (!util::IsPageAligned(offset) || !util::IsPageAligned(region.size())) throw exception("Mapping is not aligned to a page: 0x{:X}-0x{:X} (0x{:X})", region.data(), region.end().base(), offset); - auto mirror{mmap(reinterpret_cast(mirrorBase) + mirrorOffset, region.size(), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED, memoryFd, static_cast(offset))}; + auto mirror{mremap(region.data(), 0, region.size(), MREMAP_FIXED | MREMAP_MAYMOVE, reinterpret_cast(mirrorBase) + mirrorOffset)}; if (mirror == MAP_FAILED) throw exception("Failed to create mirror mapping at 0x{:X}-0x{:X} (0x{:X}): {}", region.data(), region.end().base(), offset, strerror(errno)); + mprotect(mirror, region.size(), PROT_READ | PROT_WRITE | PROT_EXEC); + mirrorOffset += region.size(); } @@ -230,16 +227,12 @@ namespace skyline::kernel { } void MemoryManager::FreeMemory(span memory) { - if (!base.contains(memory)) - throw exception("Mapping is outside of VMM base: 0x{:X} - 0x{:X}", memory.data(), memory.end().base()); + u8 *alignedStart{util::AlignUp(memory.data(), constant::PageSize)}; + u8 *alignedEnd{util::AlignDown(memory.end().base(), constant::PageSize)}; - auto offset{static_cast(memory.data() - base.data())}; - if (!util::IsPageAligned(offset) || !util::IsPageAligned(memory.size())) - throw exception("Mapping is not aligned to a page: 0x{:X}-0x{:X} (0x{:X})", memory.data(), memory.end().base(), offset); - - // We need to use fallocate(FALLOC_FL_PUNCH_HOLE) to free the backing memory rather than madvise(MADV_REMOVE) as the latter fails when the memory doesn't have write permissions, we generally need to free memory after reprotecting it to disallow accesses between the two calls which would cause UB - if (fallocate(*memoryFd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, static_cast(offset), static_cast(memory.size())) != 0) - throw exception("Failed to free memory at 0x{:X}-0x{:X} (0x{:X}): {}", memory.data(), memory.end().base(), offset, strerror(errno)); + if (alignedStart < alignedEnd) + if (madvise(alignedStart, static_cast(alignedEnd - alignedStart), MADV_REMOVE) == -1) + throw exception("Failed to free memory: {}", strerror(errno)) ; } void MemoryManager::InsertChunk(const ChunkDescriptor &chunk) { diff --git a/app/src/main/cpp/skyline/kernel/memory.h b/app/src/main/cpp/skyline/kernel/memory.h index dc3ba27b..641ecd78 100644 --- a/app/src/main/cpp/skyline/kernel/memory.h +++ b/app/src/main/cpp/skyline/kernel/memory.h @@ -225,9 +225,6 @@ namespace skyline { span stack{}; span tlsIo{}; //!< TLS/IO - FileDescriptor memoryFd{}; //!< The file descriptor of the memory backing for the entire guest address space - FileDescriptor code36BitFd{}; //!< The file descriptor of the memory backing for in the lower 36 bits of the address space for mapping code and stack on 36-bit guests - std::shared_mutex mutex; //!< Synchronizes any operations done on the VMM, it's locked in shared mode by readers and exclusive mode by writers MemoryManager(const DeviceState &state); @@ -258,7 +255,7 @@ namespace skyline { span CreateMirrors(const std::vector> ®ions); /** - * @brief Frees the underlying physical memory for a page-aligned mapping in the guest address space + * @brief Frees the underlying physical memory for all full pages in the contained mapping * @note All subsequent accesses to freed memory will return 0s */ void FreeMemory(span memory); diff --git a/app/src/main/cpp/skyline/kernel/types/KSharedMemory.cpp b/app/src/main/cpp/skyline/kernel/types/KSharedMemory.cpp index 3400967f..fe3d84c2 100644 --- a/app/src/main/cpp/skyline/kernel/types/KSharedMemory.cpp +++ b/app/src/main/cpp/skyline/kernel/types/KSharedMemory.cpp @@ -58,7 +58,7 @@ namespace skyline::kernel::type { if (guest.data() != map.data() && guest.size() != map.size()) throw exception("Unmapping KSharedMemory partially is not supported: Requested Unmap: 0x{:X} - 0x{:X} (0x{:X}), Current Mapping: 0x{:X} - 0x{:X} (0x{:X})", map.data(), map.end().base(), map.size(), guest.data(), guest.end().base(), guest.size()); - if (mmap(map.data(), map.size(), PROT_NONE, MAP_SHARED | MAP_FIXED, memoryManager.memoryFd, reinterpret_cast(map.data() - memoryManager.base.data())) == MAP_FAILED) + if (mmap(map.data(), map.size(), PROT_NONE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED) throw exception("An error occurred while unmapping shared memory in guest: {}", strerror(errno)); guest = span{}; @@ -95,7 +95,7 @@ namespace skyline::kernel::type { if (state.process && guest.valid()) { auto &memoryManager{state.process->memory}; if (objectType != KType::KTransferMemory) { - if (mmap(guest.data(), guest.size(), PROT_NONE, MAP_SHARED | MAP_FIXED, memoryManager.memoryFd, reinterpret_cast(guest.data() - memoryManager.base.data())) == MAP_FAILED) + if (mmap(guest.data(), guest.size(), PROT_NONE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED) Logger::Warn("An error occurred while unmapping shared memory: {}", strerror(errno)); state.process->memory.InsertChunk(ChunkDescriptor{ @@ -107,7 +107,7 @@ namespace skyline::kernel::type { // KTransferMemory remaps the region with R/W permissions during destruction constexpr memory::Permission UnborrowPermission{true, true, false}; - if (mmap(guest.data(), guest.size(), UnborrowPermission.Get(), MAP_SHARED | MAP_FIXED, memoryManager.memoryFd, reinterpret_cast(guest.data() - memoryManager.base.data())) == MAP_FAILED) + if (mmap(guest.data(), guest.size(), UnborrowPermission.Get(), MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED) Logger::Warn("An error occurred while remapping transfer memory: {}", strerror(errno)); else if (!host.valid()) Logger::Warn("Expected host mapping of transfer memory to be valid during KTransferMemory destruction"); diff --git a/app/src/main/cpp/skyline/nce.cpp b/app/src/main/cpp/skyline/nce.cpp index 5e79f619..00a0ee0a 100644 --- a/app/src/main/cpp/skyline/nce.cpp +++ b/app/src/main/cpp/skyline/nce.cpp @@ -736,19 +736,6 @@ namespace skyline::nce { ReprotectIntervals(handle->intervals, protection); } - void NCE::PageOutRegions(TrapHandle handle) { - TRACE_EVENT("host", "NCE::PageOutRegions"); - std::scoped_lock lock{trapMutex}; - for (auto region : handle->intervals) { - auto freeStart{util::AlignUp(region.start, constant::PageSize)}, freeEnd{util::AlignDown(region.end, constant::PageSize)}; // We want to avoid the first and last page as they may contain unrelated data - ssize_t freeSize{freeEnd - freeStart}; - - constexpr ssize_t MinimumPageoutSize{constant::PageSize}; //!< The minimum size to page out, we don't want to page out small intervals for performance reasons - if (freeSize > MinimumPageoutSize) - state.process->memory.FreeMemory(span{freeStart, static_cast(freeSize)}); - } - } - void NCE::RemoveTrap(TrapHandle handle) { TRACE_EVENT("host", "NCE::RemoveTrap"); std::scoped_lock lock{trapMutex}; diff --git a/app/src/main/cpp/skyline/nce.h b/app/src/main/cpp/skyline/nce.h index 4534ed98..2b66dc25 100644 --- a/app/src/main/cpp/skyline/nce.h +++ b/app/src/main/cpp/skyline/nce.h @@ -150,13 +150,6 @@ namespace skyline::nce { */ void TrapRegions(TrapHandle handle, bool writeOnly); - /** - * @brief Pages out the supplied trap region of memory (except border pages), any future accesses will return 0s - * @note This function is intended to be used after trapping reads to a region where the callback pages back in the data - * @note If the region is determined to be too small, this function will not do anything and is not meant to deterministically page out the region - */ - void PageOutRegions(TrapHandle handle); - /** * @brief Removes protections from a region of memory */