memory_map_windows.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. //go:build windows
  2. // +build windows
  3. package memory_map
  4. import (
  5. "os"
  6. "reflect"
  7. "syscall"
  8. "unsafe"
  9. "golang.org/x/sys/windows"
  10. )
  11. type DWORDLONG = uint64
  12. type DWORD = uint32
  13. type WORD = uint16
  14. var (
  15. modkernel32 = syscall.NewLazyDLL("kernel32.dll")
  16. procGetSystemInfo = modkernel32.NewProc("GetSystemInfo")
  17. procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx")
  18. procGetProcessWorkingSetSize = modkernel32.NewProc("GetProcessWorkingSetSize")
  19. procSetProcessWorkingSetSize = modkernel32.NewProc("SetProcessWorkingSetSize")
  20. )
  21. var currentProcess, _ = windows.GetCurrentProcess()
  22. var currentMinWorkingSet uint64 = 0
  23. var currentMaxWorkingSet uint64 = 0
  24. var _ = getProcessWorkingSetSize(uintptr(currentProcess), &currentMinWorkingSet, &currentMaxWorkingSet)
  25. var systemInfo, _ = getSystemInfo()
  26. var chunkSize = uint64(systemInfo.dwAllocationGranularity) * 128
  27. var memoryStatusEx, _ = globalMemoryStatusEx()
  28. var maxMemoryLimitBytes = uint64(float64(memoryStatusEx.ullTotalPhys) * 0.8)
  29. func (mMap *MemoryMap) CreateMemoryMap(file *os.File, maxLength uint64) {
  30. chunks := (maxLength / chunkSize)
  31. if chunks*chunkSize < maxLength {
  32. chunks = chunks + 1
  33. }
  34. alignedMaxLength := chunks * chunkSize
  35. maxLength_high := uint32(alignedMaxLength >> 32)
  36. maxLength_low := uint32(alignedMaxLength & 0xFFFFFFFF)
  37. file_memory_map_handle, err := windows.CreateFileMapping(windows.Handle(file.Fd()), nil, windows.PAGE_READWRITE, maxLength_high, maxLength_low, nil)
  38. if err == nil {
  39. mMap.File = file
  40. mMap.file_memory_map_handle = uintptr(file_memory_map_handle)
  41. mMap.write_map_views = make([]MemoryBuffer, 0, alignedMaxLength/chunkSize)
  42. mMap.max_length = alignedMaxLength
  43. mMap.End_of_file = -1
  44. }
  45. }
  46. func (mMap *MemoryMap) DeleteFileAndMemoryMap() {
  47. //First we close the file handles first to delete the file,
  48. //Then we unmap the memory to ensure the unmapping process doesn't write the data to disk
  49. windows.CloseHandle(windows.Handle(mMap.file_memory_map_handle))
  50. windows.CloseHandle(windows.Handle(mMap.File.Fd()))
  51. for _, view := range mMap.write_map_views {
  52. view.releaseMemory()
  53. }
  54. mMap.write_map_views = nil
  55. mMap.max_length = 0
  56. }
  57. func min(x, y uint64) uint64 {
  58. if x < y {
  59. return x
  60. }
  61. return y
  62. }
  63. func (mMap *MemoryMap) WriteMemory(offset uint64, length uint64, data []byte) {
  64. for {
  65. if ((offset+length)/chunkSize)+1 > uint64(len(mMap.write_map_views)) {
  66. allocateChunk(mMap)
  67. } else {
  68. break
  69. }
  70. }
  71. remaining_length := length
  72. sliceIndex := offset / chunkSize
  73. sliceOffset := offset - (sliceIndex * chunkSize)
  74. dataOffset := uint64(0)
  75. for {
  76. writeEnd := min((remaining_length + sliceOffset), chunkSize)
  77. copy(mMap.write_map_views[sliceIndex].Buffer[sliceOffset:writeEnd], data[dataOffset:])
  78. remaining_length -= (writeEnd - sliceOffset)
  79. dataOffset += (writeEnd - sliceOffset)
  80. if remaining_length > 0 {
  81. sliceIndex += 1
  82. sliceOffset = 0
  83. } else {
  84. break
  85. }
  86. }
  87. if mMap.End_of_file < int64(offset+length-1) {
  88. mMap.End_of_file = int64(offset + length - 1)
  89. }
  90. }
  91. func (mMap *MemoryMap) ReadMemory(offset uint64, length uint64) (dataSlice []byte, err error) {
  92. dataSlice = make([]byte, length)
  93. mBuffer, err := allocate(windows.Handle(mMap.file_memory_map_handle), offset, length, false)
  94. copy(dataSlice, mBuffer.Buffer)
  95. mBuffer.releaseMemory()
  96. return dataSlice, err
  97. }
  98. func (mBuffer *MemoryBuffer) releaseMemory() {
  99. windows.VirtualUnlock(mBuffer.aligned_ptr, uintptr(mBuffer.aligned_length))
  100. windows.UnmapViewOfFile(mBuffer.aligned_ptr)
  101. currentMinWorkingSet -= mBuffer.aligned_length
  102. currentMaxWorkingSet -= mBuffer.aligned_length
  103. if currentMinWorkingSet < maxMemoryLimitBytes {
  104. var _ = setProcessWorkingSetSize(uintptr(currentProcess), currentMinWorkingSet, currentMaxWorkingSet)
  105. }
  106. mBuffer.ptr = 0
  107. mBuffer.aligned_ptr = 0
  108. mBuffer.length = 0
  109. mBuffer.aligned_length = 0
  110. mBuffer.Buffer = nil
  111. }
  112. func allocateChunk(mMap *MemoryMap) {
  113. start := uint64(len(mMap.write_map_views)) * chunkSize
  114. mBuffer, err := allocate(windows.Handle(mMap.file_memory_map_handle), start, chunkSize, true)
  115. if err == nil {
  116. mMap.write_map_views = append(mMap.write_map_views, mBuffer)
  117. }
  118. }
  119. func allocate(hMapFile windows.Handle, offset uint64, length uint64, write bool) (MemoryBuffer, error) {
  120. mBuffer := MemoryBuffer{}
  121. //align memory allocations to the minium virtal memory allocation size
  122. dwSysGran := systemInfo.dwAllocationGranularity
  123. start := (offset / uint64(dwSysGran)) * uint64(dwSysGran)
  124. diff := offset - start
  125. aligned_length := diff + length
  126. offset_high := uint32(start >> 32)
  127. offset_low := uint32(start & 0xFFFFFFFF)
  128. access := windows.FILE_MAP_READ
  129. if write {
  130. access = windows.FILE_MAP_WRITE
  131. }
  132. currentMinWorkingSet += aligned_length
  133. currentMaxWorkingSet += aligned_length
  134. if currentMinWorkingSet < maxMemoryLimitBytes {
  135. // increase the process working set size to hint to windows memory manager to
  136. // prioritise keeping this memory mapped in physical memory over other standby memory
  137. var _ = setProcessWorkingSetSize(uintptr(currentProcess), currentMinWorkingSet, currentMaxWorkingSet)
  138. }
  139. addr_ptr, errno := windows.MapViewOfFile(hMapFile,
  140. uint32(access), // read/write permission
  141. offset_high,
  142. offset_low,
  143. uintptr(aligned_length))
  144. if addr_ptr == 0 {
  145. return mBuffer, errno
  146. }
  147. if currentMinWorkingSet < maxMemoryLimitBytes {
  148. windows.VirtualLock(mBuffer.aligned_ptr, uintptr(mBuffer.aligned_length))
  149. }
  150. mBuffer.aligned_ptr = addr_ptr
  151. mBuffer.aligned_length = aligned_length
  152. mBuffer.ptr = addr_ptr + uintptr(diff)
  153. mBuffer.length = length
  154. slice_header := (*reflect.SliceHeader)(unsafe.Pointer(&mBuffer.Buffer))
  155. slice_header.Data = addr_ptr + uintptr(diff)
  156. slice_header.Len = int(length)
  157. slice_header.Cap = int(length)
  158. return mBuffer, nil
  159. }
  160. //typedef struct _MEMORYSTATUSEX {
  161. // DWORD dwLength;
  162. // DWORD dwMemoryLoad;
  163. // DWORDLONG ullTotalPhys;
  164. // DWORDLONG ullAvailPhys;
  165. // DWORDLONG ullTotalPageFile;
  166. // DWORDLONG ullAvailPageFile;
  167. // DWORDLONG ullTotalVirtual;
  168. // DWORDLONG ullAvailVirtual;
  169. // DWORDLONG ullAvailExtendedVirtual;
  170. // } MEMORYSTATUSEX, *LPMEMORYSTATUSEX;
  171. //https://docs.microsoft.com/en-gb/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex
  172. type _MEMORYSTATUSEX struct {
  173. dwLength DWORD
  174. dwMemoryLoad DWORD
  175. ullTotalPhys DWORDLONG
  176. ullAvailPhys DWORDLONG
  177. ullTotalPageFile DWORDLONG
  178. ullAvailPageFile DWORDLONG
  179. ullTotalVirtual DWORDLONG
  180. ullAvailVirtual DWORDLONG
  181. ullAvailExtendedVirtual DWORDLONG
  182. }
  183. // BOOL GlobalMemoryStatusEx(
  184. // LPMEMORYSTATUSEX lpBuffer
  185. // );
  186. // https://docs.microsoft.com/en-gb/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex
  187. func globalMemoryStatusEx() (_MEMORYSTATUSEX, error) {
  188. var mem_status _MEMORYSTATUSEX
  189. mem_status.dwLength = uint32(unsafe.Sizeof(mem_status))
  190. _, _, err := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&mem_status)))
  191. if err != syscall.Errno(0) {
  192. return mem_status, err
  193. }
  194. return mem_status, nil
  195. }
  196. // typedef struct _SYSTEM_INFO {
  197. // union {
  198. // DWORD dwOemId;
  199. // struct {
  200. // WORD wProcessorArchitecture;
  201. // WORD wReserved;
  202. // };
  203. // };
  204. // DWORD dwPageSize;
  205. // LPVOID lpMinimumApplicationAddress;
  206. // LPVOID lpMaximumApplicationAddress;
  207. // DWORD_PTR dwActiveProcessorMask;
  208. // DWORD dwNumberOfProcessors;
  209. // DWORD dwProcessorType;
  210. // DWORD dwAllocationGranularity;
  211. // WORD wProcessorLevel;
  212. // WORD wProcessorRevision;
  213. // } SYSTEM_INFO;
  214. // https://docs.microsoft.com/en-gb/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info
  215. type _SYSTEM_INFO struct {
  216. dwOemId DWORD
  217. dwPageSize DWORD
  218. lpMinimumApplicationAddress uintptr
  219. lpMaximumApplicationAddress uintptr
  220. dwActiveProcessorMask uintptr
  221. dwNumberOfProcessors DWORD
  222. dwProcessorType DWORD
  223. dwAllocationGranularity DWORD
  224. wProcessorLevel WORD
  225. wProcessorRevision WORD
  226. }
  227. // void WINAPI GetSystemInfo(
  228. // _Out_ LPSYSTEM_INFO lpSystemInfo
  229. // );
  230. // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo
  231. func getSystemInfo() (_SYSTEM_INFO, error) {
  232. var si _SYSTEM_INFO
  233. _, _, err := procGetSystemInfo.Call(uintptr(unsafe.Pointer(&si)))
  234. if err != syscall.Errno(0) {
  235. return si, err
  236. }
  237. return si, nil
  238. }
  239. // BOOL GetProcessWorkingSetSize(
  240. // HANDLE hProcess,
  241. // PSIZE_T lpMinimumWorkingSetSize,
  242. // PSIZE_T lpMaximumWorkingSetSize
  243. // );
  244. // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getprocessworkingsetsize
  245. func getProcessWorkingSetSize(process uintptr, dwMinWorkingSet *uint64, dwMaxWorkingSet *uint64) error {
  246. r1, _, err := syscall.Syscall(procGetProcessWorkingSetSize.Addr(), 3, process, uintptr(unsafe.Pointer(dwMinWorkingSet)), uintptr(unsafe.Pointer(dwMaxWorkingSet)))
  247. if r1 == 0 {
  248. if err != syscall.Errno(0) {
  249. return err
  250. }
  251. }
  252. return nil
  253. }
  254. // BOOL SetProcessWorkingSetSize(
  255. // HANDLE hProcess,
  256. // SIZE_T dwMinimumWorkingSetSize,
  257. // SIZE_T dwMaximumWorkingSetSize
  258. // );
  259. // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setprocessworkingsetsize
  260. func setProcessWorkingSetSize(process uintptr, dwMinWorkingSet uint64, dwMaxWorkingSet uint64) error {
  261. r1, _, err := syscall.Syscall(procSetProcessWorkingSetSize.Addr(), 3, process, uintptr(dwMinWorkingSet), uintptr(dwMaxWorkingSet))
  262. if r1 == 0 {
  263. if err != syscall.Errno(0) {
  264. return err
  265. }
  266. }
  267. return nil
  268. }