|
|
|
EFI Protocol VS C++
; Z, f! C! _8 ]) o6 h1.Introduction
@4 D# B: m8 w, d& e* t
% H. I7 b# T* m; A2 Q% K$ K Protocol是EFI引出的新概念,翻翻EDK就会发现与protocol相关的code散落在EFI的各个角落,大到host bus driver,小到Hello Word App都和protocol 脱不了关系,因此毫不夸张的说protocol应该是EFI的精髓所在,DXE阶段模块之间的通信都经由protocol完成(我觉得PEI阶段的ppi应该和protocol一个概念,只不过是限于PEI的特殊性而稍有差异而已).EFI Driver基本上就是使用一堆protocol的实现的一个可执行的文件.一个protocol就是一堆函数指针和数据成员的集合,官方称呼为protocol interface structure;这个protocol必须要定义一个GUID作为它的唯一标识,DXE driver使用Boot Service table中的类似InstallProtocolInterface等函数将porotocol安装到device handle上这就叫做produce protocol, InstallProtocolInterface成功的话该protocol就是published,后续其它driver使用该protocol就只要用Boot Service table中的LocateProtocol等相关函数从handle database中索引出该protocol就可以了.protocol为DXE阶段模块之间的通信提供了非常有效的手段.
4 v+ u$ ^ q @6 i5 [8 m( y' I% g: D/ r( E
2.Protocol Implement OO
0 U: n" P4 {, g* ]: r- s
) U. t/ {% e0 G: C0 K2 e EFI是一个使用C实现的OO的Framework,它的OO特性主要经由protocl实现.EFI可以说是一个优秀的7 N, ~/ \( C o9 K. A- J
Framework, 对它的扩展可以优雅的实现.使用过C++这类面向对象语言的朋友都会知道OO有三大基本特性:1.Encapsulation,2.Inheritance,3.Polymorphism. 那么既然EFI是使用protocol实现的OO,那么protocol是如何实现这三大特性的呢?
. @& \8 r; t; T8 m! j
) D X1 U* G. r0 g: v6 u t% ^1.Encapsulation的实现其实比较简单,它所表达的信息隐藏的概念,这个部分在protocol中可以方便的实现,protocol通常是函数指针和属性的集合,我们导出函数的接口,而将数据属性放在函数接口内部处理.如下述EDK中的code所示:
* T$ W8 O. F |4 Z# B% Etypedef struct _EFI_CPU_ARCH_PROTOCOL {
- O7 {3 w6 {+ V" _
% U* n" }, o* w5 ~: iEFI_CPU_FLUSH_DATA_CACHE- b8 v) l9 Y3 p+ V* w& X- d
FlushDataCache;
. b4 R1 p7 V) U/ H$ b
, Y) U% B4 K! C; u! T, `EFI_CPU_ENABLE_INTERRUPT
3 |: U$ p/ ], V, ?. B6 k2 fEnableInterrupt;: E# h% q* O# R. k) D
) K6 C5 C% m+ g8 |; o0 N. J
EFI_CPU_DISABLE_INTERRUPT
0 a3 @; z. x: A" ]$ Q( W4 ~DisableInterrupt;( `4 M- Z& d/ |- g3 Y' y& H( o; M
7 j6 E* ]7 v0 N& u' PEFI_CPU_GET_INTERRUPT_STATE+ N: j# D0 V+ G4 r1 x; y! E. }& {6 a
GetInterruptState;
0 G9 f, R" E2 Z* k* ~) | X, D+ }- A2 J* @3 g0 \8 _; C
EFI_CPU_INIT
% {0 E. D/ S( _Init;/ S& S, Q6 N9 M4 ?9 F& Q U, ]3 y$ }
. b4 ? y" m( e/ \' ]9 i$ U
EFI_CPU_REGISTER_INTERRUPT_HANDLER
& J- x( F! o# V9 Y! e0 P, L+ k+ E$ }2 S. c' P4 }: \: h. l. j& |
RegisterInterruptHandler;
' \6 {+ l$ p% k- z- f; q
4 {/ Y6 m1 R; v0 y! Q- V* KEFI_CPU_GET_TIMER_VALUE& C1 _& |$ Z# t
GetTimerValue;
% T* ]9 } F0 { A6 P3 Y& T
6 u! I! q) O% g# |' REFI_CPU_SET_MEMORY_ATTRIBUTES' x; x% V1 |' m4 y# N$ N
SetMemoryAttributes;
8 m0 m* l, ?7 ]+ \7 P5 \
: {- R" ~# s9 y* KUINT32
' ]$ b d u4 M. |4 x! c* H6 l- Y
4 g6 p" ]5 y. I/ z" C YNumberOfTimers;1 X& I4 D2 P h) Q6 V
0 D) H* C4 k8 F7 l6 `% s+ YUINT32
% S$ M4 \5 I+ x* r eDmaBufferAlignment;8 S" R3 j9 {+ D- {9 f" b
} EFI_CPU_ARCH_PROTOCOL;# Z9 N) |6 } { @% \4 T0 H
像NumberOfTimers, DmaBufferAlignment就是数据属性了.
4 m- @5 B7 ]' ^% k3 s4 [& h( L# u
. r3 p* G, W# M) w2.Inheritance子类继承了父类的所有的方法和属性,在C的级别上表示就是内" K0 ~2 b4 R5 l4 P! I
存的叠加.同时它还代表的是IS-A的概念.如下面的sample所示B_PROTOCOL' G3 |/ p! ~2 y/ c6 h8 V
继承自A_PROTOCOL.
$ h/ k: o- r: u: j. o5 J3 ?6 f) Rtypedef struct _ A_PROTOCOL {
4 Z, r+ z/ s, o- @7 ^% FUINT325 A: l" M+ H# D& _' i) A+ H; |* d8 m
3 L/ g2 u5 T _2 c1 s
A;. L% L) E+ V+ {
- D M4 e9 _+ L p2 X}A_PROTOCOL; p( S5 o6 N. \% @9 T
typedef struct _ B_PROTOCOL {7 ]6 S K2 g+ O, q/ e/ l
A_ PROTOCOL, [, I$ I9 F' l6 V5 V: o" H6 S; F
( H% @& [* G, M: h SAP;9 b+ X+ n: V' w8 F& m. d: }
UINT328 F2 X- `; E1 l' Y' d% A
B;
( I7 {- j0 h4 C/ F: `}B_PROTOCOL;
0 f1 c9 W0 G' {! a它们的类图如下图1所示,内存布局如下图2所示:
$ Z8 v0 a' w) q; J' C, h! g
6 Y, _# z2 e. E# K3 X0 L2 K& M& e: s2 a2 l9 E4 V
" h) ^& {* a' q8 j5 n. u' b9 G4 V: C2 {9 G2 {7 l" V! ~
3. Polymorphism是面向对象的精髓所在了,如果没有了多态,则只能称之为基于对象了.多态是指基于相同的接口实现的不同的class(EFI之中应该是指实现protocol的driver),具有不同的行为.如两个EFI Driver A&B 它们都实现了ComponentName protocol,A在它的GetDriverName interface回复的Driver Name是”A”而Driver B则回复的是”B”,所以同样的接口具有不同的行为.protocol相当于一个共同的接口,因此我们可以以统一的方式去管理和配置这些实现了该protocol的这些Driver.如shell app dirver.efi它就是通过枚举Handle database找到挂在这些device handle上的ComponentName protocol,然后call它们的GetDriverName interface获悉driver的名字信息.
1 F3 f$ C/ a( o! w) ]6 g) }9 g- u
3.Who Win(Protocol vs C++)?8 Z; O/ l. w0 [' ^ Q! z
5 j- N- d6 y6 l9 w: v' K6 }2 g$ `2 B
看上去貌似用protocol实现OO特性还是比较费事的,那么为什么不直接使用C++实现呢?我不知道真正6 k" b0 V7 |( ~- Y/ D8 [9 t& ?! H
原因L!呵呵,可是我猜有可能的几种原因如下:
% A: i+ F6 s Q( W( _
( ^4 V3 D6 o7 i4 ?& q1. C++无法做到二进制级别上的复用,C++是一个非常复杂的语言,它有非常多的特性,C++标准委员会制定出了编译器厂商所需要实现的一堆特性,比如构造函数,析构函数,继承,重载,多态等等一堆的规则,但是标准委员会并没有规定编译器厂商如何实现这些特性于是问题就来了,我们使用A厂商编译器build driver da和B厂商编译器build同一只driver da它们的内存布局就不相同,于是二进制级别上的互操作就不可能了,设备厂商都要把source code拿出来放在一个编译器上build 才OK,不同的编译器实现不同的一个地方就体现在实现多态时,vptr存放的位置上,有一些编译器会把该指针放在对象内存布局的头部,有些厂商则喜欢放在尾部,这个差异非常大;如果存在多重继承那么就可能出现多个vptr那么vptr摆放的顺序又会有不同等等不胜其扰的问题。而protocol使用标准的c实现,它就没有二进制文件不一致的问题。( X5 a; ~; r) f$ C! w6 \
9 ]7 d# {2 Z- s2.C++实现多态,虚继承以及name mangling等会带来空间以及编译时间上的开销。C++中的多态是通过在基类的函数声明中加上virtual实现的,这一个关键字里面大有文章,我们使用一个class VI演示该过程:
$ K9 I; M5 G' nclass VI4 q5 [5 `3 a% c, f+ f6 Y
{
# t1 F! }& P+ a( ^
# A8 O! ^1 m! |* xint a;9 Q# f+ P& L( n& r7 t) e
# R& N3 b" j4 w0 z* I* P$ X) Cint b;8 R; H0 l, D" {$ p2 e6 d; J* t
% q( l' A; X6 y3 q% x- @void test0(void) virtual;
6 H. h8 R: U. W5 T7 @};, E* u* Y5 ^. a2 v7 o
没有virtual这个关键字的时候它的内存布局如下图3所示,加上之后就如图4所示:
+ z5 z0 m9 f; u7 m( }2 `1 P( V% R/ A3 k" g' q$ I9 s/ Q2 z
: Q5 G5 s$ y# S
5 N6 t4 |0 ]: ]% M
0 t Q9 X2 A/ j' J; o: E0 _4 e0 o' F8 ]( b
编译器通过增加一个vptr虚函数指针,和vtbl虚函数表实现多态的功能,一旦子类改写了test0那么子类就会修改掉vtbl中的test0的地址。如此一来开销就来了J,一旦涉及到虚继承多重继承,那个开销就更大了。相比较来看protocol的实现就完全没有这个问题。
. V! _3 V- D* b# V( N* B
% S! v) S: S8 m3.C++使用字符命名对象会有命名冲突的可能而protocol使用64位GUID是不可能出现命名冲突的。当project大到一定程度就会感受到变量命名冲突的讨厌了,你定义的变量可能在别的你不知道的模块里已经被用过了,然后编译器报上一大堆另人恐慌的错误出来。当然C++也有解决命名冲突的方法,那就是namespace可是这样又会造成性能上的影响。( O- T+ j: n6 C# S7 k4 N g
: v' {$ \- I/ p* R
所以综上所述,protocol应该较C++稍有优势一点哦J,它完整的实现了OO的所有特性,而无性能上的损耗只是感官上有些差异,如intel的spec所说的那样Look/fell very different.; W' Z7 ^: |2 l) s! j7 ~
# I( j M% J- z Z) ]6 Y7 A+ k
That’s all!
# h+ K# T' c3 H4 m* m: D5 L; y! t
/ G3 Y% ?- `; P; H% `Enjoy it!0 s# |4 c& P1 [% y
% I0 X: T- {6 }+ G
Peter# z0 y' Q5 b- T) N. c
5 e \; m" ?! R" H5 l- q) @
7 @; e) m4 r% }+ [
3 B6 U$ x; \( B8 |[ 本帖最后由 peterhu 于 2009-7-13 17:05 编辑 ] |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?加入计匠网
×
|