(轉)UEFI小結-Handle的來龍去脈

本文說明:本人剛學習UEFI不久,寫該文一是爲了將學到的東西做一個規範化的總結,二是爲了給初學UEFI的兄弟起到借鑑作用。同樣地,錯誤的地方肯定很多,還望能得到各位弟兄指正。要理解本文,您至少應該是讀過UEFI Spec,不然請先閱讀UEFI Spec。

一、一些概念的理解

UEFI中會有很多抽象概念,像serviceprotocolhandle等等,如果將這些抽象的概念放到實際的代碼中理解的話,會有更清晰地認識,有了清晰的認識之後再把它們作爲抽象來理解,就遂心應手的多了。

首先說protocol,其實它就是一個由struct定義的結構體,這個結構體通常是由數據和函數指針組成,或其一。每個結構體的定義都有一個GUID與之對應。自然並不是所有的結構體都稱之爲protocolprotocol正如其名,它是一種規範,或稱協議。比如要建立一個基於UEFI Driver ModelDriver,就必須要綁定一個EFI_DRIVER_BINGING_PROTOCOL的實例,並且要自定義且實現SupportStartStop函數以及填充實例中其他的數據成員。它就相當於已經規範了種種需求和步驟。

再說service,它就是UEFI定義的API函數,所有的service都被集中到EFI_SYSTEM_TABLE下面,都可以通過gST來調用(gST指向一個EFI_SYSTEM_TABLE的全局實例)。

接着本文重點說明handle

      
二、EFI_HANDLE的定義
           EFI_HANDLE
定義是這樣的:typedef void * EFI_HANDLEvoid * 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。這樣handle1handle2AllHandles就鏈接到了一起。但是這樣就只能進行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)由IHANDLEAllHandles引出的鏈表

IHANDLE相關的鏈表有很多,後面一一牽扯出來。IHANDLE中的AllHandles成員用來鏈接IHANDLE實例的。這個鏈表的頭部是一個空結點,定義爲:EFI_LIST_ENTRY   gHandleList。一開始gHandleList->ForwardLink=gHandleList; gHandleList->BackLink=gHandleList。每次IHANDLE都從gHandleList->BackLink插入進來。這時候大家就意識到了這個鏈表是一個環形雙向鏈表。每當Driver建立一個新的EFI_HANDLE的時候就會插入到這條鏈表中來。這條鏈表被稱之爲handle database

     
2)由IHANDLEProtocols引出的鏈表

再來關注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_INTERFACElink用於連接以IHANDLE爲空頭結點以PPOTOCOL_INTERFACE爲後續結點的鏈表。這個結構體又牽扯出更多的EFI_LIST_ENTRY。成員中Handle指向頭空結點的這個handleProtocol指向PROTOCOL_ENTRY這個結構體實例,這個實例存在於另一個鏈表中,稱之爲Protocol Database。後面再說這個Protocol Database。先說OpenList引出的鏈表。

     
3)由PROTOCOL_INTERFACEOpenList引出的鏈表

註釋中已經說明OpenList引出OPEN_PROTOCOL_DATA listOPEN_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中的ByProtocolUEFI spec中已經說一個Protocol對應一個GUID,一個Protocol因不同情況實例化多個實例。所有一個GUID對應着多個Protocol的實例。上圖中GUIDProtocol 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 specInstallProtocolInterface的定義,CoreInstallProtocolInterfaceNotify中的NotifyTRUE。這個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是否已經存在於鏈表中,若不存在在創建一個以參數ProtocolGUIDPROTOCOL_ENTRY實例PortEntry插入鏈表4中。

493行露出EFI_HANDLE的本質了,它是(IHANDLE*)

494行到518行爲創建一個handle及初始化它的過程,看仔細了,對理解handle很有用。初始化後就插入到鏈表1中。

533行到554行,對新創建的PROTOCOL_INTERFACE實例Prot進行初始化,對照鏈表結構庫看仔細了,尤其是各種指針的去向(參數Interface掛接到了Prot下面)。初始化後將Port插入到鏈表2中。

這樣這個函數就介紹的差不多了,這也只是爲了做一個引子,像其他有關handle的函數想必也都在這個文件中,頭文件的定義很多都在hand.h中,只要有耐心,應該都能看的懂。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章