MemoryManagementUnix.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Runtime.InteropServices;
  5. using System.Runtime.Versioning;
  6. using static Ryujinx.Memory.MemoryManagerUnixHelper;
  7. namespace Ryujinx.Memory
  8. {
  9. [SupportedOSPlatform("linux")]
  10. [SupportedOSPlatform("macos")]
  11. static class MemoryManagementUnix
  12. {
  13. private struct UnixSharedMemory
  14. {
  15. public IntPtr Pointer;
  16. public ulong Size;
  17. public IntPtr SourcePointer;
  18. }
  19. private static readonly List<UnixSharedMemory> _sharedMemory = new List<UnixSharedMemory>();
  20. private static readonly ConcurrentDictionary<IntPtr, ulong> _sharedMemorySource = new ConcurrentDictionary<IntPtr, ulong>();
  21. private static readonly ConcurrentDictionary<IntPtr, ulong> _allocations = new ConcurrentDictionary<IntPtr, ulong>();
  22. public static IntPtr Allocate(ulong size)
  23. {
  24. return AllocateInternal(size, MmapProts.PROT_READ | MmapProts.PROT_WRITE);
  25. }
  26. public static IntPtr Reserve(ulong size)
  27. {
  28. return AllocateInternal(size, MmapProts.PROT_NONE);
  29. }
  30. private static IntPtr AllocateInternal(ulong size, MmapProts prot, bool shared = false)
  31. {
  32. MmapFlags flags = MmapFlags.MAP_ANONYMOUS;
  33. if (shared)
  34. {
  35. flags |= MmapFlags.MAP_SHARED | MmapFlags.MAP_UNLOCKED;
  36. }
  37. else
  38. {
  39. flags |= MmapFlags.MAP_PRIVATE;
  40. }
  41. if (prot == MmapProts.PROT_NONE)
  42. {
  43. flags |= MmapFlags.MAP_NORESERVE;
  44. }
  45. IntPtr ptr = mmap(IntPtr.Zero, size, prot, flags, -1, 0);
  46. if (ptr == new IntPtr(-1L))
  47. {
  48. throw new OutOfMemoryException();
  49. }
  50. if (!_allocations.TryAdd(ptr, size))
  51. {
  52. // This should be impossible, kernel shouldn't return an already mapped address.
  53. throw new InvalidOperationException();
  54. }
  55. return ptr;
  56. }
  57. public static bool Commit(IntPtr address, ulong size)
  58. {
  59. bool success = mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) == 0;
  60. if (success)
  61. {
  62. foreach (var shared in _sharedMemory)
  63. {
  64. if ((ulong)address + size > (ulong)shared.SourcePointer && (ulong)address < (ulong)shared.SourcePointer + shared.Size)
  65. {
  66. ulong sharedAddress = ((ulong)address - (ulong)shared.SourcePointer) + (ulong)shared.Pointer;
  67. if (mprotect((IntPtr)sharedAddress, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) != 0)
  68. {
  69. return false;
  70. }
  71. }
  72. }
  73. }
  74. return success;
  75. }
  76. public static bool Decommit(IntPtr address, ulong size)
  77. {
  78. bool isShared;
  79. lock (_sharedMemory)
  80. {
  81. isShared = _sharedMemory.Exists(x => (ulong)address >= (ulong)x.Pointer && (ulong)address + size <= (ulong)x.Pointer + x.Size);
  82. }
  83. // Must be writable for madvise to work properly.
  84. mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE);
  85. madvise(address, size, isShared ? MADV_REMOVE : MADV_DONTNEED);
  86. return mprotect(address, size, MmapProts.PROT_NONE) == 0;
  87. }
  88. public static bool Reprotect(IntPtr address, ulong size, MemoryPermission permission)
  89. {
  90. return mprotect(address, size, GetProtection(permission)) == 0;
  91. }
  92. private static MmapProts GetProtection(MemoryPermission permission)
  93. {
  94. return permission switch
  95. {
  96. MemoryPermission.None => MmapProts.PROT_NONE,
  97. MemoryPermission.Read => MmapProts.PROT_READ,
  98. MemoryPermission.ReadAndWrite => MmapProts.PROT_READ | MmapProts.PROT_WRITE,
  99. MemoryPermission.ReadAndExecute => MmapProts.PROT_READ | MmapProts.PROT_EXEC,
  100. MemoryPermission.ReadWriteExecute => MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC,
  101. MemoryPermission.Execute => MmapProts.PROT_EXEC,
  102. _ => throw new MemoryProtectionException(permission)
  103. };
  104. }
  105. public static bool Free(IntPtr address)
  106. {
  107. if (_allocations.TryRemove(address, out ulong size))
  108. {
  109. return munmap(address, size) == 0;
  110. }
  111. return false;
  112. }
  113. public static IntPtr Remap(IntPtr target, IntPtr source, ulong size)
  114. {
  115. int flags = 1;
  116. if (target != IntPtr.Zero)
  117. {
  118. flags |= 2;
  119. }
  120. IntPtr result = mremap(source, 0, size, flags, target);
  121. if (result == IntPtr.Zero)
  122. {
  123. throw new InvalidOperationException();
  124. }
  125. return result;
  126. }
  127. public static IntPtr CreateSharedMemory(ulong size, bool reserve)
  128. {
  129. IntPtr result = AllocateInternal(
  130. size,
  131. reserve ? MmapProts.PROT_NONE : MmapProts.PROT_READ | MmapProts.PROT_WRITE,
  132. true);
  133. if (result == IntPtr.Zero)
  134. {
  135. throw new OutOfMemoryException();
  136. }
  137. _sharedMemorySource[result] = (ulong)size;
  138. return result;
  139. }
  140. public static void DestroySharedMemory(IntPtr handle)
  141. {
  142. lock (_sharedMemory)
  143. {
  144. foreach (var memory in _sharedMemory)
  145. {
  146. if (memory.SourcePointer == handle)
  147. {
  148. throw new InvalidOperationException("Shared memory cannot be destroyed unless fully unmapped.");
  149. }
  150. }
  151. }
  152. _sharedMemorySource.Remove(handle, out ulong _);
  153. }
  154. public static IntPtr MapSharedMemory(IntPtr handle)
  155. {
  156. // Try find the handle for this shared memory. If it is mapped, then we want to map
  157. // it a second time in another location.
  158. // If it is not mapped, then its handle is the mapping.
  159. ulong size = _sharedMemorySource[handle];
  160. if (size == 0)
  161. {
  162. throw new InvalidOperationException("Shared memory cannot be mapped after its source is unmapped.");
  163. }
  164. lock (_sharedMemory)
  165. {
  166. foreach (var memory in _sharedMemory)
  167. {
  168. if (memory.Pointer == handle)
  169. {
  170. IntPtr result = AllocateInternal(
  171. memory.Size,
  172. MmapProts.PROT_NONE
  173. );
  174. if (result == IntPtr.Zero)
  175. {
  176. throw new OutOfMemoryException();
  177. }
  178. Remap(result, handle, memory.Size);
  179. _sharedMemory.Add(new UnixSharedMemory
  180. {
  181. Pointer = result,
  182. Size = memory.Size,
  183. SourcePointer = handle
  184. });
  185. return result;
  186. }
  187. }
  188. _sharedMemory.Add(new UnixSharedMemory
  189. {
  190. Pointer = handle,
  191. Size = size,
  192. SourcePointer = handle
  193. });
  194. }
  195. return handle;
  196. }
  197. public static void UnmapSharedMemory(IntPtr address)
  198. {
  199. lock (_sharedMemory)
  200. {
  201. int removed = _sharedMemory.RemoveAll(memory =>
  202. {
  203. if (memory.Pointer == address)
  204. {
  205. if (memory.Pointer == memory.SourcePointer)
  206. {
  207. // After removing the original mapping, it cannot be mapped again.
  208. _sharedMemorySource[memory.SourcePointer] = 0;
  209. }
  210. Free(address);
  211. return true;
  212. }
  213. return false;
  214. });
  215. if (removed == 0)
  216. {
  217. throw new InvalidOperationException("Shared memory mapping could not be found.");
  218. }
  219. }
  220. }
  221. }
  222. }