Compare commits

...

2 Commits

Author SHA1 Message Date
Mounir IDRASSI
453ff2880e
Windows Driver: Make max work items count configurable. Increase default to 1024. Queue write IRPs.
- Made the maximum work items count configurable to allow flexibility based on system needs.
  - Increased the default value of max work items count to 1024 to better handle high-throughput scenarios.
  - Queue write IRPs in system worker thread to avoid potential deadlocks in write scenarios.
2024-11-23 17:44:48 +01:00
Mounir IDRASSI
5a85c54c6e
Windows Driver: Optimize spinlock usage in CompleteIrpWorkItemRoutine
Reduce the critical section protected by spinlock to only cover the list manipulation operation. Move the ActiveWorkItems counter decrement outside the spinlock using InterlockedDecrement, and separate event signaling from the locked section.
This change minimizes time spent at raised IRQL (DISPATCH_LEVEL) and reduces potential for lock contention.
2024-11-22 15:19:10 +01:00
5 changed files with 60 additions and 46 deletions

View File

@ -396,6 +396,7 @@ typedef struct
int EncryptionIoRequestCount;
int EncryptionItemCount;
int EncryptionFragmentSize;
int EncryptionMaxWorkItems;
} EncryptionQueueParameters;
#pragma pack (pop)
@ -418,6 +419,7 @@ typedef struct
#define VC_ENCRYPTION_IO_REQUEST_COUNT DRIVER_STR("VeraCryptEncryptionIoRequestCount")
#define VC_ENCRYPTION_ITEM_COUNT DRIVER_STR("VeraCryptEncryptionItemCount")
#define VC_ENCRYPTION_FRAGMENT_SIZE DRIVER_STR("VeraCryptEncryptionFragmentSize")
#define VC_ENCRYPTION_MAX_WORK_ITEMS DRIVER_STR("VeraCryptEncryptionMaxWorkItems")
#define VC_ERASE_KEYS_SHUTDOWN DRIVER_STR("VeraCryptEraseKeysShutdown")

View File

