第六章 事件

作者:Maxwell Li
日期:2017/12/09
未经作者允许,禁止转载本文任何内容。如需转载请留言。


[TOC]

UEFI 事件服务

函数名 作用
CreateEvent 生成一个事件对象
CreateEventEX 生成一个事件对象并加入到一个组内
CloseEvent 关闭事件对象
SignalEvent 触发事件对象
WaitForEvent 等待事件数组中的任意事件触发
CheckEvent 检查事件状态
SetTimer 设置定时器属性
RaiseTPL 提升任务优先级
RestoreTPL 恢复任务优先级

6.1 事件函数

6.1.1 WaitForEvent 服务

WaitForEvent 服务函数原型:

/**等待 Event 数组内任一事件被触发
  @retval EFI_SUCCESS            下标为 *index 的事件被触发
  @retval EFI_UNSUPPORTED        当前的 TPL 不是 TPL_APPLICATION
  @retval EFI_INVALID_PARAMETER  下标为 *index 的时间类型为 EVT_NOTIFY_SIGNAL
**/
typedef EFI_STATUS(EFIAPI *EFI_WAIT_FOR_EVENT)(
IN UINTN NumberOfEvents,  // Event 数组内 Event 个数
IN EFI_EVENT *Event,      // Event 数组
OUT UINTN *Index          // 返回处于触发状态的事件在数组内的下标
);

WaitForEvent 没有超时属性,如果需要只等待一定的时间,需要在事件组加入定时器事件。

6.1.2 CreateEvent 服务

CreateEvent 服务函数原型:

typedef EFI_STATUS(EFIAPI *EFICREATE_EVENT)(
  IN UINT32 Type,                               // 事件类型
  IN EFI_TPL NotifyTpl,                         // 事件 Notification 函数的优先级
  IN EFI_EVENT_NOTIFY NotifyFunction, OPTIONAL  // 事件 Notification 函数
  IN VOID *NotifyContext, OPTIONAL              // 传给事件 Notification 函数的参数
  OUT EFI_EVENT *Event                          // 生成的事件
  );

事件的类型

CreateEvent 第一个参数为 Type。

常用事件类型

事件类型 事件特征
EVT_TIMER 定时器事件。普通 Timer 事件,没有 Notification 函数。生成事件后需设置时钟属性。事件可以:
1. 通过 SetTimer() 设置等待事件
2. 到期后通过 SignalEvent() 触发
3. 通过 WaitForEvent() 等待事件被触发
4. 通过 CheckEvent() 检查状态
EVT_NOTIFY_WAIT 普通事件。该事件有一个 Notification 函数,当该事件通过 CheckEvent() 检查状态或通过 WaitForEvent() 等待时,Notification 函数会被放到待执行队列 gEventQueue[Event->NotifyTpl] 中。
EVT_NOTIFY_SIGNAL 普通事件。该事件有一个 Notification 函数,当该事件通过 SignalEvent() 被触发时,Notification 函数会被放到待执行队列 gEventQueue[Event->NotifyTpl] 中。
0x00000000 普通事件。此类事件没有 Notification 函数。事件可以:
1. 通过SignalEvent() 被触发
2. 通过 WaitForEvent() 等待事件被触发
3. 通过 CheckEvent() 检查状态
EVT_TIMER |
EVT_NOTIFY_WAIT
带 Notification 函数的定时器时间。同时具有 EVT_TIMER 和 EVT_NOTIFY_WAIT 的特性。
EVT_TIMER |
EVT_NOTIFY_SIGNAL
带 Notification 函数的定时器时间。同时具有 EVT_TIMER 和 EVT_NOTIFY_SIGNAL 的特性。

两种特殊的事件:

  • EVT_SIGNAL_EXIT_BOOT_SERVICES:是 EVT_NOTIFY_SIGNAL 和 0x00000001 的组合。当 ExitBootServices() 执行时,事件被触发。该事件不能和其他类型混合使用。它的 Notification 函数和子函数不能使用启动服务中的内存分配服务,也不能使用定时器服务。
  • EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE:是 EVT_RUNTIME_CONTEXT、EVT_RUNTIME、EVT_NOTIFY_SIGNAL、0x00000002 的组合。不能和这四类以外的类型组合使用,当 SetVirtualAddressMap() 被调用时触发。

优先级

CreateEvent 第二个参数为 NotifyTPL,是 0-31 的一个整数。优先级别搞得任务可以中断级别低的任务。

UEFI 预定任务优先级

任务优先级 用法
TPL_APPLICATION(4) 应用程序(包括 Boot Manager 和 OS Loader)运行在这个级别。
当程序运行在这个级别时,任务队列中没有任何处于就绪状态的事件 Notification 函数。
TPL_CALLBACK(8) 比较耗时的操作通常在该优先级执行。
例如:文件系统、磁盘操作等。
TPL_NOTIFY(16) 运行在该级别的程序不允许阻塞,必须尽快执行完毕且返回。
如果需要更多操作,则使用 Event 由内核重新调度。
通常底层的 IO 操作允许在这个级别。
例如:UEFI 内核中读取键盘状态的代码。
大部分 Event 的 Notification 函数允许在这个级别。
TPL_HIGH_LEVEL(31) 在该级别,中断被禁止。
UEFI 内核全局变量的修改需要允许在该级别。

Notification 函数 NotifyFunction

CreateEvent 第三个参数为 NotifiFunction,是 EFI_EVENT_NOTIFY 类型的函数指针。

  • 事件类型是 EVT_NOTIFY_WAIT,EFI_EVENT_NOTIFY 函数会在等待此事件的过程中调用。
  • 事件类型是 EVT_NOTIFY_SIGNAL,EFI_EVENT_NOTIFY 函数会在事件触发时调用。
  • 若不是以上类型,Notification 参数将被忽略。

CreateEvent 第四个参数为 NotifyContext,将在 Notification 函数被调用时作为第二个参数传递给该函数,用于指向这个 Notification 函数的上下文。

6.1.3 CreateEventEx 服务

CreateEventEx 服务用于生成事件并将事件加入事件组。

CreateEventEx 服务函数原型:

typedef EFI_STATUS(EFIAPI *EFICREATE_EVENT_EX)(
  IN UINT32 Type,                               // 事件类型
  IN EFI_TPL NotifyTpl,                         // 事件 Notification 函数的优先级
  IN EFI_EVENT_NOTIFY NotifyFunction, OPTIONAL  // 事件 Notification 函数
  IN VOID *NotifyContext, OPTIONAL              // 传给事件 Notification 函数的参数
  IN CONST EFI_GUID *EventGroup OPTIONAL,       // 事件组
  OUT EFI_EVENT *Event                          // 生成的事件
  );

由 CreateEventEx 生成的事件会加入到 EventGroup 中。当 EventGroup 中的任一事件被触发后,组内所有其他事件都会被触发,进而组内所有 Notification 函数都将加入到待执行队列。如果 EventGroup 为 NULL,则 CreateEventEx 与 CreateEvent 相同。

UEFI 预定义的四个 Event 组:

  1. EFI_EVENT_GROUP_EXIT_BOOT_SERVICES
  • GUID:gEfiEventExitBootServicesGuid
  • ExitBootServices() 执行时触发该组所有 Event,组内 Event 属性同于 EVT_SIGNAL_EXIT_BOOT_SERVICES。
  1. EFI_EVENT_GROUP_VIRTUAL_ADDRESS_CHANGE
  • GUID:fEfiEventVirtualAddressChangeGuid
  • SetVirtualAddressMap() 执行时触发该组所有 Event,组内 Event 属性同于 EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE。
  1. EFI_EVENT_GROUP_MEMORY_MAP_CHANGE
  • GUID:gEfiEventMemoryMapChangeGuid
  • Memory Map 改变时触发该组所有 Event,在 Notification 函数中不能使用启动服务的内存分配函数。
  1. EFI_EVENT_GROUP_READY_TO_BOOT
  • GUID:gEfiEventReadyToBoot
  • Boot Manager 加载并且执行一个启动项时触发该组所有 Event。

6.1.4 CheckEvent 服务

CheckEvent 服务用于检查事件的状态,返回值有以下四种情况:

  1. 事件是 EVT_NOTIFY_SIGNAL 类型:返回 EFI_INVALID_PARAMETER。
  2. 事件处于触发状态:返回 EFI_SUCCESS 且事件重置为非触发状态。
  3. 事件处于非触发状态且事件无 Notification 函数:返回 EFI_NOT_READY。
  4. 事件处于非触发状态且事件有 Notification 函数(事件只可能为 EVT_NOTIFY_WAIT 类型):执行 Notification 函数,检查事件状态表示。若处于触发状态,返回 EFI_SUCCESS,否则返回 EFI_NOT_READY。

6.1.5 SignalEvent 服务

SignalEvent 服务用于将事件设置为触发状态。

  • 如果事件类型为 EVT_NOTIFY_SIGNAL:将其 Notification 函数添加到队列准备执行。
  • 如果该类事件属于一个组:将该组内所有事件设置为触发状态,且将组内所有该类型事件的 Notification 函数添加到队列准备执行。

6.1.6 CloseEvent 服务

事件使用完毕后必须调用 CloseEvent 关闭该事件。调用该函数后,指定事件从内核中删除。

6.2 定时器事件

SetTimer 服务函数原型:

/**设置定时器属性
  @retval EFI_SUCESS             属性设置成功
  @retval EFI_INVALID_PARAMETER  参数 Event 不是 EVT_TIMER 或参数非法
**/
typedef EFI_STATUS(EFIAPI *EFI_SET_TIMER)(
  IN EFI_EVENT Event,       // Timer 事件
  IN EFI_TIMER_DELAY Type,  // 定时器类别
  IN UINT64 TriggerTime     // 定时器过期时间,100ns 为一个单位
  );

定时器类别

Type 作用
TimerCancel 用于取消定时器触发事件,设置后定时器不再触发
TimerPeriodic 重复型定时器。每 TriggerTime*100ns 定时器触发一次
TimerRelative 一次性定时器。TriggerTime*100ns 定时器触发
  • Type 为 TimerPeriodic 且 TriggerTime 为0:定时器每个时钟滴答触发一次。
  • Type 为 TimerRelative 且 TriggerTime 为0:定时器在下个时钟滴答触发。

6.3 任务优先级

6.3.1 提升和恢复任务优先级

  • RaiseTPL(NewTpl) 用于提升当前任务的优先级至 NewTpl,该函数的返回值为原来的任务优先级。
  • RestoreTPL 用于恢复至原来的任务优先级。

RaiseTPL 和 RestoreTPL 必须成对出现。当任务优先级提升至 TPL_HEIGH_LEVEL 时,将关闭中断。当任务优先级恢复到原来值时,中断被重新打开。

UEFI 是单 CPU 单线程系统,数据竞争来自中断处理函数。通过 CoreAcquireLock 和 CoreReleaseLock 实现 UEFI 锁。

6.3.2 UEFI 中的时钟中断

时钟处理函数 CoreTimerTick

时钟处理函数 CoreTimerTick 在时钟中断中调用,是时钟中断处理函数的主体。进入该函数时需要加锁,离开时需要解锁。主要功能:维持系统时间、检查定时器事件列表中到期事件。

CoreTimerTick 函数实现:

VOID EFIAPI CoreTimerTick( IN UINT64 Duration )
{
  IEVENT *Event;
  CoreAcquireLock (&mEfiSystemTimeLock);
  // 任务一:更新系统时间
  mEfiSystemTime += Duration;
  // 任务二:检查系统中的定时器时间是否到期
  if (!IsListEmpty (&mEfiTimerList)) {
    Event = CR (mEfiTimerList.ForwardLink, IEVENT, Timer.Link, EVENT_SIGNATURE);
    // 如果到期,则触发 mEfiCheckTimerEvent 事件
    // 该事件会检查并触发所有到期的定时器事件
    if (Event->Timer.TriggerTime <= mEfiSystemTime) {
      CoreSignalEvent (mEfiCheckTimerEvent);
    }
  }
  CoreReleaseLock (&mEfiSystemTimeLock);  // 释放锁并开中断
}

设置时钟处理函数及安装时钟中断

函数 CoreTimerTick 如何注册到时钟中断处理函数中:

(1)安装时钟中断处理函数的时机
(2)向 gTimer 注册 CoreTimerTick 函数
(3)向 CPU 注册中断处理函数 TimerInterruptHandler
(4)在 CPU 时钟中断向量中调用时钟中断处理函数

该部分待补充。

6.3.3 UEFI 事件 Notification 函数的派发

事件 Notification 函数的派发是在 gBS->RestoreTpl 服务中完成的。gBS->RestoreTpl 实际指向 CoreRestoreTpl 函数,实现如下:

VOID EFIAPI CoreRestoreTpl ( IN EFI_TPL NewTpl )
{
  EFI_TPL OldTpl;
  OldTpl = gEfiCurrentTpl;
  ASSERT (NewTpl <= OldTpl);
  ASSERT (VALID_TPL (NewTpl));
  if (OldTpl >= TPL_HIGH_LEVEL  &&  NewTpl < TPL_HIGH_LEVEL) {
    gEfiCurrentTpl = TPL_HIGH_LEVEL;
  }
  // 事件列表中高优先级的任务就绪
  while (((-2 << NewTpl) & gEventPending) != 0) {
    // gEfiCurrentTpl 赋值为 gEventPending 中的为 1 的最高位
    gEfiCurrentTpl = (UINTN) HighBitSet64 (gEventPending);
    if (gEfiCurrentTpl < TPL_HIGH_LEVEL) {
      CoreSetInterruptState (TRUE);
    }
    // 执行 gEventQueue[gEfiCurrentTpl] 队列中所有 Event 的 Notification 函数
    CoreDispatchEventNotifies (gEfiCurrentTpl);
  }
  // 设置新的任务优先级
  gEfiCurrentTpl = NewTpl;
  // 如果优先级低于 TPL_HIGH_LEVEL,打开中断。
  if (gEfiCurrentTpl < TPL_HIGH_LEVEL) {
    CoreSetInterruptState (TRUE);
  }
  • ((-2 << NewTpl) & gEventPending) != 0 表示事件列表中有高优先级的任务待执行。
  • 时钟中断每 10ms(DEFAULT_TIMER_TICK_DURATION)中断一次。进入时钟中断时,会调用 RaiseTpl,任务优先级提升至 TPL_HEIGH_LEVEL,中断调用返回时,会调用 RestoreTpl 恢复优先级。因此中断调用返回后,所有高于原优先级的 Notification 都已被执行。

你可能感兴趣的:(第六章 事件)