一、一些概念的理解
UEFI中會有很多抽象概念,像service、protocol、handle等等,如果將這些抽象的概念放到實際的代碼中理解的話,會有更清晰地認識,有了清晰的認識之後再把它們作爲抽象來理解,就遂心應手的多了。
首先說protocol,其實它就是一個由struct定義的結構體,這個結構體通常是由數據和函數指針組成,或其一。每個結構體的定義都有一個GUID與之對應。自然並不是所有的結構體都稱之爲protocol,protocol正如其名,它是一種規範,或稱協議。比如要建立一個基於UEFI Driver Model的Driver,就必須要綁定一個EFI_DRIVER_BINGING_PROTOCOL的實例,並且要自定義且實現Support、Start、Stop函數以及填充實例中其他的數據成員。它就相當於已經規範了種種需求和步驟。
再說service,它就是UEFI定義的API函數,所有的service都被集中到EFI_SYSTEM_TABLE下面,都可以通過gST來調用(gST指向一個EFI_SYSTEM_TABLE的全局實例)。
接着本文重點說明handle。
二、EFI_HANDLE的定義
EFI_HANDLE定義是這樣的:typedef void * EFI_HANDLE。void *
用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。這樣handle1與handle2的AllHandles就鏈接到了一起。但是這樣就只能進行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)由IHANDLE中AllHandles引出的鏈表
與IHANDLE相關的鏈表有很多,後面一一牽扯出來。IHANDLE中的AllHandles成員用來鏈接IHANDLE實例的。這個鏈表的頭部是一個空結點,定義爲:EFI_LIST_ENTRY gHandleList。一開始gHandleList->ForwardLink=gHandleList; gHandleList->BackLink=gHandleList。每次IHANDLE都從gHandleList->BackLink插入進來。這時候大家就意識到了這個鏈表是一個環形雙向鏈表。每當Driver建立一個新的EFI_HANDLE的時候就會插入到這條鏈表中來。這條鏈表被稱之爲handle database。
(2)由IHANDLE中Protocols引出的鏈表
再來關注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_INTERFACE的link用於連接以IHANDLE爲空頭結點以PPOTOCOL_INTERFACE爲後續結點的鏈表。這個結構體又牽扯出更多的EFI_LIST_ENTRY。成員中Handle指向頭空結點的這個handle,Protocol指向PROTOCOL_ENTRY這個結構體實例,這個實例存在於另一個鏈表中,稱之爲Protocol Database。後面再說這個Protocol Database。先說OpenList引出的鏈表。
(3)由PROTOCOL_INTERFACE中OpenList引出的鏈表
註釋中已經說明OpenList引出OPEN_PROTOCOL_DATA list。OPEN_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中的ByProtocol。UEFI spec中已經說一個Protocol對應一個GUID,一個Protocol因不同情況實例化多個實例。所有一個GUID對應着多個Protocol的實例。上圖中GUID由Protocol 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 spec中InstallProtocolInterface的定義,CoreInstallProtocolInterfaceNotify中的Notify爲TRUE。這個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是否已經存在於鏈表中,若不存在在創建一個以參數Protocol爲GUID的PROTOCOL_ENTRY實例PortEntry插入鏈表4中。
493行露出EFI_HANDLE的本質了,它是(IHANDLE*)。
494行到518行爲創建一個handle及初始化它的過程,看仔細了,對理解handle很有用。初始化後就插入到鏈表1中。
533行到554行,對新創建的PROTOCOL_INTERFACE實例Prot進行初始化,對照鏈表結構庫看仔細了,尤其是各種指針的去向(參數Interface掛接到了Prot下面)。初始化後將Port插入到鏈表2中。
這樣這個函數就介紹的差不多了,這也只是爲了做一個引子,像其他有關handle的函數想必也都在這個文件中,頭文件的定義很多都在hand.h中,只要有耐心,應該都能看的懂。