Protocol的设计类似于C++中的类,只不过在UEFI中是用struct代替class,用函数指针模拟成员函数,并且函数指针的第一个参数必须是指向Protocol的指针——用来模拟this指针。这里只简单描述一下Protocol的概念,其他具体的不多说。
而Handle和Protocol都是软件上的概念,个人认为把软件的概念和具体的硬件相结合更容易理解Handle与Protocol的关系。
那Handle怎么与具体的硬件相结合呢?这就需要了解EFI_HANDLE。
EFI_HANDLE在EDKII代码中的定义是:typedef VOID *EFI_HANDLE
UEFI扫描总线后,会为每个设备建立一个Controller,这个Controller就是一个EFI_HANDLE对象。当我们将一个.efi文件加载到内存中时,UEFI也会为该文件建立一个Image对象,这个Image对象也是一个EFI_HANDLE对象。在UEFI内部,EFI_HANDLE被理解为IHANDLE(以下把IHANDLE定义的简称为Handle,在EDKII源码中Handle的类型也是IHANDLE)。
EDKII中IHANDLE的定义为:
/// IHANDLE - contains a list of protocol handles
///
typedef struct {
UINTN Signature; //表明Handle的类别
/// All handles list of IHANDLE
LIST_ENTRY AllHandles; //所有IHANDLE组成的链表
/// List of PROTOCOL_INTERFACE's for this handle
LIST_ENTRY Protocols; //此Handel的列表,(是一个双向链表),链表的每一个成员指向PROTOCOL_INTERFACE结构体
UINTN LocateRequest;
/// The Handle Database Key value when this handle was last created or modified
UINT64 Key;
} IHANDLE;
由第二个成员LIST_ENTRY AllHandles;可知,所有的Handle都有在一个链表中,由 LIST_ENTRY Protocols;可知,每一个Handle中的Protocol都在一个链表中。链表类型LIST_ENTRY的定义为:
struct _LIST_ENTRY {
LIST_ENTRY *ForwardLink;
LIST_ENTRY *BackLink;
};
此链表是一个双向链表,链表中的每个成员是PROTOCOL_INTERFACE结构:
typedef struct {
UINTN Signature;
/// Link on IHANDLE.Protocols
LIST_ENTRY Link;
/// Back pointer
IHANDLE *Handle;
/// Link on PROTOCOL_ENTRY.Protocols
LIST_ENTRY ByProtocol;
/// The protocol ID
PROTOCOL_ENTRY *Protocol;
/// The interface value
VOID *Interface;
/// OPEN_PROTOCOL_DATA list
LIST_ENTRY OpenList;
UINTN OpenListCount;
} PROTOCOL_INTERFACE;
通过PROTOCOL_INTERFACE中的成员PROTOCOL_ENTRY *Protocol; 可以指向这个Protocol的实例,PROTOCOL_ENTRY的定义为:
/// PROTOCOL_ENTRY - each different protocol has 1 entry in the protocol
/// database. Each handler that supports this protocol is listed, along
/// with a list of registered notifies.
///
typedef struct {
UINTN Signature;
/// Link Entry inserted to mProtocolDatabase
LIST_ENTRY AllEntries;
/// ID of the protocol
EFI_GUID ProtocolID;
/// All protocol interfaces
LIST_ENTRY Protocols;
/// Registerd notification handlers
LIST_ENTRY Notify;
} PROTOCOL_ENTRY;
而通过PROTOCOL_INTERFACE中的成员EFI_GUID ProtocolID;就可以通过接口调用Protocol的功能(OpenProtocol、HandleProtocol和LocateProtocol都需要GUID来找Protocol)。
Handlel的链表和每个Handle中Protocol的链表关系可以看下图:
下图展示了一部分的Handle数据库,除了handles和protocols以外,每个potocol还有一个对象的列表,这个列表用来跟踪哪些agents正在使用哪些protocols。
纵向来看,Handle都连在一个链表中,横向来看,每个Handle下都挂了多个Protocol(用GUID来代表Protocol,有的Handle下可能只有一个Protocol)。这些Handle和Protocol组成了Handle Database,在UEFI初始化阶段,system firmware(系统固件),UEFI driver(UEFI驱动)和UEFI application(UEFI应用)创建Handle,并将一个或protocol附加到Handle中,Handle Database中的信息时全局的,可以被任何可执行的UEFI image访问。
Handle也有不同的类型,如Agent Handle和Controller Handle等。Handle Database中的不同类型Handle如下图(所有的handle都存在于同一个handle数据库中,每个handle类型的区别由handle下面的protocol的类型决定):
本文参考:
《UEFI原理与编程》
《Beyond_BIOS:Developing with the Unified Extensible Firmware Interface》2nd Edition