UEFI中 是一个老大难问题了,网上也有不少的前辈对这些概念进行了梳理总结,但是目前我的知识浅薄,还是没有能够领略其中的精髓,特别是将代码和实际应用联系起来十分困难。只不过最近刚刚仔细看了看网上相关的文章并且联系代码学习了一下,所以记录一下当前学习的体会,我是一个百分之百新入门菜鸟,所以我会尽量详细的记录,一是希望能够方便自己复习;二是自己的学习轨迹可能更贴近新人自己学习的方向,希望能够给后来的学习者一点帮助作用。
首先,EDK II UEFI Driver Writer’s Guide 中3.4 Handle database中的部分表述如下:
The handle database is composed of objects called handles and protocols. Handles are a collection of one or more protocols and protocols are data structures named by a GUID. The data structure for a protocol may contain data fields, services, both or none at all.
翻译一下就是handle database 是由handle和protocol组成的,handle就是一个或者更多protocol的集合。有道理又不能理解的样子。。。
其次,根据《UEFI编程与原理》这本书描述
EFI_HANDLE是指向某种对象的指针,UEFI用它来表示某个对象。UEFI扫描总线后,会为每个设备建立一个Controller对象,用于控制设备,所有该设备的驱动以Protocol的形式安装到这个Controller中,这个Controller对象就是一个EFI_HANDLE对象。当我们将一个.efi文件加载到内存中时,UEFI也会为改文件建立一个Image对象,这个Image对象也是一个EFI_HANDLE对象。在UEFI内部,EFI_HANDLE被理解为IHANDLE.
上面一段话比较抽象,到现在我也没有完全透彻的理解,就先捡着我明白的说(吐槽一下 国内的很多书都这样 第一遍读完全不知道什么意思 你只有读几遍甚至几十遍然后自己实践很久之后才能明白他什么意思):
- 是一个指针,指向某种对象
是一个指针,并且这个指针应用十分广泛。广泛到什么程度?
a) UEFI会为每一个连接的设备建立一个指针指向的对象(Controller)
b) 当一个.efi文件被加载的时候,UEFI也会为这个文件建立一个指向的对象(ImageHandle)
看到了吗,上面两句话就说明了的重要程度,Code里面广泛使用的 和都是这个指针指向的对象。
- 指向的对象实际上是一个名为的结构体
- 设备驱动会以Protocol形式安装到Handle上面
以上三点就是《UEFI编程与原理》这段话中我总结出的关键点,下面一点点来看
首先看一下Code里面的定义
是一个 类型的数据,在C语言中这表示无明确类型的指针,就是说可以进行任意转换。符合我们前述的类型为指针、应用广泛。
我们知道一个指针最终一定是要指向一段内存进行操作的,而指向的这段内存就是我们之前提到的对象,这个对象到底是什么样子呢?看下面的一段代码
可以发现,最终转换成了的形式,即指向的对象为
Code 里面关于的定义
以上可见是一个非常标准且常见的结构体形式,我们所说的的实质也就是结构体。
这这时我们继续看EDK II UEFI Driver Writer’s Guide 中3.4 Handle database中的这部分表述:
The handle database is a list of UEFI handles and is the central repository for the objects maintained by UEFI-based firmware. Each UEFI handle identified by a unique handle number is maintained by the system firmware. A handle number provides a database “key” to an entry in the handle database.
翻译:是UEFI handle 的链表并且是是由基于 UEFI 的固件维护的对象的核心部分。每个由唯一handle number 编号标识的 UEFI handle 由系统固件维护。 handle number为handle database中的每个entry都提供了“key”。
我们可以将结构体中的 理解为每一个handle 的唯一编号,作为查找、区分的标志。通过这段话,就能很好的理解jiangwei0512 博文中的 Handle其实就是一个不会重复的整型数字 这句话的意思了。
观察可以发现结构体中的成员除了常见的之外,还有一个特殊的,这个成员是构成关系的重要联系,详细了解这个成员的结构对于理解有着很重要的帮助。
依旧按照习惯先看 定义
有一点C语言基础就能够一眼看出,这是一个简单的双向链表节点结构,链表的每一个节点连接的都是结构体,也就是连接只存在于结构体之间,而包含了结构体的结构就能够通过其成员结构体产生相互连接。换句话说能够形成一个链表,将按照某种方式串在一起,我们将形成的这个链表就称为链表(important!!!回收开头)
Handledatabase 链表
循着我们刚才的思路,我们发现一个以 的成员 为节点的链表已经构建出来了,问题是这个链表的头是什么样子呢?在Code里面我们能够找到头结点的定义:
注:
以上是分散在各个文件中的与头结点完整定义相关的语句,本质上在初始化的时候两个指针都是指向了自己。我们再来看插入结构体的实现情况:
通过上述代码以及模拟情况我们可以很明确的得到结论,链表是一个环形的链表,其中头结点是一个空节点,每次有新的结构体需要添加到链表中,就在头结点后面进行插入。就是我们需要强调掌握的第一个链表。
另外通过这个结构体的特点我们可以发现,出现这个结构体,必然表示存在一条对应的链表。我们观察可以发现在结构体中,存在两个类型的成员,现在我们已经明确其中一条链表形成的是,那么另外一条链表连接的是什么呢?这个问题就需要我们详细了解了之后回头在看了。
以及其相关的的已经详细的解释过了,接下来我们再来看一下关于的部分。
首先再次回顾EDK II UEFI Driver Writer’s Guide 中的表述如下:
The handle database is composed of objects called handles and protocols. Handles are a collection of one or more protocols and protocols are data structures named by a GUID. The data structure for a protocol may contain data fields, services, both or none at all.
是由GUID命名的一个数据结构。结构体中可能包含了data 或者service或者 两者都有 或者两者都没有。从这个定义中很难看出的实质,我们不妨继续从Code里面寻找答案
我们从上面知道protocol的唯一标识符就是GUID,那么在Code中,这个GUID是在什么位置如何表示出来的呢?看下面的定义
上述结构体里面的成员就是一个GUID,他就是protocol唯一的标识。由此可以确定我们所说的protocol一定是包含了这个结构体的。
看到这个结构体,一定会有似曾相识的感觉,熟悉的在这个结构体中出现了三次,根据之前我们的经验,那就说明,这个结构体的成员能够延展出三条双向链表。
ProtocolDatabase链表
首先看第一个,这个节点的无论是命名方式还是作用都和前面的是一样的,也就是结构体中的能够相互连接,形成一个环形的链表将以某种方式连接起来,我们将连接生成的链表称为.同样的,的头结点也是一个空节点
可以发现,与的定义方式完全一致,如果在代码中你进一步的深入挖掘,你就会发现,就连向插入新的的方式也是和向插入新节点是一致的,这个部分可以参照函数去分析,这个函数中最后
就是插入新节点的对应操作。
就是我们需要强调掌握的第二个链表。
接下来我们观察第二条可以生成的链表 ,那就是以成员为头函数的另一个双向链表。在Code的注释中,能够清晰的看到这个成员连接了All protocol interfaces,那么我们接下来就看一看protocol interface 到底是一个什么样的结构?相关的各个链表是如何连接的?
protocol interface 的结构如下
在上面的结构体中我们看到熟悉的结构有:
-
三个成员表示存在三条双向链表
-
指针和 指针各一个
我们先说简单的部分和 成员。以成员为例,这是一个简单的单向指针,这个成员指针指向的就是自己所属的结构体,也就是根据这个指针,我们能够轻松的找出当前所属的handle(),这就实现了根据已知的找到其所属的handle.
同理,指向的是自己所属的结构体,这个指针说明了protocol interface 和protocol entry结构体之间是有着对应关系的,具体是什么样的对应关系后面我们详细讨论。其次这个指针也是一个单向的指针,表明我们能够通过该指针找出当前有联系的,这就实现了根据已知的找到其所属的.
在进行下面的讨论之前,我们还需要重点关注一下这个成员,这个成员连接的是实际上有效地protocol实例,请注意,这里说的实例就是指我们能够自己定义、具有一定的功能的、别人能够调取的protocol。(仔细阅读LocateProtocol这个函数的定义,就会发现其实他也是Locate到该GUID对应的第一个,也可以证明实际上实现功能的是这个)
到这里我们可以整理一下,当我们说安装protocol的时候,实际上是 生成一个新的然后将我们定义好的具有实际功能的protocol挂在下面,这样才算是完成了安装一个protocol的完整过程。
HANDLE-PROTOCOL-INTERFACE链表
接下来我们看第二个成员,Code中的注释告诉我们这个成员是与相连接形成链表的。那这样我们就能够得到一个以为头结点,为节点的双向链表。这也是在上一个部分中我们没有深入讲述的的另一条双向链表。(可以回去看一下)
关于这个头结点的状况,明显在结构体生成的时候就会对进行初始化
可见头结点的初始情况与之前所说的头结点 完全相同。
头结点讨论完,我们自然也想知道与之前讨论过的HandleDatabse链表 ProtocolDatabase链表对比,生成的这个链表是否仍然是环形的链表?新节点的插入与之前是否有什么不同?看下面一段代码,就能够找到答案。
(上面一段代码对于我们理解整个handle & protocol 都很有帮助,后面也会反复的提到这个函数,当前只选取与这部分相关的代码进行说明)
上面的代码中,当我们知道了向s为头结点的链表中插入新的节点,调用的是一个与之前不同的函数(之前使用的是),说明插入的方式还是有一定的区别的,那么看一下这个插入函数
我们模拟一下插入新节点的情况:
通过上述代码以及模拟情况我们可以得到结论,链表是一个==环形的==链表,其中头结点是,每次有新的结构体需要添加到链表中,就在最后一个节点后面进行插入。
就是我们需要强调掌握的第三个链表。
前面我们已经说过能够帮助我们单向的从找到其相关的 ,那么这个环形链表的生成就能够帮助我们从出发,找到所有与之相联系的。这样 和之间的双向查找就都能够实现了。
PROTOCOL-ENTRY-INTERFACE链表
接下来我们看第四个成员,Code中的注释清晰的说明了这个成员相关链表的情况,这个成员函数是和结构体的成员相连接的。这样我们就能够得到一个以为头结点为节点的双向链表。与之前讨论过HandleDatabse链表 ProtocolDatabase链表的链表对比,我们自然想知道,生成的这个链表是否仍然是环形的链表?新节点的插入与之前有什么不同?看下面一段代码,就能够找到答案
上面的代码(上面的代码是不是很熟悉 如果你在EDKII中详细的观察就能够发现 基本上所有的相关的代码都能够在这部分函数中找到使用的情况)已经可以很清楚的展示出来,这个PROTOCOL-ENTRY-INTERFACE链表依然是一个环形的链表,每次新节点的插入情况和之前说过的 HandleDatabse链表 ProtocolDatabase链表是一致的。所以就不在赘述。
就是我们需要强调掌握的第四个链表。
同理,和之间的双向查找也就能够根据链表来实现了。
注意 我们在这里说明PROTOCOL-ENTRY-INTERFACE是一个链表,表明了一个PROTOCOL_ENTRY对应的可能是多个ROTOCOL_INTERFACE。前面我们说过 protocol的唯一标识符就是GUID,而GUID只存在于PROTOCOL_ENTRY中,所以我们可以说:一个Protocol实际上是由一个protocol entry 和 多个protocol interface构成的我个人认为这是一个重要的概念,这能够很好的帮助我们理解整个 结构。
OPEN_PROTOCOL_DATA 链表
接下来我们需要研究的就是这个成员生成的链表了,(这个也是我目前不熟悉的链表,可能以后还需要专门研究一下这个链表)前面我们已经研究过四个链表的详细情况了,这个链表与前面几个链表的情况一模一样的。根据注释连接的是结构体
可以看到,这个结构体中唯一的一个能够形成双向链表的成员就是 ,这个双向的链表就轻而易举的构成了。头结点,节点,采用的是的插入方法。
就是我们需要强调掌握的第五个链表。
下面来看 结构体中能够延伸出的最后一个双向链表——成员,这个成员形成的链表现在我还没有研究的十分透彻,就简单的说一下。这个成员连接的是这个结构体,结构体的代码如下
与的成员会形成一个双向的链表,这个链表最重要的作用就是中的对应的protocol安装时,该链表上所有的Event都会触发。(《UEFI编程与原理》)
根据之前的描述应该也会形成一个双向的链表,但是这个链表的形成、连接、作用目前我还不知道,所以日后再说…
这个链表介绍的很简单,主要还是因为我还没有理解十分透彻 挖坑后填吧…)