/* $Id: imagecache.c 3195 2018-03-27 18:09:23Z bird $ */ /** @file * kBuild specific executable image cache for Windows. */ /* * Copyright (c) 2012 knut st. osmundsen * * This file is part of kBuild. * * kBuild is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * kBuild is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with kBuild. If not, see * */ /* No GNU coding style here! */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include "makeint.h" #include /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ typedef struct EXECCACHEENTRY { /** The name hash value. */ unsigned uHash; /** The name length. */ unsigned cwcName; /** Pointer to the next name with the same hash. */ struct EXECCACHEENTRY *pNext; /** When it was last referenced. */ unsigned uLastRef; /** The module handle, LOAD_LIBRARY_AS_DATAFILE. */ HMODULE hmod1; /** The module handle, DONT_RESOLVE_DLL_REFERENCES. */ HMODULE hmod2; /** The executable path. */ wchar_t wszName[1]; } EXECCACHEENTRY; typedef EXECCACHEENTRY *PEXECCACHEENTRY; /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** Critical section serializing all access. */ static CRITICAL_SECTION g_CritSect; /** Set if initialized. */ static int volatile g_fInitialized = 0; /** The number of cached images. */ static unsigned g_cCached; /** Used noting when entries was last used. * Increased on each kmk_cache_exec_image call. */ static unsigned g_uNow; /** The size of the hash table. */ #define EXECCACHE_HASHTAB_SIZE 128 /** The hash table. */ static PEXECCACHEENTRY g_apHashTab[EXECCACHE_HASHTAB_SIZE]; /** A sleepy approach to do-once. */ static void kmk_cache_lazy_init(void) { if (_InterlockedCompareExchange(&g_fInitialized, -1, 0) == 0) { InitializeCriticalSection(&g_CritSect); _InterlockedExchange(&g_fInitialized, 1); } else while (g_fInitialized != 1) Sleep(1); } /* sdbm: This algorithm was created for sdbm (a public-domain reimplementation of ndbm) database library. it was found to do well in scrambling bits, causing better distribution of the keys and fewer splits. it also happens to be a good general hashing function with good distribution. the actual function is hash(i) = hash(i - 1) * 65599 + str[i]; what is included below is the faster version used in gawk. [there is even a faster, duff-device version] the magic constant 65599 was picked out of thin air while experimenting with different constants, and turns out to be a prime. this is one of the algorithms used in berkeley db (see sleepycat) and elsewhere. */ static unsigned execcache_calc_hash(const wchar_t *pwsz, size_t *pcch) { wchar_t const * const pwszStart = pwsz; unsigned hash = 0; int ch; while ((ch = *pwsz++) != L'\0') hash = ch + (hash << 6) + (hash << 16) - hash; *pcch = (size_t)(pwsz - pwszStart - 1); return hash; } /** * Caches two memory mappings of the specified image so that it isn't flushed * from the kernel's cache mananger. * * Not sure exactly how much this actually helps, but whatever... * * @param pwszExec The executable. */ extern void kmk_cache_exec_image_w(const wchar_t *pwszExec) { /* * Prepare name lookup and to lazy init. */ size_t cwcName; const unsigned uHash = execcache_calc_hash(pwszExec, &cwcName); PEXECCACHEENTRY *ppCur = &g_apHashTab[uHash % EXECCACHE_HASHTAB_SIZE]; PEXECCACHEENTRY pCur; if (g_fInitialized != 1) kmk_cache_lazy_init(); /* * Do the lookup. */ EnterCriticalSection(&g_CritSect); pCur = *ppCur; while (pCur) { if ( pCur->uHash == uHash && pCur->cwcName == cwcName && !memcmp(pCur->wszName, pwszExec, cwcName * sizeof(wchar_t))) { pCur->uLastRef = ++g_uNow; LeaveCriticalSection(&g_CritSect); return; } ppCur = &pCur->pNext; pCur = pCur->pNext; } LeaveCriticalSection(&g_CritSect); /* * Not found, create a new entry. */ pCur = xmalloc(sizeof(*pCur) + cwcName * sizeof(wchar_t)); pCur->uHash = uHash; pCur->cwcName = (unsigned)cwcName; pCur->pNext = NULL; pCur->uLastRef = ++g_uNow; memcpy(pCur->wszName, pwszExec, (cwcName + 1) * sizeof(wchar_t)); pCur->hmod1 = LoadLibraryExW(pwszExec, NULL, LOAD_LIBRARY_AS_DATAFILE); if (pCur->hmod1 != NULL) pCur->hmod2 = LoadLibraryExW(pwszExec, NULL, DONT_RESOLVE_DLL_REFERENCES); else pCur->hmod2 = NULL; /* * Insert it. * Take into account that we might've been racing other threads, * fortunately we don't evict anything from the cache. */ EnterCriticalSection(&g_CritSect); if (*ppCur != NULL) { /* Find new end of chain and check for duplicate. */ PEXECCACHEENTRY pCur2 = *ppCur; while (pCur2) { if ( pCur->uHash == uHash && pCur->cwcName == cwcName && !memcmp(pCur->wszName, pwszExec, cwcName * sizeof(wchar_t))) break; ppCur = &pCur->pNext; pCur = pCur->pNext; } } if (*ppCur == NULL) { *ppCur = pCur; g_cCached++; LeaveCriticalSection(&g_CritSect); } else { LeaveCriticalSection(&g_CritSect); if (pCur->hmod1 != NULL) FreeLibrary(pCur->hmod1); if (pCur->hmod2 != NULL) FreeLibrary(pCur->hmod2); free(pCur); } } extern void kmk_cache_exec_image_a(const char *pszExec) { wchar_t wszExec[260]; int cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszExec, strlen(pszExec) + 1, wszExec, 260); if (cwc > 0) kmk_cache_exec_image_w(wszExec); }