品读鸿蒙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_HDFif (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.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的部分,我们画一张示意图总结一下,绘图如下:

好了,今天就先写到这里,更多关于HDF的内容会在后续文档中阐述,希望对大家有点帮助。

(我正在参加 CSDN 的【鸿蒙技术征文】活动,请给我点赞支持。)

品读鸿蒙HDF架构(一)相关推荐

  1. 品读鸿蒙HDF架构系列 | 读码百遍其义自见

    不同读码人都会有各自读代码的习惯和切入点,HDF是Harmony Driver Fundation的缩写,实际上便是鸿蒙形成的一套管理设备驱动的框架模型,也被称为"驱动子系统".本 ...

  2. 鸿蒙OS架构及关键技术整理

    鸿蒙OS架构及关键技术整理 一. 鸿蒙OS整体介绍 二. 子系统架构 三. 关键技术 四. 参考资料 一. 鸿蒙OS整体介绍 HarmonyOS简介 原作者:xiangzhihong8 前两天,华为发 ...

  3. 鸿蒙还是不是安卓,华为捐赠鸿蒙核心架构!是否形成“三足鼎立”?

    发布会已经开完,万物互联时代也已开启. 经过测试,鸿蒙系统支持几乎所有的安卓软件,换句话说安卓用户可以无缝过渡到鸿蒙系统,相比安卓,速度更快,耗电量更低,这无疑比安卓系统的体验好出一个档次. 现在安卓 ...

  4. 鸿蒙系统核心架构,对标 Apple Watch!魅族推首款智能手表,官宣接入华为鸿蒙系统...

    智东西(公众号:zhidxcom) 作者 | 韦世玮 编辑 | 心缘 智东西 5 月 31 日消息,今天,魅族发布首款接入华为鸿蒙系统的全智能手表 MEIZU Watch,以及 Lipro LED 智 ...

  5. 一图看懂全志XR806鸿蒙系统架构

    XR806支持的OpenHarmony属于 轻量系统(mini system) 轻量系统是面向MCU类处理器例如Arm Cortex-M.RISC-V 32位的设备,硬件资源极其有限,支持的设备最小内 ...

  6. 鸿蒙操作系统游戏模式,鸿蒙OS 2.0采用鸿蒙和Android 10双架构,游戏性能比EMUI11表现好...

    鸿蒙OS 2.0采用鸿蒙和Android 10双架构,来支持兼容安卓APP 看到报道的这个成绩,鸿蒙系统下比EMUI11系统表现更加优秀,这一点就成功了,接下来当真正完全使用鸿蒙内核之后应该有很好的表 ...

  7. 鸿蒙系统的底层架构是谁的,工信部接管鸿蒙系统 国家工信部接管鸿蒙 鸿蒙将不再属于华为?...

    您可能感兴趣的话题: 工信部接管鸿蒙系统 核心提示:工信部接管鸿蒙系统 国家工信部接管鸿蒙 鸿蒙将不再属于华为?鸿蒙系统是近期才刚刚推出的,可以说十分的不错,很多用户在更新升级了以后,都十分的喜欢,评 ...

  8. 新魔百和M304A_晶晨S905L系处理器关于JL-CW-ZN-SM-TY代工说明及鸿蒙架构全网通系统刷机教程汇总

    新魔百和M304A_晶晨S905L系处理器关于JL-CW-ZN-SM-TY代工说明及鸿蒙架构全网通系统刷机教程汇总 关于产品代工认知: 首先观察盒子背面型号标签上一般位于右下角或型号旁边有写 如:TY ...

  9. 鸿蒙系统学习笔记(一) 鸿蒙系统介绍

    个人学习鸿蒙系列 鸿蒙系统介绍 目录 个人学习鸿蒙系列 鸿蒙系统介绍 一.鸿蒙系统简介 1.简介 2.技术架构 (1)内核层 (2)系统服务层 (3)框架层 (4)应用层 3.发展进程 二.鸿蒙系统特 ...

最新文章

  1. 【前端词典】和媳妇讲代理后的意外收获
  2. gem ransack(4000✨) 简单介绍
  3. IntelliJ IDEA常用的快捷键(代码提示/注释代码/加入类注释和方法注释Javadoc)
  4. python反转义字符_Python对HTML转义字符进行反转义的实现方法
  5. Nmap经常使用的场景用法
  6. 这五张PPT告诉你,如何打造无人驾驶“最强大脑”
  7. stub_AccuREST Stub Runner发布
  8. 视觉SLAM——稀疏光流法
  9. (9.19更新:八戒退款) 砸进七万块,没想到你是这样的猪八戒网
  10. 车型代号对照表_车型代号对照表_相关文章专题_写写帮文库
  11. Industroyer:自震网病毒以来对工控系统的最大威胁
  12. 为什么mysql填不了数据库_求助,为何我的数据不能写入数据库
  13. 英文之妙语连珠超级94句
  14. 改wifi密码显示服务器拒绝访问权限,wifi密码对但是拒绝接入? | 192路由网
  15. 如何科学管理你的密码
  16. git推送代码详细教程
  17. 将网站封装成APP安卓应用
  18. 向windows服务器传输大文件时提示未知错误解决方法
  19. 图形界面介绍Floorplan ToolBox
  20. 《数据安全法》9月1日正式实施,最高可罚1000万元

热门文章

  1. mysql二分法_二分法算法总结
  2. linux 桌面 资源占用,Linux桌面使用率前20大国家
  3. java使用xquery_如何使用Java XQuery
  4. vb中/与\的区别是什么
  5. 概率论基础(7)数学期望、方差、协方差、切比雪夫不等式
  6. ffmpeg rtmp 封装发送函数_基于FFmpeg进行RTMP推流(一)
  7. GE256反射内存卡
  8. (私有云)客户给的VMware镜像磁盘如何制作成自定义镜像上传至openstack镜像服务器供客户使用
  9. 渐渐褪色的彩虹 好像一个梦
  10. TIMEWAIT与CLOSEWAIT