品讀鴻蒙HDF架構(一)|解讀鴻蒙源碼

品讀鴻蒙HDF架構(一)

侯亮

1.鋪墊一下

鴻蒙系統終於公開源代碼了,正可謂“千呼萬喚始出來”。筆者也手癢下載了一套代碼,並研讀了一二。這裏就先編寫一篇關於HDF的文檔。

其實,不同讀碼人都會有各自讀代碼的習慣和切入點,我之所以從HDF入手,完全是出於偶然。因爲在一開始讀官方文檔時,看到說一部機器可以操作另一部機器的設備,爲此,設備需要有一個重要的PublishService()函數。這種跨設備操作的能力也是鴻蒙的一大特色,應該比較有趣,於是就以這個PublishService()爲切入點,開始研讀代碼,慢慢就涉及了HDF的更多知識,現在是時候整理出來了。

所謂HDF,應該是Harmony Driver Fundation的縮寫,說到底是鴻蒙形成的一套管理設備驅動的框架模型,也被稱爲“驅動子系統”。在官網的文檔裏介紹說這個驅動子系統具有以下重要能力:

  • 彈性化的框架能力
  • 規範化的驅動接口
  • 組件化的驅動模型
  • 歸一化的配置界面

讀完這四句話,不免讓人覺得好像明白了什麼,又好像什麼都沒明白。好吧,我還是按自己的習慣直接讀代碼吧。

爲了便於理解代碼,我習慣於把軟件圖形化。爲此,我介紹一點我的圖形表達方法。在我讀Java代碼時,如果要表達A類對象的某個成員引用了另一個B類對象,我常常會這樣繪製:

但是HDF的代碼是用C寫的,所以對應的圖形表達法也要有所變化。我們要區分一下:
1)A結構以某成員組合了另一個B結構;
2)A結構某成員是個指向B的指針;
這兩種情況可以分別表示爲:

   

另外,有時候HDF會使用C語言的一些技巧進行鏈表表達或基類轉換,那麼上面的圖形畫出來就會很累贅,針對這種情況,我有時候會這樣表達(以DevmgrService結構爲例):

可以看出,DevmgrService繼承於IDevmgrService,而IDevmgrService又在起始處組合了一個HdfDeviceObject(有時候也可以說是繼承於HdfDeviceObject)。也就是說:
1)DevmgrService的起始地址;
2)DevmgrService內部IDevmgrService部分的起始地址;
3)IDevmgrService內部HdfDeviceObject部分的起始地址;
這3個起始地址其實是同一處。用這種表達法,我們就不必畫出3個分離的框圖了。

好了,鋪墊部分就先寫這麼多,下面我們來看HDF的實際內容。

2.DevmgrService和Dev Host

我們以hi3516 dv300爲例,其系統一啓動,會運行到SystemInit(),其中會調用DeviceManagerStart()啓動與HDF相關的部分:
【vendor/hisi/hi35xx/hi3516dv300/module_init/src/System_init.c】

void SystemInit(void)
{
    . . . . . .
#ifdef LOSCFG_DRIVERS_HDF
    if (DeviceManagerStart()) {
        PRINT_WARN("No drivers need load by hdf manager!");
    }
#endif
    . . . . . .
}

2.1啓動DeviceManager

【drivers/hdf/lite/manager/src/Devmgr_service_start.c】

int DeviceManagerStart()
{
    struct IDevmgrService *instance = DevmgrServiceGetInstance();

    if (instance == NULL || instance->StartService == NULL) {
        HDF_LOGE("Device manager start failed, service instance is null!");
        return HDF_FAILURE;
    }
    struct HdfIoService *ioService = HdfIoServiceBind(DEV_MGR_NODE, DEV_MGR_NODE_PERM);
    if (ioService != NULL) {
        static struct HdfIoDispatcher dispatcher = {
            .Dispatch = DeviceManagerDispatch,
        };
        ioService->dispatcher = &dispatcher;
        ioService->target = (struct HdfObject )&instance->object;
    }
    return instance->StartService(instance);
}

簡單地說,要啓動DeviceManager服務,就得先獲取一個DevmgrService實例,然後調用它的StartService(),又因爲DevmgrService繼承於IDevmgrService,所以可以強制轉換成IDevmgrService。

2.1.1獲取DevmgrService單例

獲取實例時,其實用到了HDF機制提供的一個對象管理器,相關代碼如下:

【drivers/hdf/frameworks/core/manager/src/Devmgr_service.c】

