00c69f2098
* Remove usage of Mono.Posix.NETStandard in Ryujinx project * Remove usage of Mono.Posix.NETStandard in ARMeilleure project * Remove usage of Mono.Posix.NETStandard in Ryujinx.Memory project * Address gdkchan's comments
274 lines
8.6 KiB
C#
274 lines
8.6 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.Versioning;
|
|
|
|
using static Ryujinx.Memory.MemoryManagerUnixHelper;
|
|
|
|
namespace Ryujinx.Memory
|
|
{
|
|
[SupportedOSPlatform("linux")]
|
|
[SupportedOSPlatform("macos")]
|
|
static class MemoryManagementUnix
|
|
{
|
|
private struct UnixSharedMemory
|
|
{
|
|
public IntPtr Pointer;
|
|
public ulong Size;
|
|
public IntPtr SourcePointer;
|
|
}
|
|
|
|
private static readonly List<UnixSharedMemory> _sharedMemory = new List<UnixSharedMemory>();
|
|
private static readonly ConcurrentDictionary<IntPtr, ulong> _sharedMemorySource = new ConcurrentDictionary<IntPtr, ulong>();
|
|
private static readonly ConcurrentDictionary<IntPtr, ulong> _allocations = new ConcurrentDictionary<IntPtr, ulong>();
|
|
|
|
public static IntPtr Allocate(ulong size)
|
|
{
|
|
return AllocateInternal(size, MmapProts.PROT_READ | MmapProts.PROT_WRITE);
|
|
}
|
|
|
|
public static IntPtr Reserve(ulong size)
|
|
{
|
|
return AllocateInternal(size, MmapProts.PROT_NONE);
|
|
}
|
|
|
|
private static IntPtr AllocateInternal(ulong size, MmapProts prot, bool shared = false)
|
|
{
|
|
MmapFlags flags = MmapFlags.MAP_ANONYMOUS;
|
|
|
|
if (shared)
|
|
{
|
|
flags |= MmapFlags.MAP_SHARED | MmapFlags.MAP_UNLOCKED;
|
|
}
|
|
else
|
|
{
|
|
flags |= MmapFlags.MAP_PRIVATE;
|
|
}
|
|
|
|
if (prot == MmapProts.PROT_NONE)
|
|
{
|
|
flags |= MmapFlags.MAP_NORESERVE;
|
|
}
|
|
|
|
IntPtr ptr = mmap(IntPtr.Zero, size, prot, flags, -1, 0);
|
|
|
|
if (ptr == new IntPtr(-1L))
|
|
{
|
|
throw new OutOfMemoryException();
|
|
}
|
|
|
|
if (!_allocations.TryAdd(ptr, size))
|
|
{
|
|
// This should be impossible, kernel shouldn't return an already mapped address.
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
public static bool Commit(IntPtr address, ulong size)
|
|
{
|
|
bool success = mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) == 0;
|
|
|
|
if (success)
|
|
{
|
|
foreach (var shared in _sharedMemory)
|
|
{
|
|
if ((ulong)address + size > (ulong)shared.SourcePointer && (ulong)address < (ulong)shared.SourcePointer + shared.Size)
|
|
{
|
|
ulong sharedAddress = ((ulong)address - (ulong)shared.SourcePointer) + (ulong)shared.Pointer;
|
|
|
|
if (mprotect((IntPtr)sharedAddress, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) != 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
public static bool Decommit(IntPtr address, ulong size)
|
|
{
|
|
bool isShared;
|
|
|
|
lock (_sharedMemory)
|
|
{
|
|
isShared = _sharedMemory.Exists(x => (ulong)address >= (ulong)x.Pointer && (ulong)address + size <= (ulong)x.Pointer + x.Size);
|
|
}
|
|
|
|
// Must be writable for madvise to work properly.
|
|
mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE);
|
|
|
|
madvise(address, size, isShared ? MADV_REMOVE : MADV_DONTNEED);
|
|
|
|
return mprotect(address, size, MmapProts.PROT_NONE) == 0;
|
|
}
|
|
|
|
public static bool Reprotect(IntPtr address, ulong size, MemoryPermission permission)
|
|
{
|
|
return mprotect(address, size, GetProtection(permission)) == 0;
|
|
}
|
|
|
|
private static MmapProts GetProtection(MemoryPermission permission)
|
|
{
|
|
return permission switch
|
|
{
|
|
MemoryPermission.None => MmapProts.PROT_NONE,
|
|
MemoryPermission.Read => MmapProts.PROT_READ,
|
|
MemoryPermission.ReadAndWrite => MmapProts.PROT_READ | MmapProts.PROT_WRITE,
|
|
MemoryPermission.ReadAndExecute => MmapProts.PROT_READ | MmapProts.PROT_EXEC,
|
|
MemoryPermission.ReadWriteExecute => MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC,
|
|
MemoryPermission.Execute => MmapProts.PROT_EXEC,
|
|
_ => throw new MemoryProtectionException(permission)
|
|
};
|
|
}
|
|
|
|
public static bool Free(IntPtr address)
|
|
{
|
|
if (_allocations.TryRemove(address, out ulong size))
|
|
{
|
|
return munmap(address, size) == 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static IntPtr Remap(IntPtr target, IntPtr source, ulong size)
|
|
{
|
|
int flags = 1;
|
|
|
|
if (target != IntPtr.Zero)
|
|
{
|
|
flags |= 2;
|
|
}
|
|
|
|
IntPtr result = mremap(source, 0, size, flags, target);
|
|
|
|
if (result == IntPtr.Zero)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static IntPtr CreateSharedMemory(ulong size, bool reserve)
|
|
{
|
|
IntPtr result = AllocateInternal(
|
|
size,
|
|
reserve ? MmapProts.PROT_NONE : MmapProts.PROT_READ | MmapProts.PROT_WRITE,
|
|
true);
|
|
|
|
if (result == IntPtr.Zero)
|
|
{
|
|
throw new OutOfMemoryException();
|
|
}
|
|
|
|
_sharedMemorySource[result] = (ulong)size;
|
|
|
|
return result;
|
|
}
|
|
|
|
public static void DestroySharedMemory(IntPtr handle)
|
|
{
|
|
lock (_sharedMemory)
|
|
{
|
|
foreach (var memory in _sharedMemory)
|
|
{
|
|
if (memory.SourcePointer == handle)
|
|
{
|
|
throw new InvalidOperationException("Shared memory cannot be destroyed unless fully unmapped.");
|
|
}
|
|
}
|
|
}
|
|
|
|
_sharedMemorySource.Remove(handle, out ulong _);
|
|
}
|
|
|
|
public static IntPtr MapSharedMemory(IntPtr handle)
|
|
{
|
|
// Try find the handle for this shared memory. If it is mapped, then we want to map
|
|
// it a second time in another location.
|
|
// If it is not mapped, then its handle is the mapping.
|
|
|
|
ulong size = _sharedMemorySource[handle];
|
|
|
|
if (size == 0)
|
|
{
|
|
throw new InvalidOperationException("Shared memory cannot be mapped after its source is unmapped.");
|
|
}
|
|
|
|
lock (_sharedMemory)
|
|
{
|
|
foreach (var memory in _sharedMemory)
|
|
{
|
|
if (memory.Pointer == handle)
|
|
{
|
|
IntPtr result = AllocateInternal(
|
|
memory.Size,
|
|
MmapProts.PROT_NONE
|
|
);
|
|
|
|
if (result == IntPtr.Zero)
|
|
{
|
|
throw new OutOfMemoryException();
|
|
}
|
|
|
|
Remap(result, handle, memory.Size);
|
|
|
|
_sharedMemory.Add(new UnixSharedMemory
|
|
{
|
|
Pointer = result,
|
|
Size = memory.Size,
|
|
|
|
SourcePointer = handle
|
|
});
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
_sharedMemory.Add(new UnixSharedMemory
|
|
{
|
|
Pointer = handle,
|
|
Size = size,
|
|
|
|
SourcePointer = handle
|
|
});
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
|
|
public static void UnmapSharedMemory(IntPtr address)
|
|
{
|
|
lock (_sharedMemory)
|
|
{
|
|
int removed = _sharedMemory.RemoveAll(memory =>
|
|
{
|
|
if (memory.Pointer == address)
|
|
{
|
|
if (memory.Pointer == memory.SourcePointer)
|
|
{
|
|
// After removing the original mapping, it cannot be mapped again.
|
|
_sharedMemorySource[memory.SourcePointer] = 0;
|
|
}
|
|
|
|
Free(address);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
if (removed == 0)
|
|
{
|
|
throw new InvalidOperationException("Shared memory mapping could not be found.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |