Object Hierarchy and Lifetime


Both UMDF and KMDF use reference counts in controlling object lifetime. Every object has a reference count, which ensures that the object persists as long as it is being used:

  • UMDF uses the standard COM interface. Drivers can use the AddRef and Release methods on the object to manage the object's reference count.

  • KMDF maintains its own reference counts. Drivers can use the WdfObjectReferenceXxx and WdfObjectDereferenceXxx methods.

In addition to a reference count, every framework object-except the driver object-is assigned a parent at creation. The driver object is the root object, and all other objects are its descendants. Device objects are always the children of the driver object, and queues are generally the children of device objects, although a queue can be a later descendant. The framework builds and maintains an internal object tree based on these relationships. Figure 5-2 shows a simple example of an object tree.

image from book
Figure 5-2: Sample object tree

In the figure, the driver object is the parent of the device object. The device object is the parent of each of the two queue objects. Queue object 1 is the parent of the request object. Queue object 1, the device object, and the driver object are all ancestors of the request object in the tree.

Tip 

If your driver creates objects, be sure to set the parents of those objects appropriately. As an example, consider a memory object. By default, the driver object is the parent of a memory object. This setting is appropriate for memory that a driver allocates for driver-wide use, because the memory object by default persists until the driver object is deleted. However, if the memory object is used only in particular types of I/O requests, the request object itself or the I/O target to which the driver sends those requests is a more appropriate parent. The key is how long the memory object is used. If your driver creates a new memory object for each request, you should set the request as the parent so that the memory object is deleted when the request is complete. If your driver uses the same memory object for subsequent requests to the same I/O target, set the I/O target as the parent.

When an object is deleted, the framework calls the cleanup callbacks of its most distant descendants first and then works back up the hierarchy to the object itself. For example, if the device object in Figure 5-2 is deleted, the framework first calls the cleanup callback of the request object, then calls the cleanup callbacks of the queue objects, and finally calls the cleanup callback of the device object.

The parent-child relationships in the hierarchy are important in simplifying object cleanup and state management. For example, the default I/O target object is a child of the device object. When the device object is deleted, the framework deletes the I/O target object first and calls the I/O target's cleanup callback. Consequently, the driver does not require special code to manage I/O requests that complete while the device object is being torn down. The I/O target cleanup callback typically handles any such issues.

Drivers do not typically take out references on the objects that they create, but in some cases such references are necessary to ensure that the object's handle remains valid. For example, a driver that sends an asynchronous I/O request might take out a reference on the request object to help prevent race conditions during cancellation. Before the request object can be deleted, the driver must release this reference. The driver must implement an object cleanup callback to release such a reference. See "Cleanup Callbacks" later in this chapter for more information.

image from book
Deletion, Disposal, Cleanup, and Destruction

The terms "deletion," "disposal," "cleanup," and "destruction" can sometimes be confusing. When a driver calls the framework to delete an object, the framework starts a sequence that eventually results in the destruction of the object. The first step is to dispose of the object's children by calling their cleanup callbacks. This step starts with the generation that is farthest from the parent in the object hierarchy and occurs in strict hierarchical order.

References can remain on a child or on the parent after disposal is complete. After the last reference has been removed for any of the disposed objects, the framework calls the object's deletion callback and then destroys the object.

image from book

UMDF Object Hierarchy

Figure 5-3 shows the parent-child relationships among the UMDF objects.

image from book
Figure 5-3: Parent-child relationships among UMDF objects

As the figure shows, the driver object is the root object and device objects are its children. By default, driver-created memory objects and base objects are also children of the driver object, but the driver can change the parent of a memory or base object to another object. For example, a driver might change the parent of a memory object to the driver-created I/O request object with which the memory object is used.

The device object is the default parent for all other framework objects. A driver can change the parent for a driver-created I/O request object when it creates the request. If the request is closely related to a framework-created I/O request object, the driver should set the framework request object as the parent. For example, if the driver must get information from a different device stack or send an IOCTL down its own device stack before it can complete an I/O request that arrived from the framework, the driver should set the framework-created request as the parent. When the framework-created request is deleted, the driver-created request is also deleted.

