/** * PROJECT: ExectOS * COPYRIGHT: See COPYING.md in the top level directory * FILE: xtoskrnl/po/quota.cc * DESCRIPTION: Process Quota Management * DEVELOPERS: Aiken Harris */ #include /** * Charges a specified amount of quota against a given process quota block. * * @param QuotaBlock * Supplies a pointer to the quota block from which the charge is being requested. * * @param Process * Supplies an optional pointer to the process object associated with the charge. * * @param QuotaType * Supplies the specific type of quota being charged. * * @param Amount * Supplies the exact size, in bytes, of the quota charge. * * @return This routine returns a status code indicating the success or failure of the operation. * * @since XT 1.0 */ XTAPI XTSTATUS PS::Quota::ChargeProcessQuota(IN PEPROCESS_QUOTA_BLOCK QuotaBlock, IN PEPROCESS Process, IN PS_QUOTA_TYPE QuotaType, IN SIZE_T Amount) { SIZE_T CurrentLimit, CurrentUsage, NewUsage, ObservedUsage, RecoveredQuota; PEPROCESS_QUOTA_ENTRY QuotaEntry; /* Check if the system process is being charged */ if(Process == PS::Process::GetSystemProcess()) { /* Bypass quota enforcement for the initial system process, return success */ return STATUS_SUCCESS; } /* Resolve the quota entry */ QuotaEntry = &QuotaBlock->QuotaEntry[QuotaType]; CurrentUsage = QuotaEntry->Usage; /* Memory barrier */ AR::CpuFunctions::MemoryBarrier(); /* Snapshot the current limit */ CurrentLimit = QuotaEntry->Limit; /* Enter the lock-free contention loop */ while(TRUE) { /* Calculate the projected usage */ NewUsage = CurrentUsage + Amount; /* Check if an integer overflow occurs */ if(NewUsage < CurrentUsage) { /* Reject the charge, return error code */ return STATUS_QUOTA_EXCEEDED; } /* Check if the projected usage is within the known limit */ if(NewUsage <= CurrentLimit) { /* Attempt to commit the new usage value */ ObservedUsage = RTL::Atomic::CompareExchange64((PLONG_PTR)&QuotaEntry->Usage, NewUsage, CurrentUsage); if(ObservedUsage == CurrentUsage) { /* Successfully acquired quota, update the peak usage */ UpdatePeakUsage(&QuotaEntry->Peak, NewUsage); /* Check if a process context was provided */ if(Process) { /* Add the amount to the process's usage tracker */ NewUsage = RTL::Atomic::Add64((PLONG_PTR)&Process->QuotaUsage[QuotaType], Amount); /* Update the per-process peak usage */ UpdatePeakUsage(&Process->QuotaPeak[QuotaType], NewUsage); } /* Quota charge completed, return success */ return STATUS_SUCCESS; } /* Refresh the snapshot */ CurrentUsage = ObservedUsage; /* Memory barrier */ AR::CpuFunctions::MemoryBarrier(); /* Refresh the limit snapshot */ CurrentLimit = QuotaEntry->Limit; continue; } /* Check if this is a pagefile quota */ if(QuotaType == PsPageFile) { /* Pagefile quota cannot be expanded, return error code */ return STATUS_PAGEFILE_QUOTA_EXCEEDED; } /* Attempt to reclaim quota that was returned but not yet merged */ RecoveredQuota = RTL::Atomic::Exchange64((PLONG_PTR)&QuotaEntry->Return, 0); if(RecoveredQuota > 0) { /* Reclaim successful, add the recovered amount and retry */ CurrentLimit = RTL::Atomic::Add64((PLONG_PTR)&QuotaEntry->Limit, RecoveredQuota); continue; } /* Reclaim failed, attempt to expand the quota limit */ if(MM::Quota::RaisePoolQuota((MMPOOL_TYPE)QuotaType, CurrentLimit, &CurrentLimit)) { /* Expansion successful, update the limit and retry */ RTL::Atomic::Exchange64((PLONG_PTR)&QuotaEntry->Limit, CurrentLimit); continue; } /* Quota charge failed, return error code */ return STATUS_QUOTA_EXCEEDED; } } /** * Charges specified amounts of paged and non-paged pool quotas against a given process quota block. * * @param Process * Supplies a pointer to the target process whose quota block will be charged. * * @param PagedAmount * Supplies the amount of paged pool quota to charge, in bytes. * * @param NonPagedAmount * Supplies the amount of non-paged pool quota to charge, in bytes. * * @return This routine returns a pointer to the charged quota block, or NULLPTR if either of the pool limits is exceeded. * * @since XT 1.0 */ XTAPI PEPROCESS_QUOTA_BLOCK PS::Quota::ChargeSharedPoolQuota(IN PEPROCESS Process, IN SIZE_T PagedAmount, IN SIZE_T NonPagedAmount) { XTSTATUS Status; /* Check if the system process is being charged */ if(Process == PS::Process::GetSystemProcess()) { /* Return the pseudo-marker allowing allocations without tracking */ return PS_QUOTA_BYPASS_MARKER; } /* Check if a non-paged pool charge is required */ if(NonPagedAmount) { /* Attempt to charge the requested non-paged pool amount */ Status = ChargeProcessQuota(Process->QuotaBlock, NULLPTR, PsNonPagedPool, NonPagedAmount); if(Status != STATUS_SUCCESS) { /* Non-paged pool charge failed, return NULL pointer */ return NULLPTR; } } /* Check if a paged pool charge is required */ if(PagedAmount) { /* Attempt to charge the requested paged pool amount */ Status = ChargeProcessQuota(Process->QuotaBlock, NULLPTR, PsPagedPool, PagedAmount); if(Status != STATUS_SUCCESS) { /* Paged pool charge failed, rollback the non-paged pool quota and return NULL pointer */ PS::Quota::ReturnProcessQuota(Process->QuotaBlock, NULLPTR, PsNonPagedPool, NonPagedAmount); return NULLPTR; } } /* Increment the reference count of the quota block */ RTL::Atomic::Increment32((VOLATILE PLONG)&Process->QuotaBlock->ReferenceCount); /* Return the charged quota block */ return Process->QuotaBlock; } /** * Decrements the reference count of a quota block and destroys it if it reaches zero. * * @param QuotaBlock * Supplies a pointer to the process quota block to dereference. * * @return This routine does not return any value. * * @since XT 1.0 */ XTAPI VOID PS::Quota::DereferenceQuotaBlock(IN PEPROCESS_QUOTA_BLOCK QuotaBlock) { SIZE_T ReturnAmount; ULONG Index; /* Guard against null pointer and the bypass marker */ if(QuotaBlock == NULLPTR || QuotaBlock == PS_QUOTA_BYPASS_MARKER) { /* No cleanup is required, return */ return; } /* Decrement the reference count */ if(RTL::Atomic::Decrement32((VOLATILE PLONG)&QuotaBlock->ReferenceCount) == 0) { /* Start a guarded code block */ { /* Raise runlevel to DISPATCH level */ KE::RaiseRunLevel RunLevel(DISPATCH_LEVEL); /* Iterate through all available pool quota types */ for(Index = PsNonPagedPool; Index <= PsPagedPool; Index++) { /* Calculate the total residual quota */ ReturnAmount = QuotaBlock->QuotaEntry[Index].Return + QuotaBlock->QuotaEntry[Index].Limit; /* Check if there is any residual quota left */ if(ReturnAmount > 0) { /* Return the residual quota */ MM::Quota::ReturnPoolQuota((MMPOOL_TYPE)Index, ReturnAmount); } } /* Acquire a quota lock */ KE::SpinLockGuard SpinLock(&QuotaLock); /* Unlink the quota block from the global tracking list */ RTL::LinkedList::RemoveEntryList(&QuotaBlock->QuotaList); } /* Free the physical memory */ MM::Allocator::FreePool(QuotaBlock, TAG_PS_QUOTA_BLOCK); } } /** * Initializes the system-wide process quota subsystem. * * @return This routine does not return any value. * * @since XT 1.0 */ VOID XTAPI PS::Quota::InitializeQuota(VOID) { /* Initialize the quota lock */ KE::SpinLock::InitializeSpinLock(&QuotaLock); /* Clear the default quota block */ RtlZeroMemory(&DefaultQuotaBlock, sizeof(DefaultQuotaBlock)); /* Set up the counters */ DefaultQuotaBlock.ProcessCount = 1; DefaultQuotaBlock.ReferenceCount = 1; /* Set up the limits */ DefaultQuotaBlock.QuotaEntry[PsNonPagedPool].Limit = (SIZE_T)-1; DefaultQuotaBlock.QuotaEntry[PsPagedPool].Limit = (SIZE_T)-1; DefaultQuotaBlock.QuotaEntry[PsPageFile].Limit = (SIZE_T)-1; /* Assign the default quota block to the current process */ PS::Process::GetCurrentProcess()->QuotaBlock = &DefaultQuotaBlock; } /** * Returns a specified amount of quota to a quota block and updates process usage. * * @param QuotaBlock * Supplies the quota block receiving the returned quota. * * @param Process * Supplies an optional pointer to the process whose usage is being reduced. * * @param QuotaType * Supplies the type of quota being returned. * * @param Amount * Supplies the amount of quota to return. * * @return This routine does not return any value. * * @since XT 1.0 */ XTFASTCALL VOID PS::Quota::ReturnProcessQuota(IN PEPROCESS_QUOTA_BLOCK QuotaBlock, IN PEPROCESS Process, IN PS_QUOTA_TYPE QuotaType, IN SIZE_T Amount) { SIZE_T AccumulatedGiveBack, ExtractedReturnAmount, GiveBackAmount, GiveBackLimit, MinGiveBackThreshold; SIZE_T CurrentLimit, CurrentUsage, ExpectedLimit, NewLimit; PEPROCESS_QUOTA_ENTRY QuotaEntry; /* Initialize local pointers and fetch the current usage for the specified quota type */ QuotaEntry = &QuotaBlock->QuotaEntry[QuotaType]; CurrentUsage = QuotaEntry->Usage; /* Check if process provided */ if(Process != NULLPTR) { /* Check if this is the system process */ if(Process == PS::Process::GetSystemProcess()) { /* Do not do anything for system processes, return */ return; } else if(Process->QuotaUsage[QuotaType] < Amount || CurrentUsage < Amount) { /* Quota underflow detected, raise kernel panic */ KE::Crash::Panic(0x21, (ULONG_PTR)Process, (ULONG_PTR)QuotaType, Process ? (ULONG_PTR)Process->QuotaUsage[QuotaType] : (ULONG_PTR)CurrentUsage, (ULONG_PTR)Amount); } } /* Determine the thresholds for returning excess quota limit */ CurrentLimit = QuotaEntry->Limit; MinGiveBackThreshold = (MMPAGED_QUOTA_INCREASE > MMNONPAGED_QUOTA_INCREASE) ? MMNONPAGED_QUOTA_INCREASE : MMPAGED_QUOTA_INCREASE; /* Check if the current limit exceeds usage by at least the minimum giveback threshold */ if((CurrentLimit > CurrentUsage) && ((CurrentLimit - CurrentUsage) > MinGiveBackThreshold)) { /* Only proceed if this is a pool quota */ if(QuotaType != PsPageFile && QuotaBlock != &DefaultQuotaBlock) { /* Calculate the exact amount of limit to trim based on pool type block size */ GiveBackLimit = (QuotaType == PsPagedPool) ? MMPAGED_QUOTA_INCREASE : MMNONPAGED_QUOTA_INCREASE; GiveBackAmount = (GiveBackLimit > Amount) ? Amount : GiveBackLimit; /* Attempt a lock-free update to shrink the quota limit */ NewLimit = CurrentLimit - GiveBackAmount; ExpectedLimit = (SIZE_T)RTL::Atomic::CompareExchange64((PLONG_PTR)&QuotaEntry->Limit, (LONG_PTR)NewLimit, (LONG_PTR)CurrentLimit); /* Check if the atomic limit update succeeded */ if(ExpectedLimit == CurrentLimit) { /* Add to the return counter */ AccumulatedGiveBack = RTL::Atomic::Add64((PLONG_PTR)&QuotaEntry->Return, GiveBackAmount); /* Check if the accumulated returns exceed the threshold */ if(AccumulatedGiveBack > GiveBackLimit) { /* Extract the accumulated return amount and reset the counter to zero */ ExtractedReturnAmount = (SIZE_T)RTL::Atomic::Exchange64((PLONG_PTR)&QuotaEntry->Return, (LONG_PTR)0); /* Check if there is anything to return */ if(ExtractedReturnAmount > 0) { /* Return the extracted quota back to the system pool */ MM::Quota::ReturnPoolQuota((MMPOOL_TYPE)QuotaType, ExtractedReturnAmount); } } } } } /* Decrease the block's overall usage count */ RTL::Atomic::Add64((PLONG_PTR)&QuotaEntry->Usage, (SIZE_T)(-(SSIZE_T)Amount)); /* Check if a specific process was provided */ if(Process) { /* Decrease the process's specific usage count */ RTL::Atomic::Add64((PLONG_PTR)&Process->QuotaUsage[QuotaType], (SIZE_T)(-(SSIZE_T)Amount)); } } /** * Returns previously charged pool amounts back to a shared quota block. * * @param QuotaBlock * Supplies a pointer to the process quota block. * * @param PagedAmount * Supplies the amount of paged pool quota to return, in bytes. * * @param NonPagedAmount * Supplies the amount of non-paged pool quota to return, in bytes. * * @return This routine does not return any value. * * @since XT 1.0 */ XTAPI VOID PS::Quota::ReturnSharedPoolQuota(IN PEPROCESS_QUOTA_BLOCK QuotaBlock, IN SIZE_T PagedAmount, IN SIZE_T NonPagedAmount) { /* Check for NULL pointers and bypass markers */ if(!QuotaBlock || QuotaBlock == PS_QUOTA_BYPASS_MARKER) { /* No quota tracking is required for the system process or empty blocks, return */ return; } /* Check for paged pool quota */ if(PagedAmount > 0) { /* Process the return of paged pool memory */ ReturnProcessQuota(QuotaBlock, NULLPTR, PsPagedPool, PagedAmount); } /* Check for non-paged pool quota */ if(NonPagedAmount > 0) { /* Process the return of non-paged pool memory */ ReturnProcessQuota(QuotaBlock, NULLPTR, PsNonPagedPool, NonPagedAmount); } /* Drop the reference count on the quota block */ DereferenceQuotaBlock(QuotaBlock); } /** * Updates a quota size tracker to a new peak maximum. * * @param TargetQuota * Supplies a pointer to the variable tracking the peak usage. * * @param NewQuota * Supplies the newly calculated usage to be evaluated against the peak. * * @return This routine does not return any value. * * @since XT 1.0 */ XTINLINE VOID PS::Quota::UpdatePeakUsage(IN PSIZE_T TargetQuota, IN SIZE_T NewQuota) { SIZE_T CurrentQuota; SIZE_T ObservedQuota; /* Snapshot the initial memory value */ CurrentQuota = *TargetQuota; /* Enter the CAS loop */ while(TRUE) { /* Check if the proposed quota does not exceed the current peak */ if(NewQuota <= CurrentQuota) { /* Nothing to update, break the loop */ break; } /* Attempt to overwrite the peak usage */ ObservedQuota = RTL::Atomic::CompareExchange64((PLONG_PTR)TargetQuota, NewQuota, CurrentQuota); if(ObservedQuota == CurrentQuota) { /* The peak successfully updated, break the loop */ break; } /* Update the snapshot and retry */ CurrentQuota = ObservedQuota; } }