/* DynCache - Microsoft Windows Dynamic Cache Service This service will dynamically set the System File Cache maximum limit. The System File Cache is reduced by the size of the working sets of the defined processes and any additional backoff. */ #include "DynCache.h" // Globals // Single linked list off all the defined processes to back off from PPROCESS_ENTRY g_BackoffProcessList = NULL; // This is the absolute maximum that the File System Cache will be set to size_t g_MaxCacheSizeBytesLimit = NULL; // The current maximum size of the System File Cache size_t g_CurrentMaxCacheSizeBytes = NULL; // The current minimum size of the System File Cache size_t g_CurrentMinCacheSizeBytes = NULL; // Backoff this many bytes when available memory is low size_t g_BackOffBytesOnLowMemoryNotification = NULL; // This is how many bytes we are currently backing off due to a low memory notification size_t g_CurrentLowMemoryBackOffBytes = NULL; // Total physical RAM in the system size_t g_PhysicalMemoryBytes = NULL; // This is the threshold before we update the system's file cache size // This is used to filter out minor working set changes so that we are not constantly updating the cache size // If the threshold is 100 MBytes, then we won't update the system until the new calculated size is 100 MBytes less than or more than our last system update. size_t g_CacheUpdateThresholdBytes = NULL; // Sampling interval for checking process working sets DWORD g_SampleIntervalMSecs = NULL; // Handle to the process heap HANDLE g_hProcessHeap = NULL; // Handle the HKEY_LOCAL_MACHINE HKEY g_hkLM = NULL; // Handle to the service's registry key HKEY g_hkSvc = NULL; // Array of notification events that we will wait upon HANDLE g_hEvents[MAX_NOTIFICATIONS]; // Handle to event to pause/resume the service HANDLE g_hPauseService; // Did the service complete initialization? BOOL g_bInitializationComplete = FALSE; // Found in Service.cpp extern LPWSTR SERVICE_NAME; extern BOOL g_bServiceShutdown; extern SERVICE_STATUS_HANDLE g_ServiceStatusHandle; // Validate a string // Parameters: // wszTestString - String to validate // cchTestString - Size of wszTestString in characters HRESULT ValidateString( __in_ecount(cchTestString) LPWSTR wszTestString, size_t cchTestString ) { size_t StringLength = NULL; HRESULT hrStatus = S_OK; if ((cchTestString == NULL) || (wszTestString == NULL)) { // We don't have a valid pointer or string length return (E_INVALIDARG); } else { // Validate the string's length hrStatus = StringCchLengthW(wszTestString, MAX_PATH, &StringLength); if (SUCCEEDED(hrStatus)) { if (StringLength != cchTestString) { // The string's length is not what we were told return (E_INVALIDARG); } } else { // Failed to get the string's length return (E_INVALIDARG); } } return (S_OK); } // Free the current back off process list // This is done if the registry settings are changed. void FreeBackoffProcessList( void ) { PPROCESS_ENTRY pNextEntry = NULL; if (g_BackoffProcessList) { // A current list exists. Free it. while( g_BackoffProcessList ) { // Save the pointer to the next entry pNextEntry = g_BackoffProcessList->Next; // Free any allocated strings if (g_BackoffProcessList->wszImageName) HeapFree(g_hProcessHeap, NULL, g_BackoffProcessList->wszImageName); if (g_BackoffProcessList->wszAdditionalBackOffCounter) HeapFree(g_hProcessHeap, NULL, g_BackoffProcessList->wszAdditionalBackOffCounter); // Free this entry HeapFree(g_hProcessHeap, NULL, g_BackoffProcessList); if (pNextEntry) { // There is a next entry. Change the pointer to it g_BackoffProcessList = pNextEntry; } else { // There are no more entrys, clear the global pointer g_BackoffProcessList = NULL; } } } } // Add a process to the back off list // Parameters: // wszImageName - The process's image file name // cchImageName - The number of characters in wszImageName // AdditionalBackoffBytes - Additional Bytes to back off for this process // wszAdditionalBackOffCounter - Additional counter for this process // cchAdditionalBackOffCounter - the number of characters in wszAdditionalBackOffCounter HRESULT AddBackoffProcess( __in_ecount(cchImageName) LPWSTR wszImageName, size_t cchImageName, size_t AdditionalBackoffBytes, __in_ecount_opt(cchAdditionalBackOffCounter) LPWSTR wszAdditionalBackOffCounter, size_t cchAdditionalBackOffCounter) { HRESULT hrStatus = S_OK; PPROCESS_ENTRY pTempEntry = NULL; size_t StringLength = NULL; // Validate the image filename hrStatus = ValidateString(wszImageName, cchImageName); if (FAILED(hrStatus)) { DebugMessage(L"Failed to add process (%s)(%ld) to the backoff process list due to invalid image filename!\n", wszImageName, cchImageName); return (hrStatus); } if (g_BackoffProcessList) { // Allocate a new entry pTempEntry = (PPROCESS_ENTRY) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, sizeof(_PROCESS_ENTRY)); // Insert this entry to the head of the list pTempEntry->Next = g_BackoffProcessList; g_BackoffProcessList = pTempEntry; } else { // This is the first entry in the list. Allocate a new entry g_BackoffProcessList = (PPROCESS_ENTRY) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, sizeof(_PROCESS_ENTRY)); pTempEntry = g_BackoffProcessList; } // Save the pointer to the process's image file name pTempEntry->wszImageName = wszImageName; if ((wszAdditionalBackOffCounter) && (cchAdditionalBackOffCounter > 0)) { // Validate the additional backoff counter's string length hrStatus = ValidateString(wszAdditionalBackOffCounter, cchAdditionalBackOffCounter); if (SUCCEEDED(hrStatus)) { // This process has an additional counter. Save the pointer to the string pTempEntry->wszAdditionalBackOffCounter = wszAdditionalBackOffCounter; // Determine the units for this counter. This is used to convert the counter's value into bytes. if (wcsstr(wszAdditionalBackOffCounter, L"(KB)")) { pTempEntry->AdditionalCounterUnits = 1024; } else { pTempEntry->AdditionalCounterUnits = 1; } } else { DebugMessage(L"Failed to add additional backoff counter for %s due to invalid string!\n", wszImageName); } } // Save any additional back off bytes pTempEntry->AdditionalBackoffBytes = AdditionalBackoffBytes; return (S_OK); } // Add a counter instance for this process // Parameters: // hQuery - Handle to the performance query // pProcess - Pointer to the process' entry // wszCounterString - Counter string to add // cchCounterString - Size of wszCounterString in characters HRESULT AddCounterInstance( HQUERY hQuery, PPROCESS_ENTRY pProcess, __in_ecount(cchCounterString) LPWSTR wszCounterString, size_t cchCounterString ) { HRESULT hrStatus = S_OK; PCOUNTER_INSTANCE pCounter = NULL; PDH_STATUS pdhStatus = ERROR_SUCCESS; if ((hQuery == NULL) || (pProcess == NULL)) { DebugMessage(L"Failed to add a counter to a process due to invalid parameters!\n"); return (E_INVALIDARG); } // Validate the counter string hrStatus = ValidateString(wszCounterString, cchCounterString); if (FAILED(hrStatus)) { DebugMessage(L"Failed to add a counter to %s due to an invalid string!\n", pProcess->wszImageName); return (hrStatus); } if (!pProcess->pCounter) { // This process does not currently have a counter instance. Add the first one pProcess->pCounter = (PCOUNTER_INSTANCE) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, sizeof(COUNTER_INSTANCE)); pCounter = pProcess->pCounter; } else { // This process has other counter instances, Add a new one to the begining of the list. pCounter = (PCOUNTER_INSTANCE) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, sizeof(COUNTER_INSTANCE)); pCounter->Next = pProcess->pCounter; pProcess->pCounter = pCounter; } // Add the counter to the query pdhStatus = PdhAddCounterW(hQuery, wszCounterString, 0, &pCounter->hCounter); if (pdhStatus != ERROR_SUCCESS) { DebugMessage(L"PdhAddCounterW failed with error code 0x%X. Counter: %s\n", pdhStatus, wszCounterString); pCounter->hCounter = NULL; } else { DebugMessage(L"Added Counter String: %s\n", wszCounterString); } return (hrStatus); } // Update the process working sets on our back off list void UpdateProcessWorkingSets(void) { HQUERY hQuery = NULL; PDH_STATUS pdhStatus = ERROR_SUCCESS; PDH_RAW_COUNTER WorkingSetRawCounter; WCHAR wszSearchCounter[PDH_MAX_COUNTER_PATH]; PPROCESS_ENTRY pBackOffProcess = NULL; DWORD dwSize = NULL; WCHAR wszCounterString[PDH_MAX_COUNTER_PATH]; WCHAR wszLowerCaseProcess[MAX_PATH]; LPWSTR wszThisInstance = NULL; DWORD nInstance = NULL; DWORD dwStringSize = PDH_MAX_COUNTER_PATH; LPWSTR wszObjectName = L"Process"; LPWSTR wszCounterName = L"Working Set"; LPWSTR wasObjectList = L"Not Used"; PDH_COUNTER_PATH_ELEMENTS_W CounterPathElements; size_t StringLength = NULL; if (g_BackoffProcessList == NULL) { // There are no processes to back off from return; } // Start a new performance query pdhStatus = PdhOpenQuery(0, 0, &hQuery); if (pdhStatus != ERROR_SUCCESS) { DebugMessage(L"PdhOpenQuery failed with error code 0x%X.\n"); return; } // Force a refresh of the performance object list pdhStatus = PdhEnumObjectsW(NULL, // Use the next parameter NULL, // Use local machine wasObjectList, // We don't need the object names &dwSize, // Size of buffer, we are ignoring this PERF_DETAIL_WIZARD, // Counter detail level TRUE); // referesh the cached object list pBackOffProcess = g_BackoffProcessList; while (pBackOffProcess) { // Walk the back off process list if (pBackOffProcess->wszImageName) { nInstance = 0; while (TRUE) { // Create counter strings for each instance of this process ZeroMemory(&CounterPathElements, sizeof(CounterPathElements)); CounterPathElements.szObjectName = wszObjectName; CounterPathElements.szCounterName = wszCounterName; CounterPathElements.szInstanceName = pBackOffProcess->wszImageName; CounterPathElements.dwInstanceIndex = nInstance; dwStringSize = PDH_MAX_COUNTER_PATH; pdhStatus = PdhMakeCounterPathW(&CounterPathElements, wszCounterString, &dwStringSize, 0); if (pdhStatus == ERROR_SUCCESS) { // Check to see if this is a valid counter string. // We have to do this because we are incrementally looking up each instance of this process. // This will fail once there are no more instances. pdhStatus = PdhValidatePathW(wszCounterString); } if (pdhStatus != ERROR_SUCCESS) { // This instance of this process does not exist. Stop adding counters break; } // Add this counter instance to this process StringCchLengthW(wszCounterString, PDH_MAX_COUNTER_PATH, &StringLength); AddCounterInstance(hQuery, pBackOffProcess, wszCounterString, StringLength); // Increment the instance count so that we can check for additional process instances. nInstance++; } } if ( (pBackOffProcess->pCounter) && (pBackOffProcess->wszAdditionalBackOffCounter) ) { // This process has at least one instance and an additional backoff counter. // Add the additional backoff counter to the query. pdhStatus = PdhValidatePathW(pBackOffProcess->wszAdditionalBackOffCounter); if (pdhStatus != ERROR_SUCCESS) { DebugMessage(L"PdhValidatePath failed with error code 0x%X. Counter: %s\n", pdhStatus, pBackOffProcess->wszAdditionalBackOffCounter); } else { pdhStatus = PdhAddCounterW(hQuery, pBackOffProcess->wszAdditionalBackOffCounter, 0, &pBackOffProcess->hAdditionalCounter); if (pdhStatus != ERROR_SUCCESS) { DebugMessage(L"PdhAddCounter failed with error code 0x%X. Counter: %s\n", pdhStatus, pBackOffProcess->wszAdditionalBackOffCounter); pBackOffProcess->hAdditionalCounter = NULL; } } } // Move to the next process on our back off list pBackOffProcess = pBackOffProcess->Next; } // Collect performance data for the counters that we have added pdhStatus = PdhCollectQueryData(hQuery); if (pdhStatus == ERROR_SUCCESS) { // Save the working set sizes for our back off processes pBackOffProcess = g_BackoffProcessList; PCOUNTER_INSTANCE pCounter = NULL; while (pBackOffProcess) { // Clear out the last reading pBackOffProcess->WorkingSetSizeBytes = NULL; pCounter = pBackOffProcess->pCounter; while (pCounter) { // Walk the counter instance list if (pCounter->hCounter) { // This instance has a counter. Save it's working set size. pdhStatus = PdhGetRawCounterValue(pCounter->hCounter, 0, &WorkingSetRawCounter); if (pdhStatus != ERROR_SUCCESS) { DebugMessage(L"PdhGetRawCounterValue failed with error code 0x%08X\n", pdhStatus); } else { pBackOffProcess->WorkingSetSizeBytes += (size_t) WorkingSetRawCounter.FirstValue; DebugMessage(L"WorkingSet[%s] = %I64ld bytes\n", pBackOffProcess->wszImageName, (size_t) (WorkingSetRawCounter.FirstValue)); } } pCounter = pCounter->Next; } if (pBackOffProcess->hAdditionalCounter) { // There is an additional counter for this process. Add its value to the working set size. pdhStatus = PdhGetRawCounterValue(pBackOffProcess->hAdditionalCounter, 0, &WorkingSetRawCounter); if (pdhStatus != ERROR_SUCCESS) { DebugMessage(L"PdhGetRawCounterValue failed with error code 0x%08X\n", pdhStatus); } else { size_t AdditionalCounterBytes = (size_t) ( (size_t)WorkingSetRawCounter.FirstValue * pBackOffProcess->AdditionalCounterUnits); if (AdditionalCounterBytes < g_PhysicalMemoryBytes) { DebugMessage(L"AdditionalBackOffCounter[%s] = %I64ld bytes\n", pBackOffProcess->wszAdditionalBackOffCounter, AdditionalCounterBytes); pBackOffProcess->WorkingSetSizeBytes += AdditionalCounterBytes; } else { DebugMessage(L"Invalid Value!: Not using AdditionalBackOffCounter[%s] because its value [%I64ld bytes] is larger than physical RAM [%I64ld bytes]\n", pBackOffProcess->wszAdditionalBackOffCounter, AdditionalCounterBytes, g_PhysicalMemoryBytes); } } } // Move to the next process on our back off list pBackOffProcess = pBackOffProcess->Next; } } else { DebugMessage(L"Failed PdhCollectQueryData with error code 0x%08X\n", pdhStatus); } // Close the query. We have to start a new query each time to account for process creations and terminations // We don't call PdhRemoveCounter because when we close the query, it will close all outstanding counters. PdhCloseQuery(hQuery); pBackOffProcess = g_BackoffProcessList; while (pBackOffProcess) { // Walk the back off process list PCOUNTER_INSTANCE pCounter = pBackOffProcess->pCounter; PCOUNTER_INSTANCE pNext = NULL; while (pCounter) { // Walk the process's counter list pNext = pCounter->Next; // Free the current counter instance and move on HeapFree(g_hProcessHeap, NULL, pCounter); pCounter = pNext; } // Clear out the counter instance pointer pBackOffProcess->pCounter = NULL; if (pBackOffProcess->hAdditionalCounter) { // Clear the counter instance pointer for any additional counters pBackOffProcess->hAdditionalCounter = NULL; } // Move to the next back off process pBackOffProcess = pBackOffProcess->Next; } return; } // Set the cache limit // Parameters: // MaxCacheSize - Maximum cache size in bytes // MinCacheSize - Minimum cache size in bytes void LimitCache( size_t MaxCacheSize, size_t MinCacheSize ) { DWORD dwStatus = 0; DWORD dwFlags = 0; // Get the current System File Cache limits GetSystemFileCacheSize( (PSIZE_T) &g_CurrentMinCacheSizeBytes, (PSIZE_T) &g_CurrentMaxCacheSizeBytes, &dwFlags); DebugMessage(L"Current System File Cache size in bytes: Max[%I64ld] Min[%I64ld]\n", g_CurrentMaxCacheSizeBytes, g_CurrentMinCacheSizeBytes); DebugMessage(L"Attempting to set System File Cache in bytes to: Max[%I64ld] Min[%I64ld]\n", MaxCacheSize, MinCacheSize); // Change the granularity to 1 MB. // This should give us a nice page aligned number for setting the cache to. MaxCacheSize = MaxCacheSize >> 20; MaxCacheSize = MaxCacheSize << 20; MinCacheSize = MinCacheSize >> 20; MinCacheSize = MinCacheSize << 20; if (MaxCacheSize < (MinCacheSize + (100 * 1024 * 1024))) { // The maximum size must be atleast 100 MBytes more than the minimum MaxCacheSize = MinCacheSize + (100 * 1024 * 1024); } if (MaxCacheSize > g_MaxCacheSizeBytesLimit) { // The maximum can not exceed our maximum size limit MaxCacheSize = g_MaxCacheSizeBytesLimit; DebugMessage(L"The attempted maximum System File Cache size exceeds limit. Using the limit: %I64ld bytes\n", g_MaxCacheSizeBytesLimit); } if (( MaxCacheSize > (g_CurrentMaxCacheSizeBytes + g_CacheUpdateThresholdBytes) ) || ( MaxCacheSize < (g_CurrentMaxCacheSizeBytes - g_CacheUpdateThresholdBytes) )) { DebugMessage(L"Setting the System File Cache in bytes to: Max[%I64ld] Min[%I64ld]\n", MaxCacheSize, MinCacheSize); // Set the system file cache limits dwStatus = SetSystemFileCacheSize(MinCacheSize, MaxCacheSize, 1); if (dwStatus == 0) { dwStatus = GetLastError(); if (dwStatus != ERROR_SUCCESS) { DebugMessage(L"SetSystemFileCacheSize failed with error code %08X\n", dwStatus); } return; } else { // Update our internal globals to what the system ended up using for limits. GetSystemFileCacheSize( (PSIZE_T) &g_CurrentMinCacheSizeBytes, (PSIZE_T) &g_CurrentMaxCacheSizeBytes, &dwFlags); return; } } else { DebugMessage(L"Not updating the System File Cache limits because they are within %I64ld bytes of the current setting.\n", g_CacheUpdateThresholdBytes); } } // Calcuate the maximum cache size based off of working sets and any additional backoff values void CalculateMaxCacheSize( void ) { PPROCESS_ENTRY pTempEntry = NULL; BOOL bUpdated = FALSE; size_t CalculatedBackoffSizeBytes = NULL; size_t CalculatedMaxCacheSizeBytes = NULL; pTempEntry = g_BackoffProcessList; while (pTempEntry) { // Walk the back off process list // Add this process's working set and additional back off MBytes if (pTempEntry->WorkingSetSizeBytes) { DebugMessage(L"Backing off working set[%I64ld bytes] for %s\n", pTempEntry->WorkingSetSizeBytes, pTempEntry->wszImageName); CalculatedBackoffSizeBytes += pTempEntry->WorkingSetSizeBytes; if (pTempEntry->AdditionalBackoffBytes) { // Since this process is active, add any additional backoff bytes DebugMessage(L"Backing off additional [%I64ld bytes] for %s\n", pTempEntry->AdditionalBackoffBytes, pTempEntry->wszImageName); CalculatedBackoffSizeBytes += pTempEntry->AdditionalBackoffBytes; } } // Go to the next entry pTempEntry = pTempEntry->Next; } // Calculate the maximum cache size from the process backoffs DebugMessage(L"Total calculated back off: %I64ld Bytes\n", CalculatedBackoffSizeBytes); CalculatedMaxCacheSizeBytes = g_PhysicalMemoryBytes - CalculatedBackoffSizeBytes; LimitCache(CalculatedMaxCacheSizeBytes, g_CurrentMinCacheSizeBytes); return; } // Enable a privilege // Parameters: // hToken - Access token handle // lpszPrivilege - name of privilege to enable/disable // bEnablePrivilege - to enable or disable privilege BOOL SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) { TOKEN_PRIVILEGES tp; LUID luid; if (!LookupPrivilegeValue( NULL, // lookup privilege on local system lpszPrivilege, // privilege to lookup &luid ) ) // receives LUID of privilege { DebugMessage(L"LookupPrivilegeValue error: %u\n", GetLastError() ); return FALSE; } tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; if (bEnablePrivilege) { tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; } else { tp.Privileges[0].Attributes = 0; } // Enable the privilege or disable all privileges. if (!AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL) ) { DebugMessage(L"AdjustTokenPrivileges error: %u\n", GetLastError() ); return FALSE; } if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { DebugMessage(L"The token does not have the specified privilege. \n"); return FALSE; } return TRUE; } // Read the registry settings BOOL ReadRegistrySettings() { DWORD dwStatus = ERROR_SUCCESS; DebugMessage(L"Reading Registry settings.\n"); // Connect to HKEY_LOCAL_MACHINE on the local computer if (!g_hkLM) { dwStatus = RegConnectRegistryW(NULL, HKEY_LOCAL_MACHINE, &g_hkLM); } if (dwStatus == ERROR_SUCCESS) { DWORD dwProcesses = NULL; DWORD dwMaxSubKeyLength = NULL; // Open a handle to the regkey for this service if (!g_hkSvc) { dwStatus = RegOpenKeyExW(g_hkLM, CACHESVC_REG_KEY_LOC, 0, KEY_READ, &g_hkSvc); } if (dwStatus == ERROR_SUCCESS) { DWORD dwValue = NULL; DWORD dwBufferSize = sizeof(dwValue); DWORD dwType = NULL; if (g_hEvents[REG_CHANGE_NOTIFICATION]) { // Close the handle to the existing registry change notification event CloseHandle(g_hEvents[REG_CHANGE_NOTIFICATION]); } // Create the registry change notification event g_hEvents[REG_CHANGE_NOTIFICATION] = CreateEvent(NULL, TRUE, FALSE, NULL); if (g_hEvents[REG_CHANGE_NOTIFICATION]) { // Set the change notification to the service's parameters regkey and its subkeys dwStatus = RegNotifyChangeKeyValue(g_hkSvc, TRUE, REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET, g_hEvents[REG_CHANGE_NOTIFICATION], TRUE); if (dwStatus != ERROR_SUCCESS) { DebugMessage(L"RegNotifyChangeKeyValue failed with error code 0x%X\n", dwStatus); DebugError(dwStatus); CloseHandle(g_hEvents[REG_CHANGE_NOTIFICATION]); g_hEvents[REG_CHANGE_NOTIFICATION] = NULL; } } else { dwStatus = GetLastError(); DebugMessage(L"CreateEvent failed with error code 0x%X\n", dwStatus); DebugError(dwStatus); } // Get the minimum cache size g_CurrentMinCacheSizeBytes = NULL; dwStatus = RegQueryValueExW(g_hkSvc, L"MinSystemCacheMBytes", NULL, &dwType, (LPBYTE) &dwValue, &dwBufferSize); if ((dwStatus == ERROR_SUCCESS) && (dwBufferSize == sizeof(dwValue)) && (dwType == REG_DWORD) && (dwValue > 0) ) { // Set a hard lower limit of 100 MBytes if (dwValue < 100) dwValue = 100; g_CurrentMinCacheSizeBytes = (size_t) dwValue * 1024 * 1024; DebugMessage(L"MinSystemCacheMBytes: %d MBytes\n", dwValue); } if (g_CurrentMinCacheSizeBytes == NULL) { // We couldn't read the value from the registry or it was set to the default. // Set the min limit to 100 MBytes. g_CurrentMinCacheSizeBytes = 100 * 1024 * 1024; DebugMessage(L"MinSystemCacheMBytes is not set, defaulting to 100 MBytes.\n"); } // Get the maximum cache size g_MaxCacheSizeBytesLimit = NULL; dwStatus = RegQueryValueExW(g_hkSvc, L"MaxSystemCacheMBytes", NULL, &dwType, (LPBYTE) &dwValue, &dwBufferSize); if ((dwStatus == ERROR_SUCCESS) && (dwBufferSize == sizeof(dwValue)) && (dwType == REG_DWORD) && (dwValue > 0)) { if (dwValue < 100) { // Less than 100 MBytes means we treat this as a percentage g_MaxCacheSizeBytesLimit = (size_t)((g_PhysicalMemoryBytes * dwValue) * 0.01); DebugMessage(L"MaxSystemCacheMBytes: %d%% of physical memory = %I64ld bytes\n", dwValue, g_MaxCacheSizeBytesLimit); } else { // Set a hard lower limit of 200 MBytes if (dwValue < 200) dwValue = 200; g_MaxCacheSizeBytesLimit = (size_t) dwValue * 1024 * 1024; DebugMessage(L"MaxSystemCacheMBytes: %d MBytes\n", dwValue); } } if (g_MaxCacheSizeBytesLimit == NULL) { // We couldn't read the value from the registry or it was set to the default. // Set the max limit to 90% of physical RAM with an upper limit of physical RAM - 300 MBytes g_MaxCacheSizeBytesLimit = (size_t) (g_PhysicalMemoryBytes * 0.9); if (g_MaxCacheSizeBytesLimit > (g_PhysicalMemoryBytes - (300 * 1024 * 1024))) { g_MaxCacheSizeBytesLimit = g_PhysicalMemoryBytes - (300 * 1024 * 1024); } DebugMessage(L"MaxSystemCacheMBytes is not set, defaulting to 90%% of physical RAM (with an upper limit of physical RAM - 300 MBytes): %I64ld bytes\n", g_MaxCacheSizeBytesLimit); } // Set the initial current max size to the limit g_CurrentMaxCacheSizeBytes = g_MaxCacheSizeBytesLimit; // Read the sampling interval g_SampleIntervalMSecs = NULL; dwStatus = RegQueryValueExW(g_hkSvc, L"SampleIntervalSecs", NULL, &dwType, (LPBYTE) &dwValue, &dwBufferSize); if ((dwStatus == ERROR_SUCCESS) && (dwBufferSize == sizeof(dwValue)) && (dwType == REG_DWORD) && (dwValue > 0)) { g_SampleIntervalMSecs = dwValue * 1000; DebugMessage(L"Sample Interval: %d (msecs)\n", g_SampleIntervalMSecs); } if (g_SampleIntervalMSecs == NULL) { DebugMessage(L"Sample Interval is not set. Setting the cache limits only one time.\n"); } // Clear and read the Back off MBytes on low memory g_CurrentLowMemoryBackOffBytes = NULL; g_BackOffBytesOnLowMemoryNotification = NULL; dwStatus = RegQueryValueExW(g_hkSvc, L"BackOffMBytesOnLowMemory", NULL, &dwType, (LPBYTE) &dwValue, &dwBufferSize); if ((dwStatus == ERROR_SUCCESS) && (dwBufferSize == sizeof(dwValue)) && (dwType == REG_DWORD) && (dwValue > 0)) { g_BackOffBytesOnLowMemoryNotification = (size_t) dwValue * 1024 * 1024; DebugMessage(L"BackOffMBytesOnLowMemory: %d MBytes\n", dwValue); } else { DebugMessage(L"BackOffMBytesOnLowMemory is not set. Will not back off on low memory events.\n"); } g_CacheUpdateThresholdBytes = NULL; dwStatus = RegQueryValueExW(g_hkSvc, L"CacheUpdateThresholdMBytes", NULL, &dwType, (LPBYTE) &dwValue, &dwBufferSize); if ((dwStatus == ERROR_SUCCESS) && (dwBufferSize == sizeof(dwValue)) && (dwType == REG_DWORD) && (dwValue > 0) ) { g_CacheUpdateThresholdBytes = (size_t) dwValue * 1024 * 1024; DebugMessage(L"CacheUpdateThresholdMBytes: %d MBytes\n", dwValue); } if (g_CacheUpdateThresholdBytes == NULL) { g_CacheUpdateThresholdBytes = 100 * 1024 * 1024; DebugMessage(L"CacheUpdateThresholdMBytes is not set. Defaulting to 100 MBytes.\n"); } // Get the number os subkeys (number of process to back off) dwStatus = RegQueryInfoKeyW(g_hkSvc, NULL, NULL, NULL, &dwProcesses, &dwMaxSubKeyLength, NULL, NULL, NULL, NULL, NULL, NULL); if (dwStatus == ERROR_SUCCESS) { LPWSTR wszProcessName = (LPWSTR) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, sizeof(WCHAR) * (dwMaxSubKeyLength + 1)); LPWSTR wszAdditionalBackOffCounter = NULL; size_t cchAdditionalBackOffCounter = NULL; // Free any existing back off process list FreeBackoffProcessList(); // We have subkeys, so there are processes to watch for for (DWORD i = 0; i < dwProcesses; i++) { // Get the key's name. This is the process's image file name dwStatus = RegEnumKeyW( g_hkSvc, i, wszProcessName, (dwMaxSubKeyLength + 1) ); if (dwStatus == ERROR_SUCCESS) { size_t AdditionalBackoffSizeBytes = NULL; HKEY hkProcess = NULL; dwStatus = RegOpenKeyExW(g_hkSvc, wszProcessName, 0, KEY_READ, &hkProcess); if (dwStatus != ERROR_SUCCESS) { DebugMessage(L"RegOpenKeyEx failed for %s with error code 0x%X\n", wszProcessName, GetLastError()); continue; } // Get any additional back off MBytes for this process dwBufferSize = sizeof(dwValue); dwStatus = RegQueryValueExW(hkProcess, L"AdditionalBackOffMBytes", NULL, &dwType, (LPBYTE) &dwValue, &dwBufferSize); if ((dwStatus == ERROR_SUCCESS) && (dwType == REG_DWORD) && (dwBufferSize == sizeof(dwValue)) && (dwValue > 0)) { DebugMessage(L"AdditionalBackOffMBytes for %s: %d MBytes\n", wszProcessName, dwValue); AdditionalBackoffSizeBytes = (SIZE_T) dwValue * 1024 * 1024; } dwBufferSize = NULL; dwType = REG_SZ; dwStatus = RegQueryValueExW(hkProcess, L"AdditionalBackOffCounter", NULL, &dwType, NULL, &dwBufferSize); if ((dwType == REG_SZ) && (dwStatus == ERROR_SUCCESS) && (dwBufferSize > 0)) { wszAdditionalBackOffCounter = (LPWSTR) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, dwBufferSize); dwStatus = RegQueryValueExW(hkProcess, L"AdditionalBackOffCounter", NULL, &dwType, (LPBYTE) wszAdditionalBackOffCounter, &dwBufferSize); if (dwStatus == ERROR_SUCCESS) { DebugMessage(L"AdditionalBackOffCounter for %s: %s\n", wszProcessName, wszAdditionalBackOffCounter); StringCchLengthW(wszAdditionalBackOffCounter, PDH_MAX_COUNTER_PATH, &cchAdditionalBackOffCounter); } else { DebugMessage(L"Failed to get AdditionalBackOffCounter: Error=0x%X\n", dwStatus); HeapFree(g_hProcessHeap, NULL, wszAdditionalBackOffCounter); wszAdditionalBackOffCounter = NULL; cchAdditionalBackOffCounter = NULL; } } else { wszAdditionalBackOffCounter = NULL; cchAdditionalBackOffCounter = NULL; } CloseHandle(hkProcess); // Save the process's image filename // We will free this when the backup process list is freed size_t StringLength = NULL; StringCchLengthW(wszProcessName, dwMaxSubKeyLength + 1, &StringLength); LPWSTR wszProcessNameToSave = (LPWSTR) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, (sizeof(WCHAR) * (StringLength + 1))); StringCchCopyW(wszProcessNameToSave, (StringLength + 1), wszProcessName); // Add this process to our back off process list if (SUCCEEDED(AddBackoffProcess(wszProcessNameToSave, StringLength, AdditionalBackoffSizeBytes , wszAdditionalBackOffCounter, cchAdditionalBackOffCounter))) { DebugMessage(L"Added process '%s' to the backoff process list.\n", wszProcessName); } else { DebugMessage(L"Failed to add process '%s' to the backoff process list.\n", wszProcessName); } } else { DebugMessage(L"RegEnumKey failed for %d with error code 0x%X!\n", i, dwStatus); } } HeapFree(g_hProcessHeap, NULL, wszProcessName); } } else { // Registry key for the service doesn't exist DebugMessage(L"Failed to read the service's registry key. Error = 0x%X\n", dwStatus); CloseHandle(g_hkLM); g_hkLM = NULL; return FALSE; } } else { // Failed to connect to HKLM on the local machine DebugMessage(L"Failed to connect to the registry. Error = 0x%X\n", dwStatus); return FALSE; } return TRUE; } // Back off the cache for low memory events void BackOffForLowMemory( void ) { BOOL bLowMemory = FALSE; if (g_hEvents[LOW_MEMORY_NOTIFICATION]) { // Query the notification to see if we are currently below the threshold QueryMemoryResourceNotification(g_hEvents[LOW_MEMORY_NOTIFICATION], &bLowMemory); if (bLowMemory) { // Available memory is below the low memory threshold and we are set to back off. if (g_CurrentMaxCacheSizeBytes > g_BackOffBytesOnLowMemoryNotification) { // The low memory backoff amount is less than the current max, so reduce the current max by this amount g_CurrentMaxCacheSizeBytes -= g_BackOffBytesOnLowMemoryNotification; // Keep track of how much we are backing off due to low memory g_CurrentLowMemoryBackOffBytes += g_BackOffBytesOnLowMemoryNotification; } else { // The current max cache size is less than the back off amount, drop the max to 100 MBytes more than the minimum. size_t TempSize = NULL; // Determine the difference between the current max size and the low end threshold TempSize = g_CurrentMaxCacheSizeBytes - (g_CurrentMinCacheSizeBytes + (100 * 1024 * 1024)); if ((TempSize > 0) && ( TempSize < g_PhysicalMemoryBytes )) { // We can reduce the max cache by a little more, but not as much as defined in the registry g_CurrentMaxCacheSizeBytes -= TempSize; // Keep track of this reduction g_CurrentLowMemoryBackOffBytes += TempSize; } } DebugMessage(L"Low memory threshold triggered, backing off cache %I64ld bytes\n", g_CurrentLowMemoryBackOffBytes); DebugMessage(L"Setting System Cache limit to: %I64ld bytes\n", g_CurrentMaxCacheSizeBytes); LimitCache(g_CurrentMaxCacheSizeBytes, g_CurrentMinCacheSizeBytes); } else { // Low memory condition no longer exists DebugMessage(L"Low memory condition no longer exists, restoring maximum cache size.\n"); DebugMessage(L"Current Backoff: %I64ld Bytes | System Cache Limit: %I64ld Bytes\n", g_CurrentLowMemoryBackOffBytes, g_CurrentMaxCacheSizeBytes); if (g_CurrentLowMemoryBackOffBytes > g_BackOffBytesOnLowMemoryNotification) { // Restore the cache max to before the low memory event // We backed off more than the back off amount as defined in the registry. // Slowly restore the cache by the amount in the registry g_CurrentMaxCacheSizeBytes += g_BackOffBytesOnLowMemoryNotification; g_CurrentLowMemoryBackOffBytes -= g_BackOffBytesOnLowMemoryNotification; } else { // Restore the cache max to before the low memory event // The current backoff amount is less than the amount defined in the registry // Restore the last backoff amount g_CurrentMaxCacheSizeBytes += g_CurrentLowMemoryBackOffBytes; g_CurrentLowMemoryBackOffBytes = 0; } LimitCache(g_CurrentMaxCacheSizeBytes, g_CurrentMinCacheSizeBytes); } } } // Back off from the defined process working sets and additional values void BackOffForWorkingSets( void ) { // Get the back off processes' working sets UpdateProcessWorkingSets(); // Calulate and set the max cache size based off the update CalculateMaxCacheSize(); } // Initialize the cache service BOOL InitializeCacheSvc(void) { HANDLE hToken = NULL; DWORD dwStatus = NULL; PERFORMANCE_INFORMATION PerfInfo; OSVERSIONINFO OSVerInfo; DebugMessage(L"Initializing Dynamic Cache Service.\n"); // Register with the Service Control Manager g_ServiceStatusHandle = RegisterServiceCtrlHandlerW(SERVICE_NAME, (LPHANDLER_FUNCTION) ServiceCtrlHandler); if (g_ServiceStatusHandle == (SERVICE_STATUS_HANDLE) 0 ) { dwStatus = GetLastError(); DebugMessage(L"RegisterServiceCtrlHandler failed with error code 0x%X!\n", dwStatus); SvcCleanup(dwStatus); return (FALSE); } // Notify the Service Control Manager of our progress if (!SendStatusToSCM(SERVICE_START_PENDING, NO_ERROR, 0, 1, 10000)) { dwStatus = GetLastError(); DebugMessage(L"SendStatusToSCM failed with error code 0x%X!\n", dwStatus); SvcCleanup(dwStatus); return (FALSE); } OSVerInfo.dwOSVersionInfoSize = sizeof(OSVerInfo); if ( (GetVersionEx (&OSVerInfo) == NULL) ) { dwStatus = GetLastError(); DebugMessage(L"GetVersionEx failed with error code 0x%X!\n", dwStatus); SvcCleanup(dwStatus); return (FALSE); } else { if (OSVerInfo.dwMajorVersion == 6) { if (OSVerInfo.dwMinorVersion > 1) { DebugMessage(L"Dynamic Cache Service only runs on Windows 2008 R2 or earlier 64-bit versions of Windows.\n"); SvcCleanup(ERROR_RMODE_APP); return (FALSE); } } else if (OSVerInfo.dwMajorVersion > 6) { DebugMessage(L"Dynamic Cache Service only runs on Windows Server 2008 R2 or earlier 64-bit versions of Windows.\n"); SvcCleanup(ERROR_RMODE_APP); return (FALSE); } } // Save the handle to the process heap g_hProcessHeap = GetProcessHeap(); if (OSVerInfo.dwMajorVersion == 6) { // On Vista and Windows Server 2008 enable termination on heap corruption detection HeapSetInformation(g_hProcessHeap, HeapEnableTerminationOnCorruption, NULL, 0); } // Get the total physical memory of the system ZeroMemory(&PerfInfo, sizeof(PerfInfo)); if(GetPerformanceInfo(&PerfInfo, sizeof(PerfInfo))) { g_PhysicalMemoryBytes = (PerfInfo.PhysicalTotal * PerfInfo.PageSize); DebugMessage(L"Total Physical Memory: %I64ld bytes\n", g_PhysicalMemoryBytes); } // Enable the privilege to set system file cache size dwStatus = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken); if( dwStatus == 0 ) { dwStatus = GetLastError(); DebugMessage(L"OpenProcessToken failed with error code 0x%X!\n", dwStatus); SvcCleanup(dwStatus); return (FALSE); } dwStatus = SetPrivilege(hToken, SE_INCREASE_QUOTA_NAME, TRUE); if( dwStatus == 0 ) { dwStatus = GetLastError(); DebugMessage(L"SetPrivilege failed with error code 0x%X!\n", dwStatus); SvcCleanup(dwStatus); return (FALSE); } CloseHandle (hToken); // Read the registry settings for the service if (!ReadRegistrySettings()) { dwStatus = GetLastError(); DebugMessage(L"ReadRegistrySettings failed with error code 0x%X!\n", dwStatus); SvcCleanup(dwStatus); return (FALSE); } // We just read the configuration settings from the registry, so set the initial cache size LimitCache(g_MaxCacheSizeBytesLimit, g_CurrentMinCacheSizeBytes); // The limit may not have been fully accepted, so set the new limit to what the OS will accept. g_MaxCacheSizeBytesLimit = g_CurrentMaxCacheSizeBytes; if (g_BackOffBytesOnLowMemoryNotification) { // The cache is set to back off its working set when the available memory is low. // Create a handle to the low memory threshold event g_hEvents[LOW_MEMORY_NOTIFICATION] = CreateMemoryResourceNotification(LowMemoryResourceNotification); if (g_BackOffBytesOnLowMemoryNotification > g_CurrentMaxCacheSizeBytes ) { // The backoff bytes on low memory notification was set higher than the cache limit. // Reduce it to the max cache size limit. g_BackOffBytesOnLowMemoryNotification = g_CurrentMaxCacheSizeBytes; } } // Create the internal wakeup and pause events g_hEvents[INTERNAL_WAKEUP] = CreateEvent(0, TRUE, FALSE, 0); g_hPauseService = CreateEvent(0, TRUE, TRUE, 0); // The service is now running. Notify Service Control Manager of progress if (!SendStatusToSCM(SERVICE_RUNNING, NO_ERROR, 0, 0, 0)) { dwStatus = GetLastError(); DebugMessage(L"SendStatusToSCM failed with error code 0x%X!\n", dwStatus); SvcCleanup(dwStatus); return (FALSE); } g_bInitializationComplete = TRUE; return (TRUE); } // Main function for this service void DynCacheMain(DWORD dwArgc, LPWSTR *lpwszArgv) { DWORD dwStatus = ERROR_SUCCESS; // Initialize the service if (!InitializeCacheSvc()) { DebugMessage(L"DynCache failed to initialize!\n"); return; } // Update the backoff process working sets and set the cache limits BackOffForWorkingSets(); if (g_SampleIntervalMSecs == 0) { // The service is configured to only set the cache once, then exit. // Tell the Service Control Manager that we have stopped. SendStatusToSCM(SERVICE_STOPPED, dwStatus, 0, 0, 0); return; } while(g_SampleIntervalMSecs) { // A sampling interval is set. Keep looping until its time to quit if (g_BackOffBytesOnLowMemoryNotification) { // Wait on all events dwStatus = WaitForMultipleObjects(MAX_NOTIFICATIONS, g_hEvents, FALSE, g_SampleIntervalMSecs); } else { // Wait on all event except for the low memory threshold notification dwStatus = WaitForMultipleObjects((MAX_NOTIFICATIONS - 1), g_hEvents, FALSE, g_SampleIntervalMSecs); } switch (dwStatus) { case WAIT_OBJECT_0 + INTERNAL_WAKEUP: // We received an internal wake-up event. // Clear the event. Fall out and check to see if we are pausing or exiting. ResetEvent(g_hEvents[INTERNAL_WAKEUP]); break; case WAIT_OBJECT_0 + REG_CHANGE_NOTIFICATION: // The registry settings have changed DebugMessage(L"Registry Settings changed, updating settings\n"); // Wait 10 seconds to allow for user updates to complete Sleep(10000); // Clean up the backoff process list FreeBackoffProcessList(); // Read in the new settings ReadRegistrySettings(); break; case WAIT_OBJECT_0 + LOW_MEMORY_NOTIFICATION: // Low memory notification received DebugMessage(L"Low Memory Notification received.\n"); // Reduce the max size of the cache when the available memory is low BackOffForLowMemory(); // Give the system 10 seconds to flush the pages we released and then // hand them over to whomever may need them. We will keep decreasing // our working set ever 10 seconds while there is memory pressure. Sleep(10000); break; case WAIT_TIMEOUT: // The sampling interval timeout was reach if (g_CurrentLowMemoryBackOffBytes) { // We are currently backing off the cache's working set due to a low memory event // If there is still low memory, we will back off some more. If not, we will slowly // restore the max back to where we were. BackOffForLowMemory(); } else { // We are not currently backing off the working set due to low memory. // Update the back off process working sets and adjust the cache size accordingly BackOffForWorkingSets(); } break; default: dwStatus = GetLastError(); if (dwStatus != ERROR_SUCCESS) { DebugMessage(L"WaitForMultipleObjects failed with error code 0x%X\n", dwStatus); DebugError(dwStatus); } } // The service is shutting down. Clean up and exit if (g_bServiceShutdown) { SvcCleanup(ERROR_SUCCESS); return; } // Wait here if the service is paused WaitForSingleObject(g_hPauseService, INFINITE); } // Clean up then exit SvcCleanup(ERROR_SUCCESS); return; }