The lifetime of a callback object is related to the lifetime of the corresponding framework object. When the driver creates a framework object, the driver passes a pointer to an interface on the corresponding callback object. The framework object takes out a reference on the callback object. For example, when the driver calls IWDFDriver::CreateDevice, it passes a pointer to an interface on the device callback object. The framework creates a framework device object, takes out a reference on the callback object, and returns a pointer to the IWDFDevice interface.

The reference count ensures that the object persists as long as it is being used. If the reference count falls to zero, the object can be deleted. A driver deletes an object by calling IWDFObject::DeleteWdfObject.

UMDF follows the COM rules for taking references. Accordingly, every time a UMDF method returns an interface pointer as an output parameter, the framework takes a reference on the interface. The driver must release this reference by calling Release when it has finished using the pointer.

Driver code can use the AddRef and Release methods of IUnknown to increment and decrement an object's reference count, but usually this is unnecessary. However, if a driver does take a reference on a framework object, the driver must implement the IObjectCleanup::OnCleanup interface on the corresponding callback object to remove that reference so that the framework can eventually delete the object. The framework calls this method when the object is deleted.

COM programmers are probably already familiar with circular references, where two objects hold references on each other. Although each object releases its reference on the other object when its own reference count reaches zero, neither object can be deleted on its own. An external event must force one of the two objects to drop its reference on the other, thus breaking the circular reference and allowing both objects to delete themselves. This is the main purpose of the OnCleanup callback.

A driver can often avoid circular references. The callback object can maintain a weak reference on the framework object-that is, a pointer with no associated increase in reference count. Before the framework destroys the framework object, it calls the OnCleanup method, which simply clears the pointer and stops using it.

Chapter 18, "An Introduction to COM," provides a detailed explanation of when to use the AddRef and Release methods.

KMDF Object Hierarchy

For KMDF drivers, the driver object is the root object-all other objects are considered its children. For some object types, a driver can specify the parent when it creates the object. If the driver does not specify a parent at object creation, the framework sets the default parent to the driver object.

Figure 5-4 shows the default KMDF object hierarchy.

image from book
Figure 5-4: Parent-child relationships among the KMDF objects

For each object, Figure 5-4 shows which other objects must be in its parent chain. These objects are not necessarily required to be the immediate parent, but could be the grandparent, great-grandparent, and so forth. For example, the figure shows the WDFDEVICE object as parent of the WDFQUEUE object. However, a WDFQUEUE object could be the child of a WDFIOTARGET object, which in turn is the child of a WDFDEVICE object. Thus, the WDFDEVICE object is in the parent chain for the WDFQUEUE object.

KMDF maintains an implicit reference count for each object and ensures that the object persists until all references to it have been released. If the driver explicitly deletes an object by calling a deletion method, KMDF marks the object for deletion but does not actually free the memory for the object until after its reference count reaches zero.

Although KMDF drivers rarely take out explicit references, the framework provides the WdfObjectReference and WdfObjectDereference methods for a driver to use when a reference is necessary. A driver should take an explicit reference when it must ensure that an object's handle remains valid. Before the object can be deleted, the driver must release this reference. A driver calls WdfObjectDereference from a cleanup callback to release such a reference.

Object Deletion

Either of the following actions can trigger object deletion:

  • The object's parent is deleted.

  • The driver calls a method that explicitly deletes the object.

    For UMDF drivers, this method is IWDFObject::DeleteWdfObject.

    For KMDF drivers, this method is WdfObjectDelete.

The framework controls the deletion of most objects. Drivers can call deletion methods only for the objects that they own and control. Table 5-5 lists the framework object types and indicates whether a driver can delete a framework object of each type.

Table 5-5: Which Framework Objects a Driver Can Delete
Open table as spreadsheet

Object

UMDF

KMDF

Child list

Not applicable

No

Collection

Not applicable

Yes

Device

No

No, except for control device objects and PDOs in a certain state

DMA common buffer

Not applicable

Yes

DMA enabler

Not applicable

Yes

DMA transaction

Not applicable

Yes

DPC

Not applicable

Yes

Driver

No

No

Driver-created file

Yes

Yes

File

No

No

Base or Generic object

Yes, if created by the driver

Yes, if created by the driver

I/O queue

No, for default I/O queue Yes, for other queues

Yes

I/O request

No, if created by the framework Yes, if created by the driver

No, if created by the framework Yes, if created by the driver

