UEFI中不再支持中断,所有的异步操作都要通过事件Event来完成。[^1]所谓异步操作,可以简单理解为,满足条件S时,才能够执行事件A,如果条件S没有被满足,系统先继续向下执行事件B,直到条件S满足,转回执行事件A。当事件A执行完毕后,程序从被中断的事件B继续向下执行,接着执行事件C D… 所有事件结束后,全部结束。
引例
通过一个例子先体会一下事件的作用和异步的效果
VOID
FunA ( IN EFI_EVENT Event,
IN VOID *Context)
{
Print(L"A : Time out !\n" );
}
EFI_STATUS MyTestEvent()
{
EFI_STATUS Status;
EFI_EVENT myEvent,Event;
UINTN index;
Print(L"Program Start !\n" );
Status = gBS->CreateEvent(EVT_TIMER | EVT_NOTIFY_SIGNAL , TPL_CALLBACK, (EFI_EVENT_NOTIFY)FunA, (VOID*)NULL, &myEvent);
Status = gBS->SetTimer(myEvent,TimerRelative , 10 * 1000 * 1000);
Print(L"B : doing something , the time over 10s!\n" );
Status = gBS->CreateEvent(EVT_TIMER , TPL_CALLBACK, (EFI_EVENT_NOTIFY)NULL, (VOID*)NULL, &Event);
Status = gBS->SetTimer(Event,TimerRelative , 20* 1000 * 1000);
Status = gBS->WaitForEvent(1,&Event,&index);
Print(L"B : finish Event B !\n" );
Print(L"C: doing something !\n" );
Status = gBS->CloseEvent(myEvent);
Status = gBS->CloseEvent(Event);
Print(L"Program End !\n" );
return Status;
}
EFI_STATUS TestEvent_Entry(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
Status = MyTestEvent();
return Status;
}
程序执行结果如下:
在上述程序中,程序开始首先设置了一个事件FunA,这个事件A执行的条件是程序执行超过10s,因此程序刚刚执行的时候,条件不满足,程序继续向下执行了事件B。程序中假设事件B是一个需要执行20s的函数(执行时间通过定时器进行模拟),在事件B执行期间,事件A的条件得到了满足,此时事件B暂时中断,转而执行事件A,输出结果“Time out !”, 事件A执行完成,返回继续执行事件B ,直到B执行完成输出“Finish Event B! ”,接着继续执行事件C 至完成所有任务。
可以看到在上述代码,创建了两个不同的事件,实现了两种不同的效果。以此为基础下面逐步说明UEFI 中的事件的情况。首先将常用的Event相关的函数都进行简单的说明
常用函数
生成事件 CreatEvent
CreatEvent用于生成一个事件,函数原型如下
typedef
EFI_STATUS
(EFIAPI *EFI_CREATE_EVENT)(
IN UINT32 Type,
IN EFI_TPL NotifyTpl,
IN EFI_EVENT_NOTIFY NotifyFunction,
IN VOID *NotifyContext,
OUT EFI_EVENT *Event
);
Type: 创建的事件的类型 事件的类型可以是一下类型定义的某一个或者组合
// These types can be ORed together as needed - for example,
// EVT_TIMER might be Ored with EVT_NOTIFY_WAIT or
// EVT_NOTIFY_SIGNAL.
//
#define EVT_TIMER 0x80000000
#define EVT_RUNTIME 0x40000000
#define EVT_NOTIFY_WAIT 0x00000100
#define EVT_NOTIFY_SIGNAL 0x00000200
#define EVT_SIGNAL_EXIT_BOOT_SERVICES 0x00000201
#define EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE 0x60000202
#define EVT_RUNTIME_CONTEXT 0x20000000
现在只介绍目前用到的事件类型
事件类型 | 事件说明 |
---|---|
EVT_TIMER | 定时器事件,没有Notification 函数。 生成此类型的事件后需要通过SetTimer设置定时器的属性 |
EVT_NOTIFY_WAIT | 普通事件,带Notification 函数。当事件被检查是否触发时,Notification 函数会被执行 |
EVT_NOTIFY_SIGNAL | 普通事件,带Notification 函数。当事件被触发时,Notification 函数会被执行 |
NotifyTpl:任务的优先级。 高优先级的任务可以中断比其自身优先级低的任务优先执行
UEFI定义了四个任务的优先级
// Task priority level
//
#define TPL_APPLICATION 4
#define TPL_CALLBACK 8
#define TPL_NOTIFY 16
#define TPL_HIGH_LEVEL 31
目前优先级的方面没有深入研究过
NotifyFunction : 条件满足的时候想要执行的函数
NotifyContext : 执行NotifyFunction的时候给它的参数
上述两个参数是实现异步执行的关键,想要被异步执行的函数可以被设置为NotifyFunction
等待事件 WaitForEvent
事件创建完成之后,我们可以选择继续执行其他操作,或者等待我们创建事件的发生。如果选择等待,就需要使用WaitForEvent
函数
typedef
EFI_STATUS
(EFIAPI *EFI_WAIT_FOR_EVENT)(
IN UINTN NumberOfEvents,
IN EFI_EVENT *Event,
OUT UINTN *Index
);
NumberOfEvents : 当前事件数组中的事件数
Event: 事件数组
Index : 处于触发态的事件在数组内的下标
WaitForEvent 有几点需要注意的
- WaitForEvent是阻塞函数,这也就是说,除非事件数组中有事件被触发或者有事件报错,函数会返回,否则函数会一直检查事件数组情况直到有某一个事件触发
- WaitForEvent 检查到某个事件被触发,就会返回事件在数组中的下标Index ,并且返回之前会将该事件重置为非触发的状态
- 当事件的类型为EVT_NOTIFY_SIGNAL时,WaitForEvent 返回 EFI_INVALID_PARAMETER,同时返回 Index为当前EVT_NOTIFY_SIGNAL类型事件在数组中的下标
- WaitForEvent 必须运行在 TPL_APPLICATION级别
检查事件 CheckEvent
CheckEvent 用来检查事件的状态,CheckEvent非阻塞函数 (CheckEvent函数其实也是WaitForEvent函数的核心,两者的具体情况后面分析)
typedef
EFI_STATUS
(EFIAPI *EFI_CHECK_EVENT)(
IN EFI_EVENT Event
);
根据事件的不同type和状态 CheckEvent 的返回值有四种情况
- 事件类型为EVT_NOTIFY_SIGNAL,返回 EFI_INVALID_PARAMETER(和WaitForEvent函数相同的表现)
- 事件处于触发态,返回 EFI_SUCCESS 并且返回之前会将该事件重置为非触发的状态 (和WaitForEvent函数相同的表现)
- 事件处于非触发态并且事件没有NotifyFunction函数,此时只能是Timer事件 ,返回EFI_NOT_READY
- 事件处于非触发态并且事件带有NotifyFunction函数(此时事件类型只能是EVT_NOTIFY_WAIT,因为EVT_NOTIFY_SIGNAL,返回 EFI_INVALID_PARAMETER,Timer没有NotifyFunction函数),执行NotifyFunction函数。然后再检查事件状态,如果事件处于触发态,返回EFI_SUCCESS,否则返回EFI_NOT_READY ^0bdtcs
触发事件 SignalEvent
SignalEvent 用与将事件设置为触发态。其函数原型为
typedef
EFI_STATUS
(EFIAPI *EFI_SIGNAL_EVENT)(
IN EFI_EVENT Event
);
关闭事件 CloseEvent
事件使用完毕后,需要调用CloseEvent()将事件关闭,此时事件被清除,占用的内存被释放
typedef
EFI_STATUS
(EFIAPI *EFI_CLOSE_EVENT)(
IN EFI_EVENT Event
);
常见函数进行了大致的介绍,接下来通过一些代码对其中一些我觉得比较难搞清楚的地方进行说明
事件的状态
前面我们介绍函数的时候,曾经多次提到要判断事件是否处于触发(signal)状态。首先我们需要明确的是,创建出来的Event,只有两种状态: 触发(signal) 和 未触发(wait) 。 区分这两种状态对于理解 EVT_NOTIFY_SIGNAL 类型的事件就有很重要的意义。前面说过,EVT_NOTIFY_SIGNAL 类型的事件,只有当事件被触发的时候,Notification 函数才会被执行 。看下面的例子
VOID NotifyEventForSignal ( IN EFI_EVENT Event,
IN VOID *Context)
{
Print(Context);
}
EFI_STATUS MyTestSignalEvent()
{
EFI_STATUS Status;
EFI_EVENT Event;
Print(L"Start of this function \n");
Status = gBS->CreateEvent( EVT_NOTIFY_SIGNAL, TPL_CALLBACK, (EFI_EVENT_NOTIFY)NotifyEventForSignal, (VOID*)(L"This Event have been signalled! \n"), &Event);
Print(L"Doing something else ... \n");
Status= gBS->SignalEvent(Event);
Print(L"End of this function \n");
Status= gBS->CloseEvent(Event);
return Status;
}
执行结果如下
只有 gBS->SignalEvent(Event);
执行完成,事件被signal触发,NotifyEventForSignal
函数才被执行。 这也符合我们之前所说的EVT_NOTIFY_SIGNAL 类型的事件的特点。
与之相对应的就是EVT_NOTIFY_WAIT类型的事件,前面也说过这种类型的事件是在查询或者等待的时候被执行的,下面是一个例子
VOID NotifyEventForWait ( IN EFI_EVENT Event,
IN VOID *Context)
{
static UINTN times = 0;
Print(L"NotifyEventForWait %d times \n",times);
times++;
if(times== *((UINTN*)Context)){
EFI_STATUS Status;
Status= gBS->SignalEvent(Event);
}
}
EFI_STATUS MyTestWaitEvent()
{
EFI_STATUS Status = EFI_SUCCESS;
EFI_EVENT Event;
UINTN times = 6;
UINTN Index ;
Print(L"Start of this function \n");
Status = gBS->CreateEvent( EVT_NOTIFY_WAIT, TPL_CALLBACK, (EFI_EVENT_NOTIFY)NotifyEventForWait, ×, &Event);
Print(L"Doing something else ... \n");
Status= gBS->WaitForEvent(1,&Event,&Index);
Print(L"End of this function \n");
Status= gBS->CloseEvent(Event);
return Status;
}
执行结果如下
在NotifyEventForSignal
函数中我设置了当执行到6次时,signal 当前Event。结果展示出一共查询了六次当前Event是否被Signal,每次查询都执行了NotifyEventForSignal
函数,第六次时触发了Event,此时 Status= gBS->WaitForEvent(1,&Event,&Index);
不在阻塞,程序继续向下执行。
以上两段简单的代码,我觉得能够说明 EVT_NOTIFY_SIGNAL
和 EVT_NOTIFY_WAIT
的区别了。
但是函数是否处于触发状态在代码中是如何表示的呢?这个问题先留着,后面详细的解释
定时器
在引例中和讲解生成事件函数的时候,都使用或者提到了定时器事件,定时器事件本身很常见,并且定时器和前面说过的两种普通类型事件的结合更是常用,所以接下来这部分简单说一下定时器。
除了前面举例的两种类型的事件,还有一种常见的事件类型 EVT_TIMER
,这种类型的事件可以用来使程序等待若干时间后再向下执行,还可以和 EVT_NOTIFY_WAIT
EVT_NOTIFY_SIGNAL
结合使用,实现function的定时执行。
设置定时器事件
在前面的引例中,如下这个部分就是设置了一个时长为20s的定时器,而WaitForEvent
则负责阻塞事件发生,直到定时器时间到达,程序才可以继续向下执行。
Status = gBS->CreateEvent(EVT_TIMER , TPL_CALLBACK, (EFI_EVENT_NOTIFY)NULL, (VOID*)NULL, &Event);
Status = gBS->SetTimer(Event,TimerRelative , 20* 1000 * 1000);
Status = gBS->WaitForEvent(1,&Event,&index);
接下来介绍一下定时器相关的函数
创建定时器事件
前面我们讲过创建事件的函数CreateEvent
,创建一个定时器事件的第一步就是将创建事件的类型设置为 EVT_TIMER
,这一步和之前创建 EVT_NOTIFY_WAIT
EVT_NOTIFY_SIGNAL
Event 是相同的。但是想要成功创建一个定时器事件,还需要为这个定时器设置相应的属性,未定时器设置属性的函数就是 SetTimer
SetTimer
的函数原型如下
typedef
EFI_STATUS
(EFIAPI *EFI_SET_TIMER)(
IN EFI_EVENT Event,
IN EFI_TIMER_DELAY Type,
IN UINT64 TriggerTime
);
此函数一共三个参数,
Event :为需要添加定时器属性的事件
Type : 添加的定时器的类型
TriggerTime : 想要设定的定时器时间
定时器的Type有如下三种
Type | 说明 |
---|---|
TimerPeriodic | 周期定时器,每一个设置的事件间隔过去定时器被触发一次 |
TimerRelative | 一次性定时器,达到设置的时间后,定时器触发一次 |
TimerCancel | 取消设置好的定时器 |
定时器和普通事件结合
简答的设置定时器是定时器的一种使用方法,但是更加常用的定时器的使用方式还是和EVT_NOTIFY_WAIT
EVT_NOTIFY_SIGNAL
一起使用。引例中如下的定义就是设置了一个两种属性的定时器
Status = gBS->CreateEvent(EVT_TIMER | EVT_NOTIFY_SIGNAL , TPL_CALLBACK, (EFI_EVENT_NOTIFY)FunA, (VOID*)NULL, &myEvent);
Status = gBS->SetTimer(myEvent,TimerRelative , 10 * 1000 * 1000);
从前述引例中的结果可以知道,当时间到达定时器设置的时间,NotifyFunction FunA
就会被执行输出A : Time out !
,看起来很轻松的就实现了定时执行NotifyFunction 的设定。但是在说事件状态的时候我们曾经说过创建出来的事件只有两种状态触发(signal) 和 未触发(wait) 。而事件刚刚创建出来 往往处于未触发的状态,需要我们显式的通过SignalEvent
进行事件的触发,可以参考事件的状态中的例子。可是在带了定时器的事件中,似乎并没有对事件进行触发事件到时间就自己执行了,这其中的逻辑又是怎么样的呢?
事件的底层实现
定时器事件
想要搞清楚上面说的问题,首先需要明白的就是事件在代码中的是以一种什么样的形式初始化、构成、执行的。
事件的初始化是在DxeMain中完成的,在DxeMain函数中与事件初始化相关的主要是下面两个函数
// DxeMain.c
VOID
EFIAPI
DxeMain (
IN VOID *HobStart
)
{
// ...
//
// Initialize the Event Services
//
Status = CoreInitializeEventServices ();
// ...
// Register for the GUIDs of the Architectural Protocols, so the rest of the
// EFI Boot Services and EFI Runtime Services tables can be filled in.
// Also register for the GUIDs of optional protocols.
//
CoreNotifyOnProtocolInstallation ();
// ...
}
CoreInitializeEventServices
首先来看第一个相关的函数 CoreInitializeEventServices
,其函数原型可实现可以在代码中找到
// Event.c
EFI_STATUS
CoreInitializeEventServices (
VOID
)
{
UINTN Index;
for (Index = 0; Index <= TPL_HIGH_LEVEL; Index++) {
InitializeListHead (&gEventQueue[Index]);
}
CoreInitializeTimer ();
CoreCreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
EfiEventEmptyFunction,
NULL,
&gIdleLoopEventGuid,
&gIdleLoopEvent
);
return EFI_SUCCESS;
}
这个函数并不复杂,主要就是三个部分,首先遍历初始化了一个链表数组 InitializeListHead (&gEventQueue[Index]);
gEventQueue
这个数组就是用来保存处于signal状态的待执行的Event的 gEventQueue 是需要注意的第一个链表 ^kr4vng
这个数组的原型
// Event.c
LIST_ENTRY gEventQueue[TPL_HIGH_LEVEL + 1];
这是一个以 LIST_ENTRY
为元素的数组,在0000 0001.protocol&handle一文中,就总结过,看到LIST_ENTRY
结构体,就必定会出现双向链表。在这里也不例外,函数的第一个操作就是将数组的每一个元素进行初始化,使其能够成为链表的形式——指针指向自己,方便后续成员插入链表。(这个不部分属于C基础 不详细说了 有需要的自己去补习一下)
下面一个函数 CoreInitializeTimer
函数如下
/**
Initializes timer support.
**/
VOID
CoreInitializeTimer (
VOID
)
{
EFI_STATUS Status;
Status = CoreCreateEventInternal (
EVT_NOTIFY_SIGNAL,
TPL_HIGH_LEVEL - 1,
CoreCheckTimers,
NULL,
NULL,
&mEfiCheckTimerEvent
);
ASSERT_EFI_ERROR (Status);
}
其核心就是函数 CoreCreateEventInternal
,在详细看这个函数之前,先看一下接下来的这个函数CoreCreateEventEx
,
CoreCreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
EfiEventEmptyFunction,
NULL,
&gIdleLoopEventGuid,
&gIdleLoopEvent
);
查看这个函数的原型
EFI_STATUS
EFIAPI
CoreCreateEventEx (
IN UINT32 Type,
IN EFI_TPL NotifyTpl,
IN EFI_EVENT_NOTIFY NotifyFunction OPTIONAL,
IN CONST VOID *NotifyContext OPTIONAL,
IN CONST EFI_GUID *EventGroup OPTIONAL,
OUT EFI_EVENT *Event
)
{
//
// If it's a notify type of event, check for invalid NotifyTpl
//
if ((Type & (EVT_NOTIFY_WAIT | EVT_NOTIFY_SIGNAL)) != 0) {
if ((NotifyTpl != TPL_APPLICATION) &&
(NotifyTpl != TPL_CALLBACK) &&
(NotifyTpl != TPL_NOTIFY))
{
return EFI_INVALID_PARAMETER;
}
}
return CoreCreateEventInternal (Type, NotifyTpl, NotifyFunction, NotifyContext, EventGroup, Event);
}
这个函数其实就是CreatEventEx的核心函数,而CreatEventEx可以看成是CreatEvent的一个扩展版。 可以比较这两个函数的参数,其中差别只有 EventGroup
这一个地方,EventGroup表示一个Event组的GUID 。CreatEventEx 是将创建的Event insert到GUID 为 EventGroup
的事件组中。 所谓事件组,与单独的事件一个最大的区别就是,事件组中的任意一个事件signal,事件组中的其他事件也会被signal,所有的NotificationFunction 会按照TPL的顺序依次执行
简单说明这个函数之后我们观察可以发现,这个函数的本质也是CoreCreateEventInternal
,因此想要说明这两个函数的作用,就要先弄清楚CoreCreateEventInternal
CoreCreateEventInternal
这个函数本身还是挺长的,一点一点详细看一下对我们理解Event能有很好的帮助
首先,函数的原型如下
EFI_STATUS
EFIAPI
CoreCreateEventInternal (
IN UINT32 Type,
IN EFI_TPL NotifyTpl,
IN EFI_EVENT_NOTIFY NotifyFunction OPTIONAL,
IN CONST VOID *NotifyContext OPTIONAL,
IN CONST EFI_GUID *EventGroup OPTIONAL,
OUT EFI_EVENT *Event
)
在初始的时候,对函数的参数进行了检查,保证参数的有效,这部分不是重点不做重点介绍。接下来函数根据传入的参数创建新的IEvent
T并且对其进行初始化
这一段中,我们需要格外重点关心的就是 IEvent
结构体的具体情况,这个结构体可以说是整个Event的基础,是地基。IEvent
的结构体定义如下
// Event.h
typedef struct {
UINTN Signature;
UINT32 Type;
UINT32 SignalCount;
///
/// Entry if the event is registered to be signalled
///
LIST_ENTRY SignalLink;
///
/// Notification information for this event
///
EFI_TPL NotifyTpl;
EFI_EVENT_NOTIFY NotifyFunction;
VOID *NotifyContext;
EFI_GUID EventGroup;
LIST_ENTRY NotifyLink;
UINT8 ExFlag;
///
/// A list of all runtime events
///
EFI_RUNTIME_EVENT_ENTRY RuntimeData;
TIMER_EVENT_INFO Timer;
} IEVENT;
在此我们只将结构体的具体成员列出来了,后面我们需要反复回看这个结构体来体会Event的精髓
在这个位置,函数分配了内存创建了结构体IEvent
,并且用传入的参数对IEvent
的部分成员进行了初始化。并且将 Event 指针指向了当前的结构体。同时将这个创建出来的IEvent
链接到了 gRuntime->EventHead
这个链表中,这个链表保存了创建的所有的Event gRuntime->EventHead
是需要注意的第二个链表
接下来根据传入的Type类型对 IEvent
进行处理,此处表示当传入的 Type是 EVT_RUNTIME
的时候的处理,这个类型的Event当前我还没有深入的研究,故此处不做分析,跳过
CoreAcquireEventLock
和 CoreReleaseEventLock
是为了操作安全进行的操作,先不看
那么接下来这一段的核心就在于 if 这一段。此时如果事件类型是 EVT_NOTIFY_SIGNAL
,就将这个事件插入到 gEventSignalQueue
中(这个插入是以 IEvent->SignalLink
为链表成员的 现在回看前面的 IEvent
元素 就会发现 LIST_ENTRY SignalLink
这个地方必定会插入到一个双向链表中,现在我们就知道了,这个元素插入的链表就是 gEventSignalQueue
LIST_ENTRY gEventSignalQueue = INITIALIZE_LIST_HEAD_VARIABLE (gEventSignalQueue);
#define INITIALIZE_LIST_HEAD_VARIABLE(ListHead) {&(ListHead), &(ListHead)}
可以看到这个链表头为 gEventSignalQueue
所有的EVT_NOTIFY_SIGNAL
类型的Event都需要插入到这个链表中。 gEventSignalQueue 是需要注意的第三个链表
gEventSignalQueue
: EVT_NOTIFY_SIGNAL
类型Event的链表
将CoreCreateEventInternal
事件说清之后,我们就可以回到主线,看一下 CoreInitializeTimer
h和 CoreCreateEventEx
这两个函数都干了些什么
CoreInitializeTimer
VOID
CoreInitializeTimer (
VOID
)
{
EFI_STATUS Status;
Status = CoreCreateEventInternal (
EVT_NOTIFY_SIGNAL,
TPL_HIGH_LEVEL - 1,
CoreCheckTimers,
NULL,
NULL,
&mEfiCheckTimerEvent
);
ASSERT_EFI_ERROR (Status);
}
在CoreInitializeTimer
中,创建了一个EVT_NOTIFY_SIGNAL
的Event——mEfiCheckTimerEvent
,其NotifyFunction为 CoreCheckTimers
,也就是说,每当mEfiCheckTimerEvent
被 Signal ,就执行函数 CoreCheckTimers
( CoreCheckTimers
是个很重要的函数 后面我们在详细的说这个函数)
CoreCreateEventEx
CoreCreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
EfiEventEmptyFunction,
NULL,
&gIdleLoopEventGuid,
&gIdleLoopEvent
);
经过前面这么多的分析,这个函数实现的操作也很容易看明白了: 创建一个GUID为gIdleLoopEventGuid
的Event gIdleLoopEvent
,其NotifyFunction为 EfiEventEmptyFunction
,当前EDKII代码中 EfiEventEmptyFunction
是一个空的函数没有任何的内容
小结
CoreInitializeEventServices
函数中核心的操作就是创建了 以 CoreCheckTimers
为NotifyFunction 的Event——mEfiCheckTimerEvent
Important!!!
CoreNotifyOnProtocolInstallation
CoreNotifyOnProtocolInstallation
的函数内容很简单,具体内容如下
VOID
CoreNotifyOnProtocolInstallation (
VOID
)
{
CoreNotifyOnProtocolEntryTable (mArchProtocols);
CoreNotifyOnProtocolEntryTable (mOptionalProtocols);
}
可以看到函数内就是 CoreNotifyOnProtocolEntryTable
函数传入不同的两个参数,在此我们主要感兴趣的就是第一行代码,为了搞清楚代码的作用,我们先了解一下 CoreNotifyOnProtocolEntryTable
函数
CoreNotifyOnProtocolEntryTable
VOID
CoreNotifyOnProtocolEntryTable (
EFI_CORE_PROTOCOL_NOTIFY_ENTRY *Entry
)
{
EFI_STATUS Status;
for ( ; Entry->ProtocolGuid != NULL; Entry++) {
//
// Create the event
//
Status = CoreCreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
GenericProtocolNotify,
Entry,
&Entry->Event
);
ASSERT_EFI_ERROR (Status);
//
// Register for protocol notifactions on this event
//
Status = CoreRegisterProtocolNotify (
Entry->ProtocolGuid,
Entry->Event,
&Entry->Registration
);
ASSERT_EFI_ERROR (Status);
}
}
整体来看,CoreNotifyOnProtocolEntryTable
函数主要是对传入参数进行了遍历操作,对每个参数都执行了相同的操作(for循环中的内容),下面详细看for循环中的内容。
for循环中的内容主要就是两个部分:CoreCreateEvent
和 CoreRegisterProtocolNotify
。 经过前面的学习,对于CoreCreateEvent
已经很了解了,这里不再赘述。当前这个函数就是创建了一个事件,Entry->Event指向创建的事件。这个创建的事件 NotificationFunction 是 GenericProtocolNotify
,传给NotificationFunction 的参数就是每一个元素Entry。
值得关注的是,每一个元素的NotificationFunction 都是 GenericProtocolNotify
我们重点需要看的是第二个参数 CoreRegisterProtocolNotify
CoreRegisterProtocolNotify
在这个函数中首先对参数进行了检查,保证了传入参数的有效性。接下来
ProtEntry = CoreFindProtocolEntry (Protocol, TRUE);
if (ProtEntry != NULL) {
//
// Allocate a new notification record
//
ProtNotify = AllocatePool (sizeof (PROTOCOL_NOTIFY));
if (ProtNotify != NULL) {
((IEVENT *)Event)->ExFlag |= EVT_EXFLAG_EVENT_PROTOCOL_NOTIFICATION;
ProtNotify->Signature = PROTOCOL_NOTIFY_SIGNATURE;
ProtNotify->Protocol = ProtEntry;
ProtNotify->Event = Event;
//
// start at the begining
//
ProtNotify->Position = &ProtEntry->Protocols;
InsertTailList (&ProtEntry->Notify, &ProtNotify->Link);
}
}
CoreFindProtocolEntry
在之前介绍protocol的时候详细的说过,这个函数就是查找/创建目标protocol entry
。找到目标的 protocol entry
后,初始化一个 PROTOCOL_NOTIFY
结构体,并且将这个结构体插入该protocol entry
对应的 PROTOCOL_NOTIFY
链表。
此处可以对应protocol&handle 的图来看
最后将创建的PROTOCOL_NOTIFY
返回给 Registration
。
CoreRegisterProtocolNotify
就是在对应的 protocol 的 PROTOCOL_NOTIFY
链表中增加一个成员。
小结
总结一下:
综合前面两个函数来看, CoreNotifyOnProtocolEntryTable (mArchProtocols)
函数就是为传入的mArchProtocols
的每一个成员都创建一个 NotificationFunction 是 GenericProtocolNotify
的Event并将其插入到对应protocol的 PROTOCOL_NOTIFY
链表中。 Important!!!
那么这个PROTOCOL_NOTIFY
链表中的事件什么时候能够被signal呢?之前我们在讨论protocol&handle 的时候就曾经说过
LIST_ENTRY Notify与PROTOCOL_NOTIFY的成员LIST_ENTRY Link会形成一个双向的链表,这个链表最重要的作用就是PROTOCOL_NOTIFY中的 EFI_GUID ProtocolID对应的protocol安装时,该链表上所有的Event都会触发
知识链接部分
现在我们要回收一下这个坑,既然说 PROTOCOL_NOTIFY
中的 EFI_GUID ProtocolID
对应的protocol安装时,该链表上所有的Event都会触发。 那么这个触发在代码中是在什么位置怎么实现的呢?
在protocol服务解析这篇文章中,我详细的说明了InstallProtocolInterface(原型CoreInstallProtocolInterface)
的内容,但是在函数最后有一个部分,当时确实没有详细说明的
当时由于没有详细的学习过Event的相关内容,所以只是一笔带过,现在终于可以详细的看一下这个函数了
函数的原型如下
VOID
CoreNotifyProtocolEntry (
IN PROTOCOL_ENTRY *ProtEntry
)
{
PROTOCOL_NOTIFY *ProtNotify;
LIST_ENTRY *Link;
ASSERT_LOCKED (&gProtocolDatabaseLock);
for (Link = ProtEntry->Notify.ForwardLink; Link != &ProtEntry->Notify; Link = Link->ForwardLink) {
ProtNotify = CR (Link, PROTOCOL_NOTIFY, Link, PROTOCOL_NOTIFY_SIGNATURE);
CoreSignalEvent (ProtNotify->Event);
}
}
ASSERT_LOCKED (&gProtocolDatabaseLock);
是为了安全进行的操作 不关注
下面就是一个for循环的遍历,这个for循环将当前 ProtEntry
所链接的PROTOCOL_NOTIFY
链表中的成员进行了遍历,对于每一个成员的Event进行CoreSignalEvent的操作。这也就是我们之前说的 "对应的protocol安装时,该链表上所有的Event都会触发。"的实现的位置。
这样在这个位置,同时填上前面 protocol&handle 和 protocol服务解析 两个位置遗留下来的问题,很好
我们现在回头看一下传入的参数 mArchProtocols
这是一个数组,数组中的每一个成员都是 EFI_CORE_PROTOCOL_NOTIFY_ENTRY
类型的
typedef struct {
EFI_GUID *ProtocolGuid;
VOID **Protocol;
EFI_EVENT Event;
VOID *Registration;
BOOLEAN Present;
} EFI_CORE_PROTOCOL_NOTIFY_ENTRY;
成员包含了对应的GUID,后面我们也就能在对应的protocol 的 PROTOCOL_NOTIFY
链表中插入成员。
GenericProtocolNotify
GenericProtocolNotify 是一个很长的函数,当前我们只关心和事件相关的,那么关键的就是下面这一句
if (CompareGuid (Entry->ProtocolGuid, &gEfiTimerArchProtocolGuid)) {
//
// Register the Core timer tick handler with the Timer AP
//
gTimer->RegisterHandler (gTimer, CoreTimerTick);
}
根据前面 mArchProtocols
数组可以知道 gTimer
对应的是 gEfiTimerArchProtocolGuid
,我们现在需要找的是这个函数的成员的原型然后搞清楚 gTimer->RegisterHandler
的作用。可以在如下的位置找到函数的来源
// Timer.c
// ...
EFI_TIMER_ARCH_PROTOCOL mTimer = {
TimerDriverRegisterHandler,
TimerDriverSetTimerPeriod,
TimerDriverGetTimerPeriod,
TimerDriverGenerateSoftInterrupt
};
// ...
Status = gBS->InstallMultipleProtocolInterfaces (
&mTimerHandle,
&gEfiTimerArchProtocolGuid,
&mTimer,
NULL
);
由以上函数可以知道 TimerDriverRegisterHandler
就是我们要找的东西
整个函数中,关键的只有红色框中的那一句话,就用传入的参数是给 mTimerNotifyFunction
赋值。那么我们现在需要关注的就是当前传入的参数是什么?回头找一下我们之前的内容
gTimer->RegisterHandler (gTimer, CoreTimerTick);
所以当前 mTimerNotifyFunction
就是CoreTimerTick
。
综合之前所说,这一段代码整体实现的作用就是当 gEfiTimerArchProtocolGuid
对应的 protocol 被安装的时候,将 CoreTimerTick
的值赋给mTimerNotifyFunction
mTimerNotifyFunction的作用
前面说到了 mTimerNotifyFunction
,那么这个值的具体作用是什么?找到mTimerNotifyFunction
被调用的位置
这个函数运行的优先级被设置为了 TPL_HIGH_LEVEL
, 红框部分是mTimerNotifyFunction
的使用情况,此时传入固定参数调用mTimerNotifyFunction
我们继续查找,是什么位置什么情况下才会调用的 TimerInterruptHandler
, 在函数 TimerDriverInitialize
中可以看到函数使用的情况
mCPU的具体实现可以通过protocol的安装和实现情况反向查找到
mCpu->RegisterInterruptHandler
的原型如下
红框中的赋值是一个核心操作,这里表明代码中将 TimerInterruptHandler
这个函数保存在了 ExternalInterruptHandler[PcdGet8 (PcdHpetLocalApicVector)]
中。当CPU发生时钟中断,就会调用上述的对用的Handler
综合以上所有的分析,我们可以得到一个基础的结论:每当CPU发生时钟中断,就会触发一次 TimerInterruptHandler
而当前 TimerInterruptHandler
中的mTimerNotifyFunction (mTimerPeriod);
就是 CoreTimerTick(TimerPeriod)
CoreTimerTick
接下来我们需要关注的核心就是这个当CPU发生中断的时候会调用的函数CoreTimerTick
到底是何方神圣了
这个函数整体不难。
首先更新了系统时间
mEfiSystemTime += Duration;
当mEfiTimerList
不为空的时候,遍历链表,从mEfiTimerList
读取第一个Event,对当前事件的触发时间和系统当前时间进行比较,如果 TriggerTime <= mEfiSystemTime
则执行
CoreSignalEvent (mEfiCheckTimerEvent);
仔细观察可以发现,CoreTimerTick中只对比了链表中第一个Event的时间和系统时间,这是因为==mEfiTimerList
链表是按照Event的时间由小到大进行排序的,==因此只需要通过第一个事件就能够知道链表中的事件有没有需要被Signal的。
这种按照Event的时间由小到大进行排序的操作是在CoreInsertEventTimer
中实现的,函数非常简单,可以自己看一下
/// Timer.c
VOID
CoreInsertEventTimer (
IN IEVENT *Event
)
{
UINT64 TriggerTime;
LIST_ENTRY *Link;
IEVENT *Event2;
ASSERT_LOCKED (&mEfiTimerLock);
//
// Get the timer's trigger time
//
TriggerTime = Event->Timer.TriggerTime;
//
// Insert the timer into the timer database in assending sorted order
//
for (Link = mEfiTimerList.ForwardLink; Link != &mEfiTimerList; Link = Link->ForwardLink) {
Event2 = CR (Link, IEVENT, Timer.Link, EVENT_SIGNATURE);
if (Event2->Timer.TriggerTime > TriggerTime) {
break;
}
}
InsertTailList (Link, &Event->Timer.Link);
}
CoreSignalEvent
这一步的执行很关键。我们在前面CoreInitializeEventServices 这一节中,已经看到了 mEfiCheckTimerEvent
的生成,当CoreSignalEvent执行的时候,对应执行的应该是CoreCheckTimers
这个函数
在这个函数中,对mEfiTimerList
进行了遍历,如果已经TriggerTime > mEfiSystemTime
说明这个事件还没到时间,故跳过。到时间的那些事件则从mEfiTimerList
移除并且进行CoreSignalEvent (Event);
操作,此时这个Event就变成了一个普通的被Signal的事件了。定时器事件还有一个特殊的情况就是周期的事件,如果是这种事件,那么就需要重新计算下一次的触发时间,并将时间更新完成的事件插入mEfiTimerList
中等待下一次的轮询。
函数跳出while循环使用的判断方式 是当有一个事件没有达到执行时间就跳出,这个设计的原因之前我们就已经说过:mEfiTimerList
链表是按照Event的时间由小到大进行排序的,因此一个没有达到执行时间的Event后面所有的Event都不会达到执行时间
综上,我们可以总结定时器事件底层实现的基本逻辑:
当CPU产生时钟中断,就会依次比较mEfiTimerList
中事件的TriggerTime
与mEfiSystemTime
,如果mEfiSystemTime
更大,则将当前事件从mEfiTimerList
移除并且进行CoreSignalEvent (Event);
操作,如果是周期Timer则更新TriggerTime
重新插入链表中。CoreSignalEvent (Event);
后的Event就变成了一个普通的被Signal的事件了。
将上述过程进行整理,可以形成以下的框图用以表示Event事件的初始化和执行流程
普通事件
前面整理总结了定时器事件的底层实现逻辑,我们得到结论,CoreSignalEvent (Event);
后的Event就变成了一个普通的被Signal的事件。那么普通的事件被Signal后是如何执行的呢?
CoreSignalEvent
首先来看CoreSignalEvent
函数 这是一个核心的函数
EFI_STATUS
EFIAPI
CoreSignalEvent (
IN EFI_EVENT UserEvent
)
{
IEVENT *Event;
Event = UserEvent;
if (Event == NULL) {
return EFI_INVALID_PARAMETER;
}
if (Event->Signature != EVENT_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
CoreAcquireEventLock ();
//
// If the event is not already signalled, do so
//
if (Event->SignalCount == 0x00000000) {
Event->SignalCount++;
//
// If signalling type is a notify function, queue it
//
if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0) {
if ((Event->ExFlag & EVT_EXFLAG_EVENT_GROUP) != 0) {
//
// The CreateEventEx() style requires all members of the Event Group
// to be signaled.
//
CoreReleaseEventLock ();
CoreNotifySignalList (&Event->EventGroup);
CoreAcquireEventLock ();
} else {
CoreNotifyEvent (Event);
}
}
}
CoreReleaseEventLock ();
return EFI_SUCCESS;
}
这里我们将传入的事件分成三种情况进行讨论:
- 传入事件类型是
EVT_TIMER
此时 只执行Event->SignalCount++;
之后函数就成功返回 - 传入事件类型是
EVT_NOTIFY_WAIT
此时 同样只执行Event->SignalCount++;
之后函数就成功返回 - 传入事件类型是
EVT_NOTIFY_SIGNAL
,执行Event->SignalCount++;
然后函数需要执行if分支分别对传入Event or EventGroup进行不同的处理,由于EventGroup和Event本质相同,当前就先看Event的情况,此时 执行CoreNotifyEvent (Event);
后返回。
对三种不同类型的Event执行CoreSignalEvent
之后,可以看到 Event的 SignalCount 都是增加了的,至于增加的用处我们马上就会说到,暂时先留一个问题在这 ^p6p94a
EVT_NOTIFY_SIGNAL NotifyFunction 的执行
下面我们先来看EVT_NOTIFY_SIGNAL
类型的Event 被Signal之后 的操作 CoreNotifyEvent
VOID
CoreNotifyEvent (
IN IEVENT *Event
)
{
//
// Event database must be locked
//
ASSERT_LOCKED (&gEventQueueLock);
//
// If the event is queued somewhere, remove it
//
if (Event->NotifyLink.ForwardLink != NULL) {
RemoveEntryList (&Event->NotifyLink);
Event->NotifyLink.ForwardLink = NULL;
}
//
// Queue the event to the pending notification list
//
InsertTailList (&gEventQueue[Event->NotifyTpl], &Event->NotifyLink);
gEventPending |= (UINTN)(1 << Event->NotifyTpl);
}
函数中最重要的就是
InsertTailList (&gEventQueue[Event->NotifyTpl], &Event->NotifyLink);
这句话表示将当前这个Event插入到对应TPL的 gEventQueue 链表中,前面我就说过 gEventQueue 保存的是已经处于Signal状态的待执行的Event ,此处就说明了这个链表是如何构建起来的。
已经知道signal状态的Event存储的位置,接下来的问题就是什么时候、如何执行这些Event对应的NotifiyFunction。在全局中搜索 gEventQueue
就可以发现下面的函数调用了gEventQueue
VOID
CoreDispatchEventNotifies (
IN EFI_TPL Priority
)
{
IEVENT *Event;
LIST_ENTRY *Head;
CoreAcquireEventLock ();
ASSERT (gEventQueueLock.OwnerTpl == Priority);
Head = &gEventQueue[Priority];
//
// Dispatch all the pending notifications
//
while (!IsListEmpty (Head)) {
Event = CR (Head->ForwardLink, IEVENT, NotifyLink, EVENT_SIGNATURE);
RemoveEntryList (&Event->NotifyLink);
Event->NotifyLink.ForwardLink = NULL;
//
// Only clear the SIGNAL status if it is a SIGNAL type event.
// WAIT type events are only cleared in CheckEvent()
//
if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0) {
Event->SignalCount = 0;
}
CoreReleaseEventLock ();
//
// Notify this event
//
ASSERT (Event->NotifyFunction != NULL);
Event->NotifyFunction (Event, Event->NotifyContext);
//
// Check for next pending event
//
CoreAcquireEventLock ();
}
gEventPending &= ~(UINTN)(1 << Priority);
CoreReleaseEventLock ();
}
其中这个函数中最核心的一句
Event->NotifyFunction (Event, Event->NotifyContext);
此时执行了NotifyFunction,那么接下来的问题就是谁调用了这个函数
CoreRestoreTpl 函数中 CoreDispatchEventNotifies (gEfiCurrentTpl); 就是调用的位置。这说明每次调用CoreRestoreTpl ,都会进行signal event的NotifyFunction的执行。
CoreRestoreTpl 是 gBS->RestoreTPL 的原型。前面我们介绍定时器的底层的时候曾经详细说过TimerInterruptHandler
函数 ,在这个函数最后就会调用gBS->RestoreTPL。这也就是说每次中断发生,对事件进行相应的处理之后,都会将被signal的Event进行一个统一的执行。
现在就找到了EVT_NOTIFY_SIGNAL NotifyFunction 的执行位置。这样也就能说明为什么EVT_NOTIFY_SIGNAL
类型的Event是在signal之后执行对应的NotifyFunction了
EVT_NOTIFY_WAIT NotifyFunction 的执行
说完了EVT_NOTIFY_SIGNAL
类型的Event,接下来我们看EVT_NOTIFY_WAIT
类型的Event的NotifyFunction的执行情况。在介绍CreatEvent生成的各种类型的事件的属性的时候曾经介绍过,EVT_NOTIFY_WAIT
的NotifyFunction会在每次事件检查是否被触发的时候执行。那这其中的关键就是检查事件的CheckEvent
函数了
CheckEvent
函数的原型CoreCheckEvent
///Event.c
EFI_STATUS
EFIAPI
CoreCheckEvent (
IN EFI_EVENT UserEvent
)
{
IEVENT *Event;
EFI_STATUS Status;
Event = UserEvent;
if (Event == NULL) {
return EFI_INVALID_PARAMETER;
}
if (Event->Signature != EVENT_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0) {
return EFI_INVALID_PARAMETER;
}
Status = EFI_NOT_READY;
if ((Event->SignalCount == 0) && ((Event->Type & EVT_NOTIFY_WAIT) != 0)) {
//
// Queue the wait notify function
//
CoreAcquireEventLock ();
if (Event->SignalCount == 0) {
CoreNotifyEvent (Event);
}
CoreReleaseEventLock ();
}
//
// If the even looks signalled, get the lock and clear it
//
if (Event->SignalCount != 0) {
CoreAcquireEventLock ();
if (Event->SignalCount != 0) {
Event->SignalCount = 0;
Status = EFI_SUCCESS;
}
CoreReleaseEventLock ();
}
return Status;
}
函数首先检查了传入参数的有效性,并且
if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0) {
return EFI_INVALID_PARAMETER;
}
规定了如果传入事件的类型是 EVT_NOTIFY_SIGNAL
则返回 EFI_INVALID_PARAMETER。这和我们之前介绍的CheckEvent的返回值状态是吻合的
接下来对其他的情况分别进行判断
Status = EFI_NOT_READY;
if ((Event->SignalCount == 0) && ((Event->Type & EVT_NOTIFY_WAIT) != 0)) {
//
// Queue the wait notify function
//
CoreAcquireEventLock ();
if (Event->SignalCount == 0) {
CoreNotifyEvent (Event);
}
CoreReleaseEventLock ();
}
首先看if条件里面的 第一部分 Event->SignalCount == 0。前面曾经说过,CoreSignalEvent
执行之后,对应的Event->SignalCount都是增加的。 此处要求Event->SignalCount == 0 说明当前这个Event没有执行过CoreSignalEvent
是一个处于非signal状态的函数。
现在就可以明确,Event->SignalCount是否为0,分别表示了Event是否处于Signal状态 。 这也回答了最前面提出的问题:函数是否处于触发状态在代码中是如何表示的呢?
if语句的第二个条件则是要求当前Event的类型是EVT_NOTIFY_WAIT ,同时满足以上两个条件,则执行 CoreNotifyEvent (Event); 此时函数的NotifyFunction也就被执行了
接下来继续检查事件的状态
if (Event->SignalCount != 0) {
CoreAcquireEventLock ();
if (Event->SignalCount != 0) {
Event->SignalCount = 0;
Status = EFI_SUCCESS;
}
CoreReleaseEventLock ();
}
如果事件处于signal的状态(此时前面非signal 的 EVT_NOTIFY_WAIT Event已经被触发了),则需要将事件的状态修改为非signal,即Event->SignalCount = 0; 同时返回状态为 EFI_SUCCESS ,这也与前面我们说过的CheckEvent的执行后的情况是吻合的
至此,EVT_NOTIFY_WAIT NotifyFunction 的执行逻辑已经完全说清楚了。
最后我们思考一下,如果传入的事件类型是Timer类型,
- 如果Timer已经触发 那么Event->SignalCount != 0 返回EFI_SUCCESS
- 如果Timer还没有触发 那么Status = EFI_NOT_READY; 设置完成之后,不会执行后续的所有if分支,最后会直接返回EFI_NOT_READY
Timer类型的结果同样符合我们之前说的情况
详细说明了CoreCheckEvent
函数之后,顺便提一句 CoreWaitForEvent
可以看到CoreWaitForEvent的核心就是CoreCheckEvent 所以不在详细的描述
结尾
Event的大部分概念以及底层的基本情况已经详细的介绍了,其他没有详细介绍的部分 自己看一下代码就可以了,只要掌握了前面的内容,剩下的部分就不是难事。如果还有什么新的内容后续在继续补充
[^1] UEFI原理与编程