MemoryManagementUnix.cs
1 using System; 2 using System.Collections.Concurrent; 3 using System.Runtime.InteropServices; 4 using System.Runtime.Versioning; 5 using static Ryujinx.Memory.MemoryManagerUnixHelper; 6 7 namespace Ryujinx.Memory 8 { 9 [SupportedOSPlatform("linux")] 10 [SupportedOSPlatform("macos")] 11 static class MemoryManagementUnix 12 { 13 private static readonly ConcurrentDictionary<IntPtr, ulong> _allocations = new(); 14 15 public static IntPtr Allocate(ulong size, bool forJit) 16 { 17 return AllocateInternal(size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, forJit); 18 } 19 20 public static IntPtr Reserve(ulong size, bool forJit) 21 { 22 return AllocateInternal(size, MmapProts.PROT_NONE, forJit); 23 } 24 25 private static IntPtr AllocateInternal(ulong size, MmapProts prot, bool forJit, bool shared = false) 26 { 27 MmapFlags flags = MmapFlags.MAP_ANONYMOUS; 28 29 if (shared) 30 { 31 flags |= MmapFlags.MAP_SHARED | MmapFlags.MAP_UNLOCKED; 32 } 33 else 34 { 35 flags |= MmapFlags.MAP_PRIVATE; 36 } 37 38 if (prot == MmapProts.PROT_NONE) 39 { 40 flags |= MmapFlags.MAP_NORESERVE; 41 } 42 43 if (OperatingSystem.IsMacOSVersionAtLeast(10, 14) && forJit) 44 { 45 flags |= MmapFlags.MAP_JIT_DARWIN; 46 47 if (prot == (MmapProts.PROT_READ | MmapProts.PROT_WRITE)) 48 { 49 prot |= MmapProts.PROT_EXEC; 50 } 51 } 52 53 IntPtr ptr = Mmap(IntPtr.Zero, size, prot, flags, -1, 0); 54 55 if (ptr == MAP_FAILED) 56 { 57 throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); 58 } 59 60 if (!_allocations.TryAdd(ptr, size)) 61 { 62 // This should be impossible, kernel shouldn't return an already mapped address. 63 throw new InvalidOperationException(); 64 } 65 66 return ptr; 67 } 68 69 public static void Commit(IntPtr address, ulong size, bool forJit) 70 { 71 MmapProts prot = MmapProts.PROT_READ | MmapProts.PROT_WRITE; 72 73 if (OperatingSystem.IsMacOSVersionAtLeast(10, 14) && forJit) 74 { 75 prot |= MmapProts.PROT_EXEC; 76 } 77 78 if (mprotect(address, size, prot) != 0) 79 { 80 throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); 81 } 82 } 83 84 public static void Decommit(IntPtr address, ulong size) 85 { 86 // Must be writable for madvise to work properly. 87 if (mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) != 0) 88 { 89 throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); 90 } 91 92 if (madvise(address, size, MADV_REMOVE) != 0) 93 { 94 throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); 95 } 96 97 if (mprotect(address, size, MmapProts.PROT_NONE) != 0) 98 { 99 throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); 100 } 101 } 102 103 public static bool Reprotect(IntPtr address, ulong size, MemoryPermission permission) 104 { 105 return mprotect(address, size, GetProtection(permission)) == 0; 106 } 107 108 private static MmapProts GetProtection(MemoryPermission permission) 109 { 110 return permission switch 111 { 112 MemoryPermission.None => MmapProts.PROT_NONE, 113 MemoryPermission.Read => MmapProts.PROT_READ, 114 MemoryPermission.ReadAndWrite => MmapProts.PROT_READ | MmapProts.PROT_WRITE, 115 MemoryPermission.ReadAndExecute => MmapProts.PROT_READ | MmapProts.PROT_EXEC, 116 MemoryPermission.ReadWriteExecute => MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC, 117 MemoryPermission.Execute => MmapProts.PROT_EXEC, 118 _ => throw new MemoryProtectionException(permission), 119 }; 120 } 121 122 public static bool Free(IntPtr address) 123 { 124 if (_allocations.TryRemove(address, out ulong size)) 125 { 126 return munmap(address, size) == 0; 127 } 128 129 return false; 130 } 131 132 public static bool Unmap(IntPtr address, ulong size) 133 { 134 return munmap(address, size) == 0; 135 } 136 137 public unsafe static IntPtr CreateSharedMemory(ulong size, bool reserve) 138 { 139 int fd; 140 141 if (OperatingSystem.IsMacOS()) 142 { 143 byte[] memName = "Ryujinx-XXXXXX"u8.ToArray(); 144 145 fixed (byte* pMemName = memName) 146 { 147 fd = shm_open((IntPtr)pMemName, 0x2 | 0x200 | 0x800 | 0x400, 384); // O_RDWR | O_CREAT | O_EXCL | O_TRUNC, 0600 148 if (fd == -1) 149 { 150 throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); 151 } 152 153 if (shm_unlink((IntPtr)pMemName) != 0) 154 { 155 throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); 156 } 157 } 158 } 159 else 160 { 161 byte[] fileName = "/dev/shm/Ryujinx-XXXXXX"u8.ToArray(); 162 163 fixed (byte* pFileName = fileName) 164 { 165 fd = mkstemp((IntPtr)pFileName); 166 if (fd == -1) 167 { 168 throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); 169 } 170 171 if (unlink((IntPtr)pFileName) != 0) 172 { 173 throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); 174 } 175 } 176 } 177 178 if (ftruncate(fd, (IntPtr)size) != 0) 179 { 180 throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); 181 } 182 183 return fd; 184 } 185 186 public static void DestroySharedMemory(IntPtr handle) 187 { 188 close(handle.ToInt32()); 189 } 190 191 public static IntPtr MapSharedMemory(IntPtr handle, ulong size) 192 { 193 return Mmap(IntPtr.Zero, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, MmapFlags.MAP_SHARED, handle.ToInt32(), 0); 194 } 195 196 public static void UnmapSharedMemory(IntPtr address, ulong size) 197 { 198 munmap(address, size); 199 } 200 201 public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, ulong size) 202 { 203 Mmap(location, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, MmapFlags.MAP_FIXED | MmapFlags.MAP_SHARED, sharedMemory.ToInt32(), (long)srcOffset); 204 } 205 206 public static void UnmapView(IntPtr location, ulong size) 207 { 208 Mmap(location, size, MmapProts.PROT_NONE, MmapFlags.MAP_FIXED | MmapFlags.MAP_PRIVATE | MmapFlags.MAP_ANONYMOUS | MmapFlags.MAP_NORESERVE, -1, 0); 209 } 210 } 211 }