/** Copyright (c) Scott Gasch Module Name: unix.c Abstract: FreeBSD/Linux/OSX system dependent code. Author: Scott Gasch (scott.gasch@gmail.com) 8 Apr 2004 Revision History: $Id: unix.c 200 2006-07-05 20:05:07Z scott $ **/ #include "chess.h" #include #include #include #include #include #include #include #include #include pthread_mutex_t g_SystemLock; #define LOCK_SYSTEM pthread_mutex_lock(&g_SystemLock) #define UNLOCK_SYSTEM pthread_mutex_unlock(&g_SystemLock) #ifdef DEBUG ULONG g_uTotalAlloced = 0; #define ALLOC_HASH_SIZE 0x4000 typedef struct _ALLOC_RECORD { void *p; ULONG uSize; } ALLOC_RECORD; ALLOC_RECORD g_AllocHash[ALLOC_HASH_SIZE]; #define PTR_TO_ALLOC_HASH(x) ((((size_t)(x)) >> 3) & (ALLOC_HASH_SIZE - 1)) ULONG GetHeapMemoryUsage(void) { Trace("There are now %u bytes outstanding.\n", g_uTotalAlloced); return(g_uTotalAlloced); } static void ReleaseAllocHashEntry(void *p) { ULONG u = PTR_TO_ALLOC_HASH(p); ULONG v = u; LOCK_SYSTEM; while(g_AllocHash[u].p != p) { u++; u &= (ALLOC_HASH_SIZE - 1); if (v == u) return; } g_AllocHash[u].p = NULL; g_uTotalAlloced -= g_AllocHash[u].uSize; UNLOCK_SYSTEM; // Trace("Freed %u bytes at %p.\n", g_AllocHash[u].uSize, p); } static void MarkAllocHashEntry(void *p, ULONG uSize) { ULONG u = PTR_TO_ALLOC_HASH(p); if (uSize > (1024 * 1024 * 30)) return; LOCK_SYSTEM; while(g_AllocHash[u].p != NULL) { u++; u &= (ALLOC_HASH_SIZE - 1); } g_AllocHash[u].p = p; g_AllocHash[u].uSize = uSize; g_uTotalAlloced += uSize; UNLOCK_SYSTEM; // Trace("Alloc %u bytes at %p.\n", g_AllocHash[u].uSize, p); } #endif // DEBUG // // Thread table // typedef struct _SYSTEM_THREAD_TABLE_ENTRY { DLIST_ENTRY links; ULONG uWrapperHandle; ULONG uTid; ULONG uThreadParam; pthread_t thread; THREAD_ENTRYPOINT *pEntry; ULONG uExitCode; } SYSTEM_THREAD_TABLE_ENTRY; DLIST_ENTRY g_SystemThreadList; ULONG g_uNextThreadHandle = 6000; CHAR * SystemGetTimeString(void) /** Routine description: Get the current time string from the OS. Not thread safe. Parameters: void Return value: CHAR **/ { static CHAR buf[32]; time_t t = time(NULL); struct tm *p; p = localtime(&t); snprintf(buf, ARRAY_LENGTH(buf), "%u:%02u:%02u", p->tm_hour, p->tm_min, p->tm_sec); return(buf); } CHAR * SystemGetDateString(void) /** Routine description: Get the current date string from the OS. Not thread safe. Parameters: void Return value: CHAR **/ { static CHAR buf[32]; time_t t = time(NULL); struct tm *p; p = localtime(&t); snprintf(buf, ARRAY_LENGTH(buf), "%u.%02u.%02u", p->tm_year + 1900, p->tm_mon + 1, p->tm_mday); return(buf); } // // Thread code // static SYSTEM_THREAD_TABLE_ENTRY * _SystemGetThreadTableEntry(ULONG uThreadHandle) /** Routine description: Support routine for thread functionality wrapper. Given a uThreadHandle look up its entry in the global table. Parameters: ULONG uThreadHandle Return value: SYSTEM_THREAD_TABLE_ENTRY * **/ { DLIST_ENTRY *p; SYSTEM_THREAD_TABLE_ENTRY *q; LOCK_SYSTEM; p = g_SystemThreadList.pFlink; while(p != &g_SystemThreadList) { q = CONTAINING_STRUCT(p, SYSTEM_THREAD_TABLE_ENTRY, links); if (q->uWrapperHandle == uThreadHandle) { goto end; } p = p->pFlink; } q = NULL; end: UNLOCK_SYSTEM; return(q); } void * SystemThreadEntryPoint(void *pParam) /** Routine description: The first code that any newly created thread executes. Parameters: void *pParam Return value: void **/ { SYSTEM_THREAD_TABLE_ENTRY *p = (SYSTEM_THREAD_TABLE_ENTRY *)pParam; ULONG uParam; int i; LOCK_SYSTEM; UNLOCK_SYSTEM; uParam = p->uThreadParam; i = (int)(*(p->pEntry))(uParam); // call thread's user-supplied entry return((void *)(size_t)i); } FLAG SystemCreateThread(THREAD_ENTRYPOINT *pEntry, ULONG uParam, ULONG *puHandle) /** Routine description: Wraps the OS/Library dependent thread creation call. Parameters: THREAD_ENTRYPOINT *pEntry : where should the new thread start? ULONG uParam : parameter to pass to the new thread ULONG *puHandle : "handle" to the new thread Return value: FLAG **/ { SYSTEM_THREAD_TABLE_ENTRY *p; p = malloc(sizeof(SYSTEM_THREAD_TABLE_ENTRY)); if (NULL == p) { *puHandle = 0; return(FALSE); } LOCK_SYSTEM; p->pEntry = pEntry; p->uThreadParam = uParam; if (0 != pthread_create(&(p->thread), NULL, SystemThreadEntryPoint, p)) { free(p); return(FALSE); } *puHandle = p->uWrapperHandle = g_uNextThreadHandle; g_uNextThreadHandle++; InsertHeadList(&g_SystemThreadList, &(p->links)); UNLOCK_SYSTEM; return(TRUE); } FLAG SystemWaitForThreadToExit(ULONG uThreadHandle) /** Routine description: Blocks until the thread whose handle is provieded exits. Parameters: ULONG uThreadHandle Return value: FLAG **/ { SYSTEM_THREAD_TABLE_ENTRY *q = _SystemGetThreadTableEntry(uThreadHandle); pthread_join(q->thread, NULL); return(TRUE); } FLAG SystemGetThreadExitCode(ULONG uThreadHandle, ULONG *puCode) /** Routine description: Get the exit value of an already-exited thread. Parameters: ULONG uThreadHandle, ULONG *puCode Return value: FLAG **/ { SYSTEM_THREAD_TABLE_ENTRY *q = _SystemGetThreadTableEntry(uThreadHandle); void *p; pthread_join(q->thread, &p); *puCode = ((ULONG)(size_t)p); return(TRUE); } FLAG SystemDeleteThread(ULONG uThreadHandle) /** Routine description: Kill a thread. Parameters: ULONG uThreadHandle Return value: FLAG **/ { SYSTEM_THREAD_TABLE_ENTRY *q = _SystemGetThreadTableEntry(uThreadHandle); if (NULL != q) { pthread_kill(q->thread, SIGKILL); LOCK_SYSTEM; RemoveEntryList(&(q->links)); free(q); UNLOCK_SYSTEM; return(TRUE); } return(FALSE); } void FORCEINLINE SystemDebugBreakpoint(void) /** Routine description: A hardcoded breakpoint. Parameters: void Return value: void **/ { __asm__("int3\n"); } UINT64 FASTCALL SystemReadTimeStampCounter(void) /** Routine description: Read the processor's timestamp counter. Parameters: void Return value: UINT64 FASTCALL **/ { ULONG uLow, uHigh; __asm__ volatile("rdtsc" : "=a" (uLow), "=d" (uHigh)); return(((UINT64)uHigh << 32) | (UINT64)uLow); } double INLINE SystemTimeStamp(void) /** Routine description: Return a timestamp to the caller. Parameters: void Return value: double **/ { struct timeval tv; // // Number of seconds and microseconds since the Epoch. // if (0 != gettimeofday(&tv, NULL)) { UtilPanic(UNEXPECTED_SYSTEM_CALL_FAILURE, NULL, "gettimeofday", (void *)(size_t)errno, NULL, __FILE__, __LINE__); } return((double)tv.tv_sec + (double)tv.tv_usec * 1.0e-6); } FLAG SystemDoesFileExist(CHAR *szFilename) /** Routine description: Determine if a file exists (non-atomically). Parameters: CHAR *szFilename Return value: FLAG **/ { struct stat sb; int i = stat(szFilename, &sb); if ((i == -1) && (errno == ENOENT)) return(FALSE); return(TRUE); } void FORCEINLINE SystemDeferExecution(ULONG dwMs) /** Routine description: Sleep for a few ms. Parameters: ULONG dwMs Return value: void FORCEINLINE **/ { struct timeval t = { 0, dwMs * 1000 }; if (select(0, (fd_set *)NULL, (fd_set *)NULL, (fd_set *)NULL, &t) < 0) { if (usleep(dwMs * 1000) < 0) { Bug("SystemDeferExecution: I have insomnia...\n"); } } } static void *_SystemCallMalloc(ULONG dwSizeBytes) /** Routine description: Wrapper around malloc. Parameters: ULONG dwSizeBytes Return value: static void **/ { void *p = malloc(dwSizeBytes); if (NULL == p) { UtilPanic(UNEXPECTED_SYSTEM_CALL_FAILURE, NULL, "malloc", (void *)(size_t)errno, (void *)(size_t)dwSizeBytes, __FILE__, __LINE__); } memset(p, 0, dwSizeBytes); return(p); } static void _SystemCallFree(void *p) /** Routine description: Wrapper around free. Parameters: void *p Return value: static void **/ { free(p); } char * SystemStrDup(char *p) /** Routine description: Implements strdup functionality. Parameters: char *p Return value: char **/ { char *q = SystemAllocateMemory(strlen(p) + 1); strcpy(q, p); return(q); } void * SystemAllocateMemory(ULONG dwSizeBytes) /** Routine description: Allocate some memory. Parameters: ULONG dwSizeBytes Return value: void **/ { void *p = _SystemCallMalloc(dwSizeBytes); #ifdef DEBUG MarkAllocHashEntry(p, dwSizeBytes); #endif ASSERT(NULL != p); return(p); } void SystemFreeMemory(void *pMemory) /** Routine description: Free an allocation previously allocated by SystemAllocMemory. Parameters: void *pMemory Return value: void **/ { #ifdef DEBUG ReleaseAllocHashEntry(pMemory); #endif _SystemCallFree(pMemory); } void * SystemAllocateLockedMemory(ULONG dwSizeBytes) /** Routine description: Allocate non-pagable memory (if possible). Parameters: ULONG dwSizeBytes Return value: void **/ { void *p = SystemAllocateMemory(dwSizeBytes); if (NULL != p) { if (0 != mlock(p, dwSizeBytes)) { // // Note: don't panic the program over this... do dump a // warning though as it should not happen. // Bug("WARNING: mlock for buffer at 0x%p (%u bytes) failed, " "errno=%d\n\n", p, dwSizeBytes, errno); } } return(p); } FLAG SystemMakeMemoryReadOnly(void *pMemory, ULONG dwSizeBytes) /** Routine description: Make some memory region read-only (if possible). Parameters: void *pMemory, ULONG dwSizeBytes Return value: FLAG **/ { if (0 != mprotect(pMemory, dwSizeBytes, PROT_READ)) { UtilPanic(UNEXPECTED_SYSTEM_CALL_FAILURE, NULL, "mprotect", (void *)(size_t)errno, (void *)(size_t)PROT_READ, __FILE__, __LINE__); } return(TRUE); } FLAG SystemMakeMemoryReadWrite(void *pMemory, ULONG dwSizeBytes) /** Routine description: Make a region of memory read/write. Parameters: void *pMemory, ULONG dwSizeBytes Return value: FLAG **/ { if (0 != mprotect(pMemory, dwSizeBytes, PROT_READ | PROT_WRITE)) { UtilPanic(UNEXPECTED_SYSTEM_CALL_FAILURE, NULL, "mprotect", (void *)(size_t)errno, (void *)(size_t)(PROT_READ | PROT_WRITE), __FILE__, __LINE__); } return(TRUE); } FLAG SystemCopyFile(CHAR *szSource, CHAR *szDest) /** Routine description: Make a copy of a file. Parameters: CHAR *szSource, CHAR *szDest Return value: FLAG **/ { CHAR *buf = NULL; CHAR *x = "/bin/cp -f "; ULONG uLen = strlen(szSource) + strlen(szDest) + strlen(x) + 2; buf = SystemAllocateMemory(uLen); snprintf(buf, uLen, "%s%s %s", x, szSource, szDest); if (0 == system(buf)) { SystemFreeMemory(buf); return(TRUE); } SystemFreeMemory(buf); return(FALSE); } FLAG SystemDeleteFile(CHAR *szFile) /** Routine description: Delete a file. Parameters: CHAR *szFile Return value: FLAG **/ { if (0 == unlink(szFile)) { return(TRUE); } return(FALSE); } #define MAX_LOCKS (8) typedef struct _UNIX_LOCK_ENTRY { pthread_mutex_t lock; FLAG fInUse; } UNIX_LOCK_ENTRY; UNIX_LOCK_ENTRY g_rgLockTable[MAX_LOCKS]; ULONG SystemCreateLock(void) { ULONG u; LOCK_SYSTEM; for (u = 0; u < MAX_LOCKS; u++) { if (FALSE == g_rgLockTable[u].fInUse) { pthread_mutex_init(&(g_rgLockTable[u].lock), NULL); g_rgLockTable[u].fInUse = TRUE; goto end; } } u = (ULONG)-1; // no free slot end: UNLOCK_SYSTEM; return(u); } FLAG SystemDeleteLock(ULONG u) { LOCK_SYSTEM; if ((u < MAX_LOCKS) && (g_rgLockTable[u].fInUse == TRUE)) { pthread_mutex_destroy(&(g_rgLockTable[u].lock)); g_rgLockTable[u].fInUse = FALSE; UNLOCK_SYSTEM; return(TRUE); } UNLOCK_SYSTEM; return(FALSE); } FLAG SystemBlockingWaitForLock(ULONG u) { pthread_mutex_t *p = NULL; LOCK_SYSTEM; if ((u < MAX_LOCKS) && (g_rgLockTable[u].fInUse == TRUE)) { p = &(g_rgLockTable[u].lock); } UNLOCK_SYSTEM; if (p != NULL) { pthread_mutex_lock(p); return(TRUE); } return(FALSE); } FLAG SystemReleaseLock(ULONG u) { pthread_mutex_t *p = NULL; LOCK_SYSTEM; if ((u < MAX_LOCKS) && (g_rgLockTable[u].fInUse == TRUE)) { p = &(g_rgLockTable[u].lock); } UNLOCK_SYSTEM; if (p != NULL) { pthread_mutex_unlock(p); return(TRUE); } return(FALSE); } /* * These "semaphores" only ever signal between threads of this same * process (see input.c's producer/consumer wait between the stdin * reader thread and the command-dispatch thread) -- there is no * cross-process handoff here, despite the SysV-semaphore-shaped API. * A SysV semget(IPC_PRIVATE, ...) set is kernel-global, outlives the * process, and only gets torn down by an explicit IPC_RMID -- so a * killed (rather than gracefully quit()'d) process leaks it, and on a * shared box with a low kern.ipc.semmni it doesn't take many leaked * engines to exhaust the system-wide semaphore-set table. Every * subsequent semget() then fails, and _WaitUntilTheresInputToRead * (input.c) silently falls back to 100ms-polling instead of blocking * on a real wait -- a ~1000x slowdown that looks exactly like machine * contention. A pthread_mutex_t/pthread_cond_t pair does the same * counting-semaphore job entirely in-process, with no kernel-persistent * object to leak: the OS reclaims it unconditionally the instant the * process dies, SIGKILL or not. */ #define MAX_SEM (8) typedef struct _SEM_ENTRY { FLAG fInUse; ULONG uCount; pthread_mutex_t lock; pthread_cond_t cond; } SEM_ENTRY; SEM_ENTRY g_rgSemaphores[MAX_SEM]; ULONG SystemCreateSemaphore(ULONG uValue) { ULONG u; if (uValue > MAX_USHORT) return((ULONG)-1); LOCK_SYSTEM; for (u = 0; u < MAX_SEM; u++) { if (g_rgSemaphores[u].fInUse == FALSE) { pthread_mutex_init(&(g_rgSemaphores[u].lock), NULL); pthread_cond_init(&(g_rgSemaphores[u].cond), NULL); g_rgSemaphores[u].uCount = uValue; g_rgSemaphores[u].fInUse = TRUE; #ifdef DEBUG Trace("Semaphore #%u created, initial value is %u\n", u, uValue); #endif goto end; } } u = (ULONG)-1; end: UNLOCK_SYSTEM; return(u); } FLAG SystemDeleteSemaphore(ULONG u) { FLAG fRet = TRUE; LOCK_SYSTEM; if ((u < MAX_SEM) && (g_rgSemaphores[u].fInUse == TRUE)) { pthread_mutex_destroy(&(g_rgSemaphores[u].lock)); pthread_cond_destroy(&(g_rgSemaphores[u].cond)); g_rgSemaphores[u].fInUse = FALSE; } else { fRet = FALSE; } UNLOCK_SYSTEM; return(fRet); } void SystemReleaseSemaphoreResource(ULONG u) { LOCK_SYSTEM; if ((u < MAX_SEM) && (g_rgSemaphores[u].fInUse == TRUE)) { pthread_mutex_lock(&(g_rgSemaphores[u].lock)); g_rgSemaphores[u].uCount += 1; #ifdef DEBUG Trace("Semaphore #%u value incremented to %u\n", u, g_rgSemaphores[u].uCount); #endif pthread_cond_signal(&(g_rgSemaphores[u].cond)); pthread_mutex_unlock(&(g_rgSemaphores[u].lock)); } UNLOCK_SYSTEM; } void SystemObtainSemaphoreResource(ULONG u) { LOCK_SYSTEM; if ((u < MAX_SEM) && (g_rgSemaphores[u].fInUse == TRUE)) { pthread_mutex_lock(&(g_rgSemaphores[u].lock)); UNLOCK_SYSTEM; while (g_rgSemaphores[u].uCount == 0) { pthread_cond_wait(&(g_rgSemaphores[u].cond), &(g_rgSemaphores[u].lock)); } g_rgSemaphores[u].uCount -= 1; #ifdef DEBUG Trace("Semaphore #%u value decremented to %u\n", u, g_rgSemaphores[u].uCount); #endif pthread_mutex_unlock(&(g_rgSemaphores[u].lock)); return; } UNLOCK_SYSTEM; } // // Set the first time SIGINT asks a search to abort, and cleared once // we next observe the engine idle. Lets a second ^C -- sent before the // first one's TIMER_STOPPING has actually unwound the search -- force // an immediate exit instead of silently collapsing into a no-op repeat // of the first request. // static volatile sig_atomic_t g_fSigintAbortPending = 0; static void _AbortSearchOrTerminate(int iSig) /** Routine description: Signal handler shared by SIGHUP and SIGINT. If a search is in progress, abort just the current search the same way the engine already unrolls on a timer/user-input stop (TIMER_STOPPING) -- control returns to whatever is driving the engine (interactively, or the next line of a "script" test suite), rather than killing the process. This is what a stuck ECM/EPD batch position needs: a way to bail out of one pathological search without losing the rest of the run. If nothing is being searched right now there's nothing to abort. For SIGINT specifically, fall back to the normal terminate-the- process behavior in that case (restore the default disposition and re-raise) so Ctrl-C still works as expected when the engine is idle. SIGHUP while idle is just ignored. A second ^C sent while the first one's abort is still pending (the search hasn't unwound to idle yet -- it can take a moment, e.g. if we're deep inside a long leaf-node burst between TIMER_STOPPING checks) is treated as "I mean it" and kills the process immediately, matching the usual double-^C idiom instead of silently eating the second press. Parameters: int iSig Return value: void **/ { if (FALSE == g_Options.fThinking) { g_fSigintAbortPending = 0; if (SIGINT == iSig) { signal(SIGINT, SIG_DFL); raise(SIGINT); } return; } if (SIGINT == iSig) { if (0 != g_fSigintAbortPending) { signal(SIGINT, SIG_DFL); raise(SIGINT); return; } g_fSigintAbortPending = 1; } Trace("SIGINT/SIGHUP -- STOP SEARCHING NOW\n"); g_MoveTimer.bvFlags |= TIMER_STOPPING; } FLAG SystemDependentInitialization(void) /** Routine description: This code is called to initialize operating system dependent code. It is unsynchronized. It should only ever be called once. It should definitely never be called by two threads at once! Parameters: void Return value: FLAG : TRUE on success, FALSE otherwise **/ { ULONG u; #ifdef DEBUG static FLAG fInit = FALSE; if (TRUE == fInit) { Bug("SystemDependentInitialization code called more than once!\n"); ASSERT(FALSE); return(FALSE); } fInit = TRUE; memset(g_AllocHash, 0, sizeof(g_AllocHash)); #endif for (u = 0; u < MAX_SEM; u++) { g_rgSemaphores[u].fInUse = FALSE; } InitializeListHead(&g_SystemThreadList); pthread_mutex_init(&g_SystemLock, NULL); // // SIGHUP/SIGINT: abort just the in-progress search (see // _AbortSearchOrTerminate's comment) instead of the default // terminate-the-process disposition. SA_RESTART so a blocking // syscall elsewhere in the process (e.g. the input thread's read()) // doesn't get kicked out with EINTR by a signal that has nothing to // do with it. // struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = _AbortSearchOrTerminate; sa.sa_flags = SA_RESTART; sigemptyset(&sa.sa_mask); sigaction(SIGHUP, &sa, NULL); sigaction(SIGINT, &sa, NULL); return(TRUE); }