(转)UEFI小结-Handle的来龙去脉

2024-04-16 21:08

本文主要是介绍(转)UEFI小结-Handle的来龙去脉,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文说明:本人刚学习UEFI不久,写该文一是为了将学到的东西做一个规范化的总结,二是为了给初学UEFI的兄弟起到借鉴作用。同样地,错误的地方肯定很多,还望能得到各位弟兄指正。要理解本文,您至少应该是读过UEFI Spec,不然请先阅读UEFI Spec。

一、一些概念的理解

UEFI中会有很多抽象概念,像serviceprotocolhandle等等,如果将这些抽象的概念放到实际的代码中理解的话,会有更清晰地认识,有了清晰的认识之后再把它们作为抽象来理解,就遂心应手的多了。

首先说protocol,其实它就是一个由struct定义的结构体,这个结构体通常是由数据和函数指针组成,或其一。每个结构体的定义都有一个GUID与之对应。自然并不是所有的结构体都称之为protocolprotocol正如其名,它是一种规范,或称协议。比如要建立一个基于UEFI Driver ModelDriver,就必须要绑定一个EFI_DRIVER_BINGING_PROTOCOL的实例,并且要自定义且实现SupportStartStop函数以及填充实例中其他的数据成员。它就相当于已经规范了种种需求和步骤。

再说service,它就是UEFI定义的API函数,所有的service都被集中到EFI_SYSTEM_TABLE下面,都可以通过gST来调用(gST指向一个EFI_SYSTEM_TABLE的全局实例)。

接着本文重点说明handle

      
二、EFI_HANDLE的定义
           EFI_HANDLE
定义是这样的:typedef void * EFI_HANDLEvoid * C语言来理解为不确定类型。它真正的类型是这样定义的(EDK\Foundation\Core\Dxe\Hand\Hand.h):

typedef struct {

UINTN        Signature;

EFI_LIST_ENTRY     AllHandles;

EFI_LIST_ENTRY      Protocols;

UINTN        LocateRequest;

UINT64      Key;

} IHANDLE;

比如定义一个变量EFI_HANDLE hExample,当你将它作为参数传递给service的时候,在service内部是这样使用它的:IHANDLE * Handle=(IHANDLE*)hExample。也就是说IHANDLE*才是handle的本来面目。为什么要弄的这么复杂呢?一是为了抽象以隐藏细节,二可能是为了安全

        
三、关于EFI_LIST_ENTRY

要明白IHANDLE这个结构体,就要明白EFI_LIST_ENTRY是如何被使用的。EFI_LIST_ENTRY定义如下(EDK\Foundation\Library\Dxe\Include\LinkedList.h):

typedef struct _EFI_LIST_ENTRY {

struct    _EFI_LIST_ENTRY    *ForwardLink;

struct    _EFI_LIST_ENTRY    *BackLink;

} EFI_LIST_ENTRY;

大家立刻就会反应到,它用于实现双向链表。但是与一般的链表实现方式不一样,它纯粹是EFI_LIST_ENTRY这个成员的链接,而不用在乎这个成员所在的结构体。一般的链表要求结点之间的类型一致,而这种链表只要求结构体存在EFI_LIST_ENTRY这个成员就够了。比如说IHANDLE *handle1,*handle2;初始化后, handle1->AllHandles->ForwardLink=handle2->AllHandles; handle2->AllHandles->BackLink=handle1->AllHandles。这样handle1handle2AllHandles就链接到了一起。但是这样就只能进行AllHandles的遍历了,怎么样遍历IHANLE实例呢?。这时候就要用到_CR宏,_CR宏的定义如下:

#define _CR(Record, TYPE, Field)  ((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field)))

这个宏可以通过结构体实例的成员访问到实例本身,它的原理可以参见

http://www.biosren.com/thread-1407-1-1.html或者http://blog.csdn.net/hgf1011/archive/2009/10/06/4635888.aspx

handle1遍历到handle2的方法是这样的:IHANDLE * handle=(IHANDLE*)_ CR(handle1 -> ForwardLink , IHANDLE , AllHandles )

关于EFI_LIST_ENTRY就说的这里了。总结一点就是只要看到EFI_LIST_ENTRY,就应该联想到它的链表。像IHANDLE结构体中有两个EFI_LIST_ENTRY成员,就应该联想到每个IHANDLE实例处在两条链表中。

      
四、各种链表的引出
1)由IHANDLEAllHandles引出的链表

