UEFI 事件

UEFI 不再支持中断(准确地说,UEFI 不再为开发者提供中断支持,但在UEFI内部还是使用了时钟中断 ),所有的异步操作都要通过事件(Event)来完成

启动服务为开发者提供了用于操作事件、定时器及TPL(任务优先级)的函数。这些函数可以分为三类:事件相关函数、定时器相关函数及 TPL相关函数。

事件函数

启动服务中事件相关函数有 6 个,函数名大部分以 Event 结尾。提供给事件生产者的函数有 CreateEvent/CreateEventExSignalEventCloseEvent。提供给事件使用者的有WaitForEventCheckEvent

等待事件的服务 WaitForEvent

WaitForEvent 用于等待事件的发生,类似于 Windows 提供的 WaitForMultipleObjects(...)

WaitForEvent 是阻塞操作,直到 Event 数组内任一事件被触发,或任一事件导致错误出现,WaitForEvent 才返回。WaitForEvent 从前到后依次检查 Event 数组内的事件,发现有被触发的事件或遇到错误则返回,如果所有事件都没有被触发,则从头开始重新检查。

当检查到某个事件处于触发态时*Index赋值为该事件在Event数组中的下标,返回前该事件将重置为非触发态。

当检查到某个事件是EVT_NOTIFY_SIGNAL类型时,*Index赋值为该事件在Event数组中的下标,并返回EFI_INVALID_PARAMETER
WaitForEvent必须运行在 TPL_APPLICAION 级别,否则将返回 EFI_UNSUPPORTED

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

生成事件的服务 CreateEvent

CreateEvent 用于生成一个事件。

1.事件的类型

事件的类型可以是以下一种或几种基本类型的组合:

  • EVT_TIMER :定时器事件。普通Timer事件,没有Notification函数。生成事件后需调用SetTimer服务设置时钟属性;
  • EVT_NOTIFY_WAIT :普通事件。这个事件有一个Notification函数,当这个事件通过CheckEvent()检查状态或通过 WaitForEvent()等待时,这个 Notification 函数会被放到待执行队列gEventQueue[Event->NotifyTpl]中;
  • EVT_NOTIFY_SIGNAL :普通事件。这个事件有一个Notification函数,当这个事件通过SignalEvent()被触发时,这个 Notifcation 函数会被放到待执行队列 gEventQueue[Event->NotifyTpl] 中等待执行;
  • 0x00000000:普通事件。此类事件没有Notification 函数。

还有两种特殊的事件,它们用在操作系统系统加载器从启动期向运行时期转换的过程中

  • EVT_SIGNAL_EXIT_BOOT_SERVICES :此类事件是一种特殊的 EVT_NOTIFY_SIGNAL,实际上它是 EVT_NOTIFY_SIGNAL0x00000001的组合。当ExitBootServices()执行时,事件被触发。EVT_SIGNAL_EXIT_BOOT_SERVICES不能和其他类型混合使用。它的 Notification 函数和子函数不能使用启动服务中的内存分配服务;在 Notification 函数执行前所有的定时器服务都已失效,因而在Notificaiton 函数中也不能使用定时器服务。
  • EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE :它是EVT_RUNTIME_CONTEXTEVT_RUNTIMEEVT_NOTIFY_SIGNAL0x00000002的组合。它不能和这 4 种类型之外的类型组合使用。当 SetVirtualAddressMap()被调用时触发此类事件。

2.优先级
CreateEvent 的第二个参数为NotifyTPL(即任务优先级),它可以是 0 ~ 31 的一个整数。

UEFI 预定义了以下 4 个优先级:

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

3.Notification 函数 NotifyFunction
CreateEvent的第三个参数NotifyFunctionEFI_EVENT_NOTIFY类型的函数指针。

如果事件的类型是 EVT_NOTIFY_WAIT,则 EFI_EVENT_NOTIFY 函数会在等待此事件的过程中调用;如果事件的类型是EVT_NOTIFY_SIGNAL,则 EFI_EVENT_NOTIFY 函数会在事件触发时调用。既没有 EVT_NOTIFY_WAIT 属性也没有EVT_NOTIFY_SIGNAL属性的事件,Notification 参数将被忽略。

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

CreateEventEx 服务

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

