memory_map_windows.go 9.3 KB

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