IHANDLE相关的链表有很多,后面一一牵扯出来。IHANDLE中的AllHandles成员用来链接IHANDLE实例的。这个链表的头部是一个空结点,定义为:EFI_LIST_ENTRY   gHandleList。一开始gHandleList->ForwardLink=gHandleList; gHandleList->BackLink=gHandleList。每次IHANDLE都从gHandleList->BackLink插入进来。这时候大家就意识到了这个链表是一个环形双向链表。每当Driver建立一个新的EFI_HANDLE的时候就会插入到这条链表中来。这条链表被称之为handle database

     
2)由IHANDLEProtocols引出的链表

再来关注IHANDLE中的Protocols这个成员,它又是指向何方?它指向以PROTOCOL_INTERFACE这个结构体实例。PROTOCOL_INTERFACE定义如下:

typedef struct {

UINTN     Signature;
         EFI_HANDLE    Handle;     // Back pointer

         EFI_LIST_ENTRY    Link;     // Link on IHANDLE.Protocols

         EFI_LIST_ENTRY     ByProtocol; // Link on PROTOCOL_ENTRY.Protocols

         PROTOCOL_ENTRY    *Protocol;   // The protocol ID

         VOID    *   Interface; // The interface value

         EFI_LIST_ENTRY    OpenList;    // OPEN_PROTOCOL_DATA list.

         UINTN    OpenListCount;
         EFI_HANDLE    ControllerHandle;

} PROTOCOL_INTERFACE;

Driver会为handle添加多个protocol实例,这些实例也是链表的形式存在。PROTOCOL_INTERFACElink用于连接以IHANDLE为空头结点以PPOTOCOL_INTERFACE为后续结点的链表。这个结构体又牵扯出更多的EFI_LIST_ENTRY。成员中Handle指向头空结点的这个handleProtocol指向PROTOCOL_ENTRY这个结构体实例,这个实例存在于另一个链表中,称之为Protocol Database。后面再说这个Protocol Database。先说OpenList引出的链表。

     
3)由PROTOCOL_INTERFACEOpenList引出的链表

注释中已经说明OpenList引出OPEN_PROTOCOL_DATA listOPEN_PROTOCOL_DATA定义如下:

typedef struct {
            UINTN     Signature;

            EFI_LIST_ENTRY    Link;

            EFI_HANDLE     AgentHandle;

             EFI_HANDLE    ControllerHandle;

            UINT32      Attributes;

            UINT32        OpenCount;

} OPEN_PROTOCOL_DATA;

看到这个结构体就应该想到这个链表的模型了,不多说。看到只有一个EFI_LIST_ENTRY,松了一口气,这条线路上的链表总算是到头了。

        
4)链表Protocol Database
       PROTOCOL_ENTRY
的定义如下:

typedef struct {

UINTN    Signature;

EFI_LIST_ENTRY    AllEntries;   // All entries

EFI_GUID    ProtocolID;    // ID of the protocol

EFI_LIST_ENTRY    Protocols;    // All protocol interfaces

EFI_LIST_ENTRY     Notify;      // Registerd notification handlers

} PROTOCOL_ENTRY;

这个链表也有个头空结点,定义为:EFI_LIST_ENTRY  mProtocolDatabase。这个链表通过AllEntries这个成员来链接。这里又有几个EFI_LIST_ENTRE,这意味着又有好几个链表。这样大家的脑子里可能就乱了。为了对这些链表有清晰的认识,下面是用visio画的简图,省略部分结构体成员,为了不出现飞线,结构体成员位置也挪动了一下。(此图画起来好不容易,我也要署名,呵呵)。

 

5)链表综述

 

恕我唠叨:

图中1表示以gHandleList为头空结点,以EFI_HANDLE实例的AllHandle成员为后续成员结点的环形双向链表;

图中2表示以EFI_HANDLE实例中Protocols成员为头空结点,以PROTOCOL_INTERFACE实例的Link成员为后续成员结点的环形双向链表;

图中3表示以PROTOCOL_INTERFACE实例中的OpenList成员为头空结点,以OPEN_PROTOCOL_DATA实例的Link成员为后续成员结点的环形双向链表(篇幅原因省略一部分)。

