当前位置: 首页 > 科技观察

HarmonyOS驱动加载过程分析

时间:2023-03-12 04:27:30 科技观察

更多信息请访问:HarmonyOS技术社区https://harmonyos.51cto.comHarmonyOS驱动概述HarmonyOS驱动框架采用C语言面向对象编程模型构建。通过平台解耦和内核解耦,达到兼容不同内核,统一平台基础的目的,从而帮助开发者实现驱动的“一次开发,多系统部署”。为了实现这一目标,HarmonyOS驱动框架提供:1.操作系统适配层(OSAL,操作系统抽象层):提供内核操作相关接口进行统一封装,屏蔽不同系统操作接口。2、平台驱动接口:提供对部分板卡驱动的支持(例如:I2C/SPI/UART总线等平台资源),同时对板卡硬件操作接口实现统一的适配抽象。开发者只需要开发一个新的硬件抽象接口。获得新的板件驱动程序支持。3、驱动模型:面向设备驱动,提供通用的驱动抽象模型,主要实现两个目的:1)提供标准化的设备驱动,开发者无需独立开发,通过配置即可完成驱动部署。2)提供驱动模型的抽象,屏蔽驱动与不同系统组件的交互,使驱动更加通用。为了进一步简化HarmonyOS驱动开发,HarmonyOS驱动框架支持多种驱动加载方式:1.支持动态加载和静态加载驱动,去除驱动代码和框架之间的直接代码依赖,实现驱动的独立编译和部署;2.支持按需动态加载方式,避免设备驱动程序满载,可有效减少系统资源占用。本文主要分析HarmonyOS驱动加载过程。在正式介绍之前,先了解HarmonyOS驱动架构的组成、工作原理和机制,从而了解驱动加载的细节。●官网相关介绍:https://device.harmonyos.com/cn/docs/develop/drive/oem_drive_hdfdev-0000001051715456HarmonyOS驱动架构介绍2.1HarmonyOS驱动架构HarmonyOS驱动架构主要由HDF驱动框架、驱动程序、驱动程序组成配置文件和驱动接口由四部分组成。1)HDF驱动框架提供统一的硬件资源管理、驱动加载管理、设备节点管理等功能。驱动框架采用主从模式设计,由DeviceManager和DeviceHost组成。设备管理器提供统一的驱动程序管理。DeviceManager启动时,根据DeviceInformation提供的驱动设备信息加载对应的驱动DeviceHost,并控制Host完成驱动加载。DeviceHost提供驱动运行的环境,同时预置HostFramework配合DeviceManager完成驱动的加载和调用。根据业务需求,DeviceHost可以有多个实例。说明◆DeviceHost,顾名思义就是驱动宿主,为驱动提供运行环境。◆当驱动部署在用户态时,DeviceHost可以被一个独立的进程托管。◆当驱动部署在内核态时,DeviceHost仅代表逻辑隔离。◆DeviceHost的划分原则:DeviceHost属于一种设备聚合,如Camera、Audio、Display等。◆驱动部署在一台DeviceHost上还是部署在不同的DeviceHost上,主要考虑驱动之间是否存在业务耦合。如果两个驱动之间存在依赖关系,可以考虑将这部分驱动部署在同一个Host上。2)驱动程序实现了驱动程序的具体功能。每个驱动由一个或多个驱动组成,每个驱动对应一个DriverEntry。DriverEntry主要完成驱动初始化和驱动接口绑定功能。3)驱动配置文件.hcs主要由DeviceInformation和DeviceResource组成。DeviceInformation完成对设备信息的配置,如配置接口发布策略、驱动加载方式等。DeviceResource完成对设备资源的配置,如GPIO引脚、寄存器等资源信息的配置。4)驱动接口HDI(HardwareDriverinterface)提供标准化的接口定义和实现,驱动框架提供IOService和IODispatcher机制,使驱动接口在不同的部署形式下趋于一致。当驱动部署在RTOS(Real-TimeOperatingSystem)轻量级操作系统上时,驱动接口与驱动之间采用函数调用方式,因此驱动接口只提供定义,驱动接口实现由司机。2.2HDF驱动框架工作原理设备管理器提供了统一的驱动加载管理机制和驱动接口发布机制。DeviceHost环境加载时,DeviceManager根据DeviceInformation信息请求Host加载相应的驱动程序。当DeviceHost收到请求后,进行如下操作:1)根据请求加载设备信息,在指定路径Mirror下查找并加载驱动程序或从指定段地址(section)查找驱动程序入口;2)找到驱动设备描述符,匹配对应的设备驱动;3)驱动匹配成功后,加载指定的驱动镜像;4)HostFramework在驱动镜像中加载成功后,调用驱动程序的绑定接口和初始化接口(DriverEntry),实现与驱动程序服务对象的绑定,同时初始化设备驱动程序时间;5)当DeviceInformation配置中的服务策略需要驱动接口对外暴露时,驱动框架会将驱动程序的服务对象添加到对外发布的服务对象列表中,外部客户端程序可以查询和访问通过这个列表对应的服务接口。2.3驱动接口工作机制驱动接口主要有以下实现:当驱动作为内核组件部署时,客户端程序需要通过系统调用方法调用驱动程序,驱动接口通过系统调用调用消息通过IO服务请求的方法。Kernel,并将消息分发给IODispatcher进行处理。?当驱动程序部署为用户模式服务时,客户端进程需要通过IPC进行通信以访问驱动程序进程。IOService完成IPC通信的客户端消息请求封装,IODispatcher完成驱动服务器消息请求封装,客户端消息通过IPC通信到达服务器,分发给IODispatcher处理。为了使客户端和服务端驱动的调用方式基本一致,驱动框架提供了IOService和IODispatcher机制来屏蔽调用消息传递方式的差异。驱动接口实现了统一的远程调用方法。客户端驱动接口函数将请求序列化到内存数据中,通过驱动框架提供的IOService向服务器发送消息,当服务器收到请求消息后,通过IODispatcher机制将消息发送给服务器。分发给消息处理函数进行处理,处理函数会将反序列化后的内存数据解析成相应的请求。这样做的好处是开发者只需要关注接口的定义,不需要过多关注如何在不同平台上实现接口适配。驱动加载过程分析HarmonyOS驱动根据不同的部署方式有两种驱动加载方式:?动态加载方式:采用传统的so(共享库)加载方式,驱动通过指定Symbol找到驱动函数入口进行加载。?静态加载方式:使用Scatter编译驱动程序,编译到指定的Section中,然后通过访问指定Section对应的地址找到驱动函数入口加载。下面结合一个Sample代码来讲解驱动加载过程,重点分析静态加载模式下的内核态驱动加载过程。3.1驱动初始化接口的实现在HDF驱动框架中,HdfDriverEntry对象用于描述一个驱动的实现。structHdfDriverEntry{int32_tmoduleVersion;constchar*moduleName;int32_t(*Bind)(structHdfDeviceObject*deviceObject);int32_t(*Init)(structHdfDeviceObject*deviceObject);void(*Release)(structHdfDeviceObject*deviceObject);};写一个简单的驱动,首先需要实现DriverEntry入口中的三个主要接口:?Bind接口:实现驱动接口实例化的绑定,如果需要发布驱动接口,会在驱动加载时调用进程,接口的驱动服务将被实例化并与DeviceObject绑定。?Init接口:实现驱动程序的初始化,并返回错误停止驱动程序的加载过程。?Release接口:实现驱动的卸载,在该接口释放驱动实例的软硬件资源。intSampleDriverBind(structHdfDeviceObject*deviceObject){HDF_LOGE("SampleDriverBinenter!");staticstructIDeviceIoServicetestService={.Dispatch=SampleServiceDispatch,.Open=NULL,.Release=NULL,};deviceObject->service=&testObject;returnHDF_SUCCESS;){HDF_LOGE("SampleDriverInitenter");returnHDF_SUCCESS;}voidSampleDriverRelease(structHdfDeviceObject*deviceObject){HDF_LOGE("SampleDriverReleaseenter");return;}structHdfDriverEntryg_sampleDriverEntry={.SammoduleVersion=1,.moduleName="sample.DriverBind="sample.DriverBindInit=SampleDriverInit,.Release=SampleDriverRelease,};HDF_INIT(g_sampleDriverEntry);3.2导出驱动入口符号驱动初始化后,需要通过驱动声明宏导出驱动入口,以便驱动框架识别驱动在startup存在驱动可以加载:#defineHDF_INIT(module)HDF_DRIVER_INIT(module)这里展开HDF_INIT宏:#defineHDF_SECTION__attribute__((section(".hdf.driver")))#defineHDF_DRIVER_INIT(module)\constsize_tUSED_ATTRmodule##HdfEntryHDF_SECTION=(size_t)(&(module))下面是实现原理:可以看到HDF_INIT宏定义了一个“驱动模块名+HdfEntry”符号,放在“.hdf.driver”所在的section中位于。符号指向的内存地址是驱动入口结构的地址。这个特殊部分将用于在启动时查找设备驱动程序。3.3添加设备配置在设备对应的device_info.hcs中添加示例驱动配置:sample_host::host{hostName="sample_host";sample_device::device{device0::deviceNode{policy=2;priority=100;preload=1;permission=0664;moduleName="sample_driver";serviceName="sample_service";}}}配置中定义的设备在加载过程中会生成一个设备实例,通过moduleName字段指定设备对应的驱动名,所以至于将设备连接到驱动程序连接。其中,一个设备和一个驱动可能是一对多的关系,即一个驱动可能支持多个相同类型的设备。3.4驱动启动过程我们添加的驱动是如何执行的?简单的说,系统启动的时候,首先启动驱动框架,通过解析配置文件获取设备列表,通过读取“.hdf.driver”段到DriverEntry列表,然后遍历设备列表到匹配驱动列表,加载匹配成功的驱动。驱动框架有两个核心管理器:?DeviceManager:负责设备管理,包括设备加载、卸载、查询等与设备相关的功能。?DeviceServiceManager:负责管理设备发布的接口服务,提供发布、查询接口服务等功能。驱动加载主要由DeviceManager主导。首先DeviceManager需要解析配置文件中的Host列表,根据Host列表中的信息实例化对应的Host对象。Host解析配置文件获取关联的设备列表,遍历设备列表获取匹配的驱动名称,然后根据驱动名称遍历上述“.hdf.driver”段获取驱动地址。具体过程如下所述。3.4.1获取设备列表配置文本编译后会成为一个二进制格式的配置文件,其中设备相关的信息存放在一个标有“hdf_manager”的device_info配置块中,host的内容device_info块中是以块的形式按顺序排列的,host块记录了主机名、启动优先级和设备列表信息。设备信息中的moduleName字段将用于匹配驱动入口中的moduleName,从而为设备匹配正确的驱动程序。3.4.2获取驱动列表HDF驱动框架将驱动入口符号的地址存放在一个特殊的section中,用于对驱动进行索引。在本节的开头和结尾插入两个特殊符号_hdf_drivers_start和_hdf_drivers_end,用于标明本节的范围,两个特殊符号之间的数据为驱动实现指针。3.4.3驱动加载过程设备管理器遍历设备列表。当它找到相应的驱动程序实现时,它会为该设备创建一个Device对象实例。如果设备配置中的policy字段是需要对外发布的驱动接口(SERVICE_POLICY_CAPACITY),那么会先调用驱动的Bind接口,将设备与服务实例关联起来。然后会调用驱动的Init接口,完成驱动的相关初始化工作。如果驱动被卸载或者由于硬件等原因导致Init接口返回失败,会调用Release释放驱动请求的各种资源。综上所述,本次与大家分享HarmonyOS驱动的主要设计思路,重点分析了内核态驱动的加载过程。关于HarmonyOS驱动的其他内容,后续会有更多技术文章与大家分享,敬请期待。更多信息请访问:Harmonyos.51cto.com,与华为官方合作打造的鸿蒙技术社区