OpenHarmonyHDF驱动框架介绍及驱动加载过程分析OpenHarmony")系统HDF驱动框架采用C语言面向对象编程模型构建,通过平台解耦和内核解耦,达到兼容不同内核、统一操作系统的目的。平台基础,从而帮助开发者实现一次性驱动开发和多系统部署的效果。为了实现这一目标,OpenHarmony系统HDF驱动框架提供:操作系统适配层(OSAL,操作系统抽象层):内核操作相关接口统一封装,屏蔽不同系统操作接口平台驱动接口:提供对部分板卡驱动的支持(例如I2C/SPI/UART总线等平台资源),同时实现统一的适配器Board硬件操作的接口抽象,开发者只需要开发新的硬件抽象接口以获得添加的板部分驱动程序支持。驱动模型:针对设备驱动,提供通用的驱动抽象模型,主要实现两个目的。1)提供标准化的设备驱动模型,开发者无需独立开发,通过配置即可完成驱动部署。2)提供驱动模型的抽象,屏蔽驱动与不同系统组件的交互,使驱动更加通用。为了进一步简化OpenHarmony系统驱动开发,OpenHarmony系统HDF驱动框架支持多种驱动加载方式:支持驱动的动态加载和静态加载,去除了驱动代码与框架之间的直接代码依赖,使得驱动可独立编译部署;需要采用动态加载的方式,避免设备驱动程序满载,可以有效减少系统资源的占用。本文主要分析OpenHarmony系统驱动加载过程。在正式介绍之前,先了解一下OpenHarmony系统驱动架构的组成、工作原理和机制,从而了解驱动加载的细节。HDF驱动框架介绍OpenHarmony系统HDF驱动框架组成图1HDF驱动框架OpenHarmony系统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组成。设备信息完成设备信息的配置。例如配置接口发布策略,加载驱动的方式等。DeviceResource完成设备资源的配置。比如GPIO管脚、寄存器等资源信息的配置。4、驱动接口HDI(HardwareDriverinterface)提供标准化的接口定义和实现,驱动框架提供IOService和IODispatcher机制,使得驱动接口在不同的部署形式下趋于一致。驱动接口主要有以下几种实现:当驱动作为内核组件部署时,客户端程序需要通过系统调用方法调用驱动,驱动接口通过系统调用方法向内核调用消息IOService请求,并将消息分发给IODispatcher处理。当驱动程序部署为用户态服务时,客户端进程需要通过IPC通信来访问驱动程序进程。IOService完成IPC通信的客户端消息请求的封装,IODispatcher完成驱动服务器消息请求的封装,客户端消息通过IPC进行通信。到达服务器分发给IODispatcher处理。当驱动部署在RTOS(RealTimeOperatingSystem)轻量级操作系统上时,驱动接口与驱动之间采用函数调用方式,因此驱动接口只提供定义,驱动接口实现由驱动提供.HDF驱动框架工作原理图2驱动框架工作原理DeviceManager提供了统一的驱动加载管理机制和驱动接口发布机制。DeviceHost环境加载时,DeviceManager根据DeviceInformation信息请求Host加载相应的驱动程序。当DeviceHost收到请求后,进行如下操作:根据请求加载设备信息,在指定路径下查找并加载驱动镜像或者从指定的段地址(section)中查找驱动入口。找到驱动设备描述符,匹配对应的设备驱动。当驱动匹配成功时,加载指定的驱动镜像HostFramework驱动镜像加载成功后,调用驱动的绑定接口和初始化接口(DriverEntry),实现与驱动的服务对象的绑定,并初始化设备驱动同时程序,当DeviceInformation配置中的服务策略要求驱动接口对外暴露时,驱动框架会将驱动程序的服务对象添加到对外发布的服务对象列表中,对外客户端程序可以查询和访问相应的服务接口。驱动接口的工作机制图3驱动接口的工作机制对于驱动接口的实现:当驱动部署在内核态时,驱动接口部署在用户态,驱动实现在内核状态。来电互动。当驱动部署在用户态时,驱动接口和驱动实现分别部署在两个进程中。驱动接口与驱动实现之间的调用交互是通过IPC调用方式实现的。为了使客户端和服务端驱动的调用方式基本一致,驱动框架提供了IOService和IODispatcher机制来屏蔽调用消息传递方式的差异。驱动接口统一通过远程调用实现。客户端驱动接口函数将请求序列化为内存数据,通过驱动框架提供的IOService将消息发送给服务器处理。当服务器收到请求消息时,它使用IODispatcher机制。将消息分发给消息处理函数进行处理,处理函数将反序列化后的内存数据解析成相应的请求。这样做的好处是开发者只需要关注接口的定义,不需要过多关注如何在不同平台上实现接口适配。驱动加载过程分析OpenHarmony系统驱动根据驱动部署方式的不同,有两种驱动加载方式:动态加载方式:采用传统的so(共享库)加载方式,驱动通过指定Symbol找到驱动函数入口进行加载.静态加载方式:使用Scatter将驱动编译到指定的Section中,然后通过访问指定Section对应的地址找到驱动函数入口加载。下面以示例代码讲解驱动加载过程,重点分析静态加载模式下的内核态驱动加载过程。实现驱动初始化接口在HDF驱动框架中,HdfDriverEntry对象用于描述驱动的实现。structHdfDriverEntry{int32_tmoduleVersion;constchar*moduleName;int32_t(*Bind)(structHdfDeviceObject*deviceObject);int32_t(*Init)(structHdfDeviceObject*deviceObject);void(*Release)(structHdfDeviceObject*deviceObject);};写一个简单的驱动,首先DriverEntry入口中主要有3个接口需要实现: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);导出驱动入口符号实现驱动初始化后,还需要通过驱动声明宏导出驱动入口,以便驱动框架在启动时识别驱动的存在,即可加载驱动:#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))下面是它的实现示意图4DriverEntry内存分配可以看到HDF_INIT宏定义了一个“驱动模块名+HdfEntry”的符号,放到了“.hdf.driver"段,这个符号指向的内存地址就是驱动入口结构的地址。这个特殊部分将用于在启动时查找设备驱动程序。添加设备配置在设备对应的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字段指定设备对应的驱动名,从而关联设备与驱动一般情况下,设备和驱动之间可以是一对多的关系,即一个驱动可以支持多个相同类型的设备。我们添加的驱动在驱动启动过程中是如何执行的呢?简单的说,系统启动时,首先启动驱动框架,通过解析配置文件获取设备列表,通过读取“.hdf.drivers”段来读取。驱动(DriverEntry)列表,然后遍历设备列表与驱动列表进行匹配,加载匹配成功的驱动。驱动框架有两个核心管理器:DeviceManager负责设备管理,包括设备加载、卸载、查询等与设备相关的功能。DeviceServiceManager负责管理设备发布的接口服务,提供发布、查询接口服务等功能。驱动加载主要由DeviceManager主导。首先DeviceManager需要解析配置文件中的Host列表,根据Host列表中的信息实例化对应的Host对象。Host解析配置文件获取关联的设备列表,遍历设备列表获取匹配的驱动名称,然后根据驱动名称遍历上述.hdf.driver段获取驱动地址。具体过程如下所述。获取设备列表图5设备列表结构配置文本会被编译成二进制格式的配置文件,其中与设备相关的信息存放在一个标有“hdf_manager”的device_info配置块中,host的内容为device_info块以块的形式在host块中依次排列,host块中记录了主机名、启动优先级和设备列表信息。设备信息中的moduleName字段将用于匹配驱动入口中的moduleName,从而为设备匹配正确的驱动程序。获取驱动列表图6Driver(DriverEntry)内存布局HDF驱动框架通过在一个特殊的section中存储驱动入口符号的地址来实现驱动索引,并在这个section的开头和结尾分别插入_hdf_drivers_start和_hdf_drivers_end两个特殊符号用于标示该段的范围,两个特殊符号之间的数据为驱动实现指针。驱动加载流程图7驱动加载流程DeviceManager遍历设备列表,找到对应的驱动实现后,为该设备创建一个Device对象实例。如果设备配置中的policy字段是需要发布的驱动接口(SERVICE_POLICY_CAPACITY),则首先调用驱动的Bind接口将设备与服务实例相关联。然后会调用驱动的Init接口,完成驱动的相关初始化工作。如果驱动被卸载或者由于硬件等原因导致Init接口返回失败,将调用Release释放驱动请求的各种资源。综上所述,本次与大家分享OpenHarmony系统驱动的主要设计思路,着重分析了内核态驱动的加载过程。关于OpenHarmony系统驱动的其他内容,后续会有更多技术文章与大家分享,敬请期待。更多信息请访问:Harmonyos.51cto.com,与华为官方合作打造的鸿蒙技术社区