struct IDevmgrService *DevmgrServiceGetInstance()
{
    static struct IDevmgrService *instance = NULL;  // 注意是static的,表示是個靜態單例
    if (instance == NULL) {
        instance = (struct IDevmgrService *)HdfObjectManagerGetObject(HDF_OBJECT_ID_DEVMGR_SERVICE);
    }
    return instance;
}

以後我們會看到,這個HdfObjectManagerGetObject()會在多個地方調用,以便獲取不同的HDF對象。說起來也簡單,HDF機制裏有一張表,記錄着該如何創建、釋放一些重要的HDF對象,該表格爲g_liteObjectCreators:
【drivers/hdf/lite/manager/src/Devlite_object_config.c】

static const struct HdfObjectCreator g_liteObjectCreators[]

基於讀到的代碼,我們可以畫出這個表格:

HDF類型ID 創建函數 釋放函數
HDF_OBJECT_ID_DEVMGR_SERVICE DevmgrServiceCreate DevmgrServiceRelease
HDF_OBJECT_ID_DEVSVC_MANAGER DevSvcManagerCreate DevSvcManagerRelease
HDF_OBJECT_ID_DEVHOST_SERVICE DevHostServiceCreate DevHostServiceRelease
HDF_OBJECT_ID_DRIVER_INSTALLER DriverInstallerCreate NULL
HDF_OBJECT_ID_DRIVER_LOADER HdfDriverLoaderCreate NULL
HDF_OBJECT_ID_DEVICE HdfDeviceCreate HdfDeviceRelease
HDF_OBJECT_ID_DEVICE_TOKEN HdfDeviceTokenCreate HdfDeviceTokenRelease
HDF_OBJECT_ID_DEVICE_SERVICE DeviceNodeExtCreate DeviceNodeExtRelease

概念還是比較簡單的,如果系統中的DevmgrService單例對象已經存在,就使用之。否則就利用HDF對象管理器創建一個DevmgrService對象。對於HDF對象管理器而言,不同類型的HDF對象,需要用到不同的創建函數,所以要查一下上表。比如DevmgrService對應的創建函數就是DevmgrServiceCreate(),該函數代碼如下:
【drivers/hdf/frameworks/core/manager/src/Devmgr_service.c】

struct HdfObject *DevmgrServiceCreate()
{
    static bool isDevMgrServiceInit = false;
    static struct DevmgrService devmgrServiceInstance;
    if (!isDevMgrServiceInit) {
        if (!DevmgrServiceConstruct(&devmgrServiceInstance)) {
            return NULL;
        }
        isDevMgrServiceInit = true;
    }
    return (struct HdfObject )&devmgrServiceInstance;   // ???HdfObject,有小問題!
}

在“創建”時,如果發現是首次創建,則調用一個類似構造函數的DevmgrServiceConstruct()函數,來初始化對象裏的函數表。這種做法是用C語言實現面向對象概念的常用做法。不過,此處的代碼有一個小bug,即最後那個強制轉換,從目前看到的代碼來說,DevmgrService間接繼承於HdfDeviceObject,而HdfDeviceObject並不繼承於HdfObject,所以是不應該這樣強制轉換的,除非HdfDeviceObject的第一個成員從“IDeviceIoService*”改爲“IDeviceIoService”,我估計最早的代碼就是IDeviceIoService,後來因爲某些原因,變成了指針形式,至於以後具體該怎麼修正,這個就讓鴻蒙的工程師去費腦筋吧。DevmgrService的構造函數如下:
【drivers/hdf/frameworks/core/manager/src/Devmgr_service.c】

static bool DevmgrServiceConstruct(struct DevmgrService *inst)
{
    if (OsalMutexInit(&inst->devMgrMutex) != HDF_SUCCESS) {
        HDF_LOGE("%s mutex init failed", __func__);
        return false;
    }
    struct IDevmgrService *devMgrSvcIf = (struct IDevmgrService *)inst;
    if (devMgrSvcIf != NULL) {
        devMgrSvcIf->AttachDevice      = DevmgrServiceAttachDevice;
        devMgrSvcIf->AttachDeviceHost = DevmgrServiceAttachDeviceHost;
        devMgrSvcIf->StartService      = DevmgrServiceStartService;
        devMgrSvcIf->AcquireWakeLock  = DevmgrServiceAcquireWakeLock;
        devMgrSvcIf->ReleaseWakeLock  = DevmgrServiceReleaseWakeLock;
        HdfSListInit(&inst->hosts);
    }
    return true;
}


2.1.2HdfIoServiceBind()

啓動DeviceManager時,第二個重要的動作是調用HdfIoServiceBind():

struct HdfIoService *ioService = HdfIoServiceBind(DEV_MGR_NODE, DEV_MGR_NODE_PERM);

