再谈UEFI下的Protocol概念

很久写过一个关于protocol的入门文章,但是那个时候说实话写的的确不是很用心。今天在一个论坛看到有朋友又问道protocol如何学习的问题,考虑到protocol是整个UEFI系统的核心所在,我想还是再次重新写一个UEFI protocol的文章吧,希望能够给初学protocol的朋友们一个指路明灯。


Protocol : 为什么要有Protocol


大家都知道,UEFI的英文翻译过来应该叫“可扩展固件接口”。这个名词中最重要的事实上是“可扩展”这三个字。换言之,相比传统的系统固件而言,UEFI固件具备了完善的可扩展支持。这个概念对软件行业不是一个新概念,但是对bios这样一个陈旧腐朽的东西而言,的确是一个创新的思想。


所谓可扩展的含义就是可以在系统完成后(编译为binary)之后,再次为系统增加新的功能,而不用重新rebuild整个系统。在这个大的需求的前提下,还有一些其他的重要的含义,比如必须支持不同的组件的独立开发。比如A厂商今天针对某硬件开发了一个驱动,他们发布了一个二进制包APackage;而B厂商则针对另一个硬件开发了一个驱动,他们发布了一个二进制包BPackage;现在APackage和BPackage都需要集成到bios内。且必须以二进制的形式集成。因为A和B厂商的知识产权需要得到尊重。或者像OS下那样,可以通过某种方法在系统已经运行起来后加载到内存。


更好玩的是,A厂商并不希望自己需要重新从车轮开始发明。比如他们针对的硬件是一个PCI的适配器,他们希望希望系统里已经有了诸如ReadPci()或者WritePci()这样调用来简化他们编程上的工作。换言之,他们希望目标系统能够支持不同的二进制组件之间的运行时通讯。


支持二进制组件的运行时通讯是一个十分复杂的技术。这里所谓的通讯,包括但不限于如下的含义:


1。 互操作。A组件自由可以调用B组件实现的函数。反过来也一样。

2。 数据传递。双方可以通过某种方法(shared memory, pipes and etc)互相交换数据。

3。 可探测。某组件必须具备探测另一个组件是否存在的能力。

4。 组件的开发必须是独立的。开发A组件不需要B组件的源代码,反之亦然。


这可以说是现代操作系统的一个核心概念。熟悉Windows的朋友可能十分清楚Windows下的COM组件,所有的COM组件都可以互相通讯的。且应用程序可以自由的使用任何一个组件提供的服务。


只有具备了这样的能力的系统才可以被叫做“可扩展”的。而UEFI为了达到可扩展的能力,必须提供一种机制来提供支持,于是UEFI领域的专家们发明了Protocol。


Protocol : 从约定到接口


Protocol从本质上说是一种调用者与被调用者之间的“约定”。而这种“约定”在软件开发领域有另一个更形象化的名字叫接口(Interface)。为了做到二进制间的互操作,那么参与操作的双方(调用者与被调用者)都必须做出一定的让步,这个让步就是双方必须遵循实现商量好的调用方法(接口)。而这种事先的约定的接口就是protocol的定义。而这种定义尤以.h文件的形式加以实现。


谈到这里,我插一句,由于目前UEFI还是几乎是用C语言开发,基本上不存在跨语言的问题,所以Protocol才可以表达成.h文件。而Windows下的COM的Interface,由于考虑到跨语言的问题,才专门有了一个新的通用语言叫接口定义语言(IDL,Interface Definiton Language),使用时候专门有一个IDL的compiler将IDL翻译成各个其他语言专用的表达方法(对于C语言,就是生成对应的.h文件)。


我们现在来看一个.h文件,看看一个protocol到底是怎样定义出来的。用任意一个编辑器打开Edl/Foundation/Efi/Protocol/Include/BlockIo.h文件,这个是UEFI规范内Media Access一章中所定义的EFI_BLOCK_IO_PROTOCOL的具体定义。


将关注点首先放到39行。看下面的定义:


 

typedef

EFI_STATUS

(EFIAPI *EFI_BLOCK_RESET) (

  IN EFI_BLOCK_IO_PROTOCOL          * This,

  IN BOOLEAN                        ExtendedVerification

  );

 


这是一个指向函数的指针类型的定义,它定义了一个新的指针类型叫EFI_BLOCK_RESET,如此是希望今后的系统能直接使用EFI_BLOCK_RESET类型,这样做主要希望能够提高代码的可读性。这个指针类型指向了一个函数,这个函数有两个输入参数。


之后又利用同样的方法定义了几个其他的函数。下面我们关注一下代码的199行:


typedef struct _EFI_BLOCK_IO_PROTOCOL {

  UINT64              Revision;


  EFI_BLOCK_IO_MEDIA  *Media;


  EFI_BLOCK_RESET     Reset;

  EFI_BLOCK_READ      ReadBlocks;

  EFI_BLOCK_WRITE     WriteBlocks;

  EFI_BLOCK_FLUSH     FlushBlocks;


} EFI_BLOCK_IO_PROTOCOL;


这里又定义了一个大的结构类型EFI_BLOCK_IO_PROTOCOL,注意到这个结构几乎是由之前提及的指针函数所构成的。这样定义是显而易见的,既然是约定的定义,那么自然需要约定的函数的定义。

 


Protocol : 组成


上面事实上已经说了一下Protocol的最主要的组成部分,约定的定义部分。就是那个大的结构类型,除此之外,一个Protocol还有一些其他的组成部分。


首先是GUID,正如Windows下的COM Interfaces一样,每一个Protocol也有一个定义好的,唯一的标识符。这个标识符就用GUID来表达。这样一来每个Protocol都有了自己的名字,那么使用者可以方便的通过指明不同的GUID来得到不同的Protocol。

 

其次是一个指向GUID的全局指针变量,比如我们上文引用的那个EFI_BLOCK_IO_PROTOCOL,就有一个全局的指针叫gEfiBlockIoProtocolGuid的指针,他是EFI_GUID *结构的。这样做事实上完全是为了方便,他的变量名的命名规则是统一的,即g + Efi + Protocol 名称 + ProtocolGuid。这样一来,程序员就不需记住相应GUID具体的值,而只在需要GUID作为参数的时候,使用这个全局变量即可,这有点类似Windows下的UUID宏。

 



 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值