图中4表示以mProtocolDatabase为头空结点,以PROTOCOL_ENTRY实例的AllEntries成员为后续成员结点的环形双向链表。

后文直接将它们分别称之为链表1,链表2,链表3,链表4

上面叙述过的链表这里就全部标识出来了,如果把所有的链表都画出来的话,上图就乱了,所有剩下没有标志出来的我就直接叙述了。

链表5:关于PROTOCOL_INTERFACE中的ByProtocolUEFI spec中已经说一个Protocol对应一个GUID,一个Protocol因不同情况实例化多个实例。所有一个GUID对应着多个Protocol的实例。上图中GUIDProtocol Database来管理,而Protocol实例由PROTOCOL_INTERFACE链表来管理。所以ByProtocol成员所在的链表就要以一个链表4中的PROTOCOL_ENTRY中的Protocols成员为头空结点,以PROTOCOL_INTERFACE中的ByProtocol作为后续结点的双向环链表。比如说图中链表1的第一个handle加载有ABC_PROTOCOL实例,假如第二个handle也加载有ABC_PROTOCOL实例,那么这两个对应的PROTOCOL_INTERFACE实例就会连接到ABC_PROTOCOL_GUID对应的PROTOCOL_ENTRY实例上面。可以想象的到吧?呵呵。

链表6:关于PROTOCOL_ENTRY中的Notify。这就要涉及到新的结构体PROTOCOL_NOTIFY。我觉得有必要在Notity这里打住。

上图经过抽象后就成了我们经常看到的图,如下:

 

对比之前的链表图,是否对这个图有更清晰的认识呢?

         
五、以InstallProtocolInterface为例来看handle的内部运作

有了上面的准备后,我就以InstallProtocolInterface这个service来讲述handle的内部运作了。

经过一番顺藤摸瓜后,就会发现InstallProtocolInterface最终的形式是EDK\Foundation\Core\Dxe\Hand\Handle.c

EFI_STATUS

CoreInstallProtocolInterfaceNotify (

IN OUT EFI_HANDLE   *UserHandle,

IN EFI_GUID    *Protocol,

IN EFI_INTERFACE_TYPE    InterfaceType,

IN VOID     *Interface,

IN BOOLEAN   Notify

)

对比与UEFI specInstallProtocolInterface的定义,CoreInstallProtocolInterfaceNotify中的NotifyTRUE。这个service的作用就是:当UserHandle为空时,就向handle database中插入新的handle,并且将参数中的Interface所指定的protocol加载到这个handle上面;当UserHandle不为空,就在handle database中找到这个handle,在将这个protocol加载上去。如果通过上面的链表图,你已经想象到了它是如何运作的,那么下文就已经多余了。

代码就不贴了,请直接对照EDK中的代码,从handle.c找到CoreInstallProtocolInterfaceNotify这个函数,想必这个文件大家都有。

同学们,老师要开始讲课了,翻到394行,我念一句,你们跟一句。(呵呵,开玩笑的,哪当得起哦)

462行用CoreHandleProtocol(...)检索链表1,查看UserHandle是否已存在于handle database中。

476行用CoreFindProtocolEntry(...)检索链表4,查看GUID是否已经存在于链表中,若不存在在创建一个以参数ProtocolGUIDPROTOCOL_ENTRY实例PortEntry插入链表4中。

493行露出EFI_HANDLE的本质了,它是(IHANDLE*)

494行到518行为创建一个handle及初始化它的过程,看仔细了,对理解handle很有用。初始化后就插入到链表1中。

533行到554行,对新创建的PROTOCOL_INTERFACE实例Prot进行初始化,对照链表结构库看仔细了,尤其是各种指针的去向(参数Interface挂接到了Prot下面)。初始化后将Port插入到链表2中。

这样这个函数就介绍的差不多了,这也只是为了做一个引子,像其他有关handle的函数想必也都在这个文件中,头文件的定义很多都在hand.h中,只要有耐心,应该都能看的懂。

这篇关于(转)UEFI小结-Handle的来龙去脉的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/909897

相关文章

分布式系统的个人理解小结

