Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1166 lines
26 KiB

/*++
Copyright (c) 1998-2001 Microsoft Corporation
Module Name:
thrdpool.cxx
Abstract:
This module implements the thread pool package.
Author:
Keith Moore (keithmo) 10-Jun-1998
Revision History:
--*/
#include <precomp.h>
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
//
// Private prototypes.
//
NTSTATUS
UlpCreatePoolThread(
IN PUL_THREAD_POOL pThreadPool
);
VOID
UlpThreadPoolWorker(
IN PVOID Context
);
VOID
UlpInitThreadTracker(
IN PUL_THREAD_POOL pThreadPool,
IN PETHREAD pThread,
IN PUL_THREAD_TRACKER pThreadTracker
);
VOID
UlpDestroyThreadTracker(
IN PUL_THREAD_TRACKER pThreadTracker
);
PUL_THREAD_TRACKER
UlpPopThreadTracker(
IN PUL_THREAD_POOL pThreadPool
);
VOID
UlpKillThreadWorker(
IN PUL_WORK_ITEM pWorkItem
);
#ifdef __cplusplus
}; // extern "C"
#endif // __cplusplus
//
// Private globals.
//
DECLSPEC_ALIGN(UL_CACHE_LINE)
UL_ALIGNED_THREAD_POOL g_UlThreadPool[MAXIMUM_PROCESSORS + 1];
#define CURRENT_THREAD_POOL() \
&g_UlThreadPool[KeGetCurrentProcessorNumber()].ThreadPool
#define WAIT_THREAD_POOL() \
&g_UlThreadPool[g_UlNumberOfProcessors].ThreadPool
PUL_WORK_ITEM g_pKillerWorkItems = NULL;
#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, UlInitializeThreadPool )
#pragma alloc_text( PAGE, UlTerminateThreadPool )
#pragma alloc_text( PAGE, UlpCreatePoolThread )
#pragma alloc_text( PAGE, UlpThreadPoolWorker )
#endif // ALLOC_PRAGMA
#if 0
NOT PAGEABLE -- UlpInitThreadTracker
NOT PAGEABLE -- UlpDestroyThreadTracker
NOT PAGEABLE -- UlpPopThreadTracker
#endif
//
// Public functions.
//
/***************************************************************************++
Routine Description:
Initialize the thread pool.
Arguments:
ThreadsPerCpu - Supplies the number of threads to create per CPU.
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlInitializeThreadPool(
IN USHORT ThreadsPerCpu
)
{
NTSTATUS Status;
PUL_THREAD_POOL pThreadPool;
CLONG i;
USHORT j;
//
// Sanity check.
//
PAGED_CODE();
RtlZeroMemory( g_UlThreadPool, sizeof(g_UlThreadPool) );
//
// Preallocate the small array of special work items used by
// UlTerminateThreadPool, so that we can safely shut down even
// in low-memory conditions
//
g_pKillerWorkItems = UL_ALLOCATE_ARRAY(
NonPagedPool,
UL_WORK_ITEM,
(g_UlNumberOfProcessors + 1) * ThreadsPerCpu,
UL_WORK_ITEM_POOL_TAG
);
if (g_pKillerWorkItems == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
for (i = 0; i <= g_UlNumberOfProcessors; i++)
{
pThreadPool = &g_UlThreadPool[i].ThreadPool;
//
// Initialize the kernel structures.
//
InitializeSListHead( &pThreadPool->WorkQueueSList );
KeInitializeEvent(
&pThreadPool->WorkQueueEvent,
SynchronizationEvent,
FALSE
);
UlInitializeSpinLock( &pThreadPool->ThreadSpinLock, "ThreadSpinLock" );
//
// Initialize the other fields.
//
pThreadPool->pIrpThread = NULL;
pThreadPool->ThreadCount = 0;
pThreadPool->ThreadCpu = (UCHAR)i;
InitializeListHead( &pThreadPool->ThreadListHead );
}
for (i = 0; i <= g_UlNumberOfProcessors; i++)
{
pThreadPool = &g_UlThreadPool[i].ThreadPool;
//
// Create the threads.
//
for (j = 0; j < ThreadsPerCpu; j++)
{
Status = UlpCreatePoolThread( pThreadPool );
if (NT_SUCCESS(Status))
{
pThreadPool->Initialized = TRUE;
pThreadPool->ThreadCount++;
}
else
{
break;
}
}
if (!NT_SUCCESS(Status))
{
break;
}
}
return Status;
} // UlInitializeThreadPool
/***************************************************************************++
Routine Description:
Terminates the thread pool, waiting for all worker threads to exit.
--***************************************************************************/
VOID
UlTerminateThreadPool(
VOID
)
{
PUL_THREAD_POOL pThreadPool;
PUL_THREAD_TRACKER pThreadTracker;
CLONG i, j;
PUL_WORK_ITEM pKiller = g_pKillerWorkItems;
//
// Sanity check.
//
PAGED_CODE();
//
// If there is no killer, the thread pool has never been initialized.
//
if (pKiller == NULL)
{
return;
}
for (i = 0; i <= g_UlNumberOfProcessors; i++)
{
pThreadPool = &g_UlThreadPool[i].ThreadPool;
if (pThreadPool->Initialized)
{
//
// Queue a killer work item for each thread. Each
// killer tells one thread to kill itself.
//
for (j = 0; j < pThreadPool->ThreadCount; j++)
{
//
// Need a separate work item for each thread.
// Worker threads will free the below memory
// before termination. UlpKillThreadWorker is
// a sign to a worker thread for self termination.
//
pKiller->pWorkRoutine = &UlpKillThreadWorker;
QUEUE_UL_WORK_ITEM( pThreadPool, pKiller );
pKiller++;
}
//
// Wait for all threads to go away.
//
while (pThreadTracker = UlpPopThreadTracker(pThreadPool))
{
UlpDestroyThreadTracker(pThreadTracker);
}
//
// Release the thread handle.
//
ZwClose( pThreadPool->ThreadHandle );
}
ASSERT( IsListEmpty( &pThreadPool->ThreadListHead ) );
}
UL_FREE_POOL(g_pKillerWorkItems, UL_WORK_ITEM_POOL_TAG);
g_pKillerWorkItems = NULL;
} // UlTerminateThreadPool
//
// Private functions.
//
/***************************************************************************++
Routine Description:
Creates a new pool thread, setting pIrpThread if necessary.
Arguments:
pThreadPool - Supplies the pool that is to receive the new thread.
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlpCreatePoolThread(
IN PUL_THREAD_POOL pThreadPool
)
{
NTSTATUS Status;
OBJECT_ATTRIBUTES ObjectAttributes;
PUL_THREAD_TRACKER pThreadTracker;
PETHREAD pThread;
//
// Sanity check.
//
PAGED_CODE();
//
// Ensure we can allocate a thread tracker.
//
pThreadTracker = (PUL_THREAD_TRACKER) UL_ALLOCATE_POOL(
NonPagedPool,
sizeof(*pThreadTracker),
UL_THREAD_TRACKER_POOL_TAG
);
if (pThreadTracker != NULL)
{
//
// Create the thread.
//
InitializeObjectAttributes(
&ObjectAttributes, // ObjectAttributes
NULL, // ObjectName
UL_KERNEL_HANDLE, // Attributes
NULL, // RootDirectory
NULL // SecurityDescriptor
);
UlAttachToSystemProcess();
Status = PsCreateSystemThread(
&pThreadPool->ThreadHandle, // ThreadHandle
THREAD_ALL_ACCESS, // DesiredAccess
&ObjectAttributes, // ObjectAttributes
NULL, // ProcessHandle
NULL, // ClientId
UlpThreadPoolWorker, // StartRoutine
pThreadPool // StartContext
);
if (NT_SUCCESS(Status))
{
//
// Get a pointer to the thread.
//
Status = ObReferenceObjectByHandle(
pThreadPool->ThreadHandle, // ThreadHandle
0, // DesiredAccess
*PsThreadType, // ObjectType
KernelMode, // AccessMode
(PVOID*) &pThread, // Object
NULL // HandleInformation
);
if (NT_SUCCESS(Status))
{
//
// Set up the thread tracker.
//
UlpInitThreadTracker(pThreadPool, pThread, pThreadTracker);
//
// If this is the first thread created for this pool,
// make it into the special IRP thread.
//
if (pThreadPool->pIrpThread == NULL)
{
pThreadPool->pIrpThread = pThread;
}
}
else
{
//
// That call really should not fail.
//
ASSERT(NT_SUCCESS(Status));
UL_FREE_POOL(
pThreadTracker,
UL_THREAD_TRACKER_POOL_TAG
);
//
// Preserve return val from ObReferenceObjectByHandle.
//
ZwClose( pThreadPool->ThreadHandle );
}
}
else
{
//
// Couldn't create the thread, kill the tracker.
//
UL_FREE_POOL(
pThreadTracker,
UL_THREAD_TRACKER_POOL_TAG
);
}
UlDetachFromSystemProcess();
}
else
{
//
// Couldn't create a thread tracker.
//
Status = STATUS_INSUFFICIENT_RESOURCES;
}
return Status;
} // UlpCreatePoolThread
/***************************************************************************++
Routine Description:
This is the main worker for pool threads.
Arguments:
pContext - Supplies a context value for the thread. This is actually
a PUL_THREAD_POOL pointer.
--***************************************************************************/
VOID
UlpThreadPoolWorker(
IN PVOID pContext
)
{
PSINGLE_LIST_ENTRY pListEntry;
PSINGLE_LIST_ENTRY pNext;
SINGLE_LIST_ENTRY ListHead;
PUL_WORK_ROUTINE pWorkRoutine;
PUL_THREAD_POOL pThreadPool;
PUL_THREAD_POOL pThreadPoolNext;
PUL_WORK_ITEM pWorkItem;
KAFFINITY AffinityMask;
NTSTATUS Status;
ULONG Cpu;
ULONG NextCpu;
ULONG ThreadCpu;
//
// Sanity check.
//
PAGED_CODE();
//
// Initialize the ListHead to reverse order of items from FlushSList.
//
ListHead.Next = NULL;
//
// Snag the context.
//
pThreadPool = (PUL_THREAD_POOL) pContext;
//
// Set this thread's hard affinity if enabled plus ideal processor.
//
if ( pThreadPool->ThreadCpu != g_UlNumberOfProcessors )
{
if ( g_UlEnableThreadAffinity )
{
AffinityMask = 1 << pThreadPool->ThreadCpu;
}
else
{
AffinityMask = (KAFFINITY) g_UlThreadAffinityMask;
}
Status = ZwSetInformationThread(
pThreadPool->ThreadHandle,
ThreadAffinityMask,
&AffinityMask,
sizeof(AffinityMask)
);
ASSERT( NT_SUCCESS( Status ) );
//
// Always set thread's ideal processor.
//
ThreadCpu = pThreadPool->ThreadCpu;
Status = ZwSetInformationThread(
pThreadPool->ThreadHandle,
ThreadIdealProcessor,
&ThreadCpu,
sizeof(ThreadCpu)
);
ASSERT( NT_SUCCESS( Status ) );
}
//
// Disable hard error popups.
//
IoSetThreadHardErrorMode( FALSE );
//
// Loop forever, or at least until we're told to stop.
//
while ( TRUE )
{
//
// Dqueue the next work item. If we get the special killer work
// item, then break out of this loop & handle it.
//
pListEntry = InterlockedFlushSList( &pThreadPool->WorkQueueSList );
if ( NULL == pListEntry )
{
//
// Try to get a work from other queues.
//
NextCpu = pThreadPool->ThreadCpu + 1;
for (Cpu = 0; Cpu < g_UlNumberOfProcessors; Cpu++, NextCpu++)
{
if (NextCpu >= g_UlNumberOfProcessors)
{
NextCpu = 0;
}
pThreadPoolNext = &g_UlThreadPool[NextCpu].ThreadPool;
if ( ExQueryDepthSList( &pThreadPoolNext->WorkQueueSList ) >=
g_UlMinWorkDequeueDepth )
{
pListEntry = InterlockedPopEntrySList(
&pThreadPoolNext->WorkQueueSList
);
if ( NULL != pListEntry )
{
//
// Make sure we didn't pop up a killer. If so,
// push it back to where it is poped from.
//
pWorkItem = CONTAINING_RECORD(
pListEntry,
UL_WORK_ITEM,
QueueListEntry
);
if ( pWorkItem->pWorkRoutine != &UlpKillThreadWorker )
{
pListEntry->Next = NULL;
}
else
{
WRITE_REF_TRACE_LOG(
g_pWorkItemTraceLog,
REF_ACTION_PUSH_BACK_WORK_ITEM,
0,
pWorkItem,
__FILE__,
__LINE__
);
QUEUE_UL_WORK_ITEM( pThreadPoolNext, pWorkItem );
pListEntry = NULL;
}
break;
}
}
}
}
if ( NULL == pListEntry )
{
KeWaitForSingleObject(
&pThreadPool->WorkQueueEvent,
Executive,
KernelMode,
FALSE,
0
);
continue;
}
ASSERT( NULL != pListEntry );
//
// Rebuild the list with reverse order of what we received.
//
while ( pListEntry != NULL )
{
WRITE_REF_TRACE_LOG(
g_pWorkItemTraceLog,
REF_ACTION_FLUSH_WORK_ITEM,
0,
pListEntry,
__FILE__,
__LINE__
);
pNext = pListEntry->Next;
pListEntry->Next = ListHead.Next;
ListHead.Next = pListEntry;
pListEntry = pNext;
}
//
// We can now process the work items in the order that was received.
//
while ( NULL != ( pListEntry = ListHead.Next ) )
{
ListHead.Next = pListEntry->Next;
pWorkItem = CONTAINING_RECORD(
pListEntry,
UL_WORK_ITEM,
QueueListEntry
);
WRITE_REF_TRACE_LOG(
g_pWorkItemTraceLog,
REF_ACTION_PROCESS_WORK_ITEM,
0,
pWorkItem,
__FILE__,
__LINE__
);
//
// Call the actual work item routine.
//
ASSERT( pWorkItem->pWorkRoutine != NULL );
if ( pWorkItem->pWorkRoutine == &UlpKillThreadWorker )
{
//
// Received a special signal for self-termination.
// Push all remaining work items back to the queue
// before we exit the current thread.
//
while ( NULL != ( pListEntry = ListHead.Next ) )
{
ListHead.Next = pListEntry->Next;
pWorkItem = CONTAINING_RECORD(
pListEntry,
UL_WORK_ITEM,
QueueListEntry
);
ASSERT( pWorkItem->pWorkRoutine == &UlpKillThreadWorker );
WRITE_REF_TRACE_LOG(
g_pWorkItemTraceLog,
REF_ACTION_PUSH_BACK_WORK_ITEM,
0,
pWorkItem,
__FILE__,
__LINE__
);
QUEUE_UL_WORK_ITEM( pThreadPool, pWorkItem );
}
goto exit;
}
UL_ENTER_DRIVER( "UlpThreadPoolWorker", NULL );
//
// Clear the pWorkRoutine member as an indication that this
// has started processing. Must do it before calling the
// routine, as the routine may destroy the work item.
//
pWorkRoutine = pWorkItem->pWorkRoutine;
pWorkItem->pWorkRoutine = NULL;
pWorkRoutine( pWorkItem );
UL_LEAVE_DRIVER( "UlpThreadPoolWorker" );
}
}
exit:
//
// Suicide is painless.
//
PsTerminateSystemThread( STATUS_SUCCESS );
} // UlpThreadPoolWorker
/***************************************************************************++
Routine Description:
Initializes a new thread tracker and inserts it into the thread pool.
Arguments:
pThreadPool - Supplies the thread pool to own the new tracker.
pThread - Supplies the thread for the tracker.
pThreadTracker - Supplise the tracker to be initialized
--***************************************************************************/
VOID
UlpInitThreadTracker(
IN PUL_THREAD_POOL pThreadPool,
IN PETHREAD pThread,
IN PUL_THREAD_TRACKER pThreadTracker
)
{
KIRQL oldIrql;
ASSERT( pThreadPool != NULL );
ASSERT( pThread != NULL );
ASSERT( pThreadTracker != NULL );
pThreadTracker->pThread = pThread;
UlAcquireSpinLock( &pThreadPool->ThreadSpinLock, &oldIrql );
InsertTailList(
&pThreadPool->ThreadListHead,
&pThreadTracker->ThreadListEntry
);
UlReleaseSpinLock( &pThreadPool->ThreadSpinLock, oldIrql );
} // UlpInitThreadTracker
/***************************************************************************++
Routine Description:
Removes the specified thread tracker from the thread pool.
Arguments:
pThreadPool - Supplies the thread pool that owns the tracker.
pThreadTracker - Supplies the thread tracker to remove.
Return Value:
None
--***************************************************************************/
VOID
UlpDestroyThreadTracker(
IN PUL_THREAD_TRACKER pThreadTracker
)
{
KIRQL oldIrql;
//
// Sanity check.
//
ASSERT( pThreadTracker != NULL );
//
// Wait for the thread to die.
//
KeWaitForSingleObject(
(PVOID)pThreadTracker->pThread, // Object
UserRequest, // WaitReason
KernelMode, // WaitMode
FALSE, // Alertable
NULL // Timeout
);
//
// Cleanup.
//
ObDereferenceObject( pThreadTracker->pThread );
//
// Do it.
//
UL_FREE_POOL(
pThreadTracker,
UL_THREAD_TRACKER_POOL_TAG
);
} // UlpDestroyThreadTracker
/***************************************************************************++
Routine Description:
Removes a thread tracker from the thread pool.
Arguments:
pThreadPool - Supplies the thread pool that owns the tracker.
Return Value:
A pointer to the tracker or NULL (if list is empty)
--***************************************************************************/
PUL_THREAD_TRACKER
UlpPopThreadTracker(
IN PUL_THREAD_POOL pThreadPool
)
{
PLIST_ENTRY pEntry;
PUL_THREAD_TRACKER pThreadTracker;
KIRQL oldIrql;
ASSERT( pThreadPool != NULL );
ASSERT( pThreadPool->Initialized );
UlAcquireSpinLock( &pThreadPool->ThreadSpinLock, &oldIrql );
if (IsListEmpty(&pThreadPool->ThreadListHead))
{
pThreadTracker = NULL;
}
else
{
pEntry = RemoveHeadList(&pThreadPool->ThreadListHead);
pThreadTracker = CONTAINING_RECORD(
pEntry,
UL_THREAD_TRACKER,
ThreadListEntry
);
}
UlReleaseSpinLock( &pThreadPool->ThreadSpinLock, oldIrql );
return pThreadTracker;
} // UlpPopThreadTracker
/***************************************************************************++
Routine Description:
A dummy function to indicate that the thread should be terminated.
Arguments:
pWorkItem - Supplies the dummy work item.
Return Value:
None
--***************************************************************************/
VOID
UlpKillThreadWorker(
IN PUL_WORK_ITEM pWorkItem
)
{
return;
} // UlpKillThreadWorker
/***************************************************************************++
Routine Description:
A function that queues a worker item to a thread pool.
Arguments:
pWorkItem - Supplies the work item.
pWorkRoutine - Supplies the work routine.
Return Value:
None
--***************************************************************************/
VOID
UlQueueWorkItem(
IN PUL_WORK_ITEM pWorkItem,
IN PUL_WORK_ROUTINE pWorkRoutine
REFERENCE_DEBUG_FORMAL_PARAMS
)
{
PUL_THREAD_POOL pThreadPool;
CLONG Cpu, NextCpu;
//
// Sanity check.
//
ASSERT( pWorkItem != NULL );
ASSERT( pWorkRoutine != NULL );
WRITE_REF_TRACE_LOG(
g_pWorkItemTraceLog,
REF_ACTION_QUEUE_WORK_ITEM,
0,
pWorkItem,
pFileName,
LineNumber
);
//
// Save the pointer to the worker routine, then queue the item.
//
pWorkItem->pWorkRoutine = pWorkRoutine;
//
// Queue the work item on the idle processor if possible.
//
NextCpu = KeGetCurrentProcessorNumber();
for (Cpu = 0; Cpu < g_UlNumberOfProcessors; Cpu++, NextCpu++)
{
if (NextCpu >= g_UlNumberOfProcessors)
{
NextCpu = 0;
}
pThreadPool = &g_UlThreadPool[NextCpu].ThreadPool;
if (ExQueryDepthSList(&pThreadPool->WorkQueueSList) <=
g_UlMaxWorkQueueDepth)
{
QUEUE_UL_WORK_ITEM( pThreadPool, pWorkItem );
return;
}
}
//
// Queue the work item on the current thread pool.
//
pThreadPool = CURRENT_THREAD_POOL();
QUEUE_UL_WORK_ITEM( pThreadPool, pWorkItem );
} // UlQueueWorkItem
/***************************************************************************++
Routine Description:
A function that queues a blocking worker item to a special thread pool.
Arguments:
pWorkItem - Supplies the work item.
pWorkRoutine - Supplies the work routine.
Return Value:
None
--***************************************************************************/
VOID
UlQueueBlockingItem(
IN PUL_WORK_ITEM pWorkItem,
IN PUL_WORK_ROUTINE pWorkRoutine
REFERENCE_DEBUG_FORMAL_PARAMS
)
{
PUL_THREAD_POOL pThreadPool;
//
// Sanity check.
//
ASSERT( pWorkItem != NULL );
ASSERT( pWorkRoutine != NULL );
WRITE_REF_TRACE_LOG(
g_pWorkItemTraceLog,
REF_ACTION_QUEUE_BLOCKING_ITEM,
0,
pWorkItem,
pFileName,
LineNumber
);
//
// Save the pointer to the worker routine, then queue the item.
//
pWorkItem->pWorkRoutine = pWorkRoutine;
//
// Queue the work item on the special wait thread pool.
//
pThreadPool = WAIT_THREAD_POOL();
QUEUE_UL_WORK_ITEM( pThreadPool, pWorkItem );
} // UlQueueBlockingItem
/***************************************************************************++
Routine Description:
A function that either queues a worker item to a thread pool if the
caller is at DISPATCH_LEVEL/APC_LEVEL or it simply calls the work
routine directly.
Arguments:
pWorkItem - Supplies the work item.
pWorkRoutine - Supplies the work routine.
Return Value:
None
--***************************************************************************/
VOID
UlCallPassive(
IN PUL_WORK_ITEM pWorkItem,
IN PUL_WORK_ROUTINE pWorkRoutine
REFERENCE_DEBUG_FORMAL_PARAMS
)
{
//
// Sanity check.
//
ASSERT( pWorkItem != NULL );
ASSERT( pWorkRoutine != NULL );
WRITE_REF_TRACE_LOG(
g_pWorkItemTraceLog,
REF_ACTION_CALL_PASSIVE,
0,
pWorkItem,
pFileName,
LineNumber
);
if (KeGetCurrentIrql() == PASSIVE_LEVEL)
{
//
// Clear this for consistency with UlpThreadPoolWorker.
//
pWorkItem->pWorkRoutine = NULL;
pWorkRoutine(pWorkItem);
}
else
{
UL_QUEUE_WORK_ITEM(pWorkItem, pWorkRoutine);
}
} // UlCallPassive
/***************************************************************************++
Routine Description:
Queries the "IRP thread", the special worker thread used as the
target for all asynchronous IRPs.
Arguments:
pWorkItem - Supplies the work item.
pWorkRoutine - Supplies the work routine.
Return Value:
None
--***************************************************************************/
PETHREAD
UlQueryIrpThread(
VOID
)
{
PUL_THREAD_POOL pThreadPool;
//
// Sanity check.
//
pThreadPool = CURRENT_THREAD_POOL();
ASSERT( pThreadPool->Initialized );
//
// Return the IRP thread.
//
return pThreadPool->pIrpThread;
} // UlQueryIrpThread