CreateEventEx 生成的事件会加入到EventGroup中。当EventGroup中的任一事件被触发后,组中的所有其他事件都会被触发,进而同组内所有的Notification函数都将被加入到待执行队列。同组内 NotifyTpl(优先级)高的Notification函数会先被执行。

如果输入参数EventGroupNULL,则CreateEventEx退化为 CreateEvent

Type 不能是 EVT_SIGNAL_EXIT_BOOT_SERVICESEVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE,因为这两种类型有各自对应的Group

UEFI 预定义的 4 个 Event 组:

事件相关的其他函数

1.检查事件状态的服务CheckEvent()
CheckEvent 用于检测事件的状态。与WaitForEvent不同的是,CheckEvent调用后立刻返回。

2.触发事件的服务 SignalEvent
SignalEvent 用于将事件的状态设置为触发态。如果事件类型为EVT_NOTIFY_SIGNAL,则将其 Notification函数添加到就绪队列准备执行。如果该事件属于一个组,则将该组内所有事件都设置为触发态,并将组内所有EVT_NOTIFY_SIGNAL事件的Notification函数添加到就绪队列准备执行。

3.关闭事件的服务CloseEvent

事件使用完毕后,必须调用CloseEvent关闭这个事件:

通常的原则是由事件的所有者(即调用CreateEvent产生该事件的调用者)调用CloseEvent函数。调用该函数后,指定的事件将从内核中删除。

定时器函数

定时器是一类特殊的事件,生成定时器事件后,可以通过SetTimer服务设置定时器属性。

SetTimer 服务的函数原型:

Type 是定时器类别:

任务优先级

UEFI标准虽然不支持多线程,但是UEFI中有任务的概念 :一个程序是一个任务,事件的Notification函数也是一个任务。UEFI没有给开发者提供中断接口,但 UEFI 内核的运行需要时钟中断的支持 ,时钟中断处理函数也是一个任务。

例如,时钟中断任务的重要性要大于定时器的Notification函数,而定时器的 Notification函数的重要性大于普通应用程序。UEFI为任务定义了任务级别,以便有限的计算机资源可以相对合理地分配给众多的任务。

提升和恢复任务优先级

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


RaiseTPLRestoreTPL 必须成对出现,执行了RaiseTPL后,必须尽快调用 RaiseTPL 将任务优先级恢复到原来的值。

当任务优先级提升至TPL_HIGH_LEVEL时,将关闭中断。当任务优先级从TPL_HIGH_LEVEL恢复到原来的(比TPL_HIGH_LEVEL低的)值时,中断被重新打开。

在任务优先级恢复到原优先级之前,所有高于原优先级的触发态事件的Notification函数都要执行完毕。

UEFI是单CPU单线程系统产生数据竞争的唯一可能来自中断处理函数,因而可以利用这一特性实现锁,这正是 UEFI 锁的实现机制。

UEFI 中的时钟中断

UEFI 用事件机制取代了 BIOS 中的中断机制,虽然 UEFI 不再提供中断接口,但其实现却离不开中断尤其是时钟中断。时钟中断是事件机制的基础。

1.时钟处理函数 CoreTimerTick

在时钟中断中调用,是时钟中断处理函数的主体。该函数执行期间必须关中断并且不能被其他任何任务干扰 ,因而进入函数时需要加锁 ,离开函数时需要解锁 。它的主要功能是维持系统时间,检查定时器事件列表中是否有到期的事件

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

mArchProtocols 数组是 UEFI 系统 DXE 阶段的全局变量,存放了体系结构相关的 Protocol,系统初始化时会为 mArchProtocols 中的每个元素生成一个事件,当这个元素对应的 Protocol 安装时,该事件会触发,在事件的回调函数中会对该Protocol做相应的初始化。

Protocol 在内核中的组织:

所有的 Protocol均放在 mProtocolDatabase指向的PROTOCOL_ENTRY链表中。PROTOCOL_ENTRY 包含三个链表。
AllEntriesPROTOCOL_ENTRY 链。
Protocols 指向此Protocol的所有实例。
Notify指向 PROTOCOL_NOTIFY链表,当 PROTOCOL_ENTRY.ProtocolD 对应的 Protocol 安装时,Notify 链表中所有Event都会触发。

