Enhanced Work Item Support in Vista

While writing the threaded and non-threaded PIRP entry, I had the I/O manager developers review what I wrote to make sure I was giving out correct information. After the review, the I/O manager folks wanted to know if they could write some guest entries to let driver writers know about new I/O manager features in Vista. So, for my first guest spot, we have Paul Sliwowicz who works on the I/O manager. Here is what Paul has to say...

Prior to Vista (and starting on Windows 2000), drivers had the option of creating an I/O work item using IoAllocateWorkItem() which could then be used to post some work to a system worker thread using IoQueueWorkItem(). These functions have a couple of shortcomings: they do not allow for a work item to be embedded inside a structure (vs. the deprecated WORK_QUEUE_ITEM structure which let you do this), and IoQueueWorkItem() requires a valid device object pointer to use a work item.

In order to address these two issues, several new routines were added to make things more flexible:

  • IoSizeofWorkItem()
  • IoInitializeWorkItem()
  • IoQueueWorkItemEx()
  • IoUninitializeWorkItem()

A driver can now use IoSizeofWorkItem() and IoInitializeWorkItem() to allocate and initialize their own work items and embed them in a structure such as the following (which you could have at the end of a device extension, for example):

     typedef struct _WORK_ITEM_CONTEXT {
        ULONG Size;
        ULONG Context;
        UCHAR WorkItemStart[1];
    } WORK_ITEM_CONTEXT, *PWORK_ITEM_CONTEXT;

    PWORK_ITEM_CONTEXT pContext;
    ULONG size;

    //
    // Compute the size up to the variable length array
    //
    size = FIELD_OFFSET(WORK_ITEM_CONTEXT, WorkItemStart);

    //
    // Now add the size of the work item
    //
    size += IoSizeofWorkItem();

    pContext = ExAllocatePoolWithTag(NonPagedPool, size,  <tag> );
    if (pContext == NULL) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    pContext->Size = size;

    //
    // And now we need to initialize the work item itself
    //
    IoInitializeWorkItem(DeviceObject, (PIO_WORKITEM) &pContext->WorkItemStart[0]);

And instead of freeing the work item when you are done with it, you simply uninitialize it:

     IoUninitializeWorkItem((PIO_WORKITEM) &pContext->WorkItemStart[0]);

The other routine that was added was IoQueueWorkItemEx(). This routine has a couple of benefits over IoQueueWorkItem(). First off, it eliminates the need to have a device object associated with the work item; a driver can now simply pass their driver object (with some unfortunate casting) when calling IoAllocateWorkItem() or IoInitializeWorkItem if they wanted to issue a work item unrelated to a specific device. Secondly, it allows the worker routine to receive a pointer to the work item itself along with the context that is passed.

This means the signature for the callback that you pass as a parameter to IoQueueWorkItemEx() is different than the callback you used to pass as a parameter to IoQueueWorkItem(). The IoQueueWorkItemEx() callback takes the extra work item parameter and has a PVOID as the first entry (so it can handle either a driver object or a device object):

     typedef
    VOID
    IO_WORKITEM_ROUTINE_EX (
        __in PVOID IoObject,
        __in_opt PVOID Context,
        __in PIO_WORKITEM IoWorkItem
        );

Now we could do something like this with the initialized work item we created above:

     IoQueueWorkItemEx((PIO_WORKITEM) &pContext->WorkItemStart[0],
                      WorkerRoutine,
                      DelayedWorkQueue,
                      SomeOtherContext);

And when inside the worker routine you can use CONTAINING_RECORD to get back your original context. So now we have access to both the original structure we embedded the work item into as well as the context we passed when we actually queued the request!

     VOID
    WorkItemRoutine (
        __in PVOID IoObject,
        __in_opt PVOID Context,
        __in PIO_WORKITEM IoWorkItem
        )
    {
        PWORK_ITEM_CONTEXT pWorkContext;

        pWorkContext = CONTAINING_RECORD(IoWorkItem, WORK_ITEM_CONTEXT, WorkItemStart);

        // ... do work ...

        IoUninitializeWorkItem(IoWorkItem);
        ExFreePool(pWorkContext);
        pWorkContext = NULL;
    }