一,时钟中断概述
UEFI 中的EVENT是使用时钟中断来驱动的。
在时钟中断处理函数中,它会检查系统中的定时器事件并处理到期的定时器事件,并在合适的时机调度事件的Notify函数,是事件的实现基础。时钟中断在DXE的主函数DxeMain中初始化(准确的说是在初始化事件时)并开始使用,具体的流程请看下图——当时作为UEFI小白的我画了一天!其实后来看,有一些是架构协议与触发函数注册的内容,待会浅提一下。
二,时钟中断执行流程
执行时钟中断的单元函数是CoreTimerTick,我剧透一下,因为它被注册为mTimerNotifyFunction,所以在其他架构下看到的mTimerNotifyFunction函数其实就对应CoreTimerTick。
当然你也可以叛逆期到了自己写一个……
CoreTimerTick (
IN UINT64 Duration
)
{
IEVENT *Event;
CoreAcquireLock (&mEfiSystemTimeLock);
mEfiSystemTime += Duration;
if (!IsListEmpty (&mEfiTimerList)) {
Event = CR (mEfiTimerList.ForwardLink, IEVENT, Timer.Link, EVENT_SIGNATURE);
if (Event->Timer.TriggerTime <= mEfiSystemTime) {
CoreSignalEvent (mEfiCheckTimerEvent);
}
}
CoreReleaseLock (&mEfiSystemTimeLock);
}
其实源代码里注释写的很清楚了
首先升高优先级,更新系统时间,这里的优先级是31——UEFI中定义的最高优先级,不会被抢断
mEfiTimerList是所有定时事件,按触发时间从近到远排列,如果第一个没有被触发,说明没有定时器事件到期,反之,开始触发定时器事件。
不过需要注意一点,我之前写过事件在升高降低优先级时进行调度,这里也进行了这一行为,但实际上,在进这个函数时优先级已经很高了,所以调度并不是在这里,而是在另一个函数里,这就涉及到时钟中断的注册流程。
三,时钟中断的注册流程
时钟中断的注册分为好几部分,我们先来看最简单的部分好了
句柄注册与触发函数
(好大的图片!)
这段就是我之前说的,不应该放在这里的部分,稍微提一提就好了。
我们都知道BDS阶段的入口是BdsEntry函数,然而它是怎么跳过去的呢?这就涉及架构协议注册与触发函数机制(我起的名)
在DXE中,定义了以下协议为架构协议,这些协议大多承担了比较重要的架构功能
EFI_CORE_PROTOCOL_NOTIFY_ENTRY mArchProtocols[] = {
{ &gEfiSecurityArchProtocolGuid, (VOID **)&gSecurity, NULL, NULL, FALSE },
{ &gEfiCpuArchProtocolGuid, (VOID **)&gCpu, NULL, NULL, FALSE },
{ &gEfiMetronomeArchProtocolGuid, (VOID **)&gMetronome, NULL, NULL, FALSE },
{ &gEfiTimerArchProtocolGuid, (VOID **)&gTimer, NULL, NULL, FALSE },
{ &gEfiBdsArchProtocolGuid, (VOID **)&gBds, NULL, NULL, FALSE },
{ &gEfiWatchdogTimerArchProtocolGuid, (VOID **)&gWatchdogTimer, NULL, NULL, FALSE },
{ &gEfiRuntimeArchProtocolGuid, (VOID **)&gRuntime, NULL, NULL, FALSE },
{ &gEfiVariableArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },
{ &gEfiVariableWriteArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },
{ &gEfiCapsuleArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },
{ &gEfiMonotonicCounterArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },
{ &gEfiResetArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },
{ &gEfiRealTimeClockArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },
{ NULL, (VOID **)NULL, NULL, NULL, FALSE }
};
对于这些架构协议,DXE设计了一套机制,在主函数DxeMain中调用CoreNotifyOnProtocolInstallation给这些函数都注册了一个事件,当上述协议被模块Install时,事件会触发回调函数GenericProtocolNotify
在回调函数中,UEFI注册了传入的协议对应的Entry函数(比如对于Bds,这个函数就是BdsEntry),并且,当判断到传入的协议是gTimer时,会做如下处理
if (CompareGuid (Entry->ProtocolGuid, &gEfiTimerArchProtocolGuid)) {
//
// Register the Core timer tick handler with the Timer AP
//
gTimer->RegisterHandler (gTimer, CoreTimerTick);
}
相当于,在gTimer被注册后,会调用gTimer->RegisterHandler (gTimer, CoreTimerTick);这个函数
这个函数其实就一行有用的
mTimerNotifyFunction = NotifyFunction;
其实就是把CoreTimerTick注册为时钟中断的原子函数
CPU视角下的时钟中断
在内核中,唯一不变的只有CPU一次次的打点,就像我灰暗的人生,唯一不变的只有、永远一秒一秒流逝的时间。
时钟中断的计时依赖于CPU,那么在CPU视角下,是如何注册时钟中断的呢?
这部分不同架构实现的也不太一样,而且涉及到汇编,有些复杂,这里的例子用的源码的OVM架构的,因为其他架构的我没看懂
在Ovm架构下,Timer模块初始化时运行了如下内容
Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&mCpu);
Status = gBS->LocateProtocol (&gEfiLegacy8259ProtocolGuid, NULL, (VOID **)&mLegacy8259);
TimerVector = 0;
Status = mLegacy8259->GetVector (mLegacy8259, Efi8259Irq0, (UINT8 *)&TimerVector);
Status = mCpu->RegisterInterruptHandler (mCpu, TimerVector, TimerInterruptHandler);
前两行,分别找到打开的CPU架构和8259协议,8259是芯片,它的IRQ0记录了时钟中断的中断号,在第四行被提取了出来
然后调用RegisterInterruptHandler将传入的中断号为默认中断(TimerVector),并且保存了函数指针到ExternalVectorTable[InterruptType]
在CPU驱动中,调用CommonInterruptEntry进入中断,这个函数只干三件事,保存寄存器、调用ExternalVectorTable[InterruptType]、恢复寄存器。
这部分戴正华老师的书里描述的很详细,我就不写了。