分布式系统:分的微小服务,以小而独立的业务为单位,形成子系统。 然后分布式系统中需要有统一的调用,形成大的聚合服务。 同时,微服务群,需要有交流(通讯,注册中心,同步,异步),有管理(监控,调度)。 对外服务,需要有控制的对外开发,安全网关。

Linux环境配置中问题小结

在Linux环境配置中,遇到问题首先猜测: 1、是否是权限问题; 2、软连接是否配置;

long long,_int64使用小结

前言:   在16位环境下,int/unsigned int 占16位,long/unsigned long占32位   在32位环境下,int占32位,unsigned int占16位,long/unsigned long占32位 何时需要使用:   long 和 int 范围是[-2^31,2^31),即-2147483648~2147483647,而unsigned范围是[0,2^32),

密码学读书笔记小结

密码学是保证消息的私密性和完整性以及消息认证的基础。加密算法的选择和密钥的管理是安全机制的效率、性能和可用性的关键。 公钥加密算法: 分发密钥比较容易,但是对大数据量的加密性能较差密钥加密算法: 更适合大批的加密任务混合型加密协议: 例如TLS,先用公钥加密建立一个安全通道,然后使用通道交换密钥,并将此密钥用于后续数据交换。 对分布式系统攻击的分类: 窃听: 未经授权获得消息副本伪装: 在未

win10 gpt分区+uefi引导 卸载双系统ubuntu

1、首先暴力卸载ubuntu 在win10里面磁盘管理中找到对应的linux磁盘分区 删除卷OK 2、重启 出现下面(根据机型不同界面可能不一样 ) 3、exit 退出grub引导 进入uefi引导  选择win10引导项 (当然你要是一直按着进入bios boot的那个按键的话 也不用看第二步了 直接选择windows启动项进去 dell的话是F12) 4、进入

Android 源码中jni项目 加载so目录小结

Android 源码中jni项目 加载so目录小结 文章目录 Android 源码中jni项目 加载so目录小结一、前言二、so目录验证测试1、jni so文件错误报错(1)报错1 - 未找到so文件:(2)报错2 - so文件中未找到native方法: 2、验证的几种情况(1)apk下面的 lib/arm64/ 放置正确的so文件(2)apk下面的 lib/arm64/ 放置错误的so文

maven打包成可执行的jar,以及读取配置文件问题小结

文章来源 https://blog.csdn.net/chasonsp/article/details/88852353 折腾的几天,使用maven打包后发现了问题,首先是打包的配置文件读取问题,使用getResource().getPah()会发现在访问jar包的文件时,路径里会有感叹号(杠杠滴~~)是这样的 …jar!.. 经过不断的查找资料及反复验证后,终于找到了可行的方法:

【硬刚大数据】Flink在实时在实时计算平台和实时数仓中的企业级应用小结

欢迎关注博客主页:https://blog.csdn.net/u013411339 欢迎点赞、收藏、留言 ,欢迎留言交流!本文由【王知无】原创,首发于 CSDN博客!本文首发CSDN论坛,未经过官方和本人允许,严禁转载! 本文是对《【硬刚大数据之学习路线篇】从零到大数据专家的学习指南(全面升级版)》的面试部分补充。 大数据领域自 2010 年开始,以 Hadoop、Hive 为代

Kafka常见问题学习路径源码阅读小结 | 写在Kafka3.0发布之际

严格来说,这篇文章也不是今天写的。是之前断断续续写在了几篇文章中。 2021年9月21日,随着Kafka3.0的发布,Kafka在「分布式流处理平台」这个目标上的努力进一步得到加强!Kafka不满足于「消息引擎」的定位,正式基于这样的定位,Kafka 社区于 0.10.0.0 版本正式推出了流处理组件 Kafka Streams,也正是从这个版本开始,Kafka 正式"变身"为分布式的流处理平台

UEFI——Shell下读取SMBIOS信息

一、SMBIOS简介 SMBIOS的全称为System Management BIOS,它不是一个BIOS,只是与BIOS相关。它是一个规范,定义了BIOS传递给操作系统的系统管理信息。它也表示了一系列的数据结构,包含了各类信息,由BIOS启动过程中创建并放在特定的内存,之后操作系统可以拿来用。 整个 SMBIOS_STRUCTURE 结构体定义了 SMBIOS 表中的一个基本单元。每个 SM