1/** 2 * Contains OS-level routines needed by the garbage collector. 3 * 4 * Copyright: D Language Foundation 2005 - 2021. 5 * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 * Authors: Walter Bright, David Friedman, Sean Kelly, Leandro Lucarella 7 */ 8module core.internal.gc.os; 9 10 11version (Windows) 12{ 13 import core.sys.windows.winbase : GetCurrentThreadId, VirtualAlloc, VirtualFree; 14 import core.sys.windows.winnt : MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE; 15 16 alias int pthread_t; 17 18 pthread_t pthread_self() nothrow 19 { 20 return cast(pthread_t) GetCurrentThreadId(); 21 } 22 23 //version = GC_Use_Alloc_Win32; 24} 25else version (Posix) 26{ 27 version (OSX) 28 version = Darwin; 29 else version (iOS) 30 version = Darwin; 31 else version (TVOS) 32 version = Darwin; 33 else version (WatchOS) 34 version = Darwin; 35 36 import core.sys.posix.sys.mman; 37 import core.stdc.stdlib; 38 39 40 /// Possible results for the wait_pid() function. 41 enum ChildStatus 42 { 43 done, /// The process has finished successfully 44 running, /// The process is still running 45 error /// There was an error waiting for the process 46 } 47 48 /** 49 * Wait for a process with PID pid to finish. 50 * 51 * If block is false, this function will not block, and return ChildStatus.running if 52 * the process is still running. Otherwise it will return always ChildStatus.done 53 * (unless there is an error, in which case ChildStatus.error is returned). 54 */ 55 ChildStatus wait_pid(pid_t pid, bool block = true) nothrow @nogc 56 { 57 import core.exception : onForkError; 58 59 int status = void; 60 pid_t waited_pid = void; 61 // In the case where we are blocking, we need to consider signals 62 // arriving while we wait, and resume the waiting if EINTR is returned 63 do { 64 errno = 0; 65 waited_pid = waitpid(pid, &status, block ? 0 : WNOHANG); 66 } 67 while (waited_pid == -1 && errno == EINTR); 68 if (waited_pid == 0) 69 return ChildStatus.running; 70 else if (errno == ECHILD) 71 return ChildStatus.done; // someone called posix.syswait 72 else if (waited_pid != pid || status != 0) 73 { 74 onForkError(); 75 return ChildStatus.error; 76 } 77 return ChildStatus.done; 78 } 79 80 public import core.sys.posix.unistd: pid_t, fork; 81 import core.sys.posix.sys.wait: waitpid, WNOHANG; 82 import core.stdc.errno: errno, EINTR, ECHILD; 83 84 //version = GC_Use_Alloc_MMap; 85} 86else 87{ 88 import core.stdc.stdlib; 89 90 //version = GC_Use_Alloc_Malloc; 91} 92 93/+ 94static if (is(typeof(VirtualAlloc))) 95 version = GC_Use_Alloc_Win32; 96else static if (is(typeof(mmap))) 97 version = GC_Use_Alloc_MMap; 98else static if (is(typeof(valloc))) 99 version = GC_Use_Alloc_Valloc; 100else static if (is(typeof(malloc))) 101 version = GC_Use_Alloc_Malloc; 102else static assert(false, "No supported allocation methods available."); 103+/ 104 105static if (is(typeof(VirtualAlloc))) // version (GC_Use_Alloc_Win32) 106{ 107 /** 108 * Indicates if an implementation supports fork(). 109 * 110 * The value shown here is just demostrative, the real value is defined based 111 * on the OS it's being compiled in. 112 * enum HaveFork = true; 113 */ 114 enum HaveFork = false; 115 116 /** 117 * Map memory. 118 */ 119 void *os_mem_map(size_t nbytes) nothrow @nogc 120 { 121 return VirtualAlloc(null, nbytes, MEM_RESERVE | MEM_COMMIT, 122 PAGE_READWRITE); 123 } 124 125 126 /** 127 * Unmap memory allocated with os_mem_map(). 128 * Returns: 129 * 0 success 130 * !=0 failure 131 */ 132 int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc 133 { 134 return cast(int)(VirtualFree(base, 0, MEM_RELEASE) == 0); 135 } 136} 137else static if (is(typeof(mmap))) // else version (GC_Use_Alloc_MMap) 138{ 139 enum HaveFork = true; 140 141 void *os_mem_map(size_t nbytes, bool share = false) nothrow @nogc 142 { void *p; 143 144 auto map_f = share ? MAP_SHARED : MAP_PRIVATE; 145 p = mmap(null, nbytes, PROT_READ | PROT_WRITE, map_f | MAP_ANON, -1, 0); 146 return (p == MAP_FAILED) ? null : p; 147 } 148 149 150 int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc 151 { 152 return munmap(base, nbytes); 153 } 154} 155else static if (is(typeof(valloc))) // else version (GC_Use_Alloc_Valloc) 156{ 157 enum HaveFork = false; 158 159 void *os_mem_map(size_t nbytes) nothrow @nogc 160 { 161 return valloc(nbytes); 162 } 163 164 165 int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc 166 { 167 free(base); 168 return 0; 169 } 170} 171else static if (is(typeof(malloc))) // else version (GC_Use_Alloc_Malloc) 172{ 173 // NOTE: This assumes malloc granularity is at least (void*).sizeof. If 174 // (req_size + PAGESIZE) is allocated, and the pointer is rounded up 175 // to PAGESIZE alignment, there will be space for a void* at the end 176 // after PAGESIZE bytes used by the GC. 177 178 enum HaveFork = false; 179 180 import core.internal.gc.impl.conservative.gc; 181 182 183 const size_t PAGE_MASK = PAGESIZE - 1; 184 185 186 void *os_mem_map(size_t nbytes) nothrow @nogc 187 { byte *p, q; 188 p = cast(byte *) malloc(nbytes + PAGESIZE); 189 if (!p) 190 return null; 191 q = p + ((PAGESIZE - ((cast(size_t) p & PAGE_MASK))) & PAGE_MASK); 192 * cast(void**)(q + nbytes) = p; 193 return q; 194 } 195 196 197 int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc 198 { 199 free( *cast(void**)( cast(byte*) base + nbytes ) ); 200 return 0; 201 } 202} 203else 204{ 205 static assert(false, "No supported allocation methods available."); 206} 207 208/** 209 Check for any kind of memory pressure. 210 211 Params: 212 mapped = the amount of memory mapped by the GC in bytes 213 Returns: 214 true if memory is scarce 215*/ 216// TODO: get virtual mem sizes and current usage from OS 217// TODO: compare current RSS and avail. physical memory 218bool isLowOnMem(size_t mapped) nothrow @nogc 219{ 220 version (Windows) 221 { 222 import core.sys.windows.winbase : GlobalMemoryStatusEx, MEMORYSTATUSEX; 223 224 MEMORYSTATUSEX stat; 225 stat.dwLength = stat.sizeof; 226 const success = GlobalMemoryStatusEx(&stat) != 0; 227 assert(success, "GlobalMemoryStatusEx() failed"); 228 if (!success) 229 return false; 230 231 // dwMemoryLoad is the 'approximate percentage of physical memory that is in use' 232 // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex 233 const percentPhysicalRAM = stat.ullTotalPhys / 100; 234 return (stat.dwMemoryLoad >= 95 && mapped > percentPhysicalRAM) 235 || (stat.dwMemoryLoad >= 90 && mapped > 10 * percentPhysicalRAM); 236 } 237 else 238 { 239 enum GB = 2 ^^ 30; 240 version (D_LP64) 241 return false; 242 else version (Darwin) 243 { 244 // 80 % of available 4GB is used for GC (excluding malloc and mmap) 245 enum size_t limit = 4UL * GB * 8 / 10; 246 return mapped > limit; 247 } 248 else 249 { 250 // be conservative and assume 3GB 251 enum size_t limit = 3UL * GB * 8 / 10; 252 return mapped > limit; 253 } 254 } 255} 256 257/** 258 Get the size of available physical memory 259 260 Returns: 261 size of installed physical RAM 262*/ 263version (Windows) 264{ 265 ulong os_physical_mem() nothrow @nogc 266 { 267 import core.sys.windows.winbase : GlobalMemoryStatus, MEMORYSTATUS; 268 MEMORYSTATUS stat; 269 GlobalMemoryStatus(&stat); 270 return stat.dwTotalPhys; // limited to 4GB for Win32 271 } 272} 273else version (Darwin) 274{ 275 extern (C) int sysctl(const int* name, uint namelen, void* oldp, size_t* oldlenp, const void* newp, size_t newlen) @nogc nothrow; 276 ulong os_physical_mem() nothrow @nogc 277 { 278 enum 279 { 280 CTL_HW = 6, 281 HW_MEMSIZE = 24, 282 } 283 int[2] mib = [ CTL_HW, HW_MEMSIZE ]; 284 ulong system_memory_bytes; 285 size_t len = system_memory_bytes.sizeof; 286 if (sysctl(mib.ptr, 2, &system_memory_bytes, &len, null, 0) != 0) 287 return 0; 288 return system_memory_bytes; 289 } 290} 291else version (Posix) 292{ 293 ulong os_physical_mem() nothrow @nogc 294 { 295 import core.sys.posix.unistd : sysconf, _SC_PAGESIZE, _SC_PHYS_PAGES; 296 const pageSize = sysconf(_SC_PAGESIZE); 297 const pages = sysconf(_SC_PHYS_PAGES); 298 return pageSize * pages; 299 } 300} 301