這一步在做什麼呢?我們可以這樣理解,DevmgrService作爲一個核心的系統服務,我們希望能像訪問虛文件系統的文件那樣打開它,並進一步向它傳遞諸如AttachDevice、StartServie......這樣的語義。這些語義最終會執行到上面列舉的DevmgrServiceAttachDevice、DevmgrServiceStartService等函數。

我們不必列舉太多代碼,下面是我繪製的一張關於DeviceManagerStart()的調用關係示意圖,可供參考:

圖中已經明確註明,DevmgrService在虛文件系統裏對應的路徑應該是“/dev/dev_mgr”,而上面調用HdfIoServiceBind()後,實際上建立了一個文件系統的inode節點,示意圖如下:

HdfVNodeAdapter的target在最後賦值爲(struct HdfObject*)&instance->object,說到底其實就是指向了DevmgrService。

2.1.3執行DevmgrService的StartService

    接下來是啓動DeviceManager的第三步,調用instance->StartService(),這一步其實是在調用DevmgreviceStartService()函數。
【drivers/hdf/frameworks/core/manager/src/Devmgr_service.c】

int DevmgrServiceStartService(struct IDevmgrService *inst)
{
    struct DevmgrService *dmService = (struct DevmgrService *)inst;
    if (dmService == NULL) {
        HDF_LOGE("Start device manager service failed, dmService is null");
        return HDF_FAILURE;
    }
    return DevmgrServiceStartDeviceHosts(dmService);
}

主要就是在調用一個DevmgrServiceStartDeviceHosts()函數。這個函數應該算是個重量級函數,它會負責建立起DevmgrService內部主要的數據結構。我們先繪製一下該函數第一層次的調用關係,如下圖:

在進一步深入代碼細節之前,我們最好先大概說明一下。在鴻蒙HDF架構裏,有一個“設備Host”的概念,根據官方的文檔,我們大概可以知道,一個Host用於整合若干業務相近的設備,這個原則被稱爲相似相容原則。爲了實現這個原則,HDF構造了一系列數據結構,我們列舉一下:
1)HdfHostInfo
2)DevHostServiceClnt
3)DevHostService
4)HdfDevice
5)HdfDeviceNode
. . . . . .

我們當然沒必要在一篇文檔裏列出所有的數據結構,只需先明白:
1)設備管理服務(DevmgrService)內部可以管理若干Host;
2)每個Host內部可以整合若干業務相近的設備;
3)每個Host可以拆分成兩個部分:DevHostServiceClnt 和 DevHostService;
4)每個DevHostService可以添加多個設備;

從上面的調用關係圖中,我們可以看到DevmgrServiceStartDeviceHosts()函數的主要行爲是:
1)先獲取一個驅動安裝器(單例)對象;
2)解析系統配置信息,將其轉換成一個以HdfHostInfo爲表項的列表,這個就對應着系統裏所有的host;
3)遍歷這張HdfHostInfo列表,爲每個HdfHostInfo節點創建一個對應的DevHostServiceClnt對象;
4)新創建的DevHostServiceClnt節點會被插入DevmgrService的hosts列表中;
5)針對每個HdfHostInfo節點,利用剛剛獲取的驅動安裝器具體啓動該host。

2.1.3.1獲取驅動安裝器

現在我們詳細看上圖中調用的關鍵函數。
installer = DriverInstallerGetInstance();
先拿到一個驅動安裝器。

struct IDriverInstaller *DriverInstallerGetInstance()
{
    static struct IDriverInstaller *installer = NULL;
    if (installer == NULL) {
        installer = (struct IDriverInstaller *)HdfObjectManagerGetObject(HDF_OBJECT_ID_DRIVER_INSTALLER);
    }
    return installer;
}

又看到HdfObjectManagerGetObject(),於是我們查前文那張表,可以找到驅動安裝器對應的創建函數是DriverInstallerCreate():
【drivers/hdf/frameworks/core/manager/src/Hdf_driver_installer.c】

struct HdfObject *DriverInstallerCreate(void)
{
    static bool isDriverInstInit = false;
    static struct DriverInstaller driverInstaller;
    if (!isDriverInstInit) {
        DriverInstallerConstruct(&driverInstaller);
        isDriverInstInit = true;
    }
    return (struct HdfObject *)&driverInstaller;
}

用的是一個單例的DriverInstaller對象。

2.1.3.2獲取HdfHostInfo列表

啓動所有hosts的第二步,是獲取一個HdfHostInfo列表:
HdfAttributeManagerGetHostList(&hostList)
我們摘選該函數的主要句子,如下:
【drivers/hdf/lite/manager/src/Hdf_attribute_manager.c】

