/*++ 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 // // 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