diff options
Diffstat (limited to 'src/pal/src')
24 files changed, 1188 insertions, 124 deletions
diff --git a/src/pal/src/CMakeLists.txt b/src/pal/src/CMakeLists.txt index 5314cdf86b..145c2c9ed9 100644 --- a/src/pal/src/CMakeLists.txt +++ b/src/pal/src/CMakeLists.txt @@ -181,6 +181,7 @@ set(SOURCES misc/sysinfo.cpp misc/time.cpp misc/utils.cpp + numa/numa.cpp objmgr/palobjbase.cpp objmgr/shmobject.cpp objmgr/shmobjectmanager.cpp @@ -372,6 +373,12 @@ if(CMAKE_SYSTEM_NAME STREQUAL NetBSD) ) endif(CMAKE_SYSTEM_NAME STREQUAL NetBSD) +if(HAVE_NUMA_H) + target_link_libraries(coreclrpal + numa + ) +endif(HAVE_NUMA_H) + add_subdirectory(examples) if(FEATURE_EVENT_TRACE) diff --git a/src/pal/src/arch/amd64/processor.cpp b/src/pal/src/arch/amd64/processor.cpp index 298d685c98..0ab7992286 100644 --- a/src/pal/src/arch/amd64/processor.cpp +++ b/src/pal/src/arch/amd64/processor.cpp @@ -65,7 +65,7 @@ extern "C" unsigned int XmmYmmStateSupport() "end:\n" \ : "=a"(eax) /* output in eax */ \ : /* no inputs */ \ - : "eax", "ebx", "ecx", "edx" /* registers that are clobbered */ + : "ebx", "ecx", "edx" /* registers that are clobbered */ ); // Check OS has enabled both XMM and YMM state support return ((eax & 0x06) == 0x06) ? 1 : 0; diff --git a/src/pal/src/arch/i386/asmconstants.h b/src/pal/src/arch/i386/asmconstants.h index d947cb8bcd..ff763ef16b 100644 --- a/src/pal/src/arch/i386/asmconstants.h +++ b/src/pal/src/arch/i386/asmconstants.h @@ -28,4 +28,3 @@ #define CONTEXT_Xmm5 CONTEXT_Xmm4+16 #define CONTEXT_Xmm6 CONTEXT_Xmm5+16 #define CONTEXT_Xmm7 CONTEXT_Xmm6+16 -#define CONTEXT_ResumeEsp CONTEXT_ExtendedRegisters+512 diff --git a/src/pal/src/arch/i386/context2.S b/src/pal/src/arch/i386/context2.S index 6c31b074cc..cf7581da49 100644 --- a/src/pal/src/arch/i386/context2.S +++ b/src/pal/src/arch/i386/context2.S @@ -42,7 +42,6 @@ LEAF_ENTRY CONTEXT_CaptureContext, _TEXT mov [eax + CONTEXT_Ebp], ebp lea ebx, [esp + 12] mov [eax + CONTEXT_Esp], ebx - mov [eax + CONTEXT_ResumeEsp], ebx mov ebx, [esp + 8] mov [eax + CONTEXT_Eip], ebx @@ -115,7 +114,7 @@ LOCAL_LABEL(Done_Restore_CONTEXT_FLOATING_POINT): LOCAL_LABEL(Done_Restore_CONTEXT_EXTENDED_REGISTERS): // Restore Stack - mov esp, [eax + CONTEXT_ResumeEsp] + mov esp, [eax + CONTEXT_Esp] // Create a minimal frame push DWORD PTR [eax + CONTEXT_Eip] diff --git a/src/pal/src/arch/i386/exceptionhelper.S b/src/pal/src/arch/i386/exceptionhelper.S index bf44124479..b9ceffcc13 100644 --- a/src/pal/src/arch/i386/exceptionhelper.S +++ b/src/pal/src/arch/i386/exceptionhelper.S @@ -23,7 +23,7 @@ LEAF_ENTRY ThrowExceptionFromContextInternal, _TEXT mov eax, [esp + 8] // ebx: CONTEXT * mov ebp, [eax + CONTEXT_Ebp] - mov esp, [eax + CONTEXT_ResumeEsp] + mov esp, [eax + CONTEXT_Esp] mov ebx, [eax + CONTEXT_Ebx] mov esi, [eax + CONTEXT_Esi] mov edi, [eax + CONTEXT_Edi] diff --git a/src/pal/src/arch/i386/signalhandlerhelper.cpp b/src/pal/src/arch/i386/signalhandlerhelper.cpp index 3369abe093..a7d418a788 100644 --- a/src/pal/src/arch/i386/signalhandlerhelper.cpp +++ b/src/pal/src/arch/i386/signalhandlerhelper.cpp @@ -70,7 +70,6 @@ void ExecuteHandlerOnOriginalStack(int code, siginfo_t *siginfo, void *context, // We don't care about the other registers state since the stack unwinding restores // them for the target frame directly from the signal context. context2.Esp = (size_t)sp; - context2.ResumeEsp = (size_t)sp; context2.Ebp = (size_t)fp; context2.Eip = (size_t)signal_handler_worker; diff --git a/src/pal/src/config.h.in b/src/pal/src/config.h.in index ab5fa0341d..7f37f42222 100644 --- a/src/pal/src/config.h.in +++ b/src/pal/src/config.h.in @@ -20,6 +20,9 @@ #cmakedefine01 HAVE_RUNETYPE_H #cmakedefine01 HAVE_SYS_SYSCTL_H #cmakedefine01 HAVE_GNU_LIBNAMES_H +#cmakedefine01 HAVE_PRCTL_H +#cmakedefine01 HAVE_NUMA_H +#cmakedefine01 HAVE_PTHREAD_NP_H #cmakedefine01 HAVE_KQUEUE #cmakedefine01 HAVE_GETPWUID_R @@ -32,6 +35,8 @@ #cmakedefine01 HAVE_PTHREAD_GETATTR_NP #cmakedefine01 HAVE_PTHREAD_GETCPUCLOCKID #cmakedefine01 HAVE_PTHREAD_SIGQUEUE +#cmakedefine01 HAVE_PTHREAD_GETAFFINITY_NP +#cmakedefine01 HAVE_CPUSET_T #cmakedefine01 HAVE_SIGRETURN #cmakedefine01 HAVE__THREAD_SYS_SIGRETURN #cmakedefine01 HAVE_COPYSIGN @@ -56,6 +61,7 @@ #cmakedefine01 HAS_SYSV_SEMAPHORES #cmakedefine01 HAS_PTHREAD_MUTEXES #cmakedefine01 HAVE_TTRACE +#cmakedefine01 HAVE_SCHED_GETAFFINITY #cmakedefine HAVE_UNW_GET_SAVE_LOC #cmakedefine HAVE_UNW_GET_ACCESSORS #cmakedefine01 HAVE_XSWDEV @@ -97,6 +103,7 @@ #cmakedefine01 HAVE_CLOCK_MONOTONIC_COARSE #cmakedefine01 HAVE_MACH_ABSOLUTE_TIME #cmakedefine01 HAVE_CLOCK_THREAD_CPUTIME +#cmakedefine01 HAVE_PTHREAD_CONDATTR_SETCLOCK #cmakedefine01 STATVFS64_PROTOTYPE_BROKEN #cmakedefine01 HAVE_MMAP_DEV_ZERO #cmakedefine01 MMAP_ANON_IGNORES_PROTECTION diff --git a/src/pal/src/configure.cmake b/src/pal/src/configure.cmake index 4d78f54423..b5b98d5b2d 100644 --- a/src/pal/src/configure.cmake +++ b/src/pal/src/configure.cmake @@ -34,6 +34,9 @@ check_include_files(lwp.h HAVE_LWP_H) check_include_files(libunwind.h HAVE_LIBUNWIND_H) check_include_files(runetype.h HAVE_RUNETYPE_H) check_include_files(semaphore.h HAVE_SEMAPHORE_H) +check_include_files(sys/prctl.h HAVE_PRCTL_H) +check_include_files(numa.h HAVE_NUMA_H) +check_include_files(pthread_np.h HAVE_PTHREAD_NP_H) if(NOT CMAKE_SYSTEM_NAME STREQUAL FreeBSD AND NOT CMAKE_SYSTEM_NAME STREQUAL NetBSD) set(CMAKE_REQUIRED_FLAGS "-ldl") @@ -68,6 +71,7 @@ check_library_exists(${PTHREAD_LIBRARY} pthread_attr_get_np "" HAVE_PTHREAD_ATTR check_library_exists(${PTHREAD_LIBRARY} pthread_getattr_np "" HAVE_PTHREAD_GETATTR_NP) check_library_exists(${PTHREAD_LIBRARY} pthread_getcpuclockid "" HAVE_PTHREAD_GETCPUCLOCKID) check_library_exists(${PTHREAD_LIBRARY} pthread_sigqueue "" HAVE_PTHREAD_SIGQUEUE) +check_library_exists(${PTHREAD_LIBRARY} pthread_getaffinity_np "" HAVE_PTHREAD_GETAFFINITY_NP) check_function_exists(sigreturn HAVE_SIGRETURN) check_function_exists(_thread_sys_sigreturn HAVE__THREAD_SYS_SIGRETURN) @@ -118,6 +122,14 @@ int main(int argc, char **argv) { }" HAVE_UNW_GET_ACCESSORS) set(CMAKE_REQUIRED_LIBRARIES) +check_cxx_source_compiles(" +#include <pthread_np.h> +int main(int argc, char **argv) { + cpuset_t cpuSet; + + return 0; +}" HAVE_CPUSET_T) + check_struct_has_member ("struct stat" st_atimespec "sys/types.h;sys/stat.h" HAVE_STAT_TIMESPEC) check_struct_has_member ("struct stat" st_atimensec "sys/types.h;sys/stat.h" HAVE_STAT_NSEC) check_struct_has_member ("struct tm" tm_gmtoff time.h HAVE_TM_GMTOFF) @@ -405,6 +417,9 @@ int main() exit(ret); }" HAVE_CLOCK_MONOTONIC) + +check_library_exists(pthread pthread_condattr_setclock "" HAVE_PTHREAD_CONDATTR_SETCLOCK) + check_cxx_source_runs(" #include <stdlib.h> #include <time.h> diff --git a/src/pal/src/exception/seh-unwind.cpp b/src/pal/src/exception/seh-unwind.cpp index 1f20ee0cad..ba43c2e725 100644 --- a/src/pal/src/exception/seh-unwind.cpp +++ b/src/pal/src/exception/seh-unwind.cpp @@ -155,7 +155,6 @@ static void UnwindContextToWinContext(unw_cursor_t *cursor, CONTEXT *winContext) #elif defined(_X86_) unw_get_reg(cursor, UNW_REG_IP, (unw_word_t *) &winContext->Eip); unw_get_reg(cursor, UNW_REG_SP, (unw_word_t *) &winContext->Esp); - unw_get_reg(cursor, UNW_REG_SP, (unw_word_t *) &winContext->ResumeEsp); unw_get_reg(cursor, UNW_X86_EBP, (unw_word_t *) &winContext->Ebp); unw_get_reg(cursor, UNW_X86_EBX, (unw_word_t *) &winContext->Ebx); unw_get_reg(cursor, UNW_X86_ESI, (unw_word_t *) &winContext->Esi); diff --git a/src/pal/src/file/disk.cpp b/src/pal/src/file/disk.cpp index 08880c9c20..e5d6f831dd 100644 --- a/src/pal/src/file/disk.cpp +++ b/src/pal/src/file/disk.cpp @@ -68,6 +68,7 @@ GetDiskFreeSpaceW( PathCharString dirNameBufferPathString; size_t length; char * dirNameBuffer; + const char * dirName; int size; PERF_ENTRY(GetDiskFreeSpaceW); @@ -125,7 +126,7 @@ GetDiskFreeSpaceW( if ( size != 0 ) { FILEDosToUnixPathA( dirNameBuffer ); - statfsRetVal = statfs( dirNameBuffer, &fsInfoBuffer ); + dirName = dirNameBuffer; } else { @@ -136,9 +137,11 @@ GetDiskFreeSpaceW( } else { - statfsRetVal = statfs( "/", &fsInfoBuffer ); + dirName = "/"; } + statfsRetVal = statfs( dirName, &fsInfoBuffer ); + if ( statfsRetVal == 0 ) { *lpBytesPerSector = fsInfoBuffer.f_bsize; diff --git a/src/pal/src/file/file.cpp b/src/pal/src/file/file.cpp index d70e62bd52..a4ad20db32 100644 --- a/src/pal/src/file/file.cpp +++ b/src/pal/src/file/file.cpp @@ -2999,8 +2999,11 @@ OUT PLARGE_INTEGER lpFileSize) &dwFileSizeHigh ); - lpFileSize->u.LowPart = dwFileSizeLow; - lpFileSize->u.HighPart = dwFileSizeHigh; + if (NO_ERROR == palError) + { + lpFileSize->u.LowPart = dwFileSizeLow; + lpFileSize->u.HighPart = dwFileSizeHigh; + } } else { diff --git a/src/pal/src/include/pal/dbgmsg.h b/src/pal/src/include/pal/dbgmsg.h index 7a49fc0ad6..052c6fa775 100644 --- a/src/pal/src/include/pal/dbgmsg.h +++ b/src/pal/src/include/pal/dbgmsg.h @@ -194,7 +194,7 @@ typedef enum #ifdef FEATURE_PAL_SXS DCI_SXS, #endif // FEATURE_PAL_SXS - + DCI_NUMA, DCI_LAST } DBG_CHANNEL_ID; diff --git a/src/pal/src/include/pal/numa.h b/src/pal/src/include/pal/numa.h new file mode 100644 index 0000000000..4fb2308a7d --- /dev/null +++ b/src/pal/src/include/pal/numa.h @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*++ + + + +Module Name: + + include/pal/numa.h + +Abstract: + + Header file for the NUMA functions. + + + +--*/ + +#ifndef _PAL_NUMA_H_ +#define _PAL_NUMA_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +BOOL +NUMASupportInitialize(); + +VOID +NUMASupportCleanup(); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif /* _PAL_CRITSECT_H_ */ diff --git a/src/pal/src/include/pal/process.h b/src/pal/src/include/pal/process.h index 990aec5b21..63ef5c52ec 100644 --- a/src/pal/src/include/pal/process.h +++ b/src/pal/src/include/pal/process.h @@ -121,6 +121,21 @@ Abstract VOID PROCProcessUnlock(VOID); /*++ +Function + PROCAbortInitialize() + +Abstract + Initialize the process abort crash dump program file path and + name. Doing all of this ahead of time so nothing is allocated + or copied in PROCAbort/signal handler. + +Return + TRUE - succeeds, FALSE - fails + +--*/ +BOOL PROCAbortInitialize(); + +/*++ Function: PROCAbort() @@ -130,7 +145,7 @@ Function: Does not return --*/ PAL_NORETURN -void PROCAbort(); +VOID PROCAbort(); /*++ Function: @@ -141,7 +156,7 @@ Function: (no return value) --*/ -void PROCNotifyProcessShutdown(); +VOID PROCNotifyProcessShutdown(); /*++ Function: diff --git a/src/pal/src/init/pal.cpp b/src/pal/src/init/pal.cpp index fa94922325..2fdafe4f8c 100644 --- a/src/pal/src/init/pal.cpp +++ b/src/pal/src/init/pal.cpp @@ -42,6 +42,7 @@ SET_DEFAULT_DEBUG_CHANNEL(PAL); // some headers have code with asserts, so do th #include "pal/debug.h" #include "pal/locale.h" #include "pal/init.h" +#include "pal/numa.h" #include "pal/stackstring.hpp" #if HAVE_MACH_EXCEPTIONS @@ -523,6 +524,12 @@ Initialize( goto CLEANUP15; } + if (FALSE == NUMASupportInitialize()) + { + ERROR("Unable to initialize NUMA support\n"); + goto CLEANUP15; + } + TRACE("First-time PAL initialization complete.\n"); init_count++; @@ -548,6 +555,7 @@ Initialize( } goto done; + NUMASupportCleanup(); /* No cleanup required for CRTInitStdStreams */ CLEANUP15: FILECleanupStdHandles(); @@ -650,6 +658,12 @@ PAL_InitializeCoreCLR(const char *szExePath) return ERROR_DLL_INIT_FAILED; } + if (!PROCAbortInitialize()) + { + printf("PROCAbortInitialize FAILED %d (%s)\n", errno, strerror(errno)); + return ERROR_GEN_FAILURE; + } + if (!InitializeFlushProcessWriteBuffers()) { return ERROR_GEN_FAILURE; diff --git a/src/pal/src/map/map.cpp b/src/pal/src/map/map.cpp index f3ec47b846..5fdb6fda38 100644 --- a/src/pal/src/map/map.cpp +++ b/src/pal/src/map/map.cpp @@ -44,6 +44,13 @@ using namespace CorUnix; SET_DEFAULT_DEBUG_CHANNEL(VIRTUAL); +#include "pal/utils.h" + +// This is temporary until #10981 merges. +// There will be an equivalent but opposite temporary fix in #10981 which +// will trigger a merge conflict to be sure both of these workarounds are removed +#define GetVirtualPageSize() VIRTUAL_PAGE_SIZE + // // The mapping critical section guards access to the list // of currently mapped views. If a thread needs to access @@ -2012,14 +2019,14 @@ BOOL MAPGetRegionInfo(LPVOID lpAddress, real_map_sz = pView->NumberOfBytesToMap; #endif - MappedSize = ((real_map_sz-1) & ~VIRTUAL_PAGE_MASK) + VIRTUAL_PAGE_SIZE; + MappedSize = ALIGN_UP(real_map_sz, GetVirtualPageSize()); if ( real_map_addr <= lpAddress && (VOID *)((UINT_PTR)real_map_addr+MappedSize) > lpAddress ) { if (lpBuffer) { - SIZE_T regionSize = MappedSize + (UINT_PTR) real_map_addr - - ((UINT_PTR) lpAddress & ~VIRTUAL_PAGE_MASK); + SIZE_T regionSize = MappedSize + (UINT_PTR) real_map_addr - + ALIGN_DOWN((UINT_PTR)lpAddress, GetVirtualPageSize()); lpBuffer->BaseAddress = lpAddress; lpBuffer->AllocationProtect = 0; @@ -2241,7 +2248,9 @@ MAPmmapAndRecord( PAL_ERROR palError = NO_ERROR; LPVOID pvBaseAddress = NULL; - pvBaseAddress = mmap(addr, len, prot, flags, fd, offset); + off_t adjust = offset & (GetVirtualPageSize() - 1); + + pvBaseAddress = mmap(static_cast<char *>(addr) - adjust, len + adjust, prot, flags, fd, offset - adjust); if (MAP_FAILED == pvBaseAddress) { ERROR_(LOADER)( "mmap failed with code %d: %s.\n", errno, strerror( errno ) ); @@ -2368,14 +2377,6 @@ void * MAPMapPEFile(HANDLE hFile) goto done; } - //this code requires that the file alignment be the same as the page alignment - if (ntHeader.OptionalHeader.FileAlignment < VIRTUAL_PAGE_SIZE) - { - ERROR_(LOADER)( "Optional header file alignment is bad\n" ); - palError = ERROR_INVALID_PARAMETER; - goto done; - } - //This doesn't read the entire NT header (the optional header technically has a variable length. But I //don't need more directories. @@ -2416,7 +2417,7 @@ void * MAPMapPEFile(HANDLE hFile) { //if we're forcing relocs, create an anonymous mapping at the preferred base. Only create the //mapping if we can create it at the specified address. - pForceRelocBase = mmap( (void*)preferredBase, VIRTUAL_PAGE_SIZE, PROT_NONE, MAP_ANON|MAP_FIXED|MAP_PRIVATE, -1, 0 ); + pForceRelocBase = mmap( (void*)preferredBase, GetVirtualPageSize(), PROT_NONE, MAP_ANON|MAP_FIXED|MAP_PRIVATE, -1, 0 ); if (pForceRelocBase == MAP_FAILED) { TRACE_(LOADER)("Attempt to take preferred base of %p to force relocation failed\n", (void*)preferredBase); @@ -2445,7 +2446,7 @@ void * MAPMapPEFile(HANDLE hFile) // First try to reserve virtual memory using ExecutableAllcator. This allows all PE images to be // near each other and close to the coreclr library which also allows the runtime to generate // more efficient code (by avoiding usage of jump stubs). - loadedBase = ReserveMemoryFromExecutableAllocator(pThread, virtualSize); + loadedBase = ReserveMemoryFromExecutableAllocator(pThread, ALIGN_UP(virtualSize, GetVirtualPageSize())); if (loadedBase == NULL) { // MAC64 requires we pass MAP_SHARED (or MAP_PRIVATE) flags - otherwise, the call is failed. @@ -2468,7 +2469,7 @@ void * MAPMapPEFile(HANDLE hFile) if (forceRelocs) { _ASSERTE(((SIZE_T)loadedBase) != preferredBase); - munmap(pForceRelocBase, VIRTUAL_PAGE_SIZE); // now that we've forced relocation, let the original address mapping go + munmap(pForceRelocBase, GetVirtualPageSize()); // now that we've forced relocation, let the original address mapping go } if (((SIZE_T)loadedBase) != preferredBase) { @@ -2484,7 +2485,7 @@ void * MAPMapPEFile(HANDLE hFile) //separately. size_t headerSize; - headerSize = VIRTUAL_PAGE_SIZE; // if there are lots of sections, this could be wrong + headerSize = GetVirtualPageSize(); // if there are lots of sections, this could be wrong //first, map the PE header to the first page in the image. Get pointers to the section headers palError = MAPmmapAndRecord(pFileObject, loadedBase, @@ -2519,10 +2520,8 @@ void * MAPMapPEFile(HANDLE hFile) goto doneReleaseMappingCriticalSection; } - void* prevSectionBase; - prevSectionBase = loadedBase; // the first "section" for our purposes is the header - size_t prevSectionSizeInMemory; - prevSectionSizeInMemory = headerSize; + void* prevSectionEnd; + prevSectionEnd = (char*)loadedBase + headerSize; // the first "section" for our purposes is the header for (unsigned i = 0; i < numSections; ++i) { //for each section, map the section of the file to the correct virtual offset. Gather the @@ -2532,12 +2531,13 @@ void * MAPMapPEFile(HANDLE hFile) IMAGE_SECTION_HEADER ¤tHeader = firstSection[i]; void* sectionBase = (char*)loadedBase + currentHeader.VirtualAddress; + void* sectionBaseAligned = ALIGN_DOWN(sectionBase, GetVirtualPageSize()); // Validate the section header if ( (sectionBase < loadedBase) // Did computing the section base overflow? || ((char*)sectionBase + currentHeader.SizeOfRawData < (char*)sectionBase) // Does the section overflow? || ((char*)sectionBase + currentHeader.SizeOfRawData > (char*)loadedBase + virtualSize) // Does the section extend past the end of the image as the header stated? - || ((char*)prevSectionBase + prevSectionSizeInMemory > sectionBase) // Does this section overlap the previous one? + || (prevSectionEnd > sectionBase) // Does this section overlap the previous one? ) { ERROR_(LOADER)( "section %d is corrupt\n", i ); @@ -2552,13 +2552,12 @@ void * MAPMapPEFile(HANDLE hFile) } // Is there space between the previous section and this one? If so, add a PROT_NONE mapping to cover it. - if ((char*)prevSectionBase + prevSectionSizeInMemory < sectionBase) + if (prevSectionEnd < sectionBaseAligned) { - char* gapBase = (char*)prevSectionBase + prevSectionSizeInMemory; palError = MAPRecordMapping(pFileObject, loadedBase, - (void*)gapBase, - (char*)sectionBase - gapBase, + prevSectionEnd, + (char*)sectionBaseAligned - (char*)prevSectionEnd, PROT_NONE); if (NO_ERROR != palError) { @@ -2602,20 +2601,18 @@ void * MAPMapPEFile(HANDLE hFile) } #endif // _DEBUG - prevSectionBase = sectionBase; - prevSectionSizeInMemory = (currentHeader.SizeOfRawData + VIRTUAL_PAGE_MASK) & ~VIRTUAL_PAGE_MASK; // round up to page boundary + prevSectionEnd = ALIGN_UP((char*)sectionBase + currentHeader.SizeOfRawData, GetVirtualPageSize()); // round up to page boundary } // Is there space after the last section and before the end of the mapped image? If so, add a PROT_NONE mapping to cover it. char* imageEnd; imageEnd = (char*)loadedBase + virtualSize; // actually, points just after the mapped end - if ((char*)prevSectionBase + prevSectionSizeInMemory < imageEnd) + if (prevSectionEnd < imageEnd) { - char* gapBase = (char*)prevSectionBase + prevSectionSizeInMemory; palError = MAPRecordMapping(pFileObject, loadedBase, - (void*)gapBase, - imageEnd - gapBase, + prevSectionEnd, + (char*)imageEnd - (char*)prevSectionEnd, PROT_NONE); if (NO_ERROR != palError) { diff --git a/src/pal/src/map/virtual.cpp b/src/pal/src/map/virtual.cpp index 4a55de9891..7e00843b7a 100644 --- a/src/pal/src/map/virtual.cpp +++ b/src/pal/src/map/virtual.cpp @@ -92,6 +92,7 @@ namespace VirtualMemoryLogging Commit = 0x30, Decommit = 0x40, Release = 0x50, + Reset = 0x60, }; // Indicates that the attempted operation has failed @@ -810,6 +811,58 @@ static BOOL VIRTUALStoreAllocationInfo( /****** * + * VIRTUALResetMemory() - Helper function that resets the memory + * + * + */ +static LPVOID VIRTUALResetMemory( + IN CPalThread *pthrCurrent, /* Currently executing thread */ + IN LPVOID lpAddress, /* Region to reserve or commit */ + IN SIZE_T dwSize) /* Size of Region */ +{ + LPVOID pRetVal = NULL; + UINT_PTR StartBoundary; + SIZE_T MemSize; + + TRACE( "Resetting the memory now..\n"); + + StartBoundary = (UINT_PTR)lpAddress & ~VIRTUAL_PAGE_MASK; + // Add the sizes, and round down to the nearest page boundary. + MemSize = ( ((UINT_PTR)lpAddress + dwSize + VIRTUAL_PAGE_MASK) & ~VIRTUAL_PAGE_MASK ) - + StartBoundary; + + int st; +#if HAVE_MADV_FREE + // Try to use MADV_FREE if supported. It tells the kernel that the application doesn't + // need the pages in the range. Freeing the pages can be delayed until a memory pressure + // occurs. + st = madvise((LPVOID)StartBoundary, MemSize, MADV_FREE); + if (st != 0) +#endif + { + // In case the MADV_FREE is not supported, use MADV_DONTNEED + st = madvise((LPVOID)StartBoundary, MemSize, MADV_DONTNEED); + } + + if (st == 0) + { + pRetVal = lpAddress; + } + + LogVaOperation( + VirtualMemoryLogging::VirtualOperation::Reset, + lpAddress, + dwSize, + 0, + 0, + pRetVal, + pRetVal != NULL); + + return pRetVal; +} + +/****** + * * VIRTUALReserveMemory() - Helper function that actually reserves the memory. * * NOTE: I call SetLastError in here, because many different error states @@ -837,8 +890,6 @@ static LPVOID VIRTUALReserveMemory( MemSize = ( ((UINT_PTR)lpAddress + dwSize + VIRTUAL_PAGE_MASK) & ~VIRTUAL_PAGE_MASK ) - StartBoundary; - InternalEnterCriticalSection(pthrCurrent, &virtual_critsec); - // If this is a request for special executable (JIT'ed) memory then, first of all, // try to get memory from the executable memory allocator to satisfy the request. if (((flAllocationType & MEM_RESERVE_EXECUTABLE) != 0) && (lpAddress == NULL)) @@ -881,7 +932,6 @@ static LPVOID VIRTUALReserveMemory( pRetVal, pRetVal != NULL); - InternalLeaveCriticalSection(pthrCurrent, &virtual_critsec); return pRetVal; } @@ -1211,7 +1261,7 @@ VirtualAlloc( } /* Test for un-supported flags. */ - if ( ( flAllocationType & ~( MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN | MEM_RESERVE_EXECUTABLE ) ) != 0 ) + if ( ( flAllocationType & ~( MEM_COMMIT | MEM_RESERVE | MEM_RESET | MEM_TOP_DOWN | MEM_RESERVE_EXECUTABLE ) ) != 0 ) { ASSERT( "flAllocationType can be one, or any combination of MEM_COMMIT, \ MEM_RESERVE, MEM_TOP_DOWN, or MEM_RESERVE_EXECUTABLE.\n" ); @@ -1240,6 +1290,26 @@ VirtualAlloc( NULL, TRUE); + if ( flAllocationType & MEM_RESET ) + { + if ( flAllocationType != MEM_RESET ) + { + ASSERT( "MEM_RESET cannot be used with any other allocation flags in flAllocationType.\n" ); + pthrCurrent->SetLastError( ERROR_INVALID_PARAMETER ); + goto done; + } + + InternalEnterCriticalSection(pthrCurrent, &virtual_critsec); + pRetVal = VIRTUALResetMemory( pthrCurrent, lpAddress, dwSize ); + InternalLeaveCriticalSection(pthrCurrent, &virtual_critsec); + + if ( !pRetVal ) + { + /* Error messages are already displayed, just leave. */ + goto done; + } + } + if ( flAllocationType & MEM_RESERVE ) { InternalEnterCriticalSection(pthrCurrent, &virtual_critsec); @@ -1280,7 +1350,6 @@ done: return pRetVal; } - /*++ Function: VirtualFree diff --git a/src/pal/src/misc/sysinfo.cpp b/src/pal/src/misc/sysinfo.cpp index fff051818f..70fe3e65d2 100644 --- a/src/pal/src/misc/sysinfo.cpp +++ b/src/pal/src/misc/sysinfo.cpp @@ -94,6 +94,39 @@ SET_DEFAULT_DEBUG_CHANNEL(MISC); #endif #endif // __APPLE__ +/*++ +Function: + GetNumberOfProcessors + +Return number of processors available for the current process +--*/ +int GetNumberOfProcessors() +{ + int nrcpus = 0; + +#if HAVE_SYSCONF + nrcpus = sysconf(_SC_NPROCESSORS_ONLN); + if (nrcpus < 1) + { + ASSERT("sysconf failed for _SC_NPROCESSORS_ONLN (%d)\n", errno); + } +#elif HAVE_SYSCTL + int rc; + size_t sz; + int mib[2]; + + sz = sizeof(nrcpus); + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + rc = sysctl(mib, 2, &nrcpus, &sz, NULL, 0); + if (rc != 0) + { + ASSERT("sysctl failed for HW_NCPU (%d)\n", errno); + } +#endif // HAVE_SYSCONF + + return nrcpus; +} /*++ Function: @@ -137,27 +170,7 @@ GetSystemInfo( lpSystemInfo->dwPageSize = pagesize; lpSystemInfo->dwActiveProcessorMask_PAL_Undefined = 0; -#if HAVE_SYSCONF - nrcpus = sysconf(_SC_NPROCESSORS_ONLN); - if (nrcpus < 1) - { - ASSERT("sysconf failed for _SC_NPROCESSORS_ONLN (%d)\n", errno); - } -#elif HAVE_SYSCTL - int rc; - size_t sz; - int mib[2]; - - sz = sizeof(nrcpus); - mib[0] = CTL_HW; - mib[1] = HW_NCPU; - rc = sysctl(mib, 2, &nrcpus, &sz, NULL, 0); - if (rc != 0) - { - ASSERT("sysctl failed for HW_NCPU (%d)\n", errno); - } -#endif // HAVE_SYSCONF - + nrcpus = GetNumberOfProcessors(); TRACE("dwNumberOfProcessors=%d\n", nrcpus); lpSystemInfo->dwNumberOfProcessors = nrcpus; diff --git a/src/pal/src/numa/numa.cpp b/src/pal/src/numa/numa.cpp new file mode 100644 index 0000000000..549c10a71f --- /dev/null +++ b/src/pal/src/numa/numa.cpp @@ -0,0 +1,692 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*++ + + + +Module Name: + + numa.cpp + +Abstract: + + Implementation of NUMA related APIs + +--*/ + +#include "pal/dbgmsg.h" +SET_DEFAULT_DEBUG_CHANNEL(NUMA); + +#include "pal/palinternal.h" +#include "pal/dbgmsg.h" +#include "pal/numa.h" +#include "pal/corunix.hpp" +#include "pal/thread.hpp" + +#if HAVE_NUMA_H +#include <numa.h> +#include <numaif.h> +#endif + +#if HAVE_PTHREAD_NP_H +#include <pthread_np.h> +#endif + +#include <pthread.h> + +using namespace CorUnix; + +#if HAVE_CPUSET_T +typedef cpuset_t cpu_set_t; +#endif + +int GetNumberOfProcessors(); + +// CPU affinity descriptor +struct CpuAffinity +{ + // NUMA node + BYTE Node; + // CPU number relative to the group the CPU is in + BYTE Number; + // CPU group + WORD Group; +}; + +// Array mapping global CPU index to its affinity +CpuAffinity *g_cpuToAffinity = NULL; + +// Array mapping CPU group and index in the group to the global CPU index +short *g_groupAndIndexToCpu = NULL; +// Array mapping CPU group to the corresponding affinity mask of the CPUs in the group +KAFFINITY *g_groupToCpuMask = NULL; +// Array mapping CPU group to the number of processors in the group +BYTE *g_groupToCpuCount = NULL; + +// Total number of processors in the system +int g_cpuCount = 0; +// Total number of CPU groups +int g_groupCount = 0; +// The highest NUMA node available +int g_highestNumaNode = 0; + +static const int MaxCpusPerGroup = 8 * sizeof(KAFFINITY); +static const WORD NO_GROUP = 0xffff; + +/*++ +Function: + AllocateLookupArrays + +Allocate CPU and group lookup arrays +--*/ +VOID +AllocateLookupArrays() +{ + g_groupAndIndexToCpu = (short*)malloc(g_groupCount * MaxCpusPerGroup * sizeof(short)); + g_cpuToAffinity = (CpuAffinity*)malloc(g_cpuCount * sizeof(CpuAffinity)); + g_groupToCpuMask = (KAFFINITY*)malloc(g_groupCount * sizeof(KAFFINITY)); + g_groupToCpuCount = (BYTE*)malloc(g_groupCount * sizeof(BYTE)); + + memset(g_groupAndIndexToCpu, 0xff, g_groupCount * MaxCpusPerGroup * sizeof(short)); + memset(g_cpuToAffinity, 0xff, g_cpuCount * sizeof(CpuAffinity)); + memset(g_groupToCpuMask, 0, g_groupCount * sizeof(KAFFINITY)); + memset(g_groupToCpuCount, 0, g_groupCount * sizeof(BYTE)); +} + +/*++ +Function: + FreeLookupArrays + +Free CPU and group lookup arrays +--*/ +VOID +FreeLookupArrays() +{ + free(g_groupAndIndexToCpu); + free(g_cpuToAffinity); + free(g_groupToCpuMask); + free(g_groupToCpuCount); + + g_groupAndIndexToCpu = NULL; + g_cpuToAffinity = NULL; + g_groupToCpuMask = NULL; + g_groupToCpuCount = NULL; +} + +/*++ +Function: + GetFullAffinityMask + +Get affinity mask for the specified number of processors with all +the processors enabled. +--*/ +KAFFINITY GetFullAffinityMask(int cpuCount) +{ + return ((KAFFINITY)1 << (cpuCount)) - 1; +} + +/*++ +Function: + NUMASupportInitialize + +Initialize data structures for getting and setting thread affinities to processors and +querying NUMA related processor information. +On systems with no NUMA support, it behaves as if there was a single NUMA node with +a single group of processors. +--*/ +BOOL +NUMASupportInitialize() +{ +#if HAVE_NUMA_H + if (numa_available() != -1) + { + struct bitmask *mask = numa_allocate_cpumask(); + int numaNodesCount = numa_max_node() + 1; + + g_cpuCount = numa_num_possible_cpus(); + g_groupCount = 0; + + for (int i = 0; i < numaNodesCount; i++) + { + int st = numa_node_to_cpus(i, mask); + // The only failure that can happen is that the mask is not large enough + // but that cannot happen since the mask was allocated by numa_allocate_cpumask + _ASSERTE(st == 0); + unsigned int nodeCpuCount = numa_bitmask_weight(mask); + unsigned int nodeGroupCount = (nodeCpuCount + MaxCpusPerGroup - 1) / MaxCpusPerGroup; + g_groupCount += nodeGroupCount; + } + + AllocateLookupArrays(); + + WORD currentGroup = 0; + int currentGroupCpus = 0; + + for (int i = 0; i < numaNodesCount; i++) + { + int st = numa_node_to_cpus(i, mask); + // The only failure that can happen is that the mask is not large enough + // but that cannot happen since the mask was allocated by numa_allocate_cpumask + _ASSERTE(st == 0); + unsigned int nodeCpuCount = numa_bitmask_weight(mask); + unsigned int nodeGroupCount = (nodeCpuCount + MaxCpusPerGroup - 1) / MaxCpusPerGroup; + for (int j = 0; j < g_cpuCount; j++) + { + if (numa_bitmask_isbitset(mask, j)) + { + if (currentGroupCpus == MaxCpusPerGroup) + { + g_groupToCpuCount[currentGroup] = MaxCpusPerGroup; + g_groupToCpuMask[currentGroup] = GetFullAffinityMask(MaxCpusPerGroup); + currentGroupCpus = 0; + currentGroup++; + } + g_cpuToAffinity[j].Node = i; + g_cpuToAffinity[j].Group = currentGroup; + g_cpuToAffinity[j].Number = currentGroupCpus; + g_groupAndIndexToCpu[currentGroup * MaxCpusPerGroup + currentGroupCpus] = j; + currentGroupCpus++; + } + } + + if (currentGroupCpus != 0) + { + g_groupToCpuCount[currentGroup] = currentGroupCpus; + g_groupToCpuMask[currentGroup] = GetFullAffinityMask(currentGroupCpus); + currentGroupCpus = 0; + currentGroup++; + } + } + + numa_free_cpumask(mask); + + g_highestNumaNode = numa_max_node(); + } + else +#endif // HAVE_NUMA_H + { + // No NUMA + g_cpuCount = GetNumberOfProcessors(); + g_groupCount = 1; + g_highestNumaNode = 0; + + AllocateLookupArrays(); + } + + return TRUE; +} + +/*++ +Function: + NUMASupportCleanup + +Cleanup of the NUMA support data structures +--*/ +VOID +NUMASupportCleanup() +{ + FreeLookupArrays(); +} + +/*++ +Function: + GetNumaHighestNodeNumber + +See MSDN doc. +--*/ +BOOL +PALAPI +GetNumaHighestNodeNumber( + OUT PULONG HighestNodeNumber +) +{ + PERF_ENTRY(GetNumaHighestNodeNumber); + ENTRY("GetNumaHighestNodeNumber(HighestNodeNumber=%p)\n", HighestNodeNumber); + *HighestNodeNumber = (ULONG)g_highestNumaNode; + + BOOL success = TRUE; + + LOGEXIT("GetNumaHighestNodeNumber returns BOOL %d\n", success); + PERF_EXIT(GetNumaHighestNodeNumber); + + return success; +} + +/*++ +Function: + GetNumaProcessorNodeEx + +See MSDN doc. +--*/ +BOOL +PALAPI +GetNumaProcessorNodeEx( + IN PPROCESSOR_NUMBER Processor, + OUT PUSHORT NodeNumber +) +{ + PERF_ENTRY(GetNumaProcessorNodeEx); + ENTRY("GetNumaProcessorNodeEx(Processor=%p, NodeNumber=%p)\n", Processor, NodeNumber); + + BOOL success = FALSE; + + if ((Processor->Group < g_groupCount) && + (Processor->Number < MaxCpusPerGroup) && + (Processor->Reserved == 0)) + { + short cpu = g_groupAndIndexToCpu[Processor->Group * MaxCpusPerGroup + Processor->Number]; + if (cpu != -1) + { + *NodeNumber = g_cpuToAffinity[cpu].Node; + success = TRUE; + } + } + + if (!success) + { + *NodeNumber = 0xffff; + SetLastError(ERROR_INVALID_PARAMETER); + } + + LOGEXIT("GetNumaProcessorNodeEx returns BOOL %d\n", success); + PERF_EXIT(GetNumaProcessorNodeEx); + + return success; +} + +/*++ +Function: + GetLogicalProcessorInformationEx + +See MSDN doc. +--*/ +BOOL +PALAPI +GetLogicalProcessorInformationEx( + IN LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType, + OUT OPTIONAL PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX Buffer, + IN OUT PDWORD ReturnedLength +) +{ + PERF_ENTRY(GetLogicalProcessorInformationEx); + ENTRY("GetLogicalProcessorInformationEx(RelationshipType=%d, Buffer=%p, ReturnedLength=%p)\n", RelationshipType, Buffer, ReturnedLength); + + BOOL success = FALSE; + + if (RelationshipType == RelationGroup) + { + size_t requiredSize = __builtin_offsetof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, Group); + requiredSize += __builtin_offsetof(GROUP_RELATIONSHIP, GroupInfo); + requiredSize += g_groupCount * sizeof(PROCESSOR_GROUP_INFO); + + if (*ReturnedLength >= requiredSize) + { + Buffer->Relationship = RelationGroup; + Buffer->Size = requiredSize; + Buffer->Group.MaximumGroupCount = g_groupCount; + Buffer->Group.ActiveGroupCount = g_groupCount; + for (int i = 0; i < g_groupCount; i++) + { + Buffer->Group.GroupInfo[i].MaximumProcessorCount = MaxCpusPerGroup; + Buffer->Group.GroupInfo[i].ActiveProcessorCount = g_groupToCpuCount[i]; + Buffer->Group.GroupInfo[i].ActiveProcessorMask = g_groupToCpuMask[i]; + } + + success = TRUE; + } + else + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + } + + *ReturnedLength = requiredSize; + } + else + { + // We only support the group relationship + SetLastError(ERROR_INVALID_PARAMETER); + } + + LOGEXIT("GetLogicalProcessorInformationEx returns BOOL %d\n", success); + PERF_EXIT(GetLogicalProcessorInformationEx); + + return success; +} + +/*++ +Function: + GetThreadGroupAffinityInternal + +Get the group affinity for the specified pthread +--*/ +BOOL +GetThreadGroupAffinityInternal( + IN pthread_t thread, + OUT PGROUP_AFFINITY GroupAffinity +) +{ + BOOL success = FALSE; + +#if HAVE_PTHREAD_GETAFFINITY_NP + cpu_set_t cpuSet; + + int st = pthread_getaffinity_np(thread, sizeof(cpu_set_t), &cpuSet); + + if (st == 0) + { + WORD group = NO_GROUP; + KAFFINITY mask = 0; + + for (int i = 0; i < g_cpuCount; i++) + { + if (CPU_ISSET(i, &cpuSet)) + { + WORD g = g_cpuToAffinity[i].Group; + // Unless the thread affinity was already set by SetThreadGroupAffinity, it is possible that + // the current thread has affinity with processors from multiple groups. So we report just the + // first group we find. + if (group == NO_GROUP || g == group) + { + group = g; + mask |= ((KAFFINITY)1) << g_cpuToAffinity[i].Number; + } + } + } + + GroupAffinity->Group = group; + GroupAffinity->Mask = mask; + success = TRUE; + } + else + { + SetLastError(ERROR_GEN_FAILURE); + } +#else // HAVE_PTHREAD_GETAFFINITY_NP + // There is no API to manage thread affinity, so let's return a group affinity + // with all the CPUs on the system. + GroupAffinity->Group = 0; + GroupAffinity->Mask = GetFullAffinityMask(g_cpuCount); + success = TRUE; +#endif // HAVE_PTHREAD_GETAFFINITY_NP + + return success; +} + +/*++ +Function: + GetThreadGroupAffinity + +See MSDN doc. +--*/ +BOOL +PALAPI +GetThreadGroupAffinity( + IN HANDLE hThread, + OUT PGROUP_AFFINITY GroupAffinity +) +{ + PERF_ENTRY(GetThreadGroupAffinity); + ENTRY("GetThreadGroupAffinity(hThread=%p, GroupAffinity=%p)\n", hThread, GroupAffinity); + + CPalThread *palThread = InternalGetCurrentThread(); + + BOOL success = GetThreadGroupAffinityInternal(palThread->GetPThreadSelf(), GroupAffinity); + + LOGEXIT("GetThreadGroupAffinity returns BOOL %d\n", success); + PERF_EXIT(GetThreadGroupAffinity); + + return success; +} + + +/*++ +Function: + SetThreadGroupAffinity + +See MSDN doc. +--*/ +BOOL +PALAPI +SetThreadGroupAffinity( + IN HANDLE hThread, + IN const GROUP_AFFINITY *GroupAffinity, + OUT OPTIONAL PGROUP_AFFINITY PreviousGroupAffinity +) +{ + PERF_ENTRY(SetThreadGroupAffinity); + ENTRY("SetThreadGroupAffinity(hThread=%p, GroupAffinity=%p, PreviousGroupAffinity=%p)\n", hThread, GroupAffinity, PreviousGroupAffinity); + + CPalThread *palThread = InternalGetCurrentThread(); + + pthread_t thread = palThread->GetPThreadSelf(); + + if (PreviousGroupAffinity != NULL) + { + GetThreadGroupAffinityInternal(thread, PreviousGroupAffinity); + } + +#if HAVE_PTHREAD_GETAFFINITY_NP + int groupStartIndex = GroupAffinity->Group * MaxCpusPerGroup; + KAFFINITY mask = 1; + cpu_set_t cpuSet; + CPU_ZERO(&cpuSet); + + for (int i = 0; i < MaxCpusPerGroup; i++, mask <<= 1) + { + if (GroupAffinity->Mask & mask) + { + int cpu = g_groupAndIndexToCpu[groupStartIndex + i]; + if (cpu != -1) + { + CPU_SET(cpu, &cpuSet); + } + } + } + + int st = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuSet); + + if (st == -1) + { + switch (errno) + { + case EINVAL: + // There is no processor in the mask that is allowed to execute the process + SetLastError(ERROR_INVALID_PARAMETER); + break; + case EPERM: + SetLastError(ERROR_ACCESS_DENIED); + break; + default: + SetLastError(ERROR_GEN_FAILURE); + break; + } + } + + BOOL success = (st == 0); +#else // HAVE_PTHREAD_GETAFFINITY_NP + // There is no API to manage thread affinity, so let's ignore the request + BOOL success = TRUE; +#endif // HAVE_PTHREAD_GETAFFINITY_NP + + LOGEXIT("SetThreadGroupAffinity returns BOOL %d\n", success); + PERF_EXIT(SetThreadGroupAffinity); + + return success; +} + +/*++ +Function: + GetCurrentProcessorNumberEx + +See MSDN doc. +--*/ +VOID +PALAPI +GetCurrentProcessorNumberEx( + OUT PPROCESSOR_NUMBER ProcNumber +) +{ + PERF_ENTRY(GetCurrentProcessorNumberEx); + ENTRY("GetCurrentProcessorNumberEx(ProcNumber=%p\n", ProcNumber); + + DWORD cpu = GetCurrentProcessorNumber(); + _ASSERTE(cpu < g_cpuCount); + ProcNumber->Group = g_cpuToAffinity[cpu].Group; + ProcNumber->Number = g_cpuToAffinity[cpu].Number; + + LOGEXIT("GetCurrentProcessorNumberEx\n"); + PERF_EXIT(GetCurrentProcessorNumberEx); +} + +/*++ +Function: + GetProcessAffinityMask + +See MSDN doc. +--*/ +BOOL +PALAPI +GetProcessAffinityMask( + IN HANDLE hProcess, + OUT PDWORD_PTR lpProcessAffinityMask, + OUT PDWORD_PTR lpSystemAffinityMask +) +{ + PERF_ENTRY(GetProcessAffinityMask); + ENTRY("GetProcessAffinityMask(hProcess=%p, lpProcessAffinityMask=%p, lpSystemAffinityMask=%p\n", hProcess, lpProcessAffinityMask, lpSystemAffinityMask); + + BOOL success = FALSE; + + if (hProcess == GetCurrentProcess()) + { + DWORD_PTR systemMask = GetFullAffinityMask(g_cpuCount); + +#if HAVE_SCHED_GETAFFINITY + int pid = getpid(); + cpu_set_t cpuSet; + int st = sched_getaffinity(pid, sizeof(cpu_set_t), &cpuSet); + if (st == 0) + { + WORD group = NO_GROUP; + DWORD_PTR processMask = 0; + + for (int i = 0; i < g_cpuCount; i++) + { + if (CPU_ISSET(i, &cpuSet)) + { + WORD g = g_cpuToAffinity[i].Group; + if (group == NO_GROUP || g == group) + { + group = g; + processMask |= ((DWORD_PTR)1) << g_cpuToAffinity[i].Number; + } + else + { + // The process has affinity in more than one group, in such case + // the function needs to return zero in both masks. + processMask = 0; + systemMask = 0; + group = NO_GROUP; + break; + } + } + } + + success = TRUE; + + *lpProcessAffinityMask = processMask; + *lpSystemAffinityMask = systemMask; + } + else + { + // We should not get any of the errors that the sched_getaffinity can return since none + // of them applies for the current thread, so this is an unexpected kind of failure. + SetLastError(ERROR_GEN_FAILURE); + } +#else // HAVE_SCHED_GETAFFINITY + // There is no API to manage thread affinity, so let's return both affinity masks + // with all the CPUs on the system set. + *lpSystemAffinityMask = systemMask; + *lpProcessAffinityMask = systemMask; + + success = TRUE; +#endif // HAVE_SCHED_GETAFFINITY + } + else + { + // PAL supports getting affinity mask for the current process only + SetLastError(ERROR_INVALID_PARAMETER); + } + + LOGEXIT("GetProcessAffinityMask returns BOOL %d\n", success); + PERF_EXIT(GetProcessAffinityMask); + + return success; +} + +/*++ +Function: + VirtualAllocExNuma + +See MSDN doc. +--*/ +LPVOID +PALAPI +VirtualAllocExNuma( + IN HANDLE hProcess, + IN OPTIONAL LPVOID lpAddress, + IN SIZE_T dwSize, + IN DWORD flAllocationType, + IN DWORD flProtect, + IN DWORD nndPreferred +) +{ + PERF_ENTRY(VirtualAllocExNuma); + ENTRY("VirtualAllocExNuma(hProcess=%p, lpAddress=%p, dwSize=%u, flAllocationType=%#x, flProtect=%#x, nndPreferred=%d\n", + hProcess, lpAddress, dwSize, flAllocationType, flProtect, nndPreferred); + + LPVOID result = NULL; + + if (hProcess == GetCurrentProcess()) + { + if (nndPreferred <= g_highestNumaNode) + { + result = VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect); +#if HAVE_NUMA_H + if (result != NULL) + { + int nodeMaskLength = (g_highestNumaNode + 1 + sizeof(unsigned long) - 1) / sizeof(unsigned long); + unsigned long *nodeMask = new unsigned long[nodeMaskLength]; + + memset(nodeMask, 0, nodeMaskLength); + + int index = nndPreferred / sizeof(unsigned long); + int mask = ((unsigned long)1) << (nndPreferred & (sizeof(unsigned long) - 1)); + nodeMask[index] = mask; + + int st = mbind(result, dwSize, MPOL_PREFERRED, nodeMask, g_highestNumaNode, 0); + free(nodeMask); + _ASSERTE(st == 0); + // If the mbind fails, we still return the allocated memory since the nndPreferred is just a hint + } +#endif // HAVE_NUMA_H + } + else + { + // The specified node number is larger than the maximum available one + SetLastError(ERROR_INVALID_PARAMETER); + } + } + else + { + // PAL supports allocating from the current process virtual space only + SetLastError(ERROR_INVALID_PARAMETER); + } + + LOGEXIT("VirtualAllocExNuma returns %p\n", result); + PERF_EXIT(VirtualAllocExNuma); + + return result; +} diff --git a/src/pal/src/synchmgr/synchmanager.cpp b/src/pal/src/synchmgr/synchmanager.cpp index 3aec140474..d836a177bb 100644 --- a/src/pal/src/synchmgr/synchmanager.cpp +++ b/src/pal/src/synchmgr/synchmanager.cpp @@ -450,7 +450,7 @@ namespace CorUnix if (dwTimeout != INFINITE) { // Calculate absolute timeout - palErr = GetAbsoluteTimeout(dwTimeout, &tsAbsTmo); + palErr = GetAbsoluteTimeout(dwTimeout, &tsAbsTmo, /*fPreferMonotonicClock*/ TRUE); if (NO_ERROR != palErr) { ERROR("Failed to convert timeout to absolute timeout\n"); @@ -1572,7 +1572,7 @@ namespace CorUnix ptnwdWorkerThreadNativeData = &pSynchManager->m_pthrWorker->synchronizationInfo.m_tnwdNativeData; - palErr = GetAbsoluteTimeout(WorkerThreadTerminationTimeout, &tsAbsTmo); + palErr = GetAbsoluteTimeout(WorkerThreadTerminationTimeout, &tsAbsTmo, /*fPreferMonotonicClock*/ TRUE); if (NO_ERROR != palErr) { ERROR("Failed to convert timeout to absolute timeout\n"); @@ -4078,6 +4078,9 @@ namespace CorUnix int iRet; const int MaxUnavailableResourceRetries = 10; int iEagains; + pthread_condattr_t attrs; + pthread_condattr_t *attrsPtr = nullptr; + m_shridWaitAwakened = RawSharedObjectAlloc(sizeof(DWORD), DefaultSharedPool); if (NULLSharedID == m_shridWaitAwakened) @@ -4096,6 +4099,36 @@ namespace CorUnix VolatileStore<DWORD>(pdwWaitState, TWS_ACTIVE); m_tsThreadState = TS_STARTING; +#if HAVE_CLOCK_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK + attrsPtr = &attrs; + iRet = pthread_condattr_init(&attrs); + if (0 != iRet) + { + ERROR("Failed to initialize thread synchronization condition attribute " + "[error=%d (%s)]\n", iRet, strerror(iRet)); + if (ENOMEM == iRet) + { + palErr = ERROR_NOT_ENOUGH_MEMORY; + } + else + { + palErr = ERROR_INTERNAL_ERROR; + } + goto IPrC_exit; + } + + // Ensure that the pthread_cond_timedwait will use CLOCK_MONOTONIC + iRet = pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC); + if (0 != iRet) + { + ERROR("Failed set thread synchronization condition timed wait clock " + "[error=%d (%s)]\n", iRet, strerror(iRet)); + palErr = ERROR_INTERNAL_ERROR; + pthread_condattr_destroy(&attrs); + goto IPrC_exit; + } +#endif // HAVE_CLOCK_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK + iEagains = 0; Mutex_retry: iRet = pthread_mutex_init(&m_tnwdNativeData.mutex, NULL); @@ -4121,7 +4154,9 @@ namespace CorUnix iEagains = 0; Cond_retry: - iRet = pthread_cond_init(&m_tnwdNativeData.cond, NULL); + + iRet = pthread_cond_init(&m_tnwdNativeData.cond, attrsPtr); + if (0 != iRet) { ERROR("Failed creating thread synchronization condition " @@ -4146,6 +4181,10 @@ namespace CorUnix m_tnwdNativeData.fInitialized = true; IPrC_exit: + if (attrsPtr != nullptr) + { + pthread_condattr_destroy(attrsPtr); + } if (NO_ERROR != palErr) { m_tsThreadState = TS_FAILED; @@ -4515,27 +4554,37 @@ namespace CorUnix Converts a relative timeout to an absolute one. --*/ - PAL_ERROR CPalSynchronizationManager::GetAbsoluteTimeout(DWORD dwTimeout, struct timespec * ptsAbsTmo) + PAL_ERROR CPalSynchronizationManager::GetAbsoluteTimeout(DWORD dwTimeout, struct timespec * ptsAbsTmo, BOOL fPreferMonotonicClock) { PAL_ERROR palErr = NO_ERROR; int iRet; -#if HAVE_WORKING_CLOCK_GETTIME - // Not every platform implements a (working) clock_gettime - iRet = clock_gettime(CLOCK_REALTIME, ptsAbsTmo); -#elif HAVE_WORKING_GETTIMEOFDAY - // Not every platform implements a (working) gettimeofday - struct timeval tv; - iRet = gettimeofday(&tv, NULL); - if (0 == iRet) +#if HAVE_CLOCK_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK + if (fPreferMonotonicClock) { - ptsAbsTmo->tv_sec = tv.tv_sec; - ptsAbsTmo->tv_nsec = tv.tv_usec * tccMicroSecondsToNanoSeconds; + iRet = clock_gettime(CLOCK_MONOTONIC, ptsAbsTmo); } + else + { +#endif +#if HAVE_WORKING_CLOCK_GETTIME + // Not every platform implements a (working) clock_gettime + iRet = clock_gettime(CLOCK_REALTIME, ptsAbsTmo); +#elif HAVE_WORKING_GETTIMEOFDAY + // Not every platform implements a (working) gettimeofday + struct timeval tv; + iRet = gettimeofday(&tv, NULL); + if (0 == iRet) + { + ptsAbsTmo->tv_sec = tv.tv_sec; + ptsAbsTmo->tv_nsec = tv.tv_usec * tccMicroSecondsToNanoSeconds; + } #else - #error "Don't know how to get hi-res current time on this platform" + #error "Don't know how to get hi-res current time on this platform" #endif // HAVE_WORKING_CLOCK_GETTIME, HAVE_WORKING_GETTIMEOFDAY - +#if HAVE_CLOCK_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK + } +#endif if (0 == iRet) { ptsAbsTmo->tv_sec += dwTimeout / tccSecondsToMillieSeconds; diff --git a/src/pal/src/synchmgr/synchmanager.hpp b/src/pal/src/synchmgr/synchmanager.hpp index fdef82e936..883d5b8b61 100644 --- a/src/pal/src/synchmgr/synchmanager.hpp +++ b/src/pal/src/synchmgr/synchmanager.hpp @@ -1015,7 +1015,8 @@ namespace CorUnix static PAL_ERROR GetAbsoluteTimeout( DWORD dwTimeout, - struct timespec * ptsAbsTmo); + struct timespec * ptsAbsTmo, + BOOL fPreferMonotonicClock); }; } diff --git a/src/pal/src/synchobj/mutex.cpp b/src/pal/src/synchobj/mutex.cpp index d929eaa472..692f5e2ade 100644 --- a/src/pal/src/synchobj/mutex.cpp +++ b/src/pal/src/synchobj/mutex.cpp @@ -859,7 +859,7 @@ MutexTryAcquireLockResult MutexHelpers::TryAcquireLock(pthread_mutex_t *mutex, D default: { struct timespec timeoutTime; - PAL_ERROR palError = CPalSynchronizationManager::GetAbsoluteTimeout(timeoutMilliseconds, &timeoutTime); + PAL_ERROR palError = CPalSynchronizationManager::GetAbsoluteTimeout(timeoutMilliseconds, &timeoutTime, /*fPreferMonotonicClock*/ FALSE); _ASSERTE(palError == NO_ERROR); lockResult = pthread_mutex_timedlock(mutex, &timeoutTime); break; diff --git a/src/pal/src/thread/context.cpp b/src/pal/src/thread/context.cpp index 04a6fe5aaf..cc794d5f69 100644 --- a/src/pal/src/thread/context.cpp +++ b/src/pal/src/thread/context.cpp @@ -506,8 +506,6 @@ void CONTEXTFromNativeContext(const native_context_t *native, LPCONTEXT lpContex // although the pc value retrived from native context might not have set the least bit. // This becomes especially problematic if the context is on the JIT_WRITEBARRIER. lpContext->Pc |= 0x1; -#elif defined(_X86_) - lpContext->ResumeEsp = MCREG_Esp(native->uc_mcontext); #endif } @@ -978,7 +976,6 @@ CONTEXT_GetThreadContextFromThreadState( lpContext->Esi = pState->esi; lpContext->Ebp = pState->ebp; lpContext->Esp = pState->esp; - lpContext->ResumeEsp = pState->esp; lpContext->SegSs = pState->ss; lpContext->EFlags = pState->eflags; lpContext->Eip = pState->eip; diff --git a/src/pal/src/thread/process.cpp b/src/pal/src/thread/process.cpp index 050665ce7c..2a93d3c57d 100644 --- a/src/pal/src/thread/process.cpp +++ b/src/pal/src/thread/process.cpp @@ -49,12 +49,17 @@ SET_DEFAULT_DEBUG_CHANNEL(PROCESS); // some headers have code with asserts, so d #include <sys/types.h> #include <sys/stat.h> #include <signal.h> +#if HAVE_PRCTL_H +#include <sys/prctl.h> +#include <sys/syscall.h> +#endif #include <sys/wait.h> #include <sys/time.h> #include <sys/resource.h> #include <debugmacrosext.h> #include <semaphore.h> #include <stdint.h> +#include <dlfcn.h> #ifdef __APPLE__ #include <sys/sysctl.h> @@ -67,6 +72,8 @@ SET_DEFAULT_DEBUG_CHANNEL(PROCESS); // some headers have code with asserts, so d #include <kvm.h> #endif +extern char *g_szCoreCLRPath; + using namespace CorUnix; CObjectType CorUnix::otProcess( @@ -87,18 +94,6 @@ CObjectType CorUnix::otProcess( CObjectType::NoOwner ); -static -DWORD -PALAPI -StartupHelperThread( - LPVOID p); - -static -BOOL -GetProcessIdDisambiguationKey( - IN DWORD processId, - OUT UINT64 *disambiguationKey); - // // Helper memory page used by the FlushProcessWriteBuffers // @@ -153,6 +148,9 @@ DWORD gSID = (DWORD) -1; // Function to call during PAL/process shutdown/abort Volatile<PSHUTDOWN_CALLBACK> g_shutdownCallback = nullptr; +// Crash dump generating program arguments. Initialized in PROCAbortInitialize(). +char* g_argvCreateDump[8] = { nullptr }; + // // Key used for associating CPalThread's with the underlying pthread // (through pthread_setspecific) @@ -172,22 +170,30 @@ enum FILETYPE FILE_DIR /*Directory*/ }; +static +DWORD +PALAPI +StartupHelperThread( + LPVOID p); + +static +BOOL +GetProcessIdDisambiguationKey( + IN DWORD processId, + OUT UINT64 *disambiguationKey); + PAL_ERROR PROCGetProcessStatus( CPalThread *pThread, HANDLE hProcess, PROCESS_STATE *pps, - DWORD *pdwExitCode - ); + DWORD *pdwExitCode); -static BOOL getFileName(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, - PathCharString& lpFileName); -static char ** buildArgv(LPCWSTR lpCommandLine, PathCharString& lpAppPath, - UINT *pnArg, BOOL prependLoader); +static BOOL getFileName(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, PathCharString& lpFileName); +static char ** buildArgv(LPCWSTR lpCommandLine, PathCharString& lpAppPath, UINT *pnArg, BOOL prependLoader); static BOOL getPath(PathCharString& lpFileName, PathCharString& lpPathFileName); static int checkFileType(LPCSTR lpFileName); -static BOOL PROCEndProcess(HANDLE hProcess, UINT uExitCode, - BOOL bTerminateUnconditionally); +static BOOL PROCEndProcess(HANDLE hProcess, UINT uExitCode, BOOL bTerminateUnconditionally); ProcessModules *GetProcessModulesFromHandle(IN HANDLE hProcess, OUT LPDWORD lpCount); ProcessModules *CreateProcessModules(IN DWORD dwProcessId, OUT LPDWORD lpCount); @@ -1382,7 +1388,7 @@ static BOOL PROCEndProcess(HANDLE hProcess, UINT uExitCode, BOOL bTerminateUncon // (1) it doesn't run atexit handlers // (2) can invoke CrashReporter or produce a coredump, // which is appropriate for TerminateProcess calls - abort(); + PROCAbort(); } else { @@ -2081,9 +2087,12 @@ GetProcessIdDisambiguationKey(DWORD processId, UINT64 *disambiguationKey) Builds the transport pipe names from the process id. --*/ -void +VOID PALAPI -PAL_GetTransportPipeName(char *name, DWORD id, const char *suffix) +PAL_GetTransportPipeName( + OUT char *name, + IN DWORD id, + IN const char *suffix) { UINT64 disambiguationKey = 0; BOOL ret = GetProcessIdDisambiguationKey(id, &disambiguationKey); @@ -2829,7 +2838,7 @@ Return None --*/ -void +VOID DestroyProcessModules(IN ProcessModules *listHead) { for (ProcessModules *entry = listHead; entry != NULL; ) @@ -2841,7 +2850,7 @@ DestroyProcessModules(IN ProcessModules *listHead) } /*++ -Function: +Function PROCNotifyProcessShutdown Calls the abort handler to do any shutdown cleanup. Call be called @@ -2850,7 +2859,8 @@ Function: (no return value) --*/ __attribute__((destructor)) -void PROCNotifyProcessShutdown() +VOID +PROCNotifyProcessShutdown() { // Call back into the coreclr to clean up the debugger transport pipes PSHUTDOWN_CALLBACK callback = InterlockedExchangePointer(&g_shutdownCallback, NULL); @@ -2861,6 +2871,101 @@ void PROCNotifyProcessShutdown() } /*++ +Function + PROCAbortInitialize() + +Abstract + Initialize the process abort crash dump program file path and + name. Doing all of this ahead of time so nothing is allocated + or copied in PROCAbort/signal handler. + +Return + TRUE - succeeds, FALSE - fails + +--*/ +BOOL +PROCAbortInitialize() +{ + char* enabled = getenv("COMPlus_DbgEnableMiniDump"); + if (enabled != nullptr && _stricmp(enabled, "1") == 0) + { + if (g_szCoreCLRPath == nullptr) + { + return FALSE; + } + const char* DumpGeneratorName = "createdump"; + int programLen = strlen(g_szCoreCLRPath) + strlen(DumpGeneratorName) + 1; + char* program = (char*)InternalMalloc(programLen); + if (program == nullptr) + { + return FALSE; + } + if (strcpy_s(program, programLen, g_szCoreCLRPath) != SAFECRT_SUCCESS) + { + return FALSE; + } + char *last = strrchr(program, '/'); + if (last != nullptr) + { + *(last + 1) = '\0'; + } + else + { + program[0] = '\0'; + } + if (strcat_s(program, programLen, DumpGeneratorName) != SAFECRT_SUCCESS) + { + return FALSE; + } + char* pidarg = (char*)InternalMalloc(128); + if (pidarg == nullptr) + { + return FALSE; + } + if (sprintf_s(pidarg, 128, "%d", gPID) == -1) + { + return FALSE; + } + const char** argv = (const char**)g_argvCreateDump; + *argv++ = program; + + char* envvar = getenv("COMPlus_DbgMiniDumpName"); + if (envvar != nullptr) + { + *argv++ = "--name"; + *argv++ = envvar; + } + + envvar = getenv("COMPlus_DbgMiniDumpType"); + if (envvar != nullptr) + { + if (strcmp(envvar, "1") == 0) + { + *argv++ = "--normal"; + } + else if (strcmp(envvar, "2") == 0) + { + *argv++ = "--withheap"; + } + else if (strcmp(envvar, "3") == 0) + { + *argv++ = "--triage"; + } + } + + envvar = getenv("COMPlus_CreateDumpDiagnostics"); + if (envvar != nullptr && strcmp(envvar, "1") == 0) + { + *argv++ = "--diag"; + } + + *argv++ = pidarg; + *argv = nullptr; + } + return TRUE; +} + +/*++ Function: PROCAbort() @@ -2870,10 +2975,51 @@ Function: Does not return --*/ PAL_NORETURN -void +VOID PROCAbort() { + // Do any shutdown cleanup before aborting or creating a core dump PROCNotifyProcessShutdown(); + +#if HAVE_PRCTL_H + // If enabled, launch the create minidump utility and wait until it completes + if (g_argvCreateDump[0] != nullptr) + { + // Fork the core dump child process. + pid_t childpid = fork(); + + // If error, write an error to trace log and abort + if (childpid == -1) + { + ERROR("PROCAbort: fork() FAILED %d (%s)\n", errno, strerror(errno)); + } + else if (childpid == 0) + { + // Child process + if (execve(g_argvCreateDump[0], g_argvCreateDump, palEnvironment) == -1) + { + ERROR("PROCAbort: execve FAILED %d (%s)\n", errno, strerror(errno)); + } + } + else + { + // Gives the child process permission to use /proc/<pid>/mem and ptrace + if (prctl(PR_SET_PTRACER, childpid, 0, 0, 0) == -1) + { + ERROR("PROCAbort: prctl() FAILED %d (%s)\n", errno, strerror(errno)); + } + // Parent waits until the child process is done + int wstatus; + int result = waitpid(childpid, &wstatus, 0); + if (result != childpid) + { + ERROR("PROCAbort: waitpid FAILED result %d wstatus %d errno %d (%s)\n", + result, wstatus, errno, strerror(errno)); + } + } + } +#endif // HAVE_PRCTL_H + // Abort the process after waiting for the core dump to complete abort(); } @@ -2886,7 +3032,8 @@ Abstract Return TRUE if it succeeded, FALSE otherwise --*/ -BOOL InitializeFlushProcessWriteBuffers() +BOOL +InitializeFlushProcessWriteBuffers() { // Verify that the s_helperPage is really aligned to the VIRTUAL_PAGE_SIZE _ASSERTE((((SIZE_T)s_helperPage) & (VIRTUAL_PAGE_SIZE - 1)) == 0); @@ -3273,7 +3420,7 @@ Parameter pThread: Thread object --*/ -void +VOID CorUnix::PROCAddThread( CPalThread *pCurrentThread, CPalThread *pTargetThread @@ -3306,7 +3453,7 @@ Parameter (no return value) --*/ -void +VOID CorUnix::PROCRemoveThread( CPalThread *pCurrentThread, CPalThread *pTargetThread @@ -3376,7 +3523,7 @@ Return --*/ INT CorUnix::PROCGetNumberOfThreads( - void) + VOID) { return g_dwThreadCount; } @@ -3479,7 +3626,7 @@ Note: This function is used in ExitThread and TerminateProcess --*/ -void +VOID CorUnix::TerminateCurrentProcessNoExit(BOOL bTerminateUnconditionally) { BOOL locked; |