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.
 
 
 
 
 
 

1135 lines
26 KiB

/*++
Copyright (c) 1998-2001 Microsoft Corporation
Module Name:
pipeline.cxx
Abstract:
This module implements the pipeline package.
Author:
Keith Moore (keithmo) 10-Jun-1998
Revision History:
--*/
#include <precomp.h>
//
// Private types.
//
//
// The UL_PIPELINE_THREAD_CONTEXT structure is used as a parameter to the
// pipeline worker threads. This structure enables primitive communication
// between UlCreatePipeline() and the worker threads.
//
typedef struct _UL_PIPELINE_THREAD_CONTEXT
{
PUL_PIPELINE pPipeline;
ULONG Processor;
NTSTATUS Status;
KEVENT InitCompleteEvent;
PUL_PIPELINE_THREAD_DATA pThreadData;
} UL_PIPELINE_THREAD_CONTEXT, *PUL_PIPELINE_THREAD_CONTEXT;
//
// Private prototypes.
//
BOOLEAN
UlpWaitForWorkPipeline(
IN PUL_PIPELINE pPipeline
);
PLIST_ENTRY
UlpDequeueAllWorkPipeline(
IN PUL_PIPELINE pPipeline,
IN PUL_PIPELINE_QUEUE Queue
);
BOOLEAN
UlpLockQueuePipeline(
IN PUL_PIPELINE_QUEUE pQueue
);
VOID
UlpUnlockQueuePipeline(
IN PUL_PIPELINE_QUEUE pQueue
);
PUL_PIPELINE_QUEUE
UlpFindNextQueuePipeline(
IN PUL_PIPELINE pPipeline,
IN OUT PUL_PIPELINE_QUEUE_ORDINAL pQueueOrdinal
);
VOID
UlpPipelineWorkerThread(
IN PVOID pContext
);
PUL_PIPELINE
UlpPipelineWorkerThreadStartup(
IN PUL_PIPELINE_THREAD_CONTEXT pContext
);
VOID
UlpKillPipelineWorkerThreads(
IN PUL_PIPELINE pPipeline
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, UlCreatePipeline )
#pragma alloc_text( INIT, UlInitializeQueuePipeline )
#pragma alloc_text( PAGE, UlDestroyPipeline )
#pragma alloc_text( PAGE, UlpLockQueuePipeline )
#pragma alloc_text( PAGE, UlpUnlockQueuePipeline )
#pragma alloc_text( PAGE, UlpFindNextQueuePipeline )
#pragma alloc_text( PAGE, UlpPipelineWorkerThread )
#pragma alloc_text( PAGE, UlpPipelineWorkerThreadStartup )
#endif
#if 0
NOT PAGEABLE -- UlQueueWorkPipeline
NOT PAGEABLE -- UlpWaitForWorkPipeline
NOT PAGEABLE -- UlpDequeueAllWorkPipeline
NOT PAGEABLE -- UlpKillPipelineWorkerThreads
#endif
//
// Public functions.
//
/***************************************************************************++
Routine Description:
Creates a new pipeline and the associated queues.
Arguments:
ppPipeline - Receives a pointer to the new pipeline object.
QueueCount - Supplies the number of queues in the new pipeline.
ThreadsPerCpu - Supplies the number of worker threads to create
per CPU.
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlCreatePipeline(
OUT PUL_PIPELINE * ppPipeline,
IN SHORT QueueCount,
IN SHORT ThreadsPerCpu
)
{
NTSTATUS status;
PVOID pAllocation;
PUL_PIPELINE pPipeline;
PUL_PIPELINE_QUEUE pQueue;
PUL_PIPELINE_RARE pRareData;
ULONG bytesRequired;
ULONG totalThreads;
ULONG i;
PUL_PIPELINE_THREAD_DATA pThreadData;
UL_PIPELINE_THREAD_CONTEXT context;
OBJECT_ATTRIBUTES objectAttributes;
//
// Sanity check.
//
PAGED_CODE();
ASSERT( QueueCount > 0 );
ASSERT( ThreadsPerCpu > 0 );
//
// Allocate the pool. Note that we allocate slightly larger than
// necessary then round up to the next cache-line. Also note that
// we allocate the pipeline, rare data, and thread data structures
// in a single pool block.
//
totalThreads = (ULONG)ThreadsPerCpu * g_UlNumberOfProcessors;
bytesRequired = PIPELINE_LENGTH( QueueCount, totalThreads ) +
CACHE_LINE_SIZE - 1;
pAllocation = UL_ALLOCATE_POOL(
NonPagedPool,
bytesRequired,
UL_PIPELINE_POOL_TAG
);
if( pAllocation == NULL )
{
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(
pAllocation,
bytesRequired
);
//
// Ensure the pipeline starts on a cache boundary.
//
pPipeline = (PUL_PIPELINE)ROUND_UP( pAllocation, CACHE_LINE_SIZE );
//
// Initialize the static portion of the pipeline.
//
UlInitializeSpinLock( &pPipeline->PipelineLock, "PipelineLock" );
pPipeline->ThreadsRunning = (SHORT)totalThreads;
pPipeline->QueuesWithWork = 0;
pPipeline->MaximumThreadsRunning = (SHORT)totalThreads;
pPipeline->QueueCount = QueueCount;
pPipeline->ShutdownFlag = FALSE;
KeInitializeEvent(
&pPipeline->WorkAvailableEvent, // Event
SynchronizationEvent, // Type
FALSE // State
);
//
// Initialize the rare data. Note the PIPELINE_TO_RARE_DATA() macro
// requires the QueueCount field to be set before the macro can be
// used.
//
pRareData = PIPELINE_TO_RARE_DATA( pPipeline );
pRareData->pAllocationBlock = pAllocation;
//
// Setup the thread start context. This structure allows us to
// pass enough information to the worker thread that it can
// set its affinity correctly and return any failure status
// back to us.
//
context.pPipeline = pPipeline;
context.Processor = 0;
context.Status = STATUS_SUCCESS;
KeInitializeEvent(
&context.InitCompleteEvent, // Event
SynchronizationEvent, // Type
FALSE // State
);
//
// Create the worker threads.
//
InitializeObjectAttributes(
&objectAttributes, // ObjectAttributes
NULL, // ObjectName
UL_KERNEL_HANDLE, // Attributes
NULL, // RootDirectory
NULL // SecurityDescriptor
);
UlAttachToSystemProcess();
pThreadData = PIPELINE_TO_THREAD_DATA( pPipeline );
for (i = 0 ; i < totalThreads ; i++)
{
context.pThreadData = pThreadData;
status = PsCreateSystemThread(
&pThreadData->ThreadHandle, // ThreadHandle
THREAD_ALL_ACCESS, // DesiredAccess
&objectAttributes, // ObjectAttributes
NULL, // ProcessHandle
NULL, // ClientId
&UlpPipelineWorkerThread, // StartRoutine
&context // StartContext
);
if (!NT_SUCCESS(status))
{
UlDetachFromSystemProcess();
UlDestroyPipeline( pPipeline );
return status;
}
//
// Wait for it to initialize.
//
KeWaitForSingleObject(
&context.InitCompleteEvent, // Object
UserRequest, // WaitReason
KernelMode, // WaitMode
FALSE, // Alertable
NULL // Timeout
);
status = context.Status;
if (!NT_SUCCESS(status))
{
//
// The current thread failed initialization. Since we know it
// has already exited, we can go ahead and close & NULL the
// handle. This allows UlpKillPipelineWorkerThreads() to just
// look at the first thread handle in the array to determine if
// there are any threads that need to be terminated.
//
ZwClose( pThreadData->ThreadHandle );
UlDetachFromSystemProcess();
pThreadData->ThreadHandle = NULL;
pThreadData->pThreadObject = NULL;
UlDestroyPipeline( pPipeline );
return status;
}
ASSERT( pThreadData->ThreadHandle != NULL );
ASSERT( pThreadData->pThreadObject != NULL );
//
// Advance to the next thread.
//
pThreadData++;
context.Processor++;
if (context.Processor >= g_UlNumberOfProcessors)
{
context.Processor = 0;
}
}
UlDetachFromSystemProcess();
//
// At this point, the pipeline is fully initialized *except* for
// the individual work queues. It is the caller's responsibility
// to initialize those queues.
//
*ppPipeline = pPipeline;
return STATUS_SUCCESS;
} // UlCreatePipeline
/***************************************************************************++
Routine Description:
Initializes the specified pipeline queue.
Arguments:
pPipeline - Supplies the pipeline that owns the queue.
QueueOrdinal - Supplies the queue to initialize.
pHandler - Supplies the handler routine for the queue.
--***************************************************************************/
VOID
UlInitializeQueuePipeline(
IN PUL_PIPELINE pPipeline,
IN UL_PIPELINE_QUEUE_ORDINAL QueueOrdinal,
IN PFN_UL_PIPELINE_HANDLER pHandler
)
{
PUL_PIPELINE_QUEUE pQueue;
//
// Sanity check.
//
PAGED_CODE();
ASSERT( QueueOrdinal < pPipeline->QueueCount );
//
// Initialize it.
//
pQueue = &pPipeline->Queues[QueueOrdinal];
InitializeListHead(
&pQueue->WorkQueueHead
);
pQueue->QueueLock = L_UNLOCKED;
pQueue->pHandler = pHandler;
} // UlInitializeQueuePipeline
/***************************************************************************++
Routine Description:
Destroys the specified pipeline, freeing any allocated resources
and killing the worker threads.
Arguments:
pPipeline - Supplies the pipeline to destroy.
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlDestroyPipeline(
IN PUL_PIPELINE pPipeline
)
{
//
// Sanity check.
//
PAGED_CODE();
ASSERT( pPipeline != NULL );
ASSERT( PIPELINE_TO_RARE_DATA( pPipeline ) != NULL );
ASSERT( PIPELINE_TO_RARE_DATA( pPipeline )->pAllocationBlock != NULL );
UlpKillPipelineWorkerThreads( pPipeline );
UL_FREE_POOL(
PIPELINE_TO_RARE_DATA( pPipeline )->pAllocationBlock,
UL_PIPELINE_POOL_TAG
);
return STATUS_SUCCESS;
} // UlDestroyPipeline
/***************************************************************************++
Routine Description:
Enqueues a work item to the specified pipeline queue.
Arguments:
pPipeline - Supplies the pipeline that owns the queue.
QueueOrdinal - Supplies the queue to enqueue the work on.
pWorkItem - Supplies the work item to enqueue.
--***************************************************************************/
VOID
UlQueueWorkPipeline(
IN PUL_PIPELINE pPipeline,
IN UL_PIPELINE_QUEUE_ORDINAL QueueOrdinal,
IN PUL_PIPELINE_WORK_ITEM pWorkItem
)
{
KIRQL oldIrql;
PUL_PIPELINE_QUEUE pQueue;
BOOLEAN needToSetEvent = FALSE;
ASSERT( QueueOrdinal < pPipeline->QueueCount );
pQueue = &pPipeline->Queues[QueueOrdinal];
UlAcquireSpinLock(
&pPipeline->PipelineLock,
&oldIrql
);
if (!pPipeline->ShutdownFlag)
{
//
// If the queue is currently empty, then remember that we have new
// queue with work pending. If the number of pending queues is now
// greater than the number of threads currently running (but less
// than the maximum configured), then remember that we'll need to
// set the event to unblock a new worker thread.
//
if (IsListEmpty( &pQueue->WorkQueueHead ))
{
pPipeline->QueuesWithWork++;
if (pPipeline->QueuesWithWork > pPipeline->ThreadsRunning &&
pPipeline->ThreadsRunning < pPipeline->MaximumThreadsRunning)
{
needToSetEvent = TRUE;
}
}
InsertTailList(
&pQueue->WorkQueueHead,
&pWorkItem->WorkQueueEntry
);
}
UlReleaseSpinLock(
&pPipeline->PipelineLock,
oldIrql
);
if (needToSetEvent)
{
KeSetEvent(
&pPipeline->WorkAvailableEvent, // Event
g_UlPriorityBoost, // Increment
FALSE // Wait
);
}
} // UlQueueWorkPipeline
//
// Private functions.
//
/***************************************************************************++
Routine Description:
Blocks the current thread if necessary until work is available on one
of the pipeline queues.
Arguments:
pPipeline - Supplies the pipeline to block on.
Return Value:
BOOLEAN - TRUE if the thread should exit, FALSE if it should continue
to service requests.
--***************************************************************************/
BOOLEAN
UlpWaitForWorkPipeline(
IN PUL_PIPELINE pPipeline
)
{
KIRQL oldIrql;
BOOLEAN needToWait;
BOOLEAN firstPass = TRUE;
while (TRUE)
{
UlAcquireSpinLock(
&pPipeline->PipelineLock,
&oldIrql
);
if (pPipeline->ShutdownFlag)
{
UlReleaseSpinLock(
&pPipeline->PipelineLock,
oldIrql
);
return TRUE;
}
//
// If there are more threads running than there is work available
// or if we've reached our maximum thread count, then we'll need
// to wait on the event. Otherwise, we can update the running thread
// count and bail.
//
if (pPipeline->QueuesWithWork <= pPipeline->ThreadsRunning ||
pPipeline->ThreadsRunning >= pPipeline->MaximumThreadsRunning)
{
needToWait = TRUE;
if (firstPass)
{
pPipeline->ThreadsRunning--;
firstPass = FALSE;
}
}
else
{
pPipeline->ThreadsRunning++;
needToWait = FALSE;
}
UlReleaseSpinLock(
&pPipeline->PipelineLock,
oldIrql
);
if (needToWait)
{
KeWaitForSingleObject(
&pPipeline->WorkAvailableEvent, // Object
UserRequest, // WaitReason
KernelMode, // WaitMode
FALSE, // Alertable
NULL // Timeout
);
}
else
{
break;
}
}
return FALSE;
} // UlpWaitForWorkPipeline
/***************************************************************************++
Routine Description:
Dequeues all work items from the specified queue.
Arguments:
pPipeline - Supplies the pipeline owning the queue.
pQueue - Supplies the queue dequeue the work items from.
Return Value:
PLIST_ENTRY - Pointer to the first work item if successful, or a
pointer to the head of the work queue if it is empty.
--***************************************************************************/
PLIST_ENTRY
UlpDequeueAllWorkPipeline(
IN PUL_PIPELINE pPipeline,
IN PUL_PIPELINE_QUEUE pQueue
)
{
KIRQL oldIrql;
PLIST_ENTRY pListEntry;
UlAcquireSpinLock(
&pPipeline->PipelineLock,
&oldIrql
);
//
// Dequeue all work items and update the count of queues with work.
//
pListEntry = pQueue->WorkQueueHead.Flink;
if (pListEntry != &pQueue->WorkQueueHead)
{
pPipeline->QueuesWithWork--;
}
InitializeListHead( &pQueue->WorkQueueHead );
UlReleaseSpinLock(
&pPipeline->PipelineLock,
oldIrql
);
return pListEntry;
} // UlpDequeueAllWorkPipeline
/***************************************************************************++
Routine Description:
Attempts to acquire the lock protecting the queue.
N.B. Queue locks cannot be acquired recursively.
Arguments:
pQueue - Supplies the queue to attempt to lock.
Return Value:
BOOLEAN - TRUE if the queue was locked successfully, FALSE otherwise.
--***************************************************************************/
BOOLEAN
UlpLockQueuePipeline(
IN PUL_PIPELINE_QUEUE pQueue
)
{
LONG result;
//
// Sanity check.
//
PAGED_CODE();
//
// Try to exchange the lock value with L_LOCKED. If the previous value
// was L_UNLOCKED, then we know the lock is ours.
//
result = UlInterlockedCompareExchange(
&pQueue->QueueLock,
L_LOCKED,
L_UNLOCKED
);
return ( result == L_UNLOCKED );
} // UlpLockQueuePipeline
/***************************************************************************++
Routine Description:
Unlocks a locked queue.
Arguments:
pQueue - Supplies the queue to unlock.
--***************************************************************************/
VOID
UlpUnlockQueuePipeline(
IN PUL_PIPELINE_QUEUE pQueue
)
{
//
// Sanity check.
//
PAGED_CODE();
//
// No need to interlock this, as we own the lock.
//
pQueue->QueueLock = L_UNLOCKED;
} // UlpUnlockQueuePipeline
/***************************************************************************++
Routine Description:
Scans the specified pipeline's queues looking for one that is not
currently locked.
N.B. The queues are searched backwards (from high index to low index).
Arguments:
pPipeline - Supplies the pipeline to scan.
pQueueOrdinal - Supplies a pointer to the ordinal of the most recently
touched queue. This value will get updated with the ordinal of the
queue returned (if successful).
Return Value:
PUL_PIPELINE_QUEUE - Pointer to a queue if succesful, NULL otherwise.
--***************************************************************************/
PUL_PIPELINE_QUEUE
UlpFindNextQueuePipeline(
IN PUL_PIPELINE pPipeline,
IN OUT PUL_PIPELINE_QUEUE_ORDINAL pQueueOrdinal
)
{
UL_PIPELINE_QUEUE_ORDINAL ordinal;
SHORT count;
PUL_PIPELINE_QUEUE pQueue;
//
// Sanity check.
//
PAGED_CODE();
//
// Start at the current values.
//
ordinal = *pQueueOrdinal;
pQueue = &pPipeline->Queues[ordinal];
for (count = pPipeline->QueueCount ; count > 0 ; count--)
{
//
// Go to the previous value. We scan backwards to make the
// math (and the generated code) a bit simpler.
//
pQueue--;
ordinal--;
if (ordinal < 0)
{
ordinal = pPipeline->QueueCount - 1;
pQueue = &pPipeline->Queues[ordinal];
}
//
// If the queue is not empty and we can lock the queue,
// then return it.
//
if (!IsListEmpty( &pQueue->WorkQueueHead ) &&
UlpLockQueuePipeline( pQueue ))
{
*pQueueOrdinal = ordinal;
return pQueue;
}
}
//
// If we made it this far, then all queues are being handled.
//
return NULL;
} // UlpFindNextQueuePipeline
/***************************************************************************++
Routine Description:
Worker thread for the pipeline package.
Arguments:
pContext - Supplies the context for the thread. This is actually a
PUL_PIPELINE_THREAD_CONTEXT pointer containing startup information
for this thread.
--***************************************************************************/
VOID
UlpPipelineWorkerThread(
IN PVOID pContext
)
{
PUL_PIPELINE pPipeline;
PUL_PIPELINE_QUEUE pQueue;
PUL_PIPELINE_WORK_ITEM pWorkItem;
UL_PIPELINE_QUEUE_ORDINAL ordinal;
PFN_UL_PIPELINE_HANDLER pHandler;
PUL_PIPELINE_THREAD_DATA pThreadData;
PLIST_ENTRY pListEntry;
//
// Sanity check.
//
PAGED_CODE();
ASSERT( pContext != NULL );
//
// Initialize the thread data.
//
pThreadData = ((PUL_PIPELINE_THREAD_CONTEXT)pContext)->pThreadData;
pThreadData->pThreadObject = (PVOID)PsGetCurrentThread();
//
// Initialize the thread. If this fails, we're toast.
//
pPipeline = UlpPipelineWorkerThreadStartup(
(PUL_PIPELINE_THREAD_CONTEXT)pContext
);
if (!pPipeline)
{
return;
}
//
// Loop forever, or at least until we're told to quit.
//
ordinal = 0;
while (TRUE)
{
//
// Wait for something to do.
//
if (UlpWaitForWorkPipeline( pPipeline ))
{
break;
}
//
// Try to find an unowned queue.
//
pQueue = UlpFindNextQueuePipeline( pPipeline, &ordinal );
if (pQueue != NULL)
{
#if DBG
pThreadData->QueuesHandled++;
#endif
//
// Snag the handler routine from the queue.
//
pHandler = pQueue->pHandler;
//
// Loop through all of the work items on the queue and
// invoke the handler for each.
//
pListEntry = UlpDequeueAllWorkPipeline(
pPipeline,
pQueue
);
while (pListEntry != &pQueue->WorkQueueHead)
{
pWorkItem = CONTAINING_RECORD(
pListEntry,
UL_PIPELINE_WORK_ITEM,
WorkQueueEntry
);
pListEntry = pListEntry->Flink;
(pHandler)( pWorkItem );
#if DBG
pThreadData->WorkItemsHandled++;
#endif
}
//
// We're done with the queue, so unlock it.
//
UlpUnlockQueuePipeline( pQueue );
}
}
} // UlpPipelineWorkerThread
/***************************************************************************++
Routine Description:
Startup code for pipeline worker threads.
Arguments:
pContext - Supplies a pointer to the startup context for this thread.
The context will receive the final startup completion status.
Return Value:
PUL_PIPELINE - A pointer to the thread's pipeline if successful,
NULL otherwise.
--***************************************************************************/
PUL_PIPELINE
UlpPipelineWorkerThreadStartup(
IN PUL_PIPELINE_THREAD_CONTEXT pContext
)
{
NTSTATUS status;
ULONG affinityMask;
PUL_PIPELINE pPipeline;
//
// Sanity check.
//
PAGED_CODE();
ASSERT( pContext != NULL );
ASSERT( pContext->pPipeline != NULL );
ASSERT( pContext->pThreadData != NULL );
//
// Disable hard error popups.
//
IoSetThreadHardErrorMode( FALSE );
//
// Set our affinity.
//
ASSERT( pContext->Processor < g_UlNumberOfProcessors );
affinityMask = 1 << pContext->Processor;
status = NtSetInformationThread(
NtCurrentThread(), // ThreadHandle
ThreadAffinityMask, // ThreadInformationClass
&affinityMask, // ThreadInformation
sizeof(affinityMask) // ThreadInformationLength
);
if (!NT_SUCCESS(status))
{
pContext->Status = status;
return NULL;
}
//
// Capture the pipeline from the context before setting the
// init complete event. Once we set the event, we are not allowed
// to touch the context structure.
//
pPipeline = pContext->pPipeline;
ASSERT( pPipeline != NULL );
KeSetEvent(
&pContext->InitCompleteEvent, // Event
0, // Increment
FALSE // Wait
);
return pPipeline;
} // UlpPipelineWorkerThreadStartup
/***************************************************************************++
Routine Description:
Kills all worker threads associated with the given pipeline and
waits for them to die.
Arguments:
pPipeline - Supplies the pipeline to terminate.
--***************************************************************************/
VOID
UlpKillPipelineWorkerThreads(
IN PUL_PIPELINE pPipeline
)
{
KIRQL oldIrql;
SHORT totalThreads;
SHORT i;
PUL_PIPELINE_THREAD_DATA pThreadData;
LARGE_INTEGER delayInterval;
//
// Sanity check.
//
ASSERT( pPipeline != NULL );
//
// Count the number of running threads.
//
pThreadData = PIPELINE_TO_THREAD_DATA( pPipeline );
totalThreads = 0;
for (i = 0 ; i < pPipeline->MaximumThreadsRunning ; i++)
{
if (pThreadData->ThreadHandle == NULL)
{
break;
}
pThreadData++;
totalThreads++;
}
if (totalThreads > 0)
{
//
// We have threads running, so we'll need to signal them to stop.
//
UlAcquireSpinLock(
&pPipeline->PipelineLock,
&oldIrql
);
pPipeline->ShutdownFlag = TRUE;
UlReleaseSpinLock(
&pPipeline->PipelineLock,
oldIrql
);
while (pPipeline->ThreadsRunning > 0)
{
KeSetEvent(
&pPipeline->WorkAvailableEvent, // Event
g_UlPriorityBoost, // Increment
FALSE // Wait
);
delayInterval.QuadPart = -1*10*1000*500; // .5 second
KeDelayExecutionThread(
KernelMode, // WaitMode
FALSE, // Alertable
&delayInterval // Interval
);
}
//
// Wait for them to die.
//
pThreadData = PIPELINE_TO_THREAD_DATA( pPipeline );
for (i = 0 ; i < totalThreads ; i++)
{
KeWaitForSingleObject(
(PVOID)pThreadData->pThreadObject, // Object
UserRequest, // WaitReason
KernelMode, // WaitMode
FALSE, // Alertable
NULL // Timeout
);
UlCloseSystemHandle( pThreadData->ThreadHandle );
pThreadData->ThreadHandle = NULL;
pThreadData->pThreadObject = NULL;
pThreadData++;
}
}
} // UlpKillPipelineWorkerThreads