mirror of https://github.com/tongzx/nt5src
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
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
|
|
|