0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一文解析鸿蒙系统中的HDF架构

鸿蒙系统HarmonyOS 来源:CSDN博主 作者:悠然红茶 2021-03-25 16:06 次阅读

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[]

基于读到的代码,我们可以画出这个表格:

概念还是比较简单的,如果系统中的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.2 HdfIoServiceBind()

启动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的部分,我们画一张示意图总结一下,绘图如下:

编辑:hfy

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 鸿蒙系统
    +关注

    关注

    183

    文章

    2634

    浏览量

    66203
  • HarmonyOS
    +关注

    关注

    79

    文章

    1967

    浏览量

    30001
  • HDF框架
    +关注

    关注

    0

    文章

    9

    浏览量

    2760
收藏 人收藏

    评论

    相关推荐

    原生鸿蒙操作系统正式发布,诚迈科技与华为起共建鸿蒙新世界

    特性等提升显著,标志着我国在操作系统研发领域取得了重大突破。 诚迈科技与华为直保持着紧密的伙伴关系,公司不仅是最早加入鸿蒙生态的共建者之,也是鸿
    的头像 发表于 10-28 09:29 330次阅读
    原生<b class='flag-5'>鸿蒙</b>操作<b class='flag-5'>系统</b>正式发布,诚迈科技与华为<b class='flag-5'>一</b>起共建<b class='flag-5'>鸿蒙</b>新世界

    Taro鸿蒙技术内幕系列():如何将React代码跑在ArkUI上

    基于 Taro 打造的京东鸿蒙 APP 已跟随鸿蒙 Next 系统公测,本系列文章将深入解析 Taro 如何实现使用 React 开发高性能鸿蒙
    的头像 发表于 10-25 17:24 215次阅读
    Taro<b class='flag-5'>鸿蒙</b>技术内幕系列(<b class='flag-5'>一</b>):如何将React代码跑在ArkUI上

    鸿蒙跨端实践-JS虚拟机架构实现

    类似的框架,我们需要自行实现以确保核心基础能力的完整。 鸿蒙虚拟机的开发经历了从最初 ArkTs2V8 到 JSVM + Roma新架构方案 。在此过程,我们实现了完整的鸿蒙版的“J
    的头像 发表于 09-30 14:42 2319次阅读
    <b class='flag-5'>鸿蒙</b>跨端实践-JS虚拟机<b class='flag-5'>架构</b>实现

    联合大唐芙蓉园落地智慧鸿蒙原生应用,软通动力荣获华为“行业解决方案创新奖”

    体验。华为还携手鸿蒙生态伙伴,展示了焕然新的鸿蒙原生应用,拓展鸿蒙生态的广度和深度。鸿蒙生态已成为时代的滚滚洪流,而软通动力正是这滚滚洪流
    的头像 发表于 06-24 17:31 501次阅读
    联合大唐芙蓉园落地智慧<b class='flag-5'>文</b>旅<b class='flag-5'>鸿蒙</b>原生应用,软通动力荣获华为“行业解决方案创新奖”

    最新开源代码证实!“鸿蒙原生版”微信正在积极开发

    迁移到另个操作系统平台的时间和成本会非常高。 目前看来,微信的鸿蒙原生版确实正在开发,并且已经取得了定的进展。这对于
    发表于 05-08 17:08

    鸿蒙系统三防平板怎么样

    鸿蒙系统三防平板是款功能强大、性能卓越的平板电脑,其独特的三防设计使其成为各种恶劣环境下的理想选择。无论是户外探险、工地作业还是军事应用,鸿蒙系统
    发表于 04-12 14:26

    鸿蒙开发实战:【Hdf Framework】

    该仓主要存放OpenHarmony驱动子系统核心源码信息(包括驱动框架、配置管理、配置解析、驱动通用框架模型、硬件通用平台能力接口等),旨在为开发者提供更精准、更高效的开发环境,力求做到次开发,多
    的头像 发表于 03-21 20:29 428次阅读
    <b class='flag-5'>鸿蒙</b>开发实战:【<b class='flag-5'>Hdf</b> Framework】

    鸿蒙OS之UI架构解析

    填充数据,就能正常显示个列表了。 数据从哪来 可以看到上面的代码里是没有数据的,只有个空数组。我们想要从网络获取数据。那么,数据怎么来呢?最简单粗暴的写法就是在aboutToAppear()异步
    发表于 02-20 15:36

    【JAVA UI】【HarmonyOS】【Demo】 鸿蒙如何进行 xml 解析

    鸿蒙鸿蒙如何进行数据解析 【问题描述】有时候我们从服务器获取是 xml 格式数据,我们需要将 xml 转化成 model 对象,该如何使用呢?下面举个例子说明下,将分以下几步进行
    的头像 发表于 02-19 15:59 512次阅读
    【JAVA UI】【HarmonyOS】【Demo】 <b class='flag-5'>鸿蒙</b>如何进行 xml <b class='flag-5'>解析</b>

    华为鸿蒙系统怎么样 华为鸿蒙系统和安卓系统的区别

    和稳定性。与此同时,鸿蒙系统与安卓系统架构、功能和生态系统方面都存在些区别和特点,下面将详细
    的头像 发表于 02-02 14:54 1628次阅读

    如何在鸿蒙系统上安装Google Play

    随着鸿蒙(HarmonyOS)系统的逐渐普及和用户基数的增加,些用户希望能在鸿蒙系统上使用Google Play商店以获取更多应用。然而,
    的头像 发表于 01-31 17:13 1.5w次阅读

    质疑鸿蒙系统的是什么人群?为什么要杠

    2019 年,HarmonyOS 鸿蒙操作系统正式发布。但接下来很长的时间里,鸿蒙都是在自研的基础上,内置个 AOSP 架构。目的很简单,
    的头像 发表于 01-22 22:20 303次阅读
    质疑<b class='flag-5'>鸿蒙</b><b class='flag-5'>系统</b>的是什么人群?为什么要杠

    鸿蒙系统和安卓的区别 鸿蒙系统有什么特别之处

    鸿蒙系统是华为公司自主研发的款全新操作系统,旨在替代安卓系统鸿蒙
    的头像 发表于 01-18 11:45 1.1w次阅读

    很多人质疑鸿蒙,那它算不算国产操作系统

    。这使得鸿蒙更加灵活、安全、高效。 安卓则采用单设备架构,这意味着它的多设备协作能力较弱,数据共享相对不便。 3、安全性 鸿蒙操作系统采用
    发表于 01-17 22:04

    鸿蒙系统和安卓的区别哪个好用

    些问题,如性能、隐私安全等。而安卓系统是由谷歌开发和推广的移动设备操作系统,目前在全球范围内占据主导地位。 鸿蒙系统
    的头像 发表于 01-11 11:15 1866次阅读