DPC
DPC全称Deferred Procedure Call。
Defer的意思是延迟,而这个DPC的作用就是注册函数,然后在之后的某个阶段调用。
DPC在UEFI的实现中包括两个部分。
一部分是库函数DxeDpcLib,它实现了两个函数:
QueueDpc():
/**
Add a Deferred Procedure Call to the end of the DPC queue.
@param[in] DpcTpl The EFI_TPL that the DPC should be invoked.
@param[in] DpcProcedure Pointer to the DPC's function.
@param[in] DpcContext Pointer to the DPC's context. Passed to DpcProcedure
when DpcProcedure is invoked.
@retval EFI_SUCCESS The DPC was queued.
@retval EFI_INVALID_PARAMETER DpcTpl is not a valid EFI_TPL.
@retval EFI_INVALID_PARAMETER DpcProcedure is NULL.
@retval EFI_OUT_OF_RESOURCES There are not enough resources available to
add the DPC to the queue.
**/
EFI_STATUS
EFIAPI
QueueDpc (
IN EFI_TPL DpcTpl,
IN EFI_DPC_PROCEDURE DpcProcedure,
IN VOID *DpcContext OPTIONAL
);
DispatchDpc():
/**
Dispatch the queue of DPCs. ALL DPCs that have been queued with a DpcTpl
value greater than or equal to the current TPL are invoked in the order that
they were queued. DPCs with higher DpcTpl values are invoked before DPCs with
lower DpcTpl values.
@retval EFI_SUCCESS One or more DPCs were invoked.
@retval EFI_NOT_FOUND No DPCs were invoked.
**/
EFI_STATUS
EFIAPI
DispatchDpc (
VOID
);
另一个部分是EFI_DPC_PROTOCOL:
EFI_DPC_PROTOCOL mDpc = {
DpcQueueDpc,
DpcDispatchDpc
};
实际上在DxeDpcLib中只是简单调用了EFI_DPC_PROTOCOL的接口函数。
所以真正的重点还是在EFI_DPC_PROTOCOL上。
EFI_DPC_PROTOCOL
EFI_DPC_PROTOCOL是在一个DXE模块中安装的,安装的过程也很简单,查看具体的模块入口代码:
EFI_STATUS
EFIAPI
DpcDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
UINTN Index;
//
// ASSERT() if the EFI_DPC_PROTOCOL is already present in the handle database
//
ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiDpcProtocolGuid);
//
// Initialize the DPC queue for all possible TPL values
//
for (Index = 0; Index <= TPL_HIGH_LEVEL; Index++) {
InitializeListHead (&mDpcQueue[Index]);
}
//
// Install the EFI_DPC_PROTOCOL instance onto a new handle
//
Status = gBS->InstallMultipleProtocolInterfaces (
&mDpcHandle,
&gEfiDpcProtocolGuid,
&mDpc,
NULL
);
ASSERT_EFI_ERROR (Status);
return Status;
}
这里唯一需要关注的就是mDpcQueue这个全局变量。它是一个链表数组:
LIST_ENTRY mDpcQueue[TPL_HIGH_LEVEL + 1];
每一个元素对应一种优先级。
这里创建了所有可能的优先级,但实际上常用的也就下面几种:
//
// Task priority level
//
#define TPL_APPLICATION 4
#define TPL_CALLBACK 8
#define TPL_NOTIFY 16
#define TPL_HIGH_LEVEL 31
这个链表真正的元素是DpcEntry,其结构如下:
//
// Internal data struture for managing DPCs. A DPC entry is either on the free
// list or on a DPC queue at a specific EFI_TPL.
//
typedef struct {
LIST_ENTRY ListEntry;
EFI_DPC_PROCEDURE DpcProcedure;
VOID *DpcContext;
} DPC_ENTRY;
第一个参数ListEntry就是用来处理链表的,而后面的两个参数,一个是函数指针,一个是函数的参数。
EFI_DPC_PROCEDURE定义如下:
/**
Invoke a Deferred Procedure Call.
@param DpcContext The pointer to the Deferred Procedure Call's context,
which is implementation dependent.
**/
typedef
VOID
(EFIAPI *EFI_DPC_PROCEDURE)(
IN VOID *DpcContext
);
这个就是我们的主角:Deferred Procedure Call,简称DPC。
初始化完成后,mDpcQueue大致是这样的:
EFI_DPC_PROTOCOL中就两个接口函数,下面来具体介绍。
DpcQueueDpc
EFI_STATUS
EFIAPI
DpcQueueDpc (
IN EFI_DPC_PROTOCOL *This,
IN EFI_TPL DpcTpl,
IN EFI_DPC_PROCEDURE DpcProcedure,
IN VOID *DpcContext OPTIONAL
)
该函数的操作流程如下:
1. 首先需要判断优先级DpcTpl是否满足要求;
2. 初始化一个全局的变量mDpcEntryFreeList:
需要注意以下几点:
1)64个是一次创建的个数,如果64个全用完了,就会再创建64个。这里的创建是指真正的分配了内存了的,不像mDpcQueue那样只是指针的链接。
2)这些DpcEntry会被移动到mDpcQueue中去,执行完了之后又会被放回到mDpcEntryFreeList中,以达到循环使用的目的。
这么做得好处,一是内存分配不至于太分散;二是提高了效率和利用率。
3. 从mDpcEntryFreeList中拿出一个DpcEntry,为它赋值DpcProcedure和DpcContext,这也算是初始化;
4. 将初始化好的DpcEntry插入到mDpcQueue对应的优先级上;
5. mDpcQueueDepth全局变量的值加1,表示又多了一个DPC;
以上就是大致的流程。
DpcDispatchDpc
EFI_STATUS
EFIAPI
DpcDispatchDpc (
IN EFI_DPC_PROTOCOL *This
)
该函数的操作流程如下:
1. 判断mDpcQueueDepth值是否大于0,如果大于0,表示有DPC可以被调用;
2. 遍历mDpcQueue,找到非空的链表节点,将它从mDpcQueue中删去,mDpcQueueDepth的值相应的减1;
3. 执行那个从mDpcQueue去除下来的DpcEntry对应的DpcProcedure:
//
// Invoke the DPC passing in its context
//
(DpcEntry->DpcProcedure) (DpcEntry->DpcContext);
4. 将从mDpcQueue去除下来的DpcEntry在放回到mDpcEntryFreeList中。
以上就是DpcDispatchDpc的流程。
一次DpcDispatchDpc会将mDpcQueue中的所有DPC都执行一遍。
说明
目前DPC只在网络相关的驱动中使用,并且使用的地方还很多。
具体如何使用的,后续可以举几个例子。