1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | // Interface between GC and the OS specific functionality |
5 | // |
6 | |
7 | #ifndef __GCENV_OS_H__ |
8 | #define __GCENV_OS_H__ |
9 | |
10 | #ifdef Sleep |
11 | // This is a funny workaround for the fact that "common.h" defines Sleep to be |
12 | // Dont_Use_Sleep, with the hope of causing linker errors whenever someone tries to use sleep. |
13 | // |
14 | // However, GCToOSInterface defines a function called Sleep, which (due to this define) becomes |
15 | // "Dont_Use_Sleep", which the GC in turn happily uses. The symbol that GCToOSInterface actually |
16 | // exported was called "GCToOSInterface::Dont_Use_Sleep". While we progress in making the GC standalone, |
17 | // we'll need to break the dependency on common.h (the VM header) and this problem will become moot. |
18 | #undef Sleep |
19 | #endif // Sleep |
20 | |
21 | #define NUMA_NODE_UNDEFINED UINT32_MAX |
22 | |
23 | // Critical section used by the GC |
24 | class CLRCriticalSection |
25 | { |
26 | CRITICAL_SECTION m_cs; |
27 | |
28 | public: |
29 | // Initialize the critical section |
30 | void Initialize(); |
31 | |
32 | // Destroy the critical section |
33 | void Destroy(); |
34 | |
35 | // Enter the critical section. Blocks until the section can be entered. |
36 | void Enter(); |
37 | |
38 | // Leave the critical section |
39 | void Leave(); |
40 | }; |
41 | |
42 | // Flags for the GCToOSInterface::VirtualReserve method |
43 | struct VirtualReserveFlags |
44 | { |
45 | enum |
46 | { |
47 | None = 0, |
48 | WriteWatch = 1, |
49 | }; |
50 | }; |
51 | |
52 | // Affinity of a GC thread |
53 | struct GCThreadAffinity |
54 | { |
55 | static const int None = -1; |
56 | |
57 | // Processor group index, None if no group is specified |
58 | int Group; |
59 | // Processor index, None if no affinity is specified |
60 | int Processor; |
61 | }; |
62 | |
63 | // An event is a synchronization object whose state can be set and reset |
64 | // indicating that an event has occured. It is used pervasively throughout |
65 | // the GC. |
66 | // |
67 | // Note that GCEvent deliberately leaks its contents by not having a non-trivial destructor. |
68 | // This is by design; since all uses of GCEvent have static lifetime, their destructors |
69 | // are run on process exit, potentially concurrently with other threads that may still be |
70 | // operating on the static event. To avoid these sorts of unsafety, GCEvent chooses to |
71 | // not have a destructor at all. The cost of this is leaking a small amount of memory, but |
72 | // this is not a problem since a majority of the uses of GCEvent are static. See CoreCLR#11111 |
73 | // for more details on the hazards of static destructors. |
74 | class GCEvent { |
75 | private: |
76 | class Impl; |
77 | Impl *m_impl; |
78 | |
79 | public: |
80 | // Constructs a new uninitialized event. |
81 | GCEvent(); |
82 | |
83 | // Closes the event. Attempting to use the event past calling CloseEvent |
84 | // is a logic error. |
85 | void CloseEvent(); |
86 | |
87 | // "Sets" the event, indicating that a particular event has occured. May |
88 | // wake up other threads waiting on this event. Depending on whether or |
89 | // not this event is an auto-reset event, the state of the event may |
90 | // or may not be automatically reset after Set is called. |
91 | void Set(); |
92 | |
93 | // Resets the event, resetting it back to a non-signalled state. Auto-reset |
94 | // events automatically reset once the event is set, while manual-reset |
95 | // events do not reset until Reset is called. It is a no-op to call Reset |
96 | // on an auto-reset event. |
97 | void Reset(); |
98 | |
99 | // Waits for some period of time for this event to be signalled. The |
100 | // period of time may be infinite (if the timeout argument is INFINITE) or |
101 | // it may be a specified period of time, in milliseconds. |
102 | // Returns: |
103 | // One of three values, depending on how why this thread was awoken: |
104 | // WAIT_OBJECT_0 - This event was signalled and woke up this thread. |
105 | // WAIT_TIMEOUT - The timeout interval expired without this event being signalled. |
106 | // WAIT_FAILED - The wait failed. |
107 | uint32_t Wait(uint32_t timeout, bool alertable); |
108 | |
109 | // Determines whether or not this event is valid. |
110 | // Returns: |
111 | // true if this event is invalid (i.e. it has not yet been initialized or |
112 | // has already been closed), false otherwise |
113 | bool IsValid() const |
114 | { |
115 | return m_impl != nullptr; |
116 | } |
117 | |
118 | // Initializes this event to be a host-aware manual reset event with the |
119 | // given initial state. |
120 | // Returns: |
121 | // true if the initialization succeeded, false if it did not |
122 | bool CreateManualEventNoThrow(bool initialState); |
123 | |
124 | // Initializes this event to be a host-aware auto-resetting event with the |
125 | // given initial state. |
126 | // Returns: |
127 | // true if the initialization succeeded, false if it did not |
128 | bool CreateAutoEventNoThrow(bool initialState); |
129 | |
130 | // Initializes this event to be a host-unaware manual reset event with the |
131 | // given initial state. |
132 | // Returns: |
133 | // true if the initialization succeeded, false if it did not |
134 | bool CreateOSManualEventNoThrow(bool initialState); |
135 | |
136 | // Initializes this event to be a host-unaware auto-resetting event with the |
137 | // given initial state. |
138 | // Returns: |
139 | // true if the initialization succeeded, false if it did not |
140 | bool CreateOSAutoEventNoThrow(bool initialState); |
141 | }; |
142 | |
143 | // GC thread function prototype |
144 | typedef void (*GCThreadFunction)(void* param); |
145 | |
146 | // Interface that the GC uses to invoke OS specific functionality |
147 | class GCToOSInterface |
148 | { |
149 | public: |
150 | |
151 | // |
152 | // Initialization and shutdown of the interface |
153 | // |
154 | |
155 | // Initialize the interface implementation |
156 | // Return: |
157 | // true if it has succeeded, false if it has failed |
158 | static bool Initialize(); |
159 | |
160 | // Shutdown the interface implementation |
161 | static void Shutdown(); |
162 | |
163 | // |
164 | // Virtual memory management |
165 | // |
166 | |
167 | // Reserve virtual memory range. |
168 | // Parameters: |
169 | // size - size of the virtual memory range |
170 | // alignment - requested memory alignment |
171 | // flags - flags to control special settings like write watching |
172 | // Return: |
173 | // Starting virtual address of the reserved range |
174 | // Notes: |
175 | // Previous uses of this API aligned the `size` parameter to the platform |
176 | // allocation granularity. This is not required by POSIX or Windows. Windows will |
177 | // round the size up to the nearest page boundary. POSIX does not specify what is done, |
178 | // but Linux probably also rounds up. If an implementation of GCToOSInterface needs to |
179 | // align to the allocation granularity, it will do so in its implementation. |
180 | // |
181 | // Windows guarantees that the returned mapping will be aligned to the allocation |
182 | // granularity. |
183 | static void* VirtualReserve(size_t size, size_t alignment, uint32_t flags); |
184 | |
185 | // Release virtual memory range previously reserved using VirtualReserve |
186 | // Parameters: |
187 | // address - starting virtual address |
188 | // size - size of the virtual memory range |
189 | // Return: |
190 | // true if it has succeeded, false if it has failed |
191 | static bool VirtualRelease(void *address, size_t size); |
192 | |
193 | // Commit virtual memory range. It must be part of a range reserved using VirtualReserve. |
194 | // Parameters: |
195 | // address - starting virtual address |
196 | // size - size of the virtual memory range |
197 | // Return: |
198 | // true if it has succeeded, false if it has failed |
199 | static bool VirtualCommit(void *address, size_t size, uint32_t node = NUMA_NODE_UNDEFINED); |
200 | |
201 | // Decomit virtual memory range. |
202 | // Parameters: |
203 | // address - starting virtual address |
204 | // size - size of the virtual memory range |
205 | // Return: |
206 | // true if it has succeeded, false if it has failed |
207 | static bool VirtualDecommit(void *address, size_t size); |
208 | |
209 | // Reset virtual memory range. Indicates that data in the memory range specified by address and size is no |
210 | // longer of interest, but it should not be decommitted. |
211 | // Parameters: |
212 | // address - starting virtual address |
213 | // size - size of the virtual memory range |
214 | // unlock - true if the memory range should also be unlocked |
215 | // Return: |
216 | // true if it has succeeded, false if it has failed |
217 | static bool VirtualReset(void *address, size_t size, bool unlock); |
218 | |
219 | // |
220 | // Write watching |
221 | // |
222 | |
223 | // Check if the OS supports write watching |
224 | static bool SupportsWriteWatch(); |
225 | |
226 | // Reset the write tracking state for the specified virtual memory range. |
227 | // Parameters: |
228 | // address - starting virtual address |
229 | // size - size of the virtual memory range |
230 | static void ResetWriteWatch(void *address, size_t size); |
231 | |
232 | // Retrieve addresses of the pages that are written to in a region of virtual memory |
233 | // Parameters: |
234 | // resetState - true indicates to reset the write tracking state |
235 | // address - starting virtual address |
236 | // size - size of the virtual memory range |
237 | // pageAddresses - buffer that receives an array of page addresses in the memory region |
238 | // pageAddressesCount - on input, size of the lpAddresses array, in array elements |
239 | // on output, the number of page addresses that are returned in the array. |
240 | // Return: |
241 | // true if it has succeeded, false if it has failed |
242 | static bool GetWriteWatch(bool resetState, void* address, size_t size, void** pageAddresses, uintptr_t* pageAddressesCount); |
243 | |
244 | // |
245 | // Thread and process |
246 | // |
247 | |
248 | // Causes the calling thread to sleep for the specified number of milliseconds |
249 | // Parameters: |
250 | // sleepMSec - time to sleep before switching to another thread |
251 | static void Sleep(uint32_t sleepMSec); |
252 | |
253 | // Causes the calling thread to yield execution to another thread that is ready to run on the current processor. |
254 | // Parameters: |
255 | // switchCount - number of times the YieldThread was called in a loop |
256 | static void YieldThread(uint32_t switchCount); |
257 | |
258 | // Get the number of the current processor |
259 | static uint32_t GetCurrentProcessorNumber(); |
260 | |
261 | // Check if the OS supports getting current processor number |
262 | static bool CanGetCurrentProcessorNumber(); |
263 | |
264 | // Set ideal processor for the current thread |
265 | // Parameters: |
266 | // processorIndex - index of the processor in the group |
267 | // affinity - ideal processor affinity for the thread |
268 | // Return: |
269 | // true if it has succeeded, false if it has failed |
270 | static bool SetCurrentThreadIdealAffinity(GCThreadAffinity* affinity); |
271 | |
272 | // Get numeric id of the current thread if possible on the |
273 | // current platform. It is indended for logging purposes only. |
274 | // Return: |
275 | // Numeric id of the current thread or 0 if the |
276 | static uint64_t GetCurrentThreadIdForLogging(); |
277 | |
278 | // Get id of the current process |
279 | // Return: |
280 | // Id of the current process |
281 | static uint32_t GetCurrentProcessId(); |
282 | |
283 | // |
284 | // Processor topology |
285 | // |
286 | |
287 | // Get size of the on die cache per logical processor |
288 | // Parameters: |
289 | // trueSize - true to return true cache size, false to return scaled up size based on |
290 | // the processor architecture |
291 | // Return: |
292 | // Size of the cache |
293 | static size_t GetCacheSizePerLogicalCpu(bool trueSize = true); |
294 | |
295 | // Get number of processors assigned to the current process |
296 | // Return: |
297 | // The number of processors |
298 | static uint32_t GetCurrentProcessCpuCount(); |
299 | |
300 | // Sets the calling thread's affinity to only run on the processor specified |
301 | // in the GCThreadAffinity structure. |
302 | // Parameters: |
303 | // affinity - The requested affinity for the calling thread. At most one processor |
304 | // can be provided. |
305 | // Return: |
306 | // true if setting the affinity was successful, false otherwise. |
307 | static bool SetThreadAffinity(GCThreadAffinity* affinity); |
308 | |
309 | // Boosts the calling thread's thread priority to a level higher than the default |
310 | // for new threads. |
311 | // Parameters: |
312 | // None. |
313 | // Return: |
314 | // true if the priority boost was successful, false otherwise. |
315 | static bool BoostThreadPriority(); |
316 | |
317 | // Get affinity mask of the current process |
318 | // Parameters: |
319 | // processMask - affinity mask for the specified process |
320 | // systemMask - affinity mask for the system |
321 | // Return: |
322 | // true if it has succeeded, false if it has failed |
323 | // Remarks: |
324 | // A process affinity mask is a bit vector in which each bit represents the processors that |
325 | // a process is allowed to run on. A system affinity mask is a bit vector in which each bit |
326 | // represents the processors that are configured into a system. |
327 | // A process affinity mask is a subset of the system affinity mask. A process is only allowed |
328 | // to run on the processors configured into a system. Therefore, the process affinity mask cannot |
329 | // specify a 1 bit for a processor when the system affinity mask specifies a 0 bit for that processor. |
330 | static bool GetCurrentProcessAffinityMask(uintptr_t *processMask, uintptr_t *systemMask); |
331 | |
332 | // |
333 | // Global memory info |
334 | // |
335 | |
336 | // Return the size of the user-mode portion of the virtual address space of this process. |
337 | // Return: |
338 | // non zero if it has succeeded, 0 if it has failed |
339 | static size_t GetVirtualMemoryLimit(); |
340 | |
341 | // Get the physical memory that this process can use. |
342 | // Return: |
343 | // non zero if it has succeeded, 0 if it has failed |
344 | // Remarks: |
345 | // If a process runs with a restricted memory limit, it returns the limit. If there's no limit |
346 | // specified, it returns amount of actual physical memory. |
347 | static uint64_t GetPhysicalMemoryLimit(); |
348 | |
349 | // Get memory status |
350 | // Parameters: |
351 | // memory_load - A number between 0 and 100 that specifies the approximate percentage of physical memory |
352 | // that is in use (0 indicates no memory use and 100 indicates full memory use). |
353 | // available_physical - The amount of physical memory currently available, in bytes. |
354 | // available_page_file - The maximum amount of memory the current process can commit, in bytes. |
355 | // Remarks: |
356 | // Any parameter can be null. |
357 | static void GetMemoryStatus(uint32_t* memory_load, uint64_t* available_physical, uint64_t* available_page_file); |
358 | |
359 | // Get size of an OS memory page |
360 | static uint32_t GetPageSize(); |
361 | |
362 | // |
363 | // Misc |
364 | // |
365 | |
366 | // Flush write buffers of processors that are executing threads of the current process |
367 | static void FlushProcessWriteBuffers(); |
368 | |
369 | // Break into a debugger |
370 | static void DebugBreak(); |
371 | |
372 | // |
373 | // Time |
374 | // |
375 | |
376 | // Get a high precision performance counter |
377 | // Return: |
378 | // The counter value |
379 | static int64_t QueryPerformanceCounter(); |
380 | |
381 | // Get a frequency of the high precision performance counter |
382 | // Return: |
383 | // The counter frequency |
384 | static int64_t QueryPerformanceFrequency(); |
385 | |
386 | // Get a time stamp with a low precision |
387 | // Return: |
388 | // Time stamp in milliseconds |
389 | static uint32_t GetLowPrecisionTimeStamp(); |
390 | |
391 | // Gets the total number of processors on the machine, not taking |
392 | // into account current process affinity. |
393 | // Return: |
394 | // Number of processors on the machine |
395 | static uint32_t GetTotalProcessorCount(); |
396 | |
397 | // Is NUMA support available |
398 | static bool CanEnableGCNumaAware(); |
399 | |
400 | // Gets the NUMA node for the processor |
401 | static bool GetNumaProcessorNode(PPROCESSOR_NUMBER proc_no, uint16_t *node_no); |
402 | |
403 | // Are CPU groups enabled |
404 | static bool CanEnableGCCPUGroups(); |
405 | |
406 | // Get the CPU group for the specified processor |
407 | static void GetGroupForProcessor(uint16_t processor_number, uint16_t* group_number, uint16_t* group_processor_number); |
408 | |
409 | }; |
410 | |
411 | #endif // __GCENV_OS_H__ |
412 | |