/*++ Copyright (c) 2002-2002 Microsoft Corporation Module Name: scavenger.c Abstract: The cache scavenger implementation Author: Karthik Mahesh (KarthikM) Feb-2002 Revision History: --*/ #include "precomp.h" #include "scavenger.h" #include "scavengerp.h" // MB to trim at a time SIZE_T g_UlScavengerTrimMB = DEFAULT_SCAVENGER_TRIM_MB; // Min Interval between 2 scavenger events ULONG g_UlMinScavengerInterval = DEFAULT_MIN_SCAVENGER_INTERVAL; // Pages to trim on a low memory event. ULONG_PTR g_ScavengerTrimPages; volatile BOOLEAN g_ScavengerThreadStarted; HANDLE g_ScavengerLowMemHandle; HANDLE g_ScavengerThreadHandle; KEVENT g_ScavengerLimitExceededEvent; KEVENT g_ScavengerTerminateThreadEvent; KEVENT g_ScavengerTimerEvent; KTIMER g_ScavengerTimer; KDPC g_ScavengerTimerDpc; // Event Array for Scavenger Thread PKEVENT g_ScavengerAllEvents[SCAVENGER_NUM_EVENTS]; // Number of scavenger calls since last timer event ULONG g_ScavengerAge = 0; #ifdef ALLOC_PRAGMA #pragma alloc_text( INIT, UlInitializeScavengerThread ) #pragma alloc_text( PAGE, UlTerminateScavengerThread ) #pragma alloc_text( PAGE, UlpSetScavengerTimer ) #pragma alloc_text( PAGE, UlpScavengerThread ) #pragma alloc_text( PAGE, UlpScavengerPeriodicEventHandler ) #pragma alloc_text( PAGE, UlpScavengerLowMemoryEventHandler ) #pragma alloc_text( PAGE, UlpScavengerLimitEventHandler ) #pragma alloc_text( PAGE, UlSetScavengerLimitEvent ) #endif // ALLOC_PRAGMA #if 0 NOT PAGEABLE -- UlpScavengerDpcRoutine #endif /***************************************************************************++ Routine Description: Initialize the Memory Scavenger --***************************************************************************/ NTSTATUS UlInitializeScavengerThread( VOID ) { NTSTATUS Status; UNICODE_STRING LowMemoryEventName; OBJECT_ATTRIBUTES ObjAttr; PKEVENT LowMemoryEventObject; PAGED_CODE(); // Initialize Trim Size // If trim size is not set, trim 1M for every 256M if(g_UlScavengerTrimMB > g_UlTotalPhysicalMemMB) { g_UlScavengerTrimMB = g_UlTotalPhysicalMemMB; } if(g_UlScavengerTrimMB == DEFAULT_SCAVENGER_TRIM_MB) { g_UlScavengerTrimMB = (g_UlTotalPhysicalMemMB + 255)/256; } g_ScavengerTrimPages = MEGABYTES_TO_PAGES(g_UlScavengerTrimMB); // Open Low Memory Event Object RtlInitUnicodeString( &LowMemoryEventName, LOW_MEM_EVENT_NAME ); InitializeObjectAttributes( &ObjAttr, &LowMemoryEventName, OBJ_KERNEL_HANDLE, NULL, NULL ); Status = ZwOpenEvent ( &g_ScavengerLowMemHandle, EVENT_QUERY_STATE, &ObjAttr ); if( !NT_SUCCESS(Status) ) { return Status; } Status = ObReferenceObjectByHandle( g_ScavengerLowMemHandle, EVENT_QUERY_STATE, NULL, KernelMode, (PVOID *) &LowMemoryEventObject, NULL ); if( !NT_SUCCESS(Status) ) { ZwClose (g_ScavengerLowMemHandle); return Status; } // Initialize Scavenger Timer DPC object KeInitializeDpc( &g_ScavengerTimerDpc, &UlpScavengerTimerDpcRoutine, NULL ); // Initialize Scavenger Timer KeInitializeTimer( &g_ScavengerTimer ); // Initialize other Scavenger Events KeInitializeEvent ( &g_ScavengerTerminateThreadEvent, NotificationEvent, FALSE ); KeInitializeEvent ( &g_ScavengerLimitExceededEvent, NotificationEvent, FALSE ); KeInitializeEvent ( &g_ScavengerTimerEvent, NotificationEvent, FALSE ); // Initialize Event Array for Scavenger Thread g_ScavengerAllEvents[SCAVENGER_LOW_MEM_EVENT] = LowMemoryEventObject; g_ScavengerAllEvents[SCAVENGER_TERMINATE_THREAD_EVENT] = &g_ScavengerTerminateThreadEvent; g_ScavengerAllEvents[SCAVENGER_LIMIT_EXCEEDED_EVENT] = &g_ScavengerLimitExceededEvent; g_ScavengerAllEvents[SCAVENGER_TIMER_EVENT] = &g_ScavengerTimerEvent; // Start Scavenger Thread g_ScavengerThreadStarted = TRUE; InitializeObjectAttributes(&ObjAttr, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); Status = PsCreateSystemThread( &g_ScavengerThreadHandle, THREAD_ALL_ACCESS, &ObjAttr, NULL, NULL, UlpScavengerThread, NULL ); if( !NT_SUCCESS(Status) ) { g_ScavengerThreadStarted = FALSE; ObDereferenceObject ( LowMemoryEventObject ); ZwClose( g_ScavengerLowMemHandle ); return Status; } UlTrace(URI_CACHE, ("UlInitializeScavengerThread: Started Scavenger Thread\n")); // Kick off periodic scavenger timer UlpSetScavengerTimer(); return Status; } /***************************************************************************++ Routine Description: Terminate the Memory Scavenger --***************************************************************************/ VOID UlTerminateScavengerThread( VOID ) { PETHREAD ThreadObject; NTSTATUS Status; PAGED_CODE(); UlTrace(URI_CACHE, ("UlTerminateScavengerThread: Terminating Scavenger Thread\n")); if(g_ScavengerThreadStarted) { ASSERT( g_ScavengerThreadHandle ); Status = ObReferenceObjectByHandle( g_ScavengerThreadHandle, 0, *PsThreadType, KernelMode, (PVOID *) &ThreadObject, NULL ); ASSERT( NT_SUCCESS(Status) ); // g_ScavengerThreadHandle is a valid thread handle g_ScavengerThreadStarted = FALSE; // Set the terminate event KeSetEvent( &g_ScavengerTerminateThreadEvent, 0, FALSE ); // Wait for thread to terminate KeWaitForSingleObject( ThreadObject, Executive, KernelMode, FALSE, NULL ); ObDereferenceObject( ThreadObject ); ZwClose( g_ScavengerThreadHandle ); // Close Low Mem Event handle ObDereferenceObject(g_ScavengerAllEvents[SCAVENGER_LOW_MEM_EVENT]); ZwClose( g_ScavengerLowMemHandle ); // Cancel the timer, if it fails it means the Dpc may be running // In that case, wait for it to finish if ( !KeCancelTimer( &g_ScavengerTimer ) ) { KeWaitForSingleObject( (PVOID)&g_ScavengerTimerEvent, Executive, KernelMode, FALSE, NULL ); } // Clear out any remaining zombies UlPeriodicCacheScavenger(0); } } /***************************************************************************++ Routine Description: Figures out the scavenger interval in 100 ns ticks, and sets the timer. --***************************************************************************/ VOID UlpSetScavengerTimer( VOID ) { LARGE_INTEGER Interval; PAGED_CODE(); // // convert seconds to 100 nanosecond intervals (x * 10^7) // negative numbers mean relative time // Interval.QuadPart= g_UriCacheConfig.ScavengerPeriod * -C_NS_TICKS_PER_SEC; UlTrace(URI_CACHE, ( "Http!UlpSetScavengerTimer: %d seconds = %I64d 100ns ticks\n", g_UriCacheConfig.ScavengerPeriod, Interval.QuadPart )); KeSetTimer( &g_ScavengerTimer, Interval, &g_ScavengerTimerDpc ); } // UlpSetScavengerTimer /***************************************************************************++ Routine Description: Dpc routine to set scavenger timeout event Arguments: None. --***************************************************************************/ VOID UlpScavengerTimerDpcRoutine( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) { UNREFERENCED_PARAMETER(Dpc); UNREFERENCED_PARAMETER(DeferredContext); UNREFERENCED_PARAMETER(SystemArgument1); UNREFERENCED_PARAMETER(SystemArgument2); ASSERT( DeferredContext == NULL ); KeSetEvent( &g_ScavengerTimerEvent, 0, FALSE ); } /***************************************************************************++ Routine Description: Wait for memory usage events and recycle when needed Arguments: None. --***************************************************************************/ VOID UlpScavengerThread( IN PVOID Context ) { NTSTATUS Status; KWAIT_BLOCK WaitBlockArray[SCAVENGER_NUM_EVENTS]; LARGE_INTEGER MinInterval; PAGED_CODE(); ASSERT(Context == NULL); ASSERT(g_ScavengerAllEvents[SCAVENGER_TERMINATE_THREAD_EVENT] != NULL); ASSERT(g_ScavengerAllEvents[SCAVENGER_TIMER_EVENT] != NULL); ASSERT(g_ScavengerAllEvents[SCAVENGER_LOW_MEM_EVENT] != NULL); ASSERT(g_ScavengerAllEvents[SCAVENGER_LIMIT_EXCEEDED_EVENT] != NULL); UNREFERENCED_PARAMETER(Context); MinInterval.QuadPart = g_UlMinScavengerInterval * -C_NS_TICKS_PER_SEC; while(g_ScavengerThreadStarted) { // // Pause between successive scavenger calls // KeWaitForSingleObject( g_ScavengerAllEvents[SCAVENGER_TERMINATE_THREAD_EVENT], Executive, KernelMode, FALSE, &MinInterval ); // // Wait for scavenger events // Status = KeWaitForMultipleObjects( SCAVENGER_NUM_EVENTS, g_ScavengerAllEvents, WaitAny, Executive, KernelMode, FALSE, NULL, WaitBlockArray ); ASSERT( NT_SUCCESS(Status) ); if(KeReadStateEvent( g_ScavengerAllEvents[SCAVENGER_TERMINATE_THREAD_EVENT] )) { // // Do Nothing, will exit while loop // UlTrace(URI_CACHE, ("UlpScavengerThread: Terminate Event Set\n")); break; } if(KeReadStateEvent( g_ScavengerAllEvents[SCAVENGER_TIMER_EVENT] )) { UlpScavengerPeriodicEventHandler(); } if(KeReadStateEvent( g_ScavengerAllEvents[SCAVENGER_LOW_MEM_EVENT] )) { UlpScavengerLowMemoryEventHandler(); } if(KeReadStateEvent(g_ScavengerAllEvents[SCAVENGER_LIMIT_EXCEEDED_EVENT] )) { UlpScavengerLimitEventHandler(); } } // while(g_ScavengerThreadStarted) PsTerminateSystemThread( STATUS_SUCCESS ); } /***************************************************************************++ Routine Description: Handle the "Cache Size Exceeded Limit" event Arguments: None. --***************************************************************************/ VOID UlpScavengerPeriodicEventHandler( VOID ) { PAGED_CODE(); KeClearEvent( g_ScavengerAllEvents[SCAVENGER_TIMER_EVENT] ); UlTraceVerbose(URI_CACHE, ("UlpScavengerThread: Calling Periodic Scavenger. Age = %d\n", g_ScavengerAge)); ASSERT(g_ScavengerAge <= SCAVENGER_MAX_AGE); UlPeriodicCacheScavenger(g_ScavengerAge); g_ScavengerAge = 0; // // Clear the pages exceeded event, hopefully enough memory // has been reclaimed by the scavenger // If not, this event will be set again immediately // on the next cache miss // KeClearEvent(g_ScavengerAllEvents[SCAVENGER_LIMIT_EXCEEDED_EVENT]); // // Schedule the next periodic scavenger call // UlpSetScavengerTimer(); } /***************************************************************************++ Routine Description: Handle the "Cache Size Exceeded Limit" event Arguments: None. --***************************************************************************/ VOID UlpScavengerLowMemoryEventHandler( VOID ) { ULONG_PTR PagesToRecycle; PAGED_CODE(); UlDisableCache(); ASSERT(g_ScavengerAge <= SCAVENGER_MAX_AGE); if(g_ScavengerAge < SCAVENGER_MAX_AGE) { g_ScavengerAge++; } UlTrace(URI_CACHE, ("UlpScavengerThread: Low Memory. Age = %d\n", g_ScavengerAge)); do { // // Trim up to g_ScavengerTrimPages pages // PagesToRecycle = UlGetHashTablePages(); if(PagesToRecycle > g_ScavengerTrimPages){ PagesToRecycle = g_ScavengerTrimPages; } if(PagesToRecycle > 0) { UlTrimCache( PagesToRecycle, g_ScavengerAge ); } } while(KeReadStateEvent(g_ScavengerAllEvents[SCAVENGER_LOW_MEM_EVENT]) && (PagesToRecycle > 0)); UlEnableCache(); } /***************************************************************************++ Routine Description: Handle the "Cache Size Exceeded Limit" event Arguments: None. --***************************************************************************/ VOID UlpScavengerLimitEventHandler( VOID ) { ULONG_PTR PagesToRecycle; UlDisableCache(); PagesToRecycle = UlGetHashTablePages() / 8; if( PagesToRecycle < 1 ) { PagesToRecycle = UlGetHashTablePages(); } if(g_ScavengerAge < SCAVENGER_MAX_AGE) { g_ScavengerAge++; } ASSERT(g_ScavengerAge <= SCAVENGER_MAX_AGE); UlTrace(URI_CACHE, ("UlpScavengerThread: Cache Size Exceeded Limit. Age = %d, Freeing %d pages\n", g_ScavengerAge, PagesToRecycle)); if(PagesToRecycle > 0) { UlTrimCache( PagesToRecycle, g_ScavengerAge ); } KeClearEvent(g_ScavengerAllEvents[SCAVENGER_LIMIT_EXCEEDED_EVENT]); UlEnableCache(); } /***************************************************************************++ Routine Description: Set the "Cache Size Exceeded Limit" event Arguments: None. --***************************************************************************/ VOID UlSetScavengerLimitEvent( VOID ) { PAGED_CODE(); KeSetEvent( g_ScavengerAllEvents[SCAVENGER_LIMIT_EXCEEDED_EVENT], 0, FALSE ); }