1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-11 02:13:38 +09:00
Satori/src/coreclr/vm/gcenv.os.cpp
2021-02-02 17:28:08 -08:00

1464 lines
41 KiB
C++

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/*
* gcenv.os.cpp
*
* GCToOSInterface implementation
*
*
*/
#include "common.h"
#include "gcenv.h"
#ifndef TARGET_UNIX
#include <Psapi.h>
#endif
#ifdef Sleep
#undef Sleep
#endif // Sleep
#include "../gc/env/gcenv.os.h"
#define MAX_PTR ((uint8_t*)(~(ptrdiff_t)0))
#ifdef TARGET_UNIX
uint32_t g_pageSizeUnixInl = 0;
#endif
static AffinitySet g_processAffinitySet;
class GroupProcNo
{
uint16_t m_groupProc;
public:
static const uint16_t NoGroup = 0;
GroupProcNo(uint16_t groupProc) : m_groupProc(groupProc)
{
}
GroupProcNo(uint16_t group, uint16_t procIndex) : m_groupProc((group << 6) | procIndex)
{
// Making this the same as the # of NUMA node we support.
_ASSERTE(group < 0x40);
_ASSERTE(procIndex <= 0x3f);
}
uint16_t GetGroup() { return m_groupProc >> 6; }
uint16_t GetProcIndex() { return m_groupProc & 0x3f; }
uint16_t GetCombinedValue() { return m_groupProc; }
};
#if !defined(TARGET_UNIX)
static bool g_SeLockMemoryPrivilegeAcquired = false;
bool InitLargePagesPrivilege()
{
TOKEN_PRIVILEGES tp;
LUID luid;
if (!LookupPrivilegeValueW(nullptr, SE_LOCK_MEMORY_NAME, &luid))
{
return false;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
HANDLE token;
if (!OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token))
{
return false;
}
BOOL retVal = AdjustTokenPrivileges(token, FALSE, &tp, 0, nullptr, 0);
DWORD gls = GetLastError();
CloseHandle(token);
if (!retVal)
{
return false;
}
if (gls != 0)
{
return false;
}
return true;
}
#endif // TARGET_UNIX
static void GetProcessMemoryLoad(LPMEMORYSTATUSEX pMSEX)
{
LIMITED_METHOD_CONTRACT;
pMSEX->dwLength = sizeof(MEMORYSTATUSEX);
BOOL fRet = GlobalMemoryStatusEx(pMSEX);
_ASSERTE(fRet);
}
// Initialize the interface implementation
// Return:
// true if it has succeeded, false if it has failed
bool GCToOSInterface::Initialize()
{
LIMITED_METHOD_CONTRACT;
#ifdef TARGET_UNIX
g_pageSizeUnixInl = GetOsPageSize();
uint32_t currentProcessCpuCount = PAL_GetLogicalCpuCountFromOS();
if (PAL_GetCurrentThreadAffinitySet(AffinitySet::BitsetDataSize, g_processAffinitySet.GetBitsetData()))
{
_ASSERTE(currentProcessCpuCount == g_processAffinitySet.Count());
}
else
{
// There is no way to get affinity on the current OS, set the affinity set to reflect all processors
for (size_t i = 0; i < currentProcessCpuCount; i++)
{
g_processAffinitySet.Add(i);
}
}
#else // TARGET_UNIX
if (CPUGroupInfo::CanEnableGCCPUGroups())
{
// When CPU groups are enabled, then the process is not bound by the process affinity set at process launch.
// Set the initial affinity mask so that all processors are enabled.
for (size_t i = 0; i < CPUGroupInfo::GetNumActiveProcessors(); i++)
{
g_processAffinitySet.Add(i);
}
}
else
{
// When CPU groups are disabled, the process affinity mask specified at the process launch cannot be
// escaped.
uintptr_t pmask, smask;
if (!!::GetProcessAffinityMask(::GetCurrentProcess(), (PDWORD_PTR)&pmask, (PDWORD_PTR)&smask))
{
pmask &= smask;
for (size_t i = 0; i < 8 * sizeof(uintptr_t); i++)
{
if ((pmask & ((uintptr_t)1 << i)) != 0)
{
g_processAffinitySet.Add(i);
}
}
}
}
#endif // TARGET_UNIX
return true;
}
// Shutdown the interface implementation
void GCToOSInterface::Shutdown()
{
LIMITED_METHOD_CONTRACT;
}
// Get numeric id of the current thread if possible on the
// current platform. It is indended for logging purposes only.
// Return:
// Numeric id of the current thread or 0 if the
uint64_t GCToOSInterface::GetCurrentThreadIdForLogging()
{
LIMITED_METHOD_CONTRACT;
return ::GetCurrentThreadId();
}
// Get id of the process
// Return:
// Id of the current process
uint32_t GCToOSInterface::GetCurrentProcessId()
{
LIMITED_METHOD_CONTRACT;
return ::GetCurrentProcessId();
}
// Set ideal processor for the current thread
// Parameters:
// srcProcNo - processor number the thread currently runs on
// dstProcNo - processor number the thread should be migrated to
// Return:
// true if it has succeeded, false if it has failed
bool GCToOSInterface::SetCurrentThreadIdealAffinity(uint16_t srcProcNo, uint16_t dstProcNo)
{
LIMITED_METHOD_CONTRACT;
bool success = true;
#ifndef TARGET_UNIX
GroupProcNo srcGroupProcNo(srcProcNo);
GroupProcNo dstGroupProcNo(dstProcNo);
PROCESSOR_NUMBER proc;
if (CPUGroupInfo::CanEnableGCCPUGroups())
{
if (srcGroupProcNo.GetGroup() != dstGroupProcNo.GetGroup())
{
//only set ideal processor when srcProcNo and dstProcNo are in the same cpu
//group. DO NOT MOVE THREADS ACROSS CPU GROUPS
return true;
}
proc.Group = (WORD)dstGroupProcNo.GetGroup();
proc.Number = (BYTE)dstGroupProcNo.GetProcIndex();
proc.Reserved = 0;
success = !!SetThreadIdealProcessorEx(GetCurrentThread (), &proc, NULL);
}
else
{
if (GetThreadIdealProcessorEx(GetCurrentThread(), &proc))
{
proc.Number = (BYTE)dstGroupProcNo.GetProcIndex();
success = !!SetThreadIdealProcessorEx(GetCurrentThread(), &proc, &proc);
}
}
return success;
#else // !TARGET_UNIX
// There is no way to set a thread ideal processor on Unix, so do nothing.
return true;
#endif // !TARGET_UNIX
}
bool GCToOSInterface::GetCurrentThreadIdealProc(uint16_t* procNo)
{
LIMITED_METHOD_CONTRACT;
bool success = false;
#ifndef TARGET_UNIX
PROCESSOR_NUMBER proc;
success = !!GetThreadIdealProcessorEx(GetCurrentThread(), &proc);
if (success)
{
GroupProcNo groupProcNo(proc.Group, proc.Number);
*procNo = groupProcNo.GetCombinedValue();
}
#endif //TARGET_UNIX
return success;
}
// Get the number of the current processor
uint32_t GCToOSInterface::GetCurrentProcessorNumber()
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(CanGetCurrentProcessorNumber());
#ifndef TARGET_UNIX
PROCESSOR_NUMBER proc_no_cpu_group;
GetCurrentProcessorNumberEx(&proc_no_cpu_group);
GroupProcNo groupProcNo(proc_no_cpu_group.Group, proc_no_cpu_group.Number);
return groupProcNo.GetCombinedValue();
#else
return ::GetCurrentProcessorNumber();
#endif //!TARGET_UNIX
}
// Check if the OS supports getting current processor number
bool GCToOSInterface::CanGetCurrentProcessorNumber()
{
LIMITED_METHOD_CONTRACT;
#ifdef TARGET_UNIX
return PAL_HasGetCurrentProcessorNumber();
#else
// on all Windows platforms we support this API exists
return true;
#endif
}
// Flush write buffers of processors that are executing threads of the current process
void GCToOSInterface::FlushProcessWriteBuffers()
{
LIMITED_METHOD_CONTRACT;
::FlushProcessWriteBuffers();
}
// Break into a debugger
void GCToOSInterface::DebugBreak()
{
LIMITED_METHOD_CONTRACT;
::DebugBreak();
}
// Causes the calling thread to sleep for the specified number of milliseconds
// Parameters:
// sleepMSec - time to sleep before switching to another thread
void GCToOSInterface::Sleep(uint32_t sleepMSec)
{
LIMITED_METHOD_CONTRACT;
__SwitchToThread(sleepMSec, 0);
}
// Causes the calling thread to yield execution to another thread that is ready to run on the current processor.
// Parameters:
// switchCount - number of times the YieldThread was called in a loop
void GCToOSInterface::YieldThread(uint32_t switchCount)
{
LIMITED_METHOD_CONTRACT;
__SwitchToThread(0, switchCount);
}
// Reserve virtual memory range.
// Parameters:
// address - starting virtual address, it can be NULL to let the function choose the starting address
// size - size of the virtual memory range
// alignment - requested memory alignment
// flags - flags to control special settings like write watching
// node - the NUMA node to reserve memory on
// Return:
// Starting virtual address of the reserved range
void* GCToOSInterface::VirtualReserve(size_t size, size_t alignment, uint32_t flags, uint16_t node)
{
LIMITED_METHOD_CONTRACT;
DWORD memFlags = (flags & VirtualReserveFlags::WriteWatch) ? (MEM_RESERVE | MEM_WRITE_WATCH) : MEM_RESERVE;
if (node == NUMA_NODE_UNDEFINED)
{
// This is not strictly necessary for a correctness standpoint. Windows already guarantees
// allocation granularity alignment when using MEM_RESERVE, so aligning the size here has no effect.
// However, ClrVirtualAlloc does expect the size to be aligned to the allocation granularity.
size_t aligned_size = (size + g_SystemInfo.dwAllocationGranularity - 1) & ~static_cast<size_t>(g_SystemInfo.dwAllocationGranularity - 1);
if (alignment == 0)
{
return ::ClrVirtualAlloc (0, aligned_size, memFlags, PAGE_READWRITE);
}
else
{
return ::ClrVirtualAllocAligned (0, aligned_size, memFlags, PAGE_READWRITE, alignment);
}
}
else
{
return NumaNodeInfo::VirtualAllocExNuma (::GetCurrentProcess (), NULL, size, memFlags, PAGE_READWRITE, node);
}
}
void* GCToOSInterface::VirtualReserve(void* location, size_t size)
{
DWORD memFlags = MEM_RESERVE;
return ::ClrVirtualAlloc(location, size, memFlags, PAGE_READWRITE);
}
// Release virtual memory range previously reserved using VirtualReserve
// Parameters:
// address - starting virtual address
// size - size of the virtual memory range
// Return:
// true if it has succeeded, false if it has failed
bool GCToOSInterface::VirtualRelease(void* address, size_t size)
{
LIMITED_METHOD_CONTRACT;
UNREFERENCED_PARAMETER(size);
return !!::ClrVirtualFree(address, 0, MEM_RELEASE);
}
// Commit virtual memory range.
// Parameters:
// size - size of the virtual memory range
// Return:
// Starting virtual address of the committed range
void* GCToOSInterface::VirtualReserveAndCommitLargePages(size_t size, uint16_t node)
{
LIMITED_METHOD_CONTRACT;
#if !defined(TARGET_UNIX)
if (!g_SeLockMemoryPrivilegeAcquired)
{
if (!InitLargePagesPrivilege())
{
return nullptr;
}
g_SeLockMemoryPrivilegeAcquired = true;
}
SIZE_T largePageMinimum = GetLargePageMinimum();
size = (size + (largePageMinimum - 1)) & ~(largePageMinimum - 1);
#endif
if (node == NUMA_NODE_UNDEFINED)
{
return ::ClrVirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
}
else
{
return NumaNodeInfo::VirtualAllocExNuma(::GetCurrentProcess(), nullptr, size, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE, node);
}
}
// Commit virtual memory range. It must be part of a range reserved using VirtualReserve.
// Parameters:
// address - starting virtual address
// size - size of the virtual memory range
// Return:
// true if it has succeeded, false if it has failed
bool GCToOSInterface::VirtualCommit(void* address, size_t size, uint16_t node)
{
LIMITED_METHOD_CONTRACT;
if (node == NUMA_NODE_UNDEFINED)
{
return ::ClrVirtualAlloc(address, size, MEM_COMMIT, PAGE_READWRITE) != NULL;
}
else
{
return NumaNodeInfo::VirtualAllocExNuma(::GetCurrentProcess(), address, size, MEM_COMMIT, PAGE_READWRITE, node) != NULL;
}
}
// Decomit virtual memory range.
// Parameters:
// address - starting virtual address
// size - size of the virtual memory range
// Return:
// true if it has succeeded, false if it has failed
bool GCToOSInterface::VirtualDecommit(void* address, size_t size)
{
LIMITED_METHOD_CONTRACT;
return !!::ClrVirtualFree(address, size, MEM_DECOMMIT);
}
// Reset virtual memory range. Indicates that data in the memory range specified by address and size is no
// longer of interest, but it should not be decommitted.
// Parameters:
// address - starting virtual address
// size - size of the virtual memory range
// unlock - true if the memory range should also be unlocked
// Return:
// true if it has succeeded, false if it has failed
bool GCToOSInterface::VirtualReset(void * address, size_t size, bool unlock)
{
LIMITED_METHOD_CONTRACT;
bool success = ::ClrVirtualAlloc(address, size, MEM_RESET, PAGE_READWRITE) != NULL;
#ifndef TARGET_UNIX
if (success && unlock)
{
// Remove the page range from the working set
::VirtualUnlock(address, size);
}
#endif // TARGET_UNIX
return success;
}
// Check if the OS supports write watching
bool GCToOSInterface::SupportsWriteWatch()
{
LIMITED_METHOD_CONTRACT;
#ifndef TARGET_UNIX
bool writeWatchSupported = false;
// check if the OS supports write-watch.
// Drawbridge does not support write-watch so we still need to do the runtime detection for them.
// Otherwise, all currently supported OSes do support write-watch.
void* mem = VirtualReserve(g_SystemInfo.dwAllocationGranularity, 0, VirtualReserveFlags::WriteWatch);
if (mem != NULL)
{
VirtualRelease (mem, g_SystemInfo.dwAllocationGranularity);
writeWatchSupported = true;
}
return writeWatchSupported;
#else // TARGET_UNIX
return false;
#endif // TARGET_UNIX
}
// Reset the write tracking state for the specified virtual memory range.
// Parameters:
// address - starting virtual address
// size - size of the virtual memory range
void GCToOSInterface::ResetWriteWatch(void* address, size_t size)
{
LIMITED_METHOD_CONTRACT;
#ifndef TARGET_UNIX
::ResetWriteWatch(address, size);
#endif // TARGET_UNIX
}
// Retrieve addresses of the pages that are written to in a region of virtual memory
// Parameters:
// resetState - true indicates to reset the write tracking state
// address - starting virtual address
// size - size of the virtual memory range
// pageAddresses - buffer that receives an array of page addresses in the memory region
// pageAddressesCount - on input, size of the lpAddresses array, in array elements
// on output, the number of page addresses that are returned in the array.
// Return:
// true if it has succeeded, false if it has failed
bool GCToOSInterface::GetWriteWatch(bool resetState, void* address, size_t size, void** pageAddresses, uintptr_t* pageAddressesCount)
{
LIMITED_METHOD_CONTRACT;
#ifndef TARGET_UNIX
uint32_t flags = resetState ? 1 : 0;
ULONG granularity;
bool success = ::GetWriteWatch(flags, address, size, pageAddresses, (ULONG_PTR*)pageAddressesCount, &granularity) == 0;
_ASSERTE (granularity == GetOsPageSize());
return success;
#else // TARGET_UNIX
*pageAddresses = NULL;
*pageAddressesCount = 0;
return true;
#endif // TARGET_UNIX
}
#ifdef TARGET_WINDOWS
// This function checks to see if GetLogicalProcessorInformation API is supported.
// On success, this function allocates a SLPI array, sets nEntries to number
// of elements in the SLPI array and returns a pointer to the SLPI array after filling it with information.
//
// Note: If successful, IsGLPISupported allocates memory for the SLPI array and expects the caller to
// free the memory once the caller is done using the information in the SLPI array.
//
// If the API is not supported or any failure, returns NULL
//
SYSTEM_LOGICAL_PROCESSOR_INFORMATION *IsGLPISupported( PDWORD nEntries )
{
DWORD cbslpi = 0;
DWORD dwNumElements = 0;
SYSTEM_LOGICAL_PROCESSOR_INFORMATION *pslpi = NULL;
// We setup the first call to GetLogicalProcessorInformation to fail so that we can obtain
// the size of the buffer required to allocate for the SLPI array that is returned
if (!GetLogicalProcessorInformation(pslpi, &cbslpi) &&
GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
// If we fail with anything other than an ERROR_INSUFFICIENT_BUFFER here, we punt with failure.
return NULL;
}
_ASSERTE(cbslpi);
// compute the number of SLPI entries required to hold the information returned from GLPI
dwNumElements = cbslpi / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
// allocate a buffer in the free heap to hold an array of SLPI entries from GLPI, number of elements in the array is dwNumElements
pslpi = new (nothrow) SYSTEM_LOGICAL_PROCESSOR_INFORMATION[ dwNumElements ];
if(pslpi == NULL)
{
// the memory allocation failed
return NULL;
}
// Make call to GetLogicalProcessorInformation. Returns array of SLPI structures
if (!GetLogicalProcessorInformation(pslpi, &cbslpi))
{
// GetLogicalProcessorInformation failed
delete[] pslpi ; //Allocation was fine but the API call itself failed and so we are releasing the memory before the return NULL.
return NULL ;
}
// GetLogicalProcessorInformation successful, set nEntries to number of entries in the SLPI array
*nEntries = dwNumElements;
return pslpi; // return pointer to SLPI array
}
// This function returns the size of highest level cache on the physical chip. If it cannot
// determine the cachesize this function returns 0.
size_t GetLogicalProcessorCacheSizeFromOS()
{
size_t cache_size = 0;
DWORD nEntries = 0;
// Try to use GetLogicalProcessorInformation API and get a valid pointer to the SLPI array if successful. Returns NULL
// if API not present or on failure.
SYSTEM_LOGICAL_PROCESSOR_INFORMATION *pslpi = IsGLPISupported(&nEntries) ;
if (pslpi == NULL)
{
// GetLogicalProcessorInformation not supported or failed.
goto Exit;
}
// Crack the information. Iterate through all the SLPI array entries for all processors in system.
// Will return the greatest of all the processor cache sizes or zero
{
size_t last_cache_size = 0;
for (DWORD i=0; i < nEntries; i++)
{
if (pslpi[i].Relationship == RelationCache)
{
last_cache_size = max(last_cache_size, pslpi[i].Cache.Size);
}
}
cache_size = last_cache_size;
}
Exit:
if(pslpi)
delete[] pslpi; // release the memory allocated for the SLPI array.
return cache_size;
}
#endif // TARGET_WINDOWS
// Get size of the largest cache on the processor die
// Parameters:
// trueSize - true to return true cache size, false to return scaled up size based on
// the processor architecture
// Return:
// Size of the cache
size_t GCToOSInterface::GetCacheSizePerLogicalCpu(bool trueSize)
{
LIMITED_METHOD_CONTRACT;
static volatile size_t s_maxSize;
static volatile size_t s_maxTrueSize;
size_t size = trueSize ? s_maxTrueSize : s_maxSize;
if (size != 0)
return size;
size_t maxSize, maxTrueSize;
maxSize = maxTrueSize = GetLogicalProcessorCacheSizeFromOS() ; // Returns the size of the highest level processor cache
#if defined(TARGET_ARM64)
// Bigger gen0 size helps arm64 targets
maxSize = maxTrueSize * 3;
#endif
s_maxSize = maxSize;
s_maxTrueSize = maxTrueSize;
// printf("GetCacheSizePerLogicalCpu returns %d, adjusted size %d\n", maxSize, maxTrueSize);
return trueSize ? maxTrueSize : maxSize;
}
// Sets the calling thread's affinity to only run on the processor specified
// Parameters:
// procNo - The requested processor for the calling thread.
// Return:
// true if setting the affinity was successful, false otherwise.
bool GCToOSInterface::SetThreadAffinity(uint16_t procNo)
{
LIMITED_METHOD_CONTRACT;
#ifndef TARGET_UNIX
GroupProcNo groupProcNo(procNo);
if (CPUGroupInfo::CanEnableGCCPUGroups())
{
GROUP_AFFINITY ga;
ga.Group = (WORD)groupProcNo.GetGroup();
ga.Reserved[0] = 0; // reserve must be filled with zero
ga.Reserved[1] = 0; // otherwise call may fail
ga.Reserved[2] = 0;
ga.Mask = (size_t)1 << groupProcNo.GetProcIndex();
return !!SetThreadGroupAffinity(GetCurrentThread(), &ga, nullptr);
}
else
{
return !!SetThreadAffinityMask(GetCurrentThread(), (DWORD_PTR)1 << groupProcNo.GetProcIndex());
}
#else // TARGET_UNIX
return PAL_SetCurrentThreadAffinity(procNo);
#endif // TARGET_UNIX
}
// Boosts the calling thread's thread priority to a level higher than the default
// for new threads.
// Parameters:
// None.
// Return:
// true if the priority boost was successful, false otherwise.
bool GCToOSInterface::BoostThreadPriority()
{
return !!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
}
// Set the set of processors enabled for GC threads for the current process based on config specified affinity mask and set
// Parameters:
// configAffinityMask - mask specified by the GCHeapAffinitizeMask config
// configAffinitySet - affinity set specified by the GCHeapAffinitizeRanges config
// Return:
// set of enabled processors
const AffinitySet* GCToOSInterface::SetGCThreadsAffinitySet(uintptr_t configAffinityMask, const AffinitySet* configAffinitySet)
{
#ifndef TARGET_UNIX
if (CPUGroupInfo::CanEnableGCCPUGroups())
#endif // !TARGET_UNIX
{
if (!configAffinitySet->IsEmpty())
{
// Update the process affinity set using the configured set
for (size_t i = 0; i < MAX_SUPPORTED_CPUS; i++)
{
if (g_processAffinitySet.Contains(i) && !configAffinitySet->Contains(i))
{
g_processAffinitySet.Remove(i);
}
}
}
}
#ifndef TARGET_UNIX
else
{
if (configAffinityMask != 0)
{
// Update the process affinity set using the configured mask
for (size_t i = 0; i < 8 * sizeof(uintptr_t); i++)
{
if (g_processAffinitySet.Contains(i) && ((configAffinityMask & ((uintptr_t)1 << i)) == 0))
{
g_processAffinitySet.Remove(i);
}
}
}
}
#endif // !TARGET_UNIX
return &g_processAffinitySet;
}
// Get number of processors assigned to the current process
// Return:
// The number of processors
uint32_t GCToOSInterface::GetCurrentProcessCpuCount()
{
LIMITED_METHOD_CONTRACT;
#ifndef TARGET_UNIX
// GetCurrentProcessCpuCount only returns up to 64 procs.
return CPUGroupInfo::CanEnableGCCPUGroups() ?
GCToOSInterface::GetTotalProcessorCount():
::GetCurrentProcessCpuCount();
#else // !TARGET_UNIX
return ::GetCurrentProcessCpuCount();
#endif // !TARGET_UNIX
}
// Return the size of the user-mode portion of the virtual address space of this process.
// Return:
// non zero if it has succeeded, (size_t)-1 if not available
size_t GCToOSInterface::GetVirtualMemoryLimit()
{
LIMITED_METHOD_CONTRACT;
MEMORYSTATUSEX memStatus;
GetProcessMemoryLoad(&memStatus);
return (size_t)memStatus.ullTotalVirtual;
}
static size_t g_RestrictedPhysicalMemoryLimit = (size_t)MAX_PTR;
#ifndef TARGET_UNIX
static size_t GetRestrictedPhysicalMemoryLimit()
{
LIMITED_METHOD_CONTRACT;
// The limit was cached already
if (g_RestrictedPhysicalMemoryLimit != (size_t)MAX_PTR)
return g_RestrictedPhysicalMemoryLimit;
size_t job_physical_memory_limit = (size_t)MAX_PTR;
uint64_t total_virtual = 0;
uint64_t total_physical = 0;
BOOL in_job_p = FALSE;
if (!IsProcessInJob(GetCurrentProcess(), NULL, &in_job_p))
goto exit;
if (in_job_p)
{
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info;
if (QueryInformationJobObject(NULL, JobObjectExtendedLimitInformation, &limit_info,
sizeof(limit_info), NULL))
{
size_t job_memory_limit = (size_t)MAX_PTR;
size_t job_process_memory_limit = (size_t)MAX_PTR;
size_t job_workingset_limit = (size_t)MAX_PTR;
// Notes on the NT job object:
//
// You can specific a bigger process commit or working set limit than
// job limit which is pointless so we use the smallest of all 3 as
// to calculate our "physical memory load" or "available physical memory"
// when running inside a job object, ie, we treat this as the amount of physical memory
// our process is allowed to use.
//
// The commit limit is already reflected by default when you run in a
// job but the physical memory load is not.
//
if ((limit_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_JOB_MEMORY) != 0)
job_memory_limit = limit_info.JobMemoryLimit;
if ((limit_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_PROCESS_MEMORY) != 0)
job_process_memory_limit = limit_info.ProcessMemoryLimit;
if ((limit_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_WORKINGSET) != 0)
job_workingset_limit = limit_info.BasicLimitInformation.MaximumWorkingSetSize;
if ((job_memory_limit != (size_t)MAX_PTR) ||
(job_process_memory_limit != (size_t)MAX_PTR) ||
(job_workingset_limit != (size_t)MAX_PTR))
{
job_physical_memory_limit = min (job_memory_limit, job_process_memory_limit);
job_physical_memory_limit = min (job_physical_memory_limit, job_workingset_limit);
MEMORYSTATUSEX ms;
GetProcessMemoryLoad(&ms);
total_virtual = ms.ullTotalVirtual;
total_physical = ms.ullAvailPhys;
// A sanity check in case someone set a larger limit than there is actual physical memory.
job_physical_memory_limit = (size_t) min (job_physical_memory_limit, ms.ullTotalPhys);
}
}
}
exit:
if (job_physical_memory_limit == (size_t)MAX_PTR)
{
job_physical_memory_limit = 0;
}
// Check to see if we are limited by VM.
if (total_virtual == 0)
{
MEMORYSTATUSEX ms;
GetProcessMemoryLoad(&ms);
total_virtual = ms.ullTotalVirtual;
total_physical = ms.ullTotalPhys;
}
if (job_physical_memory_limit != 0)
{
total_physical = job_physical_memory_limit;
}
if (total_virtual < total_physical)
{
// Limited by virtual address space
job_physical_memory_limit = 0;
}
VolatileStore(&g_RestrictedPhysicalMemoryLimit, job_physical_memory_limit);
return g_RestrictedPhysicalMemoryLimit;
}
#else
static size_t GetRestrictedPhysicalMemoryLimit()
{
LIMITED_METHOD_CONTRACT;
// The limit was cached already
if (g_RestrictedPhysicalMemoryLimit != (size_t)MAX_PTR)
return g_RestrictedPhysicalMemoryLimit;
size_t memory_limit = PAL_GetRestrictedPhysicalMemoryLimit();
VolatileStore(&g_RestrictedPhysicalMemoryLimit, memory_limit);
return g_RestrictedPhysicalMemoryLimit;
}
#endif // TARGET_UNIX
// Get the physical memory that this process can use.
// Return:
// non zero if it has succeeded, 0 if it has failed
uint64_t GCToOSInterface::GetPhysicalMemoryLimit(bool* is_restricted)
{
LIMITED_METHOD_CONTRACT;
if (is_restricted)
*is_restricted = false;
size_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
if (restricted_limit != 0)
{
if (is_restricted)
*is_restricted = true;
return restricted_limit;
}
MEMORYSTATUSEX memStatus;
GetProcessMemoryLoad(&memStatus);
return memStatus.ullTotalPhys;
}
// Get memory status
// Parameters:
// memory_load - A number between 0 and 100 that specifies the approximate percentage of physical memory
// that is in use (0 indicates no memory use and 100 indicates full memory use).
// available_physical - The amount of physical memory currently available, in bytes.
// available_page_file - The maximum amount of memory the current process can commit, in bytes.
// Remarks:
// Any parameter can be null.
void GCToOSInterface::GetMemoryStatus(uint64_t restricted_limit, uint32_t* memory_load, uint64_t* available_physical, uint64_t* available_page_file)
{
LIMITED_METHOD_CONTRACT;
if (restricted_limit != 0)
{
size_t workingSetSize;
BOOL status = FALSE;
#ifndef TARGET_UNIX
PROCESS_MEMORY_COUNTERS pmc;
status = GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc));
workingSetSize = pmc.WorkingSetSize;
#else
status = PAL_GetPhysicalMemoryUsed(&workingSetSize);
#endif
if (status)
{
if (memory_load)
*memory_load = (uint32_t)((float)workingSetSize * 100.0 / (float)restricted_limit);
if (available_physical)
{
if(workingSetSize > restricted_limit)
*available_physical = 0;
else
*available_physical = restricted_limit - workingSetSize;
}
// Available page file doesn't mean much when physical memory is restricted since
// we don't know how much of it is available to this process so we are not going to
// bother to make another OS call for it.
if (available_page_file)
*available_page_file = 0;
return;
}
}
MEMORYSTATUSEX ms;
GetProcessMemoryLoad(&ms);
#ifndef TARGET_UNIX
// For 32-bit processes the virtual address range could be smaller than the amount of physical
// memory on the machine/in the container, we need to restrict by the VM.
if (ms.ullTotalVirtual < ms.ullTotalPhys)
{
if (memory_load != NULL)
*memory_load = (uint32_t)((float)(ms.ullTotalVirtual - ms.ullAvailVirtual) * 100.0 / (float)ms.ullTotalVirtual);
if (available_physical != NULL)
*available_physical = ms.ullTotalVirtual;
// Available page file isn't helpful when we are restricted by virtual memory
// since the amount of memory we can reserve is less than the amount of
// memory we can commit.
if (available_page_file != NULL)
*available_page_file = 0;
}
else
#endif //!TARGET_UNIX
{
if (memory_load != NULL)
*memory_load = ms.dwMemoryLoad;
if (available_physical != NULL)
*available_physical = ms.ullAvailPhys;
if (available_page_file != NULL)
*available_page_file = ms.ullAvailPageFile;
}
}
// Get a high precision performance counter
// Return:
// The counter value
int64_t GCToOSInterface::QueryPerformanceCounter()
{
LIMITED_METHOD_CONTRACT;
LARGE_INTEGER ts;
if (!::QueryPerformanceCounter(&ts))
{
DebugBreak();
_ASSERTE(!"Fatal Error - cannot query performance counter.");
EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); // TODO: fatal error
}
return ts.QuadPart;
}
// Get a frequency of the high precision performance counter
// Return:
// The counter frequency
int64_t GCToOSInterface::QueryPerformanceFrequency()
{
LIMITED_METHOD_CONTRACT;
LARGE_INTEGER frequency;
if (!::QueryPerformanceFrequency(&frequency))
{
DebugBreak();
_ASSERTE(!"Fatal Error - cannot query performance counter.");
EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); // TODO: fatal error
}
return frequency.QuadPart;
}
// Get a time stamp with a low precision
// Return:
// Time stamp in milliseconds
uint32_t GCToOSInterface::GetLowPrecisionTimeStamp()
{
LIMITED_METHOD_CONTRACT;
return ::GetTickCount();
}
uint32_t GCToOSInterface::GetTotalProcessorCount()
{
LIMITED_METHOD_CONTRACT;
#ifndef TARGET_UNIX
if (CPUGroupInfo::CanEnableGCCPUGroups())
{
return CPUGroupInfo::GetNumActiveProcessors();
}
else
{
return g_SystemInfo.dwNumberOfProcessors;
}
#else // !TARGET_UNIX
return PAL_GetTotalCpuCount();
#endif // !TARGET_UNIX
}
bool GCToOSInterface::CanEnableGCNumaAware()
{
LIMITED_METHOD_CONTRACT;
return NumaNodeInfo::CanEnableGCNumaAware() != FALSE;
}
bool GCToOSInterface::GetNumaInfo(uint16_t* total_nodes, uint32_t* max_procs_per_node)
{
#ifndef TARGET_UNIX
return NumaNodeInfo::GetNumaInfo(total_nodes, (DWORD*)max_procs_per_node);
#else
return false;
#endif //!TARGET_UNIX
}
bool GCToOSInterface::GetCPUGroupInfo(uint16_t* total_groups, uint32_t* max_procs_per_group)
{
#ifndef TARGET_UNIX
return CPUGroupInfo::GetCPUGroupInfo(total_groups, (DWORD*)max_procs_per_group);
#else
return false;
#endif //!TARGET_UNIX
}
bool GCToOSInterface::CanEnableGCCPUGroups()
{
LIMITED_METHOD_CONTRACT;
#ifndef TARGET_UNIX
return CPUGroupInfo::CanEnableGCCPUGroups() != FALSE;
#else
return false;
#endif //!TARGET_UNIX
}
// Get processor number and optionally its NUMA node number for the specified heap number
// Parameters:
// heap_number - heap number to get the result for
// proc_no - set to the selected processor number
// node_no - set to the NUMA node of the selected processor or to NUMA_NODE_UNDEFINED
// Return:
// true if it succeeded
bool GCToOSInterface::GetProcessorForHeap(uint16_t heap_number, uint16_t* proc_no, uint16_t* node_no)
{
bool success = false;
// Locate heap_number-th available processor
uint16_t procIndex;
size_t cnt = heap_number;
for (uint16_t i = 0; i < MAX_SUPPORTED_CPUS; i++)
{
if (g_processAffinitySet.Contains(i))
{
if (cnt == 0)
{
procIndex = i;
success = true;
break;
}
cnt--;
}
}
if (success)
{
#ifndef TARGET_UNIX
WORD gn, gpn;
if (CPUGroupInfo::CanEnableGCCPUGroups())
{
CPUGroupInfo::GetGroupForProcessor(procIndex, &gn, &gpn);
}
else
{
gn = GroupProcNo::NoGroup;
gpn = procIndex;
}
GroupProcNo groupProcNo(gn, gpn);
*proc_no = groupProcNo.GetCombinedValue();
PROCESSOR_NUMBER procNumber;
if (CPUGroupInfo::CanEnableGCCPUGroups())
{
procNumber.Group = gn;
}
else
{
// Get the current processor group
GetCurrentProcessorNumberEx(&procNumber);
}
if (GCToOSInterface::CanEnableGCNumaAware())
{
procNumber.Number = (BYTE)gpn;
procNumber.Reserved = 0;
if (!NumaNodeInfo::GetNumaProcessorNodeEx(&procNumber, node_no))
{
*node_no = NUMA_NODE_UNDEFINED;
}
}
else
{ // no numa setting, each cpu group is treated as a node
*node_no = procNumber.Group;
}
#else // !TARGET_UNIX
*proc_no = procIndex;
if (!GCToOSInterface::CanEnableGCNumaAware() || !NumaNodeInfo::GetNumaProcessorNodeEx(procIndex, (WORD*)node_no))
{
*node_no = NUMA_NODE_UNDEFINED;
}
#endif // !TARGET_UNIX
}
return success;
}
// Parse the confing string describing affinitization ranges and update the passed in affinitySet accordingly
// Parameters:
// config_string - string describing the affinitization range, platform specific
// start_index - the range start index extracted from the config_string
// end_index - the range end index extracted from the config_string, equal to the start_index if only an index and not a range was passed in
// Return:
// true if the configString was successfully parsed, false if it was not correct
bool GCToOSInterface::ParseGCHeapAffinitizeRangesEntry(const char** config_string, size_t* start_index, size_t* end_index)
{
size_t index_offset = 0;
char* number_end;
#ifndef TARGET_UNIX
size_t group_number = strtoul(*config_string, &number_end, 10);
if ((number_end == *config_string) || (*number_end != ':'))
{
// No number or no colon after the number found, invalid format
return false;
}
WORD group_begin;
WORD group_size;
if (!CPUGroupInfo::GetCPUGroupRange((WORD)group_number, &group_begin, &group_size))
{
// group number out of range
return false;
}
index_offset = group_begin;
*config_string = number_end + 1;
#endif // !TARGET_UNIX
size_t start, end;
if (!ParseIndexOrRange(config_string, &start, &end))
{
return false;
}
#ifndef TARGET_UNIX
if ((start >= group_size) || (end >= group_size))
{
// Invalid CPU index values or range
return false;
}
#endif // !TARGET_UNIX
*start_index = index_offset + start;
*end_index = index_offset + end;
return true;
}
// Initialize the critical section
void CLRCriticalSection::Initialize()
{
WRAPPER_NO_CONTRACT;
InitializeCriticalSection(&m_cs);
}
// Destroy the critical section
void CLRCriticalSection::Destroy()
{
WRAPPER_NO_CONTRACT;
DeleteCriticalSection(&m_cs);
}
// Enter the critical section. Blocks until the section can be entered.
void CLRCriticalSection::Enter()
{
WRAPPER_NO_CONTRACT;
EnterCriticalSection(&m_cs);
}
// Leave the critical section
void CLRCriticalSection::Leave()
{
WRAPPER_NO_CONTRACT;
LeaveCriticalSection(&m_cs);
}
// An implementatino of GCEvent that delegates to
// a CLREvent, which in turn delegates to the PAL. This event
// is also host-aware.
class GCEvent::Impl
{
private:
CLREvent m_event;
public:
Impl() = default;
bool IsValid()
{
WRAPPER_NO_CONTRACT;
return !!m_event.IsValid();
}
void CloseEvent()
{
WRAPPER_NO_CONTRACT;
_ASSERTE(m_event.IsValid());
m_event.CloseEvent();
}
void Set()
{
WRAPPER_NO_CONTRACT;
_ASSERTE(m_event.IsValid());
m_event.Set();
}
void Reset()
{
WRAPPER_NO_CONTRACT;
_ASSERTE(m_event.IsValid());
m_event.Reset();
}
uint32_t Wait(uint32_t timeout, bool alertable)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(m_event.IsValid());
return m_event.Wait(timeout, alertable);
}
bool CreateAutoEvent(bool initialState)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
return !!m_event.CreateAutoEventNoThrow(initialState);
}
bool CreateManualEvent(bool initialState)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
return !!m_event.CreateManualEventNoThrow(initialState);
}
bool CreateOSAutoEvent(bool initialState)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
return !!m_event.CreateOSAutoEventNoThrow(initialState);
}
bool CreateOSManualEvent(bool initialState)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
return !!m_event.CreateOSManualEventNoThrow(initialState);
}
};
GCEvent::GCEvent()
: m_impl(nullptr)
{
}
void GCEvent::CloseEvent()
{
WRAPPER_NO_CONTRACT;
_ASSERTE(m_impl != nullptr);
m_impl->CloseEvent();
}
void GCEvent::Set()
{
WRAPPER_NO_CONTRACT;
_ASSERTE(m_impl != nullptr);
m_impl->Set();
}
void GCEvent::Reset()
{
WRAPPER_NO_CONTRACT;
_ASSERTE(m_impl != nullptr);
m_impl->Reset();
}
uint32_t GCEvent::Wait(uint32_t timeout, bool alertable)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(m_impl != nullptr);
return m_impl->Wait(timeout, alertable);
}
bool GCEvent::CreateManualEventNoThrow(bool initialState)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
_ASSERTE(m_impl == nullptr);
NewHolder<GCEvent::Impl> event = new (nothrow) GCEvent::Impl();
if (!event)
{
return false;
}
event->CreateManualEvent(initialState);
m_impl = event.Extract();
return true;
}
bool GCEvent::CreateAutoEventNoThrow(bool initialState)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
_ASSERTE(m_impl == nullptr);
NewHolder<GCEvent::Impl> event = new (nothrow) GCEvent::Impl();
if (!event)
{
return false;
}
event->CreateAutoEvent(initialState);
m_impl = event.Extract();
return IsValid();
}
bool GCEvent::CreateOSAutoEventNoThrow(bool initialState)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
_ASSERTE(m_impl == nullptr);
NewHolder<GCEvent::Impl> event = new (nothrow) GCEvent::Impl();
if (!event)
{
return false;
}
event->CreateOSAutoEvent(initialState);
m_impl = event.Extract();
return IsValid();
}
bool GCEvent::CreateOSManualEventNoThrow(bool initialState)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
_ASSERTE(m_impl == nullptr);
NewHolder<GCEvent::Impl> event = new (nothrow) GCEvent::Impl();
if (!event)
{
return false;
}
event->CreateOSManualEvent(initialState);
m_impl = event.Extract();
return IsValid();
}