bool HdfAttributeManagerGetHostList(struct HdfSList *hostList)
{
    . . . . . .
    hdfManagerNode = GetHdfManagerNode(HcsGetRootNode());
    . . . . . .
    hostNode = hdfManagerNode->child;
    while (hostNode != NULL) {
        struct HdfHostInfo *hostInfo = HdfHostInfoNewInstance();
        . . . . . .
        if (!GetHostInfo(hostNode, hostInfo)) {
            HdfHostInfoFreeInstance(hostInfo);
            hostInfo = NULL;
            hostNode = hostNode->sibling;
            continue;
        }
        hostInfo->hostId = hostId;
        if (!HdfSListAddOrder(hostList, &hostInfo->node, HdfHostListCompare)) {
            HdfHostInfoFreeInstance(hostInfo);
            hostInfo = NULL;
            hostNode = hostNode->sibling;
            continue;
        }
        hostId++;
        hostNode = hostNode->sibling;
    }
    return true;
}

我們稍微擴展一點知識來說明一下。在鴻蒙系統中,有一些系統級的配置文件,叫做HCS文件。系統可以利用類似hc-gen這樣的工具,根據配置文件生成二進制碼。當HDF啓動時,它會將二進制信息傳給DriverConfig模塊。該模塊會將二進制碼轉換成配置樹,並向開發者提供API去查詢這棵樹。

配置樹的根節點是g_hcsTreeRoot,節點類型爲DeviceResourceNode。這棵配置樹裏有一個特殊的節點,具有“hdf_manager”屬性,上面代碼中調用GetHdfManagerNode()一句,就是在獲取這個特殊節點。接着,上面的代碼裏會嘗試遍歷該節點的所有child,並將每個child的信息整理進一個HdfHostInfo對象裏。注意此時就會給HdfHostInfo分派一個hostId了,這個hostId後續還會用到。所有讀出的HdfHostInfo節點會按照其內記錄的優先級進行排序,並連成一個列表,優先級越高越靠近表頭。

2.1.3.3遍歷HdfHostInfo列表

得到HdfHostInfo列表後,緊接着就會嘗試遍歷這張表。因爲每個HdfHostInfo節點代表的就是一個host,所以每讀取一個HdfHostInfo,就會對應地生成一個DevHostServiceClnt對象。這些生成的DevHostServiceClnt都會插入到DevmgrService的hosts列表中。

每讀取一個HdfHostInfo信息後,就會利用驅動安裝器,啓動對應的host。

2.1.3.4啓動host

啓動host的動作是installer->StartDeviceHost()一步,它的調用關係如下:

大家還記得前文我說過,每個Host可以拆分成兩個部分:DevHostServiceClnt 和 DevHostService。這個就體現在上面的調用關係裏。

StartDeviceHost一開始就會創建一個DevHostService對象,
【drivers/hdf/frameworks/core/manager/src/Hdf_driver_installer.c】

static int DriverInstallerStartDeviceHost(uint32_t devHostId, const char *devHostName)
{
    struct IDevHostService *hostServiceIf = DevHostServiceNewInstance(devHostId, devHostName);
    if ((hostServiceIf == NULL) || (hostServiceIf->StartService == NULL)) {
        HDF_LOGE("hostServiceIf or hostServiceIf->StartService is null");
        return HDF_FAILURE;
    }
    int ret = hostServiceIf->StartService(hostServiceIf);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("Start host service failed, ret is: %d", ret);
        DevHostServiceFreeInstance(hostServiceIf);
    }
    return ret;
}

隨後調用的StartService,實際上對應DevHostServiceStartService()函數:
【drivers/hdf/frameworks/core/host/src/Devhost_service.c】

static int DevHostServiceStartService(struct IDevHostService *service)
{
    struct DevHostService *hostService = (struct DevHostService*)service;
    if (hostService == NULL) {
        HDF_LOGE("Start device service failed, hostService is null");
        return HDF_FAILURE;
    }
    return DevmgrServiceClntAttachDeviceHost(hostService->hostId, service);
}

此處調用的DevmgrServiceClntAttachDeviceHost()函數,內部涉及的內容挺多,我打算在下一篇文檔裏再細說。現在我們已經對“啓動DeviceManager”的流程有了一點初步的認識,爲了便於理解裏面host的部分,我們畫一張示意圖總結一下,繪圖如下:

好了,今天就先寫到這裏,更多關於HDF的內容會在後續文檔中闡述,希望對大家有點幫助。

(本文參與了「解讀鴻蒙源碼」技術徵文,歡迎正在閱讀的你也加入。

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