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意味着该事件是一个定时器,IEventTimer域会被填充,其中包含一个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非空,意味着该事件属于一个事件组

  1. EVT_SIGNAL_EXIT_BOOT_SERVICESEVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE类型的事件是不允许加入其他事件组的,这两类事件各自有自己的事件组
  2. 如果EventGroup这个GUIDgEfiEventExitBootServicesGuid,则类型必须是EVT_SIGNAL_EXIT_BOOT_SERVICES
  3. 如果EventGroup这个GUIDgEfiEventVirtualAddressChangeGuid,则类型必须是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_SERVICESEVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE,则将其加入指定的事件组


3、处理Notify

当且仅当类型包含了EVT_NOTIFY_WAITEVT_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                                                                     

简而言之就是所需的数据均存于IEventRuntimeData域并链接到全局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 ();                                 
  1. 只有在SignalCount0的时候,才会加一
  2. 只有在类型是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                                                                              

若事件的SignalCount0且该事件的确是一个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

  1. 该事件在创建的时候(若没有其他类型属性)则不会加入任何链表
  2. 该事件在唤醒的时候,即调用SignalEvent时,仅在SignalCount0的情况下将其增加一,并不会被Norify

事件类型EVT_NOTIFY_SIGNAL

  1. 该事件在创建时会被加入gEventSignalQueue,使用IEvent->SignalLink
  2. 该事件可以调用SignalEvent来唤醒,当SignalCount0时事件会被Norify

常见情景

EVT_NORIFY_WAIT类型

  1. 创建一个EVT_NORIFY_WAIT类型的事件
  2. 使用WaitForEvent来等待该事件的发生,其不断检查SignalCount
  3. 使用SignalEvent来唤醒该事件,其将SignalCount增加一
  4. WaitForEvent调用的CheckEvent检查到事件的发生,将事件Notify来执行回调函数,并返回事件已经发生,此时WaitForEvent退出无限循环,继续往下执行

EVT_NORIFY_SIGNAL类型

  1. 创建一个EVT_NORIFY_SIGNAL类型的事件,其会被链接到gEventSignalQueue
  2. 使用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的异步事件服务.第三部分.定时器与时钟中断

UEFI.源码分析.DXE的异步事件服务.第三部分.定时器与时钟中断

UEFI.源码分析.DXE的内存服务.第一部分.初始化

UEFI.源码分析.DXE阶段的执行