@ -271,6 +271,7 @@ static VOID CompleteIrpWorkItemRoutine(PDEVICE_OBJECT DeviceObject, PVOID Contex
PCOMPLETE_IRP_WORK_ITEM workItem = (PCOMPLETE_IRP_WORK_ITEM)Context;
EncryptedIoQueueItem* item = (EncryptedIoQueueItem * ) workItem->Item;
EncryptedIoQueue* queue = item->Queue;
KIRQL oldIrql;
UNREFERENCED_PARAMETER(DeviceObject);
__try
@ -283,19 +284,14 @@ static VOID CompleteIrpWorkItemRoutine(PDEVICE_OBJECT DeviceObject, PVOID Contex
}
__finally
{
// Return the work item to the free list
KIRQL oldIrql;
KeAcquireSpinLock(&queue->WorkItemLock, &oldIrql);
// Decrement ActiveWorkItems
LONG activeWorkItems = InterlockedDecrement(&queue->ActiveWorkItems);
// If no active work items remain, signal the event
if (activeWorkItems == 0)
if (InterlockedDecrement(&queue->ActiveWorkItems) == 0)
{
KeSetEvent(&queue->NoActiveWorkItemsEvent, IO_NO_INCREMENT, FALSE);
}
// Return the work item to the free list
KeAcquireSpinLock(&queue->WorkItemLock, &oldIrql);
InsertTailList(&queue->FreeWorkItemsList, &workItem->ListEntry);
KeReleaseSpinLock(&queue->WorkItemLock, oldIrql);
@ -307,8 +303,43 @@ static VOID CompleteIrpWorkItemRoutine(PDEVICE_OBJECT DeviceObject, PVOID Contex
}
}
// Handles the completion of the original IRP.
static VOID HandleCompleteOriginalIrp(EncryptedIoQueue* queue, EncryptedIoRequest* request)
{
NTSTATUS status = KeWaitForSingleObject(&queue->WorkItemSemaphore, Executive, KernelMode, FALSE, NULL);
if (queue->ThreadExitRequested)
return;
if (!NT_SUCCESS(status))
{
// Handle wait failure: we call the completion routine directly.
// This is not ideal since it can cause deadlock that we are trying to fix but it is better than losing the IRP.
CompleteOriginalIrp(request->Item, STATUS_INSUFFICIENT_RESOURCES, 0);
}
else
{
// Obtain a work item from the free list.
KIRQL oldIrql;
KeAcquireSpinLock(&queue->WorkItemLock, &oldIrql);
PLIST_ENTRY freeEntry = RemoveHeadList(&queue->FreeWorkItemsList);
KeReleaseSpinLock(&queue->WorkItemLock, oldIrql);
PCOMPLETE_IRP_WORK_ITEM workItem = CONTAINING_RECORD(freeEntry, COMPLETE_IRP_WORK_ITEM, ListEntry);
// Increment ActiveWorkItems.
InterlockedIncrement(&queue->ActiveWorkItems);
KeResetEvent(&queue->NoActiveWorkItemsEvent);
// Prepare the work item.
workItem->Irp = request->Item->OriginalIrp;
workItem->Status = request->Item->Status;
workItem->Information = NT_SUCCESS(request->Item->Status) ? request->Item->OriginalLength : 0;
workItem->Item = request->Item;
// Queue the work item.
IoQueueWorkItem(workItem->WorkItem, CompleteIrpWorkItemRoutine, DelayedWorkQueue, workItem);
}
}
static VOID CompletionThreadProc(PVOID threadArg)
{
@ -352,39 +383,7 @@ static VOID CompletionThreadProc(PVOID threadArg)
if (request->CompleteOriginalIrp)
{
// Wait for a work item to become available
NTSTATUS status = KeWaitForSingleObject(&queue->WorkItemSemaphore, Executive, KernelMode, FALSE, NULL);
if (queue->ThreadExitRequested)
break;
if (!NT_SUCCESS(status))
{
// Handle wait failure: we call the completion routine directly.
// This is not ideal since it can cause deadlock that we are trying to fix but it is better than losing the IRP.
CompleteOriginalIrp(request->Item, STATUS_INSUFFICIENT_RESOURCES, 0);
}
else
{
// Obtain a work item from the free list
KIRQL oldIrql;
KeAcquireSpinLock(&queue->WorkItemLock, &oldIrql);
PLIST_ENTRY freeEntry = RemoveHeadList(&queue->FreeWorkItemsList);
KeReleaseSpinLock(&queue->WorkItemLock, oldIrql);
PCOMPLETE_IRP_WORK_ITEM workItem = CONTAINING_RECORD(freeEntry, COMPLETE_IRP_WORK_ITEM, ListEntry);
// Increment ActiveWorkItems
InterlockedIncrement(&queue->ActiveWorkItems);
KeResetEvent(&queue->NoActiveWorkItemsEvent);
// Prepare the work item
workItem->Irp = request->Item->OriginalIrp;
workItem->Status = request->Item->Status;
workItem->Information = NT_SUCCESS(request->Item->Status) ? request->Item->OriginalLength : 0;
workItem->Item = request->Item;
// Queue the work item
IoQueueWorkItem(workItem->WorkItem, CompleteIrpWorkItemRoutine, DelayedWorkQueue, workItem);
}
HandleCompleteOriginalIrp(queue, request);
}
ReleasePoolBuffer(queue, request);
@ -545,8 +544,7 @@ static VOID IoThreadProc (PVOID threadArg)
if (request->CompleteOriginalIrp)
{
CompleteOriginalIrp (request->Item, request->Item->Status,
NT_SUCCESS (request->Item->Status) ? request->Item->OriginalLength : 0);
HandleCompleteOriginalIrp(queue, request);
}
ReleasePoolBuffer (queue, request);
@ -1152,10 +1150,10 @@ NTSTATUS EncryptedIoQueueStart (EncryptedIoQueue *queue)
// Initialize the free work item list
InitializeListHead(&queue->FreeWorkItemsList);
KeInitializeSemaphore(&queue->WorkItemSemaphore, VC_MAX_WORK_ITEMS, VC_MAX_WORK_ITEMS);
KeInitializeSemaphore(&queue->WorkItemSemaphore, EncryptionMaxWorkItems, EncryptionMaxWorkItems);
KeInitializeSpinLock(&queue->WorkItemLock);
queue->MaxWorkItems = VC_MAX_WORK_ITEMS;
queue->MaxWorkItems = EncryptionMaxWorkItems;
queue->WorkItemPool = (PCOMPLETE_IRP_WORK_ITEM)TCalloc(sizeof(COMPLETE_IRP_WORK_ITEM) * queue->MaxWorkItems);
if (!queue->WorkItemPool)
{

View File

@ -26,7 +26,7 @@
#define TC_ENC_IO_QUEUE_PREALLOCATED_IO_REQUEST_COUNT 16
#define TC_ENC_IO_QUEUE_PREALLOCATED_IO_REQUEST_MAX_COUNT 8192
#define VC_MAX_WORK_ITEMS 256
#define VC_MAX_WORK_ITEMS 1024
typedef struct EncryptedIoQueueBufferStruct
{

View File

@ -145,6 +145,7 @@ static BOOL RamEncryptionActivated = FALSE;
int EncryptionIoRequestCount = 0;
int EncryptionItemCount = 0;
int EncryptionFragmentSize = 0;
int EncryptionMaxWorkItems = 0;
PDEVICE_OBJECT VirtualVolumeDeviceObjects[MAX_MOUNTED_VOLUME_DRIVE_NUMBER + 1];
@ -2776,6 +2777,7 @@ NTSTATUS ProcessMainDeviceControlIrp (PDEVICE_OBJECT DeviceObject, PEXTENSION Ex
if (ValidateIOBufferSize (Irp, sizeof (EncryptionQueueParameters), ValidateOutput))
{
EncryptionQueueParameters* pParams = (EncryptionQueueParameters*) Irp->AssociatedIrp.SystemBuffer;
pParams->EncryptionMaxWorkItems = EncryptionMaxWorkItems;
pParams->EncryptionFragmentSize = EncryptionFragmentSize;
pParams->EncryptionIoRequestCount = EncryptionIoRequestCount;
pParams->EncryptionItemCount = EncryptionItemCount;
@ -4646,6 +4648,14 @@ NTSTATUS ReadRegistryConfigFlags (BOOL driverEntry)
TCfree (data);
}
if (driverEntry && NT_SUCCESS(TCReadRegistryKey(&name, VC_ENCRYPTION_MAX_WORK_ITEMS, &data)))
{
if (data->Type == REG_DWORD)
EncryptionMaxWorkItems = *(uint32*)data->Data;
TCfree(data);
}
if (driverEntry)
{
if (EncryptionIoRequestCount < TC_ENC_IO_QUEUE_PREALLOCATED_IO_REQUEST_COUNT)
@ -4663,6 +4673,9 @@ NTSTATUS ReadRegistryConfigFlags (BOOL driverEntry)
EncryptionFragmentSize = TC_ENC_IO_QUEUE_MAX_FRAGMENT_SIZE;
else if (EncryptionFragmentSize > (8 * TC_ENC_IO_QUEUE_MAX_FRAGMENT_SIZE))
EncryptionFragmentSize = 8 * TC_ENC_IO_QUEUE_MAX_FRAGMENT_SIZE;
if (EncryptionMaxWorkItems == 0)
EncryptionMaxWorkItems = VC_MAX_WORK_ITEMS;
}

View File

@ -128,6 +128,7 @@ extern BOOL AllowWindowsDefrag;
extern int EncryptionIoRequestCount;
extern int EncryptionItemCount;
extern int EncryptionFragmentSize;
extern int EncryptionMaxWorkItems;
extern BOOL EraseKeysOnShutdown;
/* Helper macro returning x seconds in units of 100 nanoseconds */
#define WAIT_SECONDS(x) ((x)*10000000)