Windows2000/private/ntos/ke/balmgr.c
2020-09-30 17:12:32 +02:00

865 lines
20 KiB
C

/*++
Copyright (c) 1991-1994 Microsoft Corporation
Module Name:
balmgr.c
Abstract:
This module implements the NT balance set manager. Normally the kernel
does not contain "policy" code. However, the balance set manager needs
to be able to traverse the kernel data structures and, therefore, the
code has been located as logically part of the kernel.
The balance set manager performs the following operations:
1. Makes the kernel stack of threads that have been waiting for a
certain amount of time, nonresident.
2. Removes processes from the balance set when memory gets tight
and brings processes back into the balance set when there is
more memory available.
3. Makes the kernel stack resident for threads whose wait has been
completed, but whose stack is nonresident.
4. Arbitrarily boosts the priority of a selected set of threads
to prevent priority inversion in variable priority levels.
In general, the balance set manager only is active during periods when
memory is tight.
Author:
David N. Cutler (davec) 13-Jul-1991
Environment:
Kernel mode only.
Revision History:
--*/
#include "ki.h"
// Define balance set wait object types.
typedef enum _BALANCE_OBJECT {
TimerExpiration,
WorkingSetManagerEvent,
MaximumObject
} BALANCE_OBJECT;
// Define maximum number of thread stacks that can be out swapped in
// a single time period.
#define MAXIMUM_THREAD_STACKS 20
// Define periodic wait interval value.
#define PERIODIC_INTERVAL (1 * 1000 * 1000 * 10)
// Define amount of time a thread can be in the ready state without having
// is priority boosted (approximately 4 seconds).
#define READY_WITHOUT_RUNNING (4 * 75)
// Define kernel stack protect time. For small systems the protect time
// is 3 seconds. For all other systems, the protect time is 7 seconds.
#define SMALL_SYSTEM_STACK_PROTECT_TIME (3 * 75)
#define STACK_PROTECT_TIME (7 * 75)
#define STACK_SCAN_PERIOD 4
ULONG KiStackProtectTime;
// Define number of threads to scan each period and the priority boost bias.
#define THREAD_BOOST_BIAS 1
#define THREAD_BOOST_PRIORITY (LOW_REALTIME_PRIORITY - THREAD_BOOST_BIAS)
#define THREAD_SCAN_PRIORITY (THREAD_BOOST_PRIORITY - 1)
#define THREAD_READY_COUNT 10
#define THREAD_SCAN_COUNT 16
#define EXECUTION_TIME_LIMITS_PERIOD 7
// Define local procedure prototypes.
VOID
KiInSwapKernelStacks (
IN KIRQL PreviousIrql
);
VOID
KiInSwapProcesses (
IN KIRQL PreviousIrql
);
VOID
KiOutSwapKernelStacks (
IN KIRQL PreviousIrql
);
VOID
KiOutSwapProcesses (
IN KIRQL PreviousIrql
);
VOID
KiScanReadyQueues (
VOID
);
// Define thread table index static data.
ULONG KiReadyQueueIndex = 1;
// Define swap request flag.
BOOLEAN KiStackOutSwapRequest = FALSE;
VOID
KeBalanceSetManager (
IN PVOID Context
)
/*++
Routine Description:
This function is the startup code for the balance set manager. The
balance set manager thread is created during system initialization
and begins execution in this function.
Arguments:
Context - Supplies a pointer to an arbitrary data structure (NULL).
Return Value:
None.
--*/
{
LARGE_INTEGER DueTime;
KTIMER PeriodTimer;
KIRQL OldIrql;
ULONG StackScanPeriod;
ULONG ExecutionTimeLimitPeriod;
NTSTATUS Status;
KWAIT_BLOCK WaitBlockArray[MaximumObject];
PVOID WaitObjects[MaximumObject];
// Raise the thread priority to the lowest realtime level.
KeSetPriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY);
// Initialize the periodic timer, set it to expire one period from
// now, and set the stack scan period.
KeInitializeTimer(&PeriodTimer);
DueTime.QuadPart = - PERIODIC_INTERVAL;
KeSetTimer(&PeriodTimer, DueTime, NULL);
StackScanPeriod = STACK_SCAN_PERIOD;
ExecutionTimeLimitPeriod = EXECUTION_TIME_LIMITS_PERIOD;
// Compute the stack protect time based on the system size.
if (MmQuerySystemSize() == MmSmallSystem) {
KiStackProtectTime = SMALL_SYSTEM_STACK_PROTECT_TIME;
} else {
KiStackProtectTime = STACK_PROTECT_TIME;
}
// Initialize the wait objects array.
WaitObjects[TimerExpiration] = (PVOID)&PeriodTimer;
WaitObjects[WorkingSetManagerEvent] = (PVOID)&MmWorkingSetManagerEvent;
// Loop forever processing balance set manager events.
do {
// Wait for a memory management memory low event, a swap event,
// or the expiration of the period timout rate that the balance
// set manager runs at.
Status = KeWaitForMultipleObjects(MaximumObject,
&WaitObjects[0],
WaitAny,
Executive,
KernelMode,
FALSE,
NULL,
&WaitBlockArray[0]);
// Switch on the wait status.
switch (Status) {
// Periodic timer expiration.
case TimerExpiration:
// Attempt to initiate outswaping of kernel stacks.
StackScanPeriod -= 1;
if (StackScanPeriod == 0) {
StackScanPeriod = STACK_SCAN_PERIOD;
KiLockDispatcherDatabase(&OldIrql);
if (KiStackOutSwapRequest == FALSE) {
KiStackOutSwapRequest = TRUE;
KiUnlockDispatcherDatabase(OldIrql);
KeSetEvent(&KiSwapEvent, 0, FALSE);
} else {
KiUnlockDispatcherDatabase(OldIrql);
}
}
// Adjust the depth of lookaside lists.
ExAdjustLookasideDepth();
// Scan ready queues and boost thread priorities as appropriate.
KiScanReadyQueues();
// Execute the virtual memory working set manager.
MmWorkingSetManager();
// Enforce execution time limits
ExecutionTimeLimitPeriod -= 1;
if (ExecutionTimeLimitPeriod == 0) {
ExecutionTimeLimitPeriod = EXECUTION_TIME_LIMITS_PERIOD;
PsEnforceExecutionTimeLimits();
}
// Set the timer to expire at the next periodic interval.
KeSetTimer(&PeriodTimer, DueTime, NULL);
break;
// Working set manager event.
case WorkingSetManagerEvent:
// Call the working set manager to trim working sets.
MmWorkingSetManager();
break;
// Illegal return status.
default:
KdPrint(("BALMGR: Illegal wait status, %lx =\n", Status));
break;
}
} while (TRUE);
return;
}
VOID
KeSwapProcessOrStack (
IN PVOID Context
)
/*++
Routine Description:
This thread controls the swapping of processes and kernel stacks. The
order of evaluation is:
Outswap kernel stacks
Outswap processes
Inswap processes
Inswap kernel stacks
Arguments:
Context - Supplies a pointer to the routine context - not used.
Return Value:
None.
--*/
{
KIRQL OldIrql;
NTSTATUS Status;
// Raise the thread priority to the lowest realtime level + 7 (i.e.,
// priority 23).
KeSetPriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY + 7);
// Loop for ever processing swap events.
do {
// Wait for a swap event to occur.
Status = KeWaitForSingleObject(&KiSwapEvent,
Executive,
KernelMode,
FALSE,
NULL);
// Raise IRQL to dispatcher level and lock dispatcher database.
KiLockDispatcherDatabase(&OldIrql);
// Loop until all of the four possible actions cannot be initiated.
do {
// If a request has been made to out swap kernel stacks, then
// attempt to outswap kernel stacks. Otherwise, if the process
// out swap list is not empty, then initiate process outswapping.
// Otherwise, if the process inswap list is not empty, then start
// process inswapping. Otherwise, if the kernal stack inswap list
// is not active, then initiate kernel stack inswapping. Otherwise,
// no work is available.
if (KiStackOutSwapRequest != FALSE) {
KiStackOutSwapRequest = FALSE;
KiOutSwapKernelStacks(OldIrql);
continue;
} else if (IsListEmpty(&KiProcessOutSwapListHead) == FALSE) {
KiOutSwapProcesses(OldIrql);
continue;
} else if (IsListEmpty(&KiProcessInSwapListHead) == FALSE) {
KiInSwapProcesses(OldIrql);
continue;
} else if (IsListEmpty(&KiStackInSwapListHead) == FALSE) {
KiInSwapKernelStacks(OldIrql);
continue;
} else {
break;
}
} while (TRUE);
// Unlock the dispatcher database and lower IRQL to its previous
// value.
KiUnlockDispatcherDatabase(OldIrql);
} while (TRUE);
return;
}
VOID
KiInSwapKernelStacks (
IN KIRQL PreviousIrql
)
/*++
Routine Description:
This function in swaps the kernel stack for threads whose wait has been
completed and whose kernel stack is nonresident.
N.B. The dispatcher data lock is held on entry to this routine and must
be help on exit to this routine.
Arguments:
PreviousIrql - Supplies the previous IRQL.
Return Value:
None.
--*/
{
PLIST_ENTRY NextEntry;
KIRQL OldIrql;
PKTHREAD Thread;
// Process the stack in swap list and for each thread removed from the
// list, make its kernel stack resident, and ready it for execution.
OldIrql = PreviousIrql;
NextEntry = KiStackInSwapListHead.Flink;
while (NextEntry != &KiStackInSwapListHead) {
Thread = CONTAINING_RECORD(NextEntry, KTHREAD, WaitListEntry);
RemoveEntryList(NextEntry);
KiUnlockDispatcherDatabase(OldIrql);
MmInPageKernelStack(Thread);
KiLockDispatcherDatabase(&OldIrql);
Thread->KernelStackResident = TRUE;
KiReadyThread(Thread);
NextEntry = KiStackInSwapListHead.Flink;
}
return;
}
VOID
KiInSwapProcesses (
IN KIRQL PreviousIrql
)
/*++
Routine Description:
This function in swaps processes.
N.B. The dispatcher data lock is held on entry to this routine and must
be help on exit to this routine.
Arguments:
PreviousIrql - Supplies the previous IRQL.
Return Value:
None.
--*/
{
PLIST_ENTRY NextEntry;
KIRQL OldIrql;
PKPROCESS Process;
PKTHREAD Thread;
// Process the process in swap list and for each process removed from
// the list, make the process resident, and process its ready list.
OldIrql = PreviousIrql;
NextEntry = KiProcessInSwapListHead.Flink;
while (NextEntry != &KiProcessInSwapListHead) {
Process = CONTAINING_RECORD(NextEntry, KPROCESS, SwapListEntry);
RemoveEntryList(NextEntry);
Process->State = ProcessInSwap;
KiUnlockDispatcherDatabase(OldIrql);
MmInSwapProcess(Process);
KiLockDispatcherDatabase(&OldIrql);
Process->State = ProcessInMemory;
NextEntry = Process->ReadyListHead.Flink;
while (NextEntry != &Process->ReadyListHead) {
Thread = CONTAINING_RECORD(NextEntry, KTHREAD, WaitListEntry);
RemoveEntryList(NextEntry);
Thread->ProcessReadyQueue = FALSE;
KiReadyThread(Thread);
NextEntry = Process->ReadyListHead.Flink;
}
NextEntry = KiProcessInSwapListHead.Flink;
}
return;
}
VOID
KiOutSwapKernelStacks (
IN KIRQL PreviousIrql
)
/*++
Routine Description:
This function attempts to out swap the kernel stack for threads whose
wait mode is user and which have been waiting longer than the stack
protect time.
N.B. The dispatcher data lock is held on entry to this routine and must
be help on exit to this routine.
Arguments:
PreviousIrql - Supplies the previous IRQL.
Return Value:
None.
--*/
{
ULONG CurrentTick;
PLIST_ENTRY NextEntry;
ULONG NumberOfThreads;
KIRQL OldIrql;
PKPROCESS Process;
PKTHREAD Thread;
PKTHREAD ThreadObjects[MAXIMUM_THREAD_STACKS];
ULONG WaitTime;
// Scan the waiting in list and check if the wait time exceeds the
// stack protect time. If the protect time is exceeded, then make
// the kernel stack of the waiting thread nonresident. If the count
// of the number of stacks that are resident for the process reaches
// zero, then insert the process in the outswap list and set its state
// to transition.
CurrentTick = KiQueryLowTickCount();
OldIrql = PreviousIrql;
NextEntry = KiWaitInListHead.Flink;
NumberOfThreads = 0;
while ((NextEntry != &KiWaitInListHead) &&
(NumberOfThreads < MAXIMUM_THREAD_STACKS)) {
Thread = CONTAINING_RECORD(NextEntry, KTHREAD, WaitListEntry);
ASSERT(Thread->WaitMode == UserMode);
NextEntry = NextEntry->Flink;
WaitTime = CurrentTick - Thread->WaitTime;
if ((WaitTime >= KiStackProtectTime) &&
KiIsThreadNumericStateSaved(Thread)) {
Thread->KernelStackResident = FALSE;
ThreadObjects[NumberOfThreads] = Thread;
NumberOfThreads += 1;
RemoveEntryList(&Thread->WaitListEntry);
InsertTailList(&KiWaitOutListHead, &Thread->WaitListEntry);
Process = Thread->ApcState.Process;
Process->StackCount -= 1;
if (Process->StackCount == 0) {
Process->State = ProcessInTransition;
InsertTailList(&KiProcessOutSwapListHead,
&Process->SwapListEntry);
}
}
}
// Unlock the dispatcher database and lower IRQL to its previous
// value.
KiUnlockDispatcherDatabase(OldIrql);
// Out swap the kernels stack for the selected set of threads.
while (NumberOfThreads > 0) {
NumberOfThreads -= 1;
Thread = ThreadObjects[NumberOfThreads];
MmOutPageKernelStack(Thread);
}
// Raise IRQL to dispatcher level and lock dispatcher database.
KiLockDispatcherDatabase(&OldIrql);
return;
}
VOID
KiOutSwapProcesses (
IN KIRQL PreviousIrql
)
/*++
Routine Description:
This function out swaps processes.
N.B. The dispatcher data lock is held on entry to this routine and must
be help on exit to this routine.
Arguments:
PreviousIrql - Supplies the previous IRQL.
Return Value:
None.
--*/
{
PLIST_ENTRY NextEntry;
KIRQL OldIrql;
PKPROCESS Process;
PKTHREAD Thread;
// Process the process out swap list and for each process removed from
// the list, make the process nonresident, and process its ready list.
OldIrql = PreviousIrql;
NextEntry = KiProcessOutSwapListHead.Flink;
while (NextEntry != &KiProcessOutSwapListHead) {
Process = CONTAINING_RECORD(NextEntry, KPROCESS, SwapListEntry);
RemoveEntryList(NextEntry);
// If there are any threads in the process ready list, then don't
// out swap the process and ready all threads in the process ready
// list. Otherwsie, out swap the process.
NextEntry = Process->ReadyListHead.Flink;
if (NextEntry != &Process->ReadyListHead) {
Process->State = ProcessInMemory;
while (NextEntry != &Process->ReadyListHead) {
Thread = CONTAINING_RECORD(NextEntry, KTHREAD, WaitListEntry);
RemoveEntryList(NextEntry);
Thread->ProcessReadyQueue = FALSE;
KiReadyThread(Thread);
NextEntry = Process->ReadyListHead.Flink;
}
} else {
Process->State = ProcessOutSwap;
KiUnlockDispatcherDatabase(OldIrql);
MmOutSwapProcess(Process);
KiLockDispatcherDatabase(&OldIrql);
// While the process was being outswapped there may have been one
// or more threads that attached to the process. If the process
// ready list is not empty, then in swap the process. Otherwise,
// mark the process as out of memory.
NextEntry = Process->ReadyListHead.Flink;
if (NextEntry != &Process->ReadyListHead) {
Process->State = ProcessInTransition;
InsertTailList(&KiProcessInSwapListHead, &Process->SwapListEntry);
} else {
Process->State = ProcessOutOfMemory;
}
}
NextEntry = KiProcessOutSwapListHead.Flink;
}
return;
}
VOID
KiScanReadyQueues (
VOID
)
/*++
Routine Description:
This function scans a section of the ready queues and attempts to
boost the priority of threads that run at variable priority levels.
Arguments:
None.
Return Value:
None.
--*/
{
ULONG Count = 0;
ULONG CurrentTick;
PLIST_ENTRY Entry;
ULONG Index;
PLIST_ENTRY ListHead;
ULONG Number = 0;
KIRQL OldIrql;
PKPROCESS Process;
ULONG Summary;
PKTHREAD Thread;
ULONG WaitTime;
// Lock the dispatcher database and check if there are any ready threads
// queued at the scannable priority levels.
KiLockDispatcherDatabase(&OldIrql);
Summary = KiReadySummary & ((1 << THREAD_BOOST_PRIORITY) - 2);
if (Summary != 0) {
Count = THREAD_READY_COUNT;
CurrentTick = KiQueryLowTickCount();
Index = KiReadyQueueIndex;
Number = THREAD_SCAN_COUNT;
do {
// If the current ready queue index is beyond the end of the range
// of priorities that are scanned, then wrap back to the beginning
// priority.
if (Index > THREAD_SCAN_PRIORITY) {
Index = 1;
}
// If there are any ready threads queued at the current priority
// level, then attempt to boost the thread priority.
if (((Summary >> Index) & 1) != 0) {
Summary ^= (1 << Index);
ListHead = &KiDispatcherReadyListHead[Index];
Entry = ListHead->Flink;
ASSERT(Entry != ListHead);
do {
Thread = CONTAINING_RECORD(Entry, KTHREAD, WaitListEntry);
// If the thread has been waiting for an extended period,
// then boost the priority of the selected.
WaitTime = CurrentTick - Thread->WaitTime;
if (WaitTime >= READY_WITHOUT_RUNNING) {
// Remove the thread from the respective ready queue.
Entry = Entry->Blink;
RemoveEntryList(Entry->Flink);
if (IsListEmpty(ListHead) != FALSE) {
ClearMember(Index, KiReadySummary);
}
// Compute the priority decrement value, set the new
// thread priority, set the decrement count, set the
// thread quantum, and ready the thread for execution.
Thread->PriorityDecrement +=
THREAD_BOOST_PRIORITY - Thread->Priority;
Thread->DecrementCount = ROUND_TRIP_DECREMENT_COUNT;
Thread->Priority = THREAD_BOOST_PRIORITY;
Process = Thread->ApcState.Process;
Thread->Quantum = Process->ThreadQuantum * 2;
KiReadyThread(Thread);
Count -= 1;
}
Entry = Entry->Flink;
Number -= 1;
} while ((Entry != ListHead) & (Number != 0) & (Count != 0));
}
Index += 1;
} while ((Summary != 0) & (Number != 0) & (Count != 0));
}
// Unlock the dispatcher database and save the last read queue index
// for the next scan.
KiUnlockDispatcherDatabase(OldIrql);
if ((Count != 0) && (Number != 0)) {
KiReadyQueueIndex = 1;
} else {
KiReadyQueueIndex = Index;
}
return;
}