I/O target

No, for the default I/O target Yes, for all other targets

No, for the default I/O target Yes, for all other targets

Interrupt

Not applicable

No

Lookaside list

Not applicable

Yes

Memory

No, if created by the framework Yes, if created by the driver

No, if created by the framework Yes, if created by the driver

Named property store

No

Not applicable

Registry key

Not applicable

Yes

Resource list

Not applicable

No

Resource range list

Not applicable

Yes

Resource requirements list

Not applicable

No

String

Not applicable

Yes

Synchronization: spin lock

Not applicable

Yes

Synchronization: wait lock

Not applicable

Yes

Timer

Not applicable

Yes

USB device

No, for the default target Yes, for all other targets

Yes

USB interface

No, for the default target Yes, for all other targets

No

USB pipe

No, for the default target Yes, for all other targets

No

WMI instance

Not applicable

Yes

WMI provider

Not applicable

No

Work item

Not applicable

Yes

The framework owns most framework objects and controls their lifetime. For example, the framework controls the device object and the default I/O target object. When the device is removed from the system, the framework deletes the device object and the default I/O target object.

If your driver owns an object, the driver should explicitly delete the object when the object is no longer required. When the driver calls a deletion method, the framework:

  • Releases its references on the callback object's interfaces. (UMDF only)

  • Calls the object's cleanup callback immediately, regardless of the object's reference count.

  • Decrements the internal reference count on the framework object.

  • Marks the framework object for deletion.

  • Deletes the framework object when the reference count reaches zero.

If outstanding references remain on the object, the framework marks the object for deletion but does not actually delete it until its reference count reaches zero. In this way, the framework ensures that the object's reference count remains nonzero-and thus the object itself is valid-any time it might be used. When the reference count reaches zero, the framework calls the deletion callback that the driver registered for the object and then removes the object from the internal object tree.

If the driver does not explicitly delete an object, the framework deletes the object when the object's parent is deleted.

A driver must not call a deletion method on an object that the framework controls:

  • In a UMDF driver, this causes the driver to stop with an error.

  • In a KMDF driver, this causes a bug check.

Cleanup Callbacks

A driver can support a cleanup callback for any object. The framework calls the cleanup callback before it deletes the object, the object's parent, or any of the object's more distant ancestors. The cleanup callback should release any outstanding references that the object has taken, free any resources that the driver allocated on behalf of the object, and perform any other related tasks that are required before the object is deleted.

 UMDF  UMDF drivers support a cleanup callback by implementing the IObjectCleanup interface on a callback object. This interface has one method: OnCleanup. The framework calls OnCleanup as part of the object destruction sequence before it releases the callback object.

 KMDF  KMDF defines the EvtCleanupCallback callback. The framework calls EvtCleanupCallback when the object is being deleted but before its reference count reaches zero. A driver supplies a pointer to this callback in the object attributes structure when it creates the object.

Destroy Callbacks

A driver can implement a destroy callback for an object in addition to-or instead of-a cleanup callback.

 UMDF  UMDF does not define a destroy callback. When a callback object's reference count drops to zero, the object typically triggers its destructor, which acts as a destroy callback. In the destructor, the driver can clean up any resources that it allocated on behalf of the callback object. However, the driver cannot track the destruction of the framework object. Deletion of the framework object and the callback object do not necessarily occur at the same time. The callback object can be deleted some time after the framework object if the driver or other framework objects still have references on the callback object.

 KMDF  For a KMDF driver, the destroy function is an EvtDestroyCallback callback, which the driver registers in the object attributes structure at object creation. The framework calls the destroy function after the object's reference count reaches zero and after the cleanup callbacks of the object, its parent, and any further ancestors-grandparents and so forth-have returned. The framework actually deletes the object after the destroy callback returns.

UMDF Object Deletion

The framework deletes most of the objects that a UMDF driver uses. When the device is removed, the framework deletes the device object and all of the children of the device object. If the driver sets an appropriate parent for the objects that it creates, the framework deletes those objects when it deletes the parent. For example, a driver that creates a memory object to send in an I/O request should set the I/O request as the parent of the memory object. When the I/O request is completed, the framework deletes the memory object.

A driver-created I/O request is the most common object that a UMDF driver might explicitly delete. The Fx2_Driver, for example, creates and sends I/O requests and then deletes the request object in its I/O completion callback. Listing 5-3 shows how the driver calls IWDFObject::DeleteWdfObject from its IWDFRequestCallbackRequestCompletion::OnCompletion method in the Device.cpp file.

Listing 5-3: Deleting an object in a UMDF driver

image from book
 VOID CMyDevice::OnCompletion(     IWDFIoRequest*                 FxRequest,     IWDFIoTarget*                  pIoTarget,     IWDFRequestCompletionParams*   pParams,     PVOID                          pContext     ) {     . . . //Code omitted     HRESULT hrCompletion = pParams->GetCompletionStatus();     if (FAILED(hrCompletion)) {         m_InterruptReadProblem = hrCompletion; }     else  {         // Get the returned parameters and other information         . . . //Code omitted     }     FxRequest->DeleteWdfObject();     . . . //Code omitted } 
image from book

In the completion callback, the driver retrieves all of the information that it requires from the request object before the call to DeleteWdfObject. After DeleteWdfObject returns, the driver can no longer access the object.

KMDF Object Deletion

When a KMDF object is deleted, all of the object's descendants are also deleted. Object deletion starts from the descendant farthest from the object and works up the object hierarchy toward the root until it reaches the object itself.

In the interest of clarity, assume that the object being deleted is YourObject. The framework takes the following steps to delete YourObject:

  1. Calls the EvtCleanupCallback function of the descendant that is farthest from YourObject in the framework's internal object tree.

    The EvtCleanupCallback function for any individual object should perform any cleanup tasks that must be done before that object's parent is deleted. Such tasks might include releasing explicit references on the object or a parent object. When an object's EvtCleanupCallback runs, the object's children still exist, even though their EvtCleanupCallback functions have already been invoked.

  2. Repeats step 1 for every descendant, proceeding back up the object tree, and finally for YourObject itself.

  3. Traverses the tree again in the same order to remove the framework's reference on the objects.

    If the object's reference count reaches zero as a result, the framework calls the object's EvtDestroyCallback function and then deallocates the memory that was allocated to the object and its context area.

The framework guarantees that the cleanup callbacks for children are called before the cleanup callbacks of their parent objects, but the framework makes no guarantees about the order in which it calls the cleanup callbacks of siblings. For example, if a device object has three child queue objects, the framework can call the siblings' cleanup callbacks in any order.

image from book
On Implementing Object Cleanup and Deletion in KMDF

At first, implementing the object deletion pattern described in this chapter appeared to be a simple task, but in reality it turned out to be surprisingly complex. When you take a step back and look at the pattern, you see that deletion is a state transaction that involves multiple objects. Any object can be in the middle of the same transaction that you are initiating for one of its ancestors, such as deleting a parent object in one thread and a descendant object in another thread. Every object must be able to go from the created state to the disposing state and must deal with race conditions. For example, the framework can simultaneously dispose of an object and its parent or the client driver can explicitly delete an object at the same time that the framework is deleting the object's parent and thus implicitly deleting the object as well.

As with many problems in the framework, we solved this by implementing a simple state machine and formalizing the different inputs that affect an object's lifetime. At that point, we knew how to dispose of a tree, but we still had to figure out when disposal was complete. The goal was to make handling device removal and shutdown as easy as possible for a KMDF driver, without the synchronization that is usually required in WDM drivers. In KMDF, object disposal had to occur at the device object level, so that the framework could guarantee that all of the device object's children were destroyed before the device object itself and that all of the driver-global objects were destroyed before the driver object. We solved this problem by placing each object on a device-specific or driver-global "dispose list." The framework checks the list to ensure that objects are destroyed at the right time.
-Doron Holan, Windows Driver Foundation Team, Microsoft

image from book

Order of KMDF Calls to EvtCleanupCallback

If your driver relies on the framework's object hierarchy to handle the deletion of child objects, you are guaranteed that the parent object still exists when the child's EvtCleanupCallback routine runs, except as described below. If the driver explicitly deletes an object by calling WdfObjectDelete, however, you cannot assume that the parent still exists.

KMDF can call the EvtCleanupCallback functions for some types of objects at either PASSIVE_LEVEL or DISPATCH_LEVEL. However, the framework always calls the EvtCleanupCallback functions for the following types of objects at PASSIVE_LEVEL:

WDFDEVICE

WDFQUEUE

WDFDPC

WDFSTRING

WDFIOTARGET

WDFTIMER

WDFLOOKASIDE objects that allocate paged pool

WDFWORKITEM

WDFMEMORY objects that contain paged pool

 

An object requires cleanup at PASSIVE_LEVEL if it must wait for something to complete or if it accesses paged memory. If the driver attempts to delete such an object-or the parent of such an object-at DISPATCH_LEVEL, KMDF defers the disposal of the entire tree and queues all of the cleanup callbacks to a work item for later processing at PASSIVE_LEVEL. Thus, after the cleanup callbacks for all of the children have run, the cleanup callback for the parent runs. Currently, KMDF maintains one work item queue for each device object and calls the EvtCleanupCallback routines for the device object and its children through this queue as required.

Objects that require cleanup at PASSIVE_LEVEL do not necessarily require creation at PASSIVE_LEVEL.

WDFWORKITEM, WDFTIMER, WDFDPC, and WDFQUEUE objects can be created at DISPATCH_LEVEL, as well as WDFMEMORY and WDFLOOKASIDE objects that use nonpaged pool.

WDFMEMORY and WDFLOOKASIDE objects that use paged pool and WDFDEVICE, WDFIOTARGET, WDFCOMMONBUFFER, WDFKEY, WDFCHILDLIST, and WDFSTRING objects cannot be created at DISPATCH_LEVEL.

image from book
Exception: Completed I/O Requests

The exception to the guarantee that the parent object still exists when its children's cleanup callbacks run applies to I/O requests that are completed at DISPATCH_LEVEL. If such an I/O request object has one or more children whose EvtCleanupCallback routines must be called at PASSIVE_LEVEL, the parent I/O request object might be deleted before one or more of its children is deleted.

Consider what happens when a driver completes an I/O request that has a child timer object. According to the contract for an I/O request object, KMDF calls the request object's EvtCleanupCallback while the pointer to the underlying WDM IRP is still valid and accessible. For performance reasons, KMDF must complete the IRP as soon as possible. As soon as the IRP is complete, however, the IRP pointer is no longer valid. Instead of waiting to complete the IRP after the EvtCleanupCallback routines have run at PASSIVE_LEVEL, KMDF queues the timer's cleanup callback to a work item and then completes the IRP. The EvtCleanupCallback for the timer therefore might run after the request has been completed, the IRP pointer has been freed, and the request object has been deleted.

To avoid any problems that might result from this behavior, drivers should not set the I/O request object as the parent of any object that requires cleanup at PASSIVE_LEVEL.

image from book

Order of KMDF Calls to EvtDestroyCallback

KMDF calls the EvtDestroyCallback routine for an object after calling its EvtCleanupCallback routine and after its reference count has reached zero. The EvtDestroyCallback routines for an object and its children can be called in any order-and the EvtDestroyCallback for a parent can be called before that of a child. The EvtDestroyCallback can access the object context area but cannot call any methods on the object.

The framework makes no guarantees about the IRQL at which it calls an object's EvtDestroyCallback.

KMDF Object Deletion Example

The example in Listing 5-4 shows a simple example of object deletion from the Echo sample's Driver.c file.

Listing 5-4: Deleting a KMDF object

image from book
 NTSTATUS status; WDFSTRING string; WDF_DRIVER_VERSION_AVAILABLE_PARAMS ver; status = WdfStringCreate(NULL, WDF_NO_OBJECT_ATTRIBUTES, &string); if (!NT_SUCCESS(status)) {     . . .//Code omitted } status = WdfDriverRetrieveVersionString(WdfGetDriver(), string); if (!NT_SUCCESS(status)) {     . . .//Code omitted } . . .//Code omitted WdfObjectDelete(string); string = NULL; // To avoid referencing a deleted object. 
image from book

In this example, the Echo sample driver creates a string object to pass to a method that retrieves the driver version. After the driver has finished using the object, the driver calls WdfObjectDelete.




Developing Drivers with the Microsoft Windows Driver Foundation
Developing Drivers with the Windows Driver Foundation (Pro Developer)
ISBN: 0735623740
EAN: 2147483647
Year: 2007
Pages: 224

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net