Cstyle的UEFI导读之Event && Timer (下篇:原理篇)

    这次来说说Event和Timer在UEFI当中是怎么实现的以及原理,我们先从Timer开始,然后细细的拨开隐藏在底层的实现。
    先说Timer,那什么是Timer呢?其实在中文里面我们把它叫做定时/计数器,但是我的理解它不仅仅是一个定时/计数器硬件而是一个被程序设计者设定为工作在特殊模式下的 做定时/计数器 ,仅仅是一个硬件的定时器还不能算是Timer。定时/计数器在几乎所有的数字处理器系统当中都是一个必备的设备,没有它我们的各种运行在cpu上的系统软件都会瘫痪,他们就会变成生活在桃花源当中的世外人一样,完全没有时间参考,不知世事更替,所有的原来的秩序都会变得混乱,所以来说他应该是我们系统软件人员必须要关注和处理的一个设备。
    
    说到 做定时/计数器 ,最经典的当数intel兼容的8253/8254定时器,它几乎是所有的PC必须兼容支持的一颗IC,当然在其他的微处理器系统当中也是支持的,比如MSC8051,以及其他的微处理器当中。不过Timer不止一种在PC当中有许多的定时器可以作为Timer来使用,比如ACPI timer,HPET timer等等,虽然他们叫法不一,功能强弱不一,所提供给系统软件设计者的编程接口不完全相同,但是他们都提供了一个最基本的功能,那就是定时,计数功能。几乎所有的Timer都能在不需要外力干涉的情况下在系统的时钟脉冲的驱动之下,自动计数,并且在计数值到达预设值的时候会通知cpu去执行特定的动作,并且在处理完之后能自动接着计数,不辞辛劳做着机械性的无差错的动作。这一点非常有意思,也正是这一点特性,改变了系统软件的格局,当然这里面也包括了UEFI的核心程序的格局。
    
    再说Event,在上篇当中提到了Event的作用,也就是实现所谓的线程(暂且这么叫,其实它并不是传统意义的线程,只是有类似多线程的,异步与消息机制,但实际上是单线程工作的)间的同步与消息机制,这里线程的实现靠的就是Timer来实现,由Timer来驱动消息的传递和callback服务的相互调用,资源互斥,资源锁等的实现。
    
    在PC/AT架构UEFI当中我们通常会使用8254来作为核心心跳Timer,它工作在mode 3,时钟频率是1.1931816MHz,设定的tick(mTimerPeriod)间隔,默认是1ms(10000个tick基础计数单位=10000*100ns),并且打开CPU中断IRQ0。当计数器减到0的时候,就会通过OUT pin给8259的IRQ0发送中断,这个时候CPU就会中断当前操作,进行Timer的中断处理服务。当然前提是我们在此之前有准备好cpu archprotocol和Legacy8259Protocol,设置好中断向量表,当中断到来的时候,cpu会从IDT表中依据中断号,调用Timer中断服务TimerInterruptHandler。
    
    看下进入Timer中断前我们需要准备哪些东西,在系统进入DXE阶段之后(只有在DXE及后续阶段才需要Timer),会处理以下几件事情:

1.确认中断是关的,并关掉中断Cli

2.设置GDT表,根据系统的设定是IA32模式还是X64模式,选择在EfiruntimeServicesData内存创建并加载不同的GDT表,并且通过相应的跳转指令,跳转到相应的段当中去,一般设置两个段,Code和Data段。

3.分配EfiBootServicesData类型内存,来保存异常向量表,以及异常服务指针数组ExternalVectorTable(默认为0),同时让设置IDT表中的异常服务地址指向该数组,当异常发生的时候,就能够调用相对应的服务。此时中断还是关闭的。

4.install CpuArch protocol,为后续的driver提供cpu相关的访问方法,比如安装Timer中断的中断向量

5.注册一个idle event group,Event类型EVT_NOTIFY_SIGNAL,callback服务是一个待机服务,当Event在Timer中断当中被Signal的时候,如果没有可用的其他的Event,执行CPU的hlt指令,等待cpu被唤醒。

    在CPU的异常向量入口设置一个256项的interrup cate descriptor,在X64保护模式下,填充如下的数据结构,当然在IA32下,IDT只有8Bytes,对应于C语言当中的结构体如下:
typedef union {
    struct {
                    UINT32  OffsetLow:16;   ///< Offset bits 15..0.
                    UINT32  Selector:16;    ///< Selector.
                    UINT32  Reserved_0:8;   ///< Reserved.
                    UINT32  GateType:8;     ///< Gate Type.  See #defines above.
                    UINT32  OffsetHigh:16;  ///< Offset bits 31..16.
                    UINT32  OffsetUpper:32; ///< Offset bits 63..32.
                    UINT32  Reserved_1:32;  ///< Reserved.
              } Bits;
  struct {
                    UINT64  Uint64;
                    UINT64  Uint64_1;
              } Uint128;   
} IA32_IDT_GATE_DESCRIPTOR;

    在实际的操作过程当中我们使用汇编来实现一个模板,数据结构表述如下,每一项8Bytes,当cpu发生异常的时候,就会自动把PC指针指向这256项的中断向量表当中。当PC把下面的A位置的数据读取到PC当中并执行的时候,就会发生跳转call到公共异常处理服务

ComInterrEntry当中,同时在跳转之前会把异常向量B.压入堆栈当中(其实入栈的并不是函数返回地址,而是中断向量号),然后再后续的过程当中来处理各种异常。在ComInterrEntry当中我们需要调用我们注册的256个异常服务,我们会创建一个异常服务数组,使用异常向量来作为索引值来索引,调用不同类型的服务,当然这里面就包括我们的Timer服务。
struct
{
A.)     call  ComInterrEntry ;1+4(Opcode +ComInterrEntry)=5 Bytes
B.)     dw  VecNum ;vector number,2Bytes
C.)     nop   ;1Bytes
}

    现在再来看Timer中断异常是怎么做的,它会提供哪些服务,以下是简单的列举。
1.关中断

2.清除中断源

3.获取系统时间锁(关中断,虽然中断此时是关的)

4.调用注册的具体的Timer服务,这里我们称之为CoreTimerTick(mTimerPeriod)/CoreTimerTick(100ms),它是在TimerArchprotocol被install的时候由EVT_NOTIFY_SIGNAL类型的Event来触发注册的。

5.全局Sytem time累加mEfiSystemTime =mEfiSystemTime+ 1Tick(100ms)

6. 在timer event list双向链表当中查找已经到时间了的event,如果找到,就使用BS->SignalEvent去signal它们。

BS->SignalEvent--->CoreNotifyEvent-->CoreRestoreTpl-->CoreDispatchEventNotifies-->A.如果是EVT_NOTIFY_SIGNAL类型,就清除count(Event->SignalCount = 0),然后嵌套直接调用Event->NotifyFunction (Event, Event->NotifyContext)函数,此时中断是开的,然后清除该Tpl的gEventPending mask bit,清除该event。

7.释放系统时间锁(开中断,虽然中断此时是关的)

8.开中断

注:在完成上面的步骤之前,我们需要先初始化一些数据结构:
        1.  gEventQueue--- 双向链表数组,A list of event's to notify for each priority level
        2. mEfiTimerList----双向链表,所有的等待timer触发的event列表。
        3. gEventSignalQueue ---双向链表A list of events to signal based on EventGroup type
        4. 对于EVT_NOTIFY_WAIT类型的event,我们使用BS->   CoreCheckEvent->CoreNotifyEvent-->CoreRestoreTpl-->CoreDispatchEventNotifies来激活,它同样是往  gEventQueue插入IEvent的节点。

  Cstyle的UEFI导读之Event && Timer (下篇:原理篇),就先分享到这里,具体怎么使用Event可以参考前面的文章“Cstyle的UEFI导读之Event && Timer (上篇:实战篇)”,要获取更多的相关信息,可以参考UEFI相关文档。
参考资料:
        1.event.c,tpl.c,timer.c,cpudxe.c,DxeProtocolNotify.c,CpuAsm.asm,IvtAsm.asm
        2.IA32/x64编程指导手册      
        3.IA32/X64 calling conventions    




转载请注明出处
[email protected]  //  http://blog.csdn.net/CStyle_0x007

 

你可能感兴趣的:(Cstyle的UEFI导读)