/*++ Copyright (c) 1989 Microsoft Corporation Module Name: heapmgr.c Abstract: This module contains initialization and termination routines for server FSP heap, as well as debug routines for memory tracking. --*/ #include "precomp.h" #include "heapmgr.tmh" #pragma hdrstop // Make the retry time 15 milli-seconds #define SRV_LOW_PRIORITY_RETRY_TIME -1*1000*10*15 #ifdef POOL_TAGGING // // Array correlating BlockType numbers to pool tags. // // *** This array must be maintained in concert with the BlockType // definitions in srvblock.h! // ULONG SrvPoolTags[BlockTypeMax-1] = { 'fbSL', // BlockTypeBuffer 'ncSL', // BlockTypeConnection 'peSL', // BlockTypeEndpoint 'flSL', // BlockTypeLfcb 'fmSL', // BlockTypeMfcb 'frSL', // BlockTypeRfcb 'rsSL', // BlockTypeSearch 'csSL', // BlockTypeSearchCore 'lbSL', // BlockTypeByteRangeLock for persistent handles 'ssSL', // BlockTypeSession 'hsSL', // BlockTypeShare 'rtSL', // BlockTypeTransaction 'ctSL', // BlockTypeTreeConnect 'poSL', // BlockTypeOplockBreak 'dcSL', // BlockTypeCommDevice 'iwSL', // BlockTypeWorkContextInitial 'nwSL', // BlockTypeWorkContextNormal 'rwSL', // BlockTypeWorkContextRaw 'swSL', // BlockTypeWorkContextSpecial 'dcSL', // BlockTypeCachedDirectory 'bdSL', // BlockTypeDataBuffer 'btSL', // BlockTypeTable 'hnSL', // BlockTypeNonpagedHeader 'cpSL', // BlockTypePagedConnection 'rpSL', // BlockTypePagedRfcb 'mpSL', // BlockTypePagedMfcb 'itSL', // BlockTypeTimer 'caSL', // BlockTypeAdminCheck 'qwSL', // BlockTypeWorkQueue 'fsSL', // BlockTypeDfs 'rlSL', // BlockTypeLargeReadX 'saSL', // BlockTypeAdapterStatus 'rsSL', // BlockTypeShareRemark 'dsSL', // BlockTypeShareSecurityDescriptor 'ivSL', // BlockTypeVolumeInformation 'nfSL', // BlockTypeFSName 'inSL', // BlockTypeNameInfo 'idSL', // BlockTypeDirectoryInfo 'cdSL', // BlockTypeDirCache 'imSL', // BlockTypeMisc 'nsSL', // BlockTypeSnapShot #ifdef INCLUDE_SMB_PERSISTENT 'spSL', // BlockTypePersistentState 'bpSL', // BlockTypePersistentBitMap 'hpSL', // BlockTypePersistShareState #endif }; // // Macro to map from block type to pool tag. // #define TAG_FROM_TYPE(_type) SrvPoolTags[(_type)-1] #else #define TAG_FROM_TYPE(_type) ignoreme #endif // def POOL_TAGGING #ifdef ALLOC_PRAGMA #pragma alloc_text( PAGE, SrvAllocatePagedPool ) #pragma alloc_text( PAGE, SrvFreePagedPool ) #pragma alloc_text( PAGE, SrvClearLookAsideList ) #endif #if 0 NOT PAGEABLE -- SrvAllocateNonPagedPool NOT PAGEABLE -- SrvFreeNonPagedPool #endif extern LONG SrvMemoryAllocationRetries; extern LONG SrvMemoryAllocationRetriesSuccessful; PVOID SRVFASTCALL SrvInterlockedAllocate( PLOOK_ASIDE_LIST l, ULONG NumberOfBytes, PLONG statistics ) { PPOOL_HEADER newPool; PPOOL_HEADER *pentry = NumberOfBytes > LOOK_ASIDE_SWITCHOVER ? l->LargeFreeList : l->SmallFreeList; PPOOL_HEADER *pend = pentry + LOOK_ASIDE_MAX_ELEMENTS; do { // // Exchange with the lookaside spot and see if we get anything // newPool = NULL; newPool = (PPOOL_HEADER)InterlockedExchangePointer( pentry, newPool ); if( newPool == NULL ) { continue; } if( newPool->RequestedSize >= NumberOfBytes ) { // // The one we got is big enough! Return it. // ++(l->AllocHit); return newPool + 1; } // // It wasn't big enough, so put it back. // newPool = (PPOOL_HEADER)InterlockedExchangePointer( pentry, newPool ); if( newPool == NULL ) { continue; } // // Oops, somebody else freed some memory to this spot. Can we use it? // if( newPool->RequestedSize >= NumberOfBytes ) { // // We can use it! // ++(l->AllocHit); return newPool + 1; } // // Can't use the memory -- so really free it and keep looking // if( statistics ) { InterlockedExchangeAdd( statistics, -(LONG)newPool->RequestedSize ); } ExFreePool( newPool ); } while( ++pentry < pend ); ++(l->AllocMiss); return NULL; } PPOOL_HEADER SRVFASTCALL SrvInterlockedFree( PPOOL_HEADER block ) { PPOOL_HEADER *pentry = block->FreeList; PPOOL_HEADER *pend = pentry + LOOK_ASIDE_MAX_ELEMENTS; do { block = (PPOOL_HEADER)InterlockedExchangePointer( pentry, block ); } while( block != NULL && ++pentry < pend ); return block; } VOID SRVFASTCALL SrvClearLookAsideList( PLOOK_ASIDE_LIST l, VOID (SRVFASTCALL *FreeRoutine )( PVOID ) ) { PPOOL_HEADER *pentry, *pend, block; PAGED_CODE(); // // Clear out the list of large chunks // pentry = l->LargeFreeList; pend = pentry + LOOK_ASIDE_MAX_ELEMENTS; do { block = NULL; block = (PPOOL_HEADER)InterlockedExchangePointer( pentry, block ); if( block != NULL ) { block->FreeList = NULL; FreeRoutine( block + 1 ); } } while( ++pentry < pend ); // // Clear out the list of small chunks // pentry = l->SmallFreeList; pend = pentry + LOOK_ASIDE_MAX_ELEMENTS; do { block = NULL; block = (PPOOL_HEADER)InterlockedExchangePointer( pentry, block ); if( block != NULL ) { block->FreeList = NULL; FreeRoutine( block + 1 ); } } while( ++pentry < pend ); } PVOID SRVFASTCALL SrvAllocateNonPagedPool ( IN CLONG NumberOfBytes #ifdef POOL_TAGGING , IN CLONG BlockType #endif ) /*++ Routine Description: This routine allocates nonpaged pool in the server. A check is made to ensure that the server's total nonpaged pool usage is below the configurable limit. Arguments: NumberOfBytes - the number of bytes to allocate. BlockType - the type of block (used to pass pool tag to allocator) Return Value: PVOID - a pointer to the allocated memory or NULL if the memory could not be allocated. --*/ { PPOOL_HEADER newPool; PPOOL_HEADER *FreeList = NULL; ULONG newUsage; BOOLEAN IsLowPriority = FALSE; LARGE_INTEGER interval; #ifdef POOL_TAGGING ASSERT( BlockType > 0 && BlockType < BlockTypeMax ); #endif // // Pull this allocation off the per-processor free list if we can // if( SrvWorkQueues ) { PWORK_QUEUE queue = PROCESSOR_TO_QUEUE(); if( NumberOfBytes <= queue->NonPagedPoolLookAsideList.MaxSize ) { newPool = SrvInterlockedAllocate( &queue->NonPagedPoolLookAsideList, NumberOfBytes, (PLONG)&SrvStatistics.CurrentNonPagedPoolUsage ); if( newPool != NULL ) { return newPool; } FreeList = NumberOfBytes > LOOK_ASIDE_SWITCHOVER ? queue->NonPagedPoolLookAsideList.LargeFreeList : queue->NonPagedPoolLookAsideList.SmallFreeList ; } } // // Account for this allocation in the statistics database and make // sure that this allocation will not put us over the limit of // nonpaged pool that we can allocate. // newUsage = InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentNonPagedPoolUsage, (LONG)NumberOfBytes ); newUsage += NumberOfBytes; if ( newUsage > SrvMaxNonPagedPoolUsage ) { // // Count the failure, but do NOT log an event. The scavenger // will log an event when it next wakes up. This keeps us from // flooding the event log. // SrvNonPagedPoolLimitHitCount++; SrvStatistics.NonPagedPoolFailures++; InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentNonPagedPoolUsage, -(LONG)NumberOfBytes ); return NULL; } if (SrvStatistics.CurrentNonPagedPoolUsage > SrvStatistics.PeakNonPagedPoolUsage) { SrvStatistics.PeakNonPagedPoolUsage = SrvStatistics.CurrentNonPagedPoolUsage; } // // Do the actual memory allocation. Allocate extra space so that we // can store the size of the allocation for the free routine. // if( NumberOfBytes > 2*4096 ) { IsLowPriority = TRUE; } newPool = ExAllocatePoolWithTagPriority( NonPagedPool, NumberOfBytes + sizeof(POOL_HEADER), TAG_FROM_TYPE(BlockType), IsLowPriority ? LowPoolPriority : NormalPoolPriority ); if( (newPool == NULL) && IsLowPriority && (KeGetCurrentIrql() <= APC_LEVEL) ) { interval.QuadPart = SRV_LOW_PRIORITY_RETRY_TIME; InterlockedIncrement( &SrvMemoryAllocationRetries ); // Wait and try again KeDelayExecutionThread( KernelMode, FALSE, &interval ); newPool = ExAllocatePoolWithTagPriority( NonPagedPool, NumberOfBytes + sizeof(POOL_HEADER), TAG_FROM_TYPE(BlockType), LowPoolPriority ); if( newPool ) { InterlockedIncrement( &SrvMemoryAllocationRetriesSuccessful ); } } // // If the system couldn't satisfy the request, return NULL. // if ( newPool != NULL ) { // // Save the size of this block in the extra space we allocated. // newPool->RequestedSize = NumberOfBytes; newPool->FreeList = FreeList; // // Return a pointer to the memory after the size longword. // return (PVOID)( newPool + 1 ); } // // Count the failure, but do NOT log an event. The scavenger // will log an event when it next wakes up. This keeps us from // flooding the event log. // SrvStatistics.NonPagedPoolFailures++; InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentNonPagedPoolUsage, -(LONG)NumberOfBytes ); return NULL; } // SrvAllocateNonPagedPool VOID SRVFASTCALL SrvFreeNonPagedPool ( IN PVOID Address ) /*++ Routine Description: Frees the memory allocated by a call to SrvAllocateNonPagedPool. The statistics database is updated to reflect the current nonpaged pool usage. Arguments: Address - the address of allocated memory returned by SrvAllocateNonPagedPool. Return Value: None. --*/ { PPOOL_HEADER actualBlock = (PPOOL_HEADER)Address - 1; // // See if we can stash this bit of memory away in the NonPagedPoolFreeList // if( actualBlock->FreeList ) { actualBlock = SrvInterlockedFree( actualBlock ); } if( actualBlock != NULL ) { // // Update the nonpaged pool usage statistic. // InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentNonPagedPoolUsage, -(LONG)actualBlock->RequestedSize ); // // Free the pool and return. // ExFreePool( actualBlock ); } } // SrvFreeNonPagedPool PVOID SRVFASTCALL SrvAllocatePagedPool ( IN POOL_TYPE PoolType, IN CLONG NumberOfBytes #ifdef POOL_TAGGING , IN CLONG BlockType #endif ) /*++ Routine Description: This routine allocates Paged pool in the server. A check is made to ensure that the server's total Paged pool usage is below the configurable limit. Arguments: NumberOfBytes - the number of bytes to allocate. BlockType - the type of block (used to pass pool tag to allocator) Return Value: PVOID - a pointer to the allocated memory or NULL if the memory could not be allocated. --*/ { PPOOL_HEADER newPool; PPOOL_HEADER *FreeList = NULL; ULONG newUsage; BOOLEAN IsLowPriority = FALSE; LARGE_INTEGER interval; PAGED_CODE(); #ifdef POOL_TAGGING ASSERT( BlockType > 0 && BlockType < BlockTypeMax ); #endif // // Pull this allocation off the per-processor free list if we can // if( SrvWorkQueues ) { PWORK_QUEUE queue = PROCESSOR_TO_QUEUE(); if( NumberOfBytes <= queue->PagedPoolLookAsideList.MaxSize ) { newPool = SrvInterlockedAllocate( &queue->PagedPoolLookAsideList, NumberOfBytes, (PLONG)&SrvStatistics.CurrentPagedPoolUsage ); if( newPool != NULL ) { return newPool; } FreeList = NumberOfBytes > LOOK_ASIDE_SWITCHOVER ? queue->PagedPoolLookAsideList.LargeFreeList : queue->PagedPoolLookAsideList.SmallFreeList ; } } // // Account for this allocation in the statistics database and make // sure that this allocation will not put us over the limit of // nonpaged pool that we can allocate. // newUsage = InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentPagedPoolUsage, (LONG)NumberOfBytes ); newUsage += NumberOfBytes; if ( newUsage > SrvMaxPagedPoolUsage ) { // // Count the failure, but do NOT log an event. The scavenger // will log an event when it next wakes up. This keeps us from // flooding the event log. // SrvPagedPoolLimitHitCount++; SrvStatistics.PagedPoolFailures++; InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentPagedPoolUsage, -(LONG)NumberOfBytes ); return NULL; } if (SrvStatistics.CurrentPagedPoolUsage > SrvStatistics.PeakPagedPoolUsage ) { SrvStatistics.PeakPagedPoolUsage = SrvStatistics.CurrentPagedPoolUsage; } // // Do the actual memory allocation. Allocate extra space so that we // can store the size of the allocation for the free routine. // if( NumberOfBytes > 2*4096 ) { IsLowPriority = TRUE; } newPool = ExAllocatePoolWithTagPriority( PoolType, NumberOfBytes + sizeof(POOL_HEADER), TAG_FROM_TYPE(BlockType), IsLowPriority ? LowPoolPriority : NormalPoolPriority ); if( (newPool == NULL) && IsLowPriority && (KeGetCurrentIrql() <= APC_LEVEL) ) { interval.QuadPart = SRV_LOW_PRIORITY_RETRY_TIME; InterlockedIncrement( &SrvMemoryAllocationRetries ); // Wait and try again KeDelayExecutionThread( KernelMode, FALSE, &interval ); newPool = ExAllocatePoolWithTagPriority( PoolType, NumberOfBytes + sizeof(POOL_HEADER), TAG_FROM_TYPE(BlockType), LowPoolPriority ); if( newPool ) { InterlockedIncrement( &SrvMemoryAllocationRetriesSuccessful ); } } if( newPool != NULL ) { newPool->FreeList = FreeList; newPool->RequestedSize = NumberOfBytes; // // Return a pointer to the memory after the POOL_HEADER // return newPool + 1; } // // If the system couldn't satisfy the request, return NULL. // // Count the failure, but do NOT log an event. The scavenger // will log an event when it next wakes up. This keeps us from // flooding the event log. // SrvStatistics.PagedPoolFailures++; InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentPagedPoolUsage, -(LONG)NumberOfBytes ); return NULL; } // SrvAllocatePagedPool VOID SRVFASTCALL SrvFreePagedPool ( IN PVOID Address ) /*++ Routine Description: Frees the memory allocated by a call to SrvAllocatePagedPool. The statistics database is updated to reflect the current Paged pool usage. If this routine is change, look at scavengr.c Arguments: Address - the address of allocated memory returned by SrvAllocatePagedPool. Return Value: None. --*/ { PPOOL_HEADER actualBlock = (PPOOL_HEADER)Address - 1; PAGED_CODE(); ASSERT( actualBlock != NULL ); // // See if we can stash this bit of memory away in the PagedPoolFreeList // if( actualBlock->FreeList ) { actualBlock = SrvInterlockedFree( actualBlock ); } if( actualBlock != NULL ) { // // Update the Paged pool usage statistic. // ASSERT( SrvStatistics.CurrentPagedPoolUsage >= actualBlock->RequestedSize ); InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentPagedPoolUsage, -(LONG)actualBlock->RequestedSize ); ASSERT( (LONG)SrvStatistics.CurrentPagedPoolUsage >= 0 ); // // Free the pool and return. // ExFreePool( actualBlock ); } } // SrvFreePagedPool