UEFI.源码分析.DXE的异步事件服务.第一部分.事件驱动
Posted 木艮氵
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UEFI.源码分析.DXE的异步事件服务.第一部分.事件驱动相关的知识,希望对你有一定的参考价值。
- 源代码:EDK2
- 版本:UDK2017
- UEFI源码分析第二篇,异步事件服务
- 第一部分,事件驱动
- 优先级的讨论将在另一篇,即第二部分。
- 定时器类型
EVT_TIMER
将在第三部分
事件
每一个事件都是一个IEvent
类型
/** /Dxe/Event/Event.h **/
45 #define EVENT_SIGNATURE SIGNATURE_32('e','v','n','t')
46 typedef struct
47 UINTN Signature;
48 UINT32 Type;
49 UINT32 SignalCount;
53 LIST_ENTRY SignalLink;
57 EFI_TPL NotifyTpl;
58 EFI_EVENT_NOTIFY NotifyFunction;
59 VOID *NotifyContext;
60 EFI_GUID EventGroup;
61 LIST_ENTRY NotifyLink;
62 UINT8 ExFlag;
66 EFI_RUNTIME_EVENT_ENTRY RuntimeData;
67 TIMER_EVENT_INFO Timer;
68 IEVENT;
可以看到有多个链表
SignalLink
NotifyLink
这是提供给不同事件类型所使用。事件有以下几种类型。
/** MdePkg/Include/Uefi/UefiSpec.h **/
384 //
385 // These types can be ORed together as needed - for example,
386 // EVT_TIMER might be Ored with EVT_NOTIFY_WAIT or
387 // EVT_NOTIFY_SIGNAL.
388 //
389 #define EVT_TIMER 0x80000000
390 #define EVT_RUNTIME 0x40000000
391 #define EVT_NOTIFY_WAIT 0x00000100
392 #define EVT_NOTIFY_SIGNAL 0x00000200
393
394 #define EVT_SIGNAL_EXIT_BOOT_SERVICES 0x00000201
395 #define EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE 0x60000202
大致有如下特性
- Notify
意味着有NotifyFunction
在事件发生的时候被调用,也就是回调函数
- Wait
意味着可以使用WaitForEvent
来等待事件的发生,其阻塞直到事件被Signal
- Signal
意味着事件可以被唤醒
- Timer
意味着该事件是一个定时器,IEvent
的Timer
域会被填充,其中包含一个Link
来链接所有的定时器
- 这些特性是可以被OR
来组合到一起的,即运算符|
两个类型只在此提一下,不详述
- EVT_SIGNAL_EXIT_BOOT_SERVICES
是在UEFI系统退出(ExitBootServices()
)的时候调用,通常用来回收资源
- EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE
在操作系统加载器调用运行时服务RuntimeServices
的虚拟地址服务来进行虚拟地址转换的时候调用。
主要关注两个类型
- EVT_NOTIFY_WAIT
可等待事件
- EVT_NOTIFY_SIGNAL
可唤醒事件
重要的全局变量
gEventSignalQueue
保存所有未被唤醒、类型为EVT_NOTIFY_SIGNAL
的事件gEventQueue
保存已经被唤醒、需要执行回调函数的事件,根据优先级分为多个链表gEventPending
使用位图来保存gEventQueue
是否含有某个优先级的链表mEventTable
包含了所有合法的事件类型
相关接口
创建事件CreateEvent
/** /Dxe/Event/Event.c **/
378 EFI_STATUS
379 EFIAPI
380 CoreCreateEventInternal (
381 IN UINT32 Type,
382 IN EFI_TPL NotifyTpl,
383 IN EFI_EVENT_NOTIFY NotifyFunction, OPTIONAL
384 IN CONST VOID *NotifyContext, OPTIONAL
385 IN CONST EFI_GUID *EventGroup, OPTIONAL
386 OUT EFI_EVENT *Event
387 )
388
Type
域即事件的类型
NotifyTpl
是回调函数的优先级,在本系列的第二部分会介绍,NorifyFunction
即回调函数本身,NofityContext
即传递给回调函数的参数。
EventGroup
是事件组特性,其使用一个GUID
来标识该事件属于一个事件组,同组内的事件均含有相同的GUID
,不同事件组的GUID
必然不同。
Event
是返回的、创建完成的事件,EFI_EVENT
类型本质上是IEvent*
类型。
1、检查事件的类型
401 Status = EFI_INVALID_PARAMETER;
402 for (Index = 0; Index < (sizeof (mEventTable) / sizeof (UINT32)); Index++)
403 if (Type == mEventTable[Index])
404 Status = EFI_SUCCESS;
405 break;
406
407
408 if(EFI_ERROR (Status))
409 return EFI_INVALID_PARAMETER;
410
对类型的检查使用了全局变量mEventTable
,该变量规定了UEFI系统所支持的所有事件类型,源码中注释写得很清楚了,这里由于只关注三类,所以也就不贴出注释了。
/** /Dxe/Event/Event.c **/
48 UINT32 mEventTable[] =
53 EVT_TIMER | EVT_NOTIFY_SIGNAL,
58 EVT_TIMER,
63 EVT_NOTIFY_WAIT,
68 EVT_NOTIFY_SIGNAL,
72 EVT_SIGNAL_EXIT_BOOT_SERVICES,
76 EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE,
77
83 0x00000000,
88 EVT_TIMER | EVT_NOTIFY_WAIT,
89 ;
2、处理事件组
如果EventGroup
非空,意味着该事件属于一个事件组
EVT_SIGNAL_EXIT_BOOT_SERVICES
和EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE
类型的事件是不允许加入其他事件组的,这两类事件各自有自己的事件组- 如果
EventGroup
这个GUID
是gEfiEventExitBootServicesGuid
,则类型必须是EVT_SIGNAL_EXIT_BOOT_SERVICES
- 如果
EventGroup
这个GUID
是gEfiEventVirtualAddressChangeGuid
,则类型必须是EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE
415 if (EventGroup != NULL)
420 if ((Type == EVT_SIGNAL_EXIT_BOOT_SERVICES) || (Type == EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE))
421 return EFI_INVALID_PARAMETER;
422
423 if (CompareGuid (EventGroup, &gEfiEventExitBootServicesGuid))
424 Type = EVT_SIGNAL_EXIT_BOOT_SERVICES;
425 else if (CompareGuid (EventGroup, &gEfiEventVirtualAddressChangeGuid))
426 Type = EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE;
427
428 else
432 if (Type == EVT_SIGNAL_EXIT_BOOT_SERVICES)
433 EventGroup = &gEfiEventExitBootServicesGuid;
434 else if (Type == EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE)
435 EventGroup = &gEfiEventVirtualAddressChangeGuid;
436
437
如果EventGroup
为空,但类型却是EVT_SIGNAL_EXIT_BOOT_SERVICES
或EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE
,则将其加入指定的事件组
3、处理Notify
当且仅当类型包含了EVT_NOTIFY_WAIT
或EVT_NOTIFY_SIGNAL
才允许拥有回调函数,且优先级(NorifyTpl
)必须在合法范围内。
442 if ((Type & (EVT_NOTIFY_WAIT | EVT_NOTIFY_SIGNAL)) != 0)
446 if ((NotifyFunction == NULL) ||
447 (NotifyTpl <= TPL_APPLICATION) ||
448 (NotifyTpl >= TPL_HIGH_LEVEL))
449 return EFI_INVALID_PARAMETER;
450
452 else
456 NotifyTpl = 0;
457 NotifyFunction = NULL;
458 NotifyContext = NULL;
459
否则将Notify
变量均置为空,不允许也没必要拥有回调函数。
4、创建事件IEvent
对于运行时的事件,需要申请运行时的内存空间,避免被释放
464 if ((Type & EVT_RUNTIME) != 0)
465 IEvent = AllocateRuntimeZeroPool (sizeof (IEVENT));
466 else
467 IEvent = AllocateZeroPool (sizeof (IEVENT));
468
赋值即可。对于事件组,则复制EventGroup
这个GUID
并在IEvent->ExFlag
中标记上该事件属于一个事件组。
473 IEvent->Signature = EVENT_SIGNATURE;
474 IEvent->Type = Type;
475
476 IEvent->NotifyTpl = NotifyTpl;
477 IEvent->NotifyFunction = NotifyFunction;
478 IEvent->NotifyContext = (VOID *)NotifyContext;
479 if (EventGroup != NULL)
480 CopyGuid (&IEvent->EventGroup, EventGroup);
481 IEvent->ExFlag |= EVT_EXFLAG_EVENT_GROUP;
482
483
484 *Event = IEvent;
5、处理运行时事件
486 if ((Type & EVT_RUNTIME) != 0)
487 //
488 // Keep a list of all RT events so we can tell the RT AP.
489 //
490 IEvent->RuntimeData.Type = Type;
491 IEvent->RuntimeData.NotifyTpl = NotifyTpl;
492 IEvent->RuntimeData.NotifyFunction = NotifyFunction;
493 IEvent->RuntimeData.NotifyContext = (VOID *) NotifyContext;
494 IEvent->RuntimeData.Event = (EFI_EVENT *) IEvent;
495 InsertTailList (&gRuntime->EventHead, &IEvent->RuntimeData.Link);
496
简而言之就是所需的数据均存于IEvent
的RuntimeData
域并链接到全局gRuntime
6、处理可唤醒事件EVT_NOTIFY_SIGNAL
插入全局gEventSignalQueue
,使用IEvent->SignalLink
这个操作需要在锁的保护下进行
498 CoreAcquireEventLock ();
499
500 if ((Type & EVT_NOTIFY_SIGNAL) != 0x00000000)
501 //
502 // The Event's NotifyFunction must be queued whenever the event is signaled
503 //
504 InsertHeadList (&gEventSignalQueue, &IEvent->SignalLink);
505
506
507 CoreReleaseEventLock ();
唤醒事件SignalEvent
空检查和签名检查就不说了
527 EFI_STATUS
528 EFIAPI
529 CoreSignalEvent (
530 IN EFI_EVENT UserEvent
531 )
532
533 IEVENT *Event;
534
535 Event = UserEvent;
同样需要在锁的保护下进行
545 CoreAcquireEventLock ();
551 if (Event->SignalCount == 0x00000000)
552 Event->SignalCount++;
553
557 if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0)
558 if ((Event->ExFlag & EVT_EXFLAG_EVENT_GROUP) != 0)
563 CoreReleaseEventLock ();
564 CoreNotifySignalList (&Event->EventGroup);
565 CoreAcquireEventLock ();
566 else
567 CoreNotifyEvent (Event);
568
569
570
571
572 CoreReleaseEventLock ();
- 只有在
SignalCount
为0
的时候,才会加一 - 只有在类型是
EVT_NOTIFY_SIGNAL
的时候才会调用CoreNotifyEvent
来执行回调函数
事件回调NorifyEvent
220 VOID
221 CoreNotifyEvent (
222 IN IEVENT *Event
223 )
224
确保在锁的保护下
229 ASSERT_LOCKED (&gEventQueueLock);
若该事件IEvent->Notify
不为空,则将其从原链表中删除
235 if (Event->NotifyLink.ForwardLink != NULL)
236 RemoveEntryList (&Event->NotifyLink);
237 Event->NotifyLink.ForwardLink = NULL;
238
执行事件的回调函数其实是只是将该事件链入全局的gEventQueue
。
全局变量gEventQueue
根据不同的NotifyTpl
即优先级,保存了所有已经唤醒、需要执行回调函数的事件。
全局变量gEventPending
使用位图来保存gEventQueue
中是否含有某个优先级的事件。
244 InsertTailList (&gEventQueue[Event->NotifyTpl], &Event->NotifyLink);
245 gEventPending |= (UINTN)(1 << Event->NotifyTpl);
前文的CoreNotifySignalList
只是根据事件组,对改组内的所有事件调用CoreNotifyEvent
函数而已。
等待事件发生WaitForEvent
该接口提供等待多个事件的机制,传递进来一个事件数组,返回发生事件的索引
659 EFI_STATUS
660 EFIAPI
661 CoreWaitForEvent (
662 IN UINTN NumberOfEvents,
663 IN EFI_EVENT *UserEvents,
664 OUT UINTN *UserIndex
665 )
666
主体部分即一个无限循环(忙等待),直到一个事件发生
685 for(;;)
686
687 for(Index = 0; Index < NumberOfEvents; Index++)
688
689 Status = CoreCheckEvent (UserEvents[Index]);
690
694 if (Status != EFI_NOT_READY)
695 if (UserIndex != NULL)
696 *UserIndex = Index;
697
698 return Status;
699
700
701
705 CoreSignalEvent (gIdleLoopEvent);
706
调用CoreCheckEvent
来检查事件是否发生。
检查事件发生CheckEvent
588 EFI_STATUS
589 EFIAPI
590 CoreCheckEvent (
591 IN EFI_EVENT UserEvent
592 )
593
事件类型为EVT_NOTIFY_SIGNAL
的事件是不允许CheckEvent
的,也就意味着不允许被WaitForEvent
607 if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0)
608 return EFI_INVALID_PARAMETER;
609
主要返回三个值
- EFI_INVALID_PARAMETER
参数违法
- EFI_NOT_READY
事件未发生(未被唤醒)
- EFI_SUCCESS
事件已发生(已唤醒)
613 if ((Event->SignalCount == 0) && ((Event->Type & EVT_NOTIFY_WAIT) != 0))
614
618 CoreAcquireEventLock ();
619 if (Event->SignalCount == 0)
620 CoreNotifyEvent (Event);
621
622 CoreReleaseEventLock ();
623
若事件的SignalCount
为0
且该事件的确是一个EVT_NOTIFY_WAIT
类型,则将该事件Norify
,即加入到gEventQueue
来执行回调函数。
629 if (Event->SignalCount != 0)
630 CoreAcquireEventLock ();
631
632 if (Event->SignalCount != 0)
633 Event->SignalCount = 0;
634 Status = EFI_SUCCESS;
635
636
637 CoreReleaseEventLock ();
638
若SignalCount
不为0
,则意味着该事件已发生了,将计数置为零后返回成功。
总结
接口简述
SignalEvent
唤醒事件,将事件Norify
NorifyEvent
回调事件,将事件加入gEventQueue
来执行回调函数WaitForEvent
等待事件,循环调用CheckEvent
检查事件是否被唤醒,直到事件发生CheckEvent
检查事件,若事件的SignalCount
不为0
则认为事件已发生,重置为零后返回事件已经发生了
事件类型EVT_NOTIFY_WAIT
- 该事件在创建的时候(若没有其他类型属性)则不会加入任何链表
- 该事件在唤醒的时候,即调用
SignalEvent
时,仅在SignalCount
为0
的情况下将其增加一,并不会被Norify
事件类型EVT_NOTIFY_SIGNAL
- 该事件在创建时会被加入
gEventSignalQueue
,使用IEvent->SignalLink
- 该事件可以调用
SignalEvent
来唤醒,当SignalCount
为0
时事件会被Norify
常见情景
EVT_NORIFY_WAIT
类型
- 创建一个
EVT_NORIFY_WAIT
类型的事件 - 使用
WaitForEvent
来等待该事件的发生,其不断检查SignalCount
- 使用
SignalEvent
来唤醒该事件,其将SignalCount
增加一 WaitForEvent
调用的CheckEvent
检查到事件的发生,将事件Notify
来执行回调函数,并返回事件已经发生,此时WaitForEvent
退出无限循环,继续往下执行
EVT_NORIFY_SIGNAL
类型
- 创建一个
EVT_NORIFY_SIGNAL
类型的事件,其会被链接到gEventSignalQueue
- 使用
SignalEvent
来唤醒该事件,其Norify
该事件
补充
gEventQueue
保存被Norify
的事件,这些事件的回调函数的执行是由时钟中断完成,更具体而言,是通过任务优先级来完成。- 通常认为事件被加入到
gEventQueue
的时候,也就意味着回调函数被执行了。 - 执行回调函数的接口是
CoreDispatchEventNotifies
164 VOID
165 CoreDispatchEventNotifies (
166 IN EFI_TPL Priority
167 )
168
获取锁,并得到gEventQueue
中的指定优先级的事件链表
172 CoreAcquireEventLock ();
173 ASSERT (gEventQueueLock.OwnerTpl == Priority);
174 Head = &gEventQueue[Priority];
遍历该链表的所有事件,执行其回调函数NorifyFunction
对于EVT_NORIFY_SIGNAL
类型的事件,会清除其SignalCount
179 while (!IsListEmpty (Head))
180
181 Event = CR (Head->ForwardLink, IEVENT, NotifyLink, EVENT_SIGNATURE);
182 RemoveEntryList (&Event->NotifyLink);
183
184 Event->NotifyLink.ForwardLink = NULL;
185
190 if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0)
191 Event->SignalCount = 0;
192
193
194 CoreReleaseEventLock ();
195
199 ASSERT (Event->NotifyFunction != NULL);
200 Event->NotifyFunction (Event, Event->NotifyContext);
201
205 CoreAcquireEventLock ();
206
最后清除gEventPending
的位图标记,并释放锁
208 gEventPending &= ~(UINTN)(1 << Priority);
209 CoreReleaseEventLock ();
以上是关于UEFI.源码分析.DXE的异步事件服务.第一部分.事件驱动的主要内容,如果未能解决你的问题,请参考以下文章
UEFI.源码分析.DXE的异步事件服务.第二部分.任务优先级
UEFI.源码分析.DXE的异步事件服务.第二部分.任务优先级
UEFI.源码分析.DXE的异步事件服务.第三部分.定时器与时钟中断