例如,mArchProtocols[3]{&gEfiTimerArchProtocolGuid, (VOID**)&gTimer, NULL, NULL, FALSE},是EFI_TIMER_ARCH_PROTOCOL 对应的 EFI_CORE_PROTOCOL_NOTIFY_ENTRY 项;
CoreNotifyOnProtocolInstallation 执行后,mArchProtocols[3]{&gEfiTimerArchProtocolGuid, ( VOlD** )&gTimer, timerEvent, Registration, FALSE} ,TimerEventNotification 函数被 CoreRegisterProtocolNotify 函数注册到系统。


CoreRegisterProtocolNotify(...)函数的主要功能是在 mProtocolDatabase 数据库中注册PROTOCOL_NOTIFY。当Protocol安装时,会检查该Protocol对应的PROTOCOL_ENTRY.Notify。如果PROTOCOL_ENTRY.Notify存在,则触发它指向的事件。

EFI_TIMER_ARCH_PROTOCOL安装时,mArchProtocols[3].Event事件会触发,然后这个事件的响应函数 GenericProtocolNotify会执行,在 GenericProtocoINotify中通过 EFI_TIMER_ARCH_PROTOCOLRegisterHandler 时钟中断处理函数。

向 gimer 注册 CoreTimerTick 函数
gTimerEFI_TIMER_ARCH_PROTOCOL*类型的全局变量。

gTimer->RegisterHandler 这个函数指针指向了函数 TimerDriverRegisterHandler

mTimerNotifyFunction这个函数指针将在时钟中断处理函数TimerInterruptHandler中被调用。

向 CPU 注册中断处理函数 TimerInterruptHandler


TimerDriverlnitialize 中,最终通过 EFI_CPU_ARCH_PROTOCOLRegisterInterruptHandler注册了 TimerInterruptHandler。另外,在中断处理函数中将会调用 TimerInterruptHandler,而TimerlnterruptHandler 又会调用 mTimerNotifyFunction, 即 CoreTimerTick

mCpu->RegisterInterruptHandler 将会调用CpuRegisterInterruptHandler 函数:

此函数注册并启用由 InterruptHandler 为处理器中断或由InterruptType指定的异常类型指定的处理程序。如果InterruptHandlerNULL,则取消安装由InterruptType指定的处理器中断或异常类型的处理程序。安装的处理程序对于每个处理器中断或异常调用一次。

在 CPU 时钟中断向量中调用时钟中断处理函数

默认的中断向量主要功能是调用CommonInterruptEntry。该函数主要完成以下任务:

1)保存寄存器;

2)调用ExternalVectorTable[InterruptType]

3)恢复寄存器,从中断处理返回。

在时钟中断向量中,ExternalVectorTable[InterruptType]指向函数 TimerInterruptHandler

时钟中断的执行过程:

注册时钟中断函数和注册时钟处理函数的过程:

UEFI 事件 Notification 函数的派发

Event的一个重要作用是实现异步操作 ,事件 Notification函数的派发是在gBS->RestoreTpl服务中完成的。gBS->RestoreTpl实际指向CoreRestoreTpl函数:

内容来源于 《UEFI 原理与编程》。。。

相关推荐
星原飞火1 小时前
2-2-18-13 QNX系统架构之原生网络(Qnet)
网络·车载系统·系统架构·qnx·blackberry·qnet
Lalolander9 小时前
2024信创数据库TOP30之华为Gauss DB
大数据·数据库·科技·华为·系统架构
小康师兄18 小时前
【软考速通笔记】系统架构设计师⑯——通信系统架构设计
笔记·系统架构·系统架构师·局域网·通信系统·广域网·软件定义网络
zyy291829181 天前
嵌入式Linux(SOC带GPU树莓派)无窗口系统下搭建 OpenGL ES + Qt 开发环境,并绘制旋转金字塔
linux·运维·数据库·嵌入式硬件·qt·物联网·系统架构
牧泽_3 天前
如何高效地架构一个Java项目
java·开发语言·spring boot·架构·系统架构·intellij-idea·mybatis
星原飞火3 天前
2-2-18-11 QNX系统架构之字符I/O
车载系统·系统架构·blackberry
星原飞火4 天前
2-2-18-9 QNX系统架构之文件系统(一)
车载系统·系统架构·blackberry
星原飞火4 天前
2-2-18-9 QNX系统架构之文件系统(三)
车载系统·系统架构·blackberry
小康师兄4 天前
【软考速通笔记】系统架构设计师⑥——数据库设计基础知识
数据库·redis·笔记·系统架构·系统架构师·全国计算机和软件专业·关系代数运算