openharmony中hdf框架的驅動消息機制的實現原理
在分析hdf框架時發現繞來繞去的,整體梳理畫了一遍流程圖,發現還是有點模糊甚至不清楚如何使用的,詳細的每個點都去剖析細節又過于消耗時間,所以有時間便從功能應用的角度一塊塊的去梳理。
此文為參考官方源碼(oh5.0版本)中的docs\zh-cn\device-dev\driver\driver-hdf-manage.md驅動開發手冊,將驅動消息機制這個小章節拿出來,單獨做剖析的。官方手冊中只涉及了如何使用,未涉及具體的原理。本文會先整體說下實現原理,然后結合驅動開發手冊將涉及使用hdf接口函數的部分再往下剖了一下,目的是了解具體的實現邏輯。
概述
HDF框架提供統一的驅動消息機制,支持用戶態應用向內核態驅動發送消息,也支持內核態驅動向用戶態應用發送消息,用于當用戶態應用和內核態驅動需要交互的場景。
使用分析
由原理可知消息機制的功能主要有以下兩種:
- 用戶態應用發送消息到驅動。
- 用戶態應用接收驅動主動上報事件。
表2 消息機制接口
方法 | 描述 |
---|---|
struct HdfIoService *HdfIoServiceBind(const char *serviceName); | 用戶態獲取驅動的服務,獲取該服務之后通過服務中的Dispatch方法向驅動發送消息。 |
void HdfIoServiceRecycle(struct HdfIoService *service); | 用戶態釋放驅動的服務,與HdfIoServiceBind對應 |
int HdfDeviceRegisterEventListener(struct HdfIoService *target, struct HdfDevEventlistener *listener); | 用戶態程序注冊接收驅動上報事件的操作方法。 |
int32_t HdfDeviceSendEvent(const struct HdfDeviceObject *deviceObject, uint32_t id, const struct HdfSBuf *data) | 驅動主動上報事件接口。當驅動服務調用此函數發送消息時,所有通過 HdfDeviceRegisterEventListener注冊了監聽器的用戶級應用程序都將收到該消息。 |
HdfIoServiceBind
用戶態獲取驅動的服務,獲取該服務之后通過服務中的Dispatch方法向驅動發送消息(見示例),函數的聲明在接口文件中hdf_core\interfaces\inner_api\core\hdf_io_service_if.h,驅動開發者可以直接包含調用。
struct HdfIoService *HdfIoServiceBind(const char *serviceName)|-->HdfIoServiceAdapterObtain(serviceName) //獲取設備服務接口的適配器函數|-->svcMgr = DevSvcManagerClntGetInstance();//獲取設備服務管理實例|-->HdfDeviceObject *deviceObject = svcMgr->devSvcMgrIf->GetObject(svcMgr->devSvcMgrIf, serviceName);//通過設備服務管理器獲取設備對象|-->DevSvcManagerGetObject //通過獲取設備服務管理實例的創建過程可知上述函數的回調為此函數|-->serviceRecord = DevSvcManagerSearchServiceLocked(inst, serviceKey)//從設備服務管理器中搜索該名稱的服務|-->return serviceRecord->value //返回設備對象|-->HdfIoServiceKClient *kClient = HdfHdfIoServiceKClientInstance(deviceObject)//通過設備對象獲取設備服務的客戶端實例|-->kDispatcher = {.Dispatch = HdfKIoServiceDispatch,};|-->struct HdfIoServiceKClient *client = OsalMemCalloc(sizeof(struct HdfIoServiceKClient))//分配內存|-->if (deviceObject->service->Open(&client->client) != HDF_SUCCESS)//回調驅動層的open函數|-->client->ioService.dispatcher = &kDispatcher//綁定具體的接口,|-->return &kClient->ioService//返回設備服務接口
HdfIoServiceRecycle
用戶態釋放驅動的服務,與HdfIoServiceBind對應,聲明在接口文件中hdf_core\interfaces\inner_api\core\hdf_io_service_if.h,驅動開發者可以直接包含調用。
void HdfIoServiceRecycle(struct HdfIoService *service)|-->HdfIoServiceAdapterRecycle(service)|-->HdfIoServiceKClient *kClient = CONTAINER_OF(ioService, struct HdfIoServiceKClient, ioService)//根據ioService反推HdfIoServiceKClient對象①|-->kClient->client.device->service->Release(&kClient->client)//客戶端設備的釋放②|-->OsalMemFree(kClient)//對應HdfIoServiceBind函數中的HdfHdfIoServiceKClientInstance
①:關于反推函數CONTAINER_OF需要詳細的了解的可以參考這篇文章
②:客戶端設備的釋放是在設備構建過程中構建的io服務的設備接口(IDeviceIoService),從整體的流程圖中方便看,但太大了沒法放上來,后續有時間整理了這部分再重新放上鏈接。??
HdfDeviceRegisterEventListener
用戶態程序注冊接收驅動上報事件的操作方法
int HdfDeviceRegisterEventListener(struct HdfIoService *target, struct HdfDevEventlistener *listener)|-->return HdfDeviceRegisterEventListenerWithSchedPolicy(target, listener, SCHED_OTHER)//為設備服務注冊事件監聽器,并指定事件處理線程的調度策略|-->struct HdfSyscallAdapter *adapter = CONTAINER_OF(target, struct HdfSyscallAdapter, super)//從HdfIoService結構體中獲取其所屬的HdfSyscallAdapter實例|-->if (!AddListenerToAdapterLocked(adapter, listener)) //將事件監聽器添加到適配器中|--> ret = HdfIoServiceGroupThreadStart(adapter->group, policy) //如果適配器屬于一個服務組(adapter->group不為空)便啟動服務組的線程,并指定調度策略(policy即傳入的SCHED_OTHER)|--> if (HdfIoServiceGroupThreadInit(group) != HDF_SUCCESS) //對線程的初始化|-->HdfDevListenerThreadDoInit(thread) |-->int32_t ret = OsalThreadCreate(&thread->thread, HdfDevEventListenTask, thread)//創建監聽線程|-->//....路徑較多省略了|-->int32_t HdfDevEventDispatchLocked(const struct HdfDevListenerThread *thread, struct HdfSyscallAdapter *adapter, const struct HdfWriteReadBuf *bwr)|-->(void)listener->callBack(listener->priv, bwr->cmdCode, sbuf)//此處進行監聽事件的回調①|--> int32_t ret = HdfDevListenerThreadStart(group->thread)//初始化設備監聽線程,并啟動一個線程來處理設備事件|-->int32_t ret = HdfListenThreadInitPollFds(thread)//初始化監聽的文件描述符列表|-->if (HdfAdapterStartListenIoctl(thread->pfds[i].fd)) {//啟動監聽|-->if (OsalThreadStart(&thread->thread, &config) != HDF_SUCCESS) {//啟動線程return ret;|-->if (HdfIoServiceStartListen(adapter, policy) != HDF_SUCCESS)//如果適配器不屬于服務組,啟動事件處理線程,并指定調度策略|-->return HdfDevListenerThreadStart(adapter->thread)//初始化設備監聽線程,并啟動一個線程來處理設備事件
-
SCHED_OTHER是linux系統中默認的進程調度策略,想詳細了解進程調度策略可以參考這篇文章
-
在本文后續的使用示例中可見此函數的使用的目的,主要是為了將事件進行回調,其中回調部分可參看本段代碼的①處。
static struct HdfDevEventlistener listener = {.callBack = OnDevEventReceived,.priv ="Service0"
};
if (HdfDeviceRegisterEventListener(serv, &listener) != 0) {HDF_LOGE("fail to register event listener");return HDF_FAILURE;
}
- 在此段代碼中還可見有文件描述符的處理,即應用中我們用的select
、
poll或
epoll等。
HdfDeviceSendEvent
驅動主動上報事件接口,驅動服務調用此函數發送消息時,所有通過 HdfDeviceRegisterEventListener注冊了監聽器的用戶級應用程序都將收到該消息。聲明的頭文件在hdf_core\interfaces\inner_api\host\shared\hdf_device_desc.h,意味著用戶態可以直接包含調用。
int32_t HdfDeviceSendEvent(const struct HdfDeviceObject *deviceObject, uint32_t id, const struct HdfSBuf *data)|-->struct HdfDeviceNode *deviceNode = CONTAINER_OF(deviceObject, struct HdfDeviceNode, deviceObject)|-->adapter = (struct HdfVNodeAdapter *)(((struct DeviceNodeExt *)deviceNode)->ioService)|-->return HdfVNodeAdapterSendDevEvent(adapter, NULL, id, data)|-->ret = VNodeAdapterSendDevEventToClient(client, id, data)//將設備事件從驅動程序發送到客戶端(通常是用戶態應用程序或服務)|-->event = OsalMemAlloc(sizeof(struct HdfDevEvent));//分配事件對象|--> event->data = HdfSbufCopy(data);//給事件對象賦值|-->DListInsertTail(&event->listNode, &vnodeClient->eventQueue) //將事件對象插入到客戶端的事件隊列|-->wake_up_interruptible(&vnodeClient->pollWait)//喚醒等待事件的客戶端線程
- 喚醒等待事件的客戶端線程的wake_up_interruptible函數為linux內核的api函數,有需要詳細了解的可以直接百度,想了解簡單使用方法的可以參考這篇文章。
使用示例
驅動消息機制管理開發
-
將驅動配置信息(device_info.hcs)中服務策略policy字段設置為2(SERVICE_POLICY_CAPACITY,驅動對內核態和用戶態都發布服務)。
device_sample :: Device {policy = 2;permission = 0644;... }
-
配置驅動信息中的服務設備節點權限(permission字段)是框架給驅動創建設備節點的權限,默認是0666,驅動開發者根據驅動的實際使用場景配置驅動設備節點的權限。
權限值 含義 0666
所有用戶都可以讀寫該設備節點 0644
所有者可以讀寫,所屬組和其他用戶可以讀取 0640
所有者可以讀寫,所屬組可以讀取,其他用戶無法訪問 0600
只有所有者可以讀寫,其他用戶無法訪問 -
在服務實現過程中,實現服務基類成員IDeviceIoService中的Dispatch方法。
#include <hdf_log.h> #include <hdf_device_io.h> #include <hdf_device_desc.h> #include <hdf_sbuf.h>// 假設的其他服務函數 int32_t SampleDriverServiceA(struct HdfDeviceIoClient *client, struct HdfSBuf *data, struct HdfSBuf *reply) {HDF_LOGI("SampleDriverServiceA called");return HDF_SUCCESS; }int32_t SampleDriverServiceB(struct HdfDeviceIoClient *client, struct HdfSBuf *data, struct HdfSBuf *reply) {HDF_LOGI("SampleDriverServiceB called");return HDF_SUCCESS; }// I/O 請求處理函數 int32_t SampleDriverDispatch(struct HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply) {HDF_LOGI("SampleDriverDispatch called with cmdCode: %d", cmdCode);// 根據 cmdCode 處理不同的命令switch (cmdCode) {case 1:HDF_LOGI("Handling command 1");// 處理命令 1 的邏輯const char *msg = "Hello from driver";struct HdfSBuf *eventData = HdfSbufObtainDefaultSize(); // 創建事件數據緩沖區if (eventData == NULL) {HDF_LOGE("fail to obtain sbuf for event data");return HDF_DEV_ERR_NO_MEMORY;}if (!HdfSbufWriteString(eventData, msg)) { // 寫入事件數據HDF_LOGE("fail to write event data");HdfSbufRecycle(eventData);return HDF_FAILURE;}break;case 2:HDF_LOGI("Handling command 2");// 處理命令 2 的邏輯break;default:HDF_LOGE("Unknown command code: %d", cmdCode);return HDF_ERR_INVALID_PARAM;}// 上報事件int ret = HdfDeviceSendEvent(client->device, cmdCode, eventData);HdfSbufRecycle(eventData); // 釋放事件數據緩沖區return HDF_SUCCESS; }// 驅動綁定函數 int32_t SampleDriverBind(struct HdfDeviceObject *device) {HDF_LOGI("SampleDriverBind called");if (device == NULL) {HDF_LOGE("Invalid device object");return HDF_FAILURE;}// 定義服務接口static struct ISampleDriverService sampleDriverA = {.ioService.Dispatch = SampleDriverDispatch,.ServiceA = SampleDriverServiceA,.ServiceB = SampleDriverServiceB,};// 將服務接口綁定到設備對象device->service = (struct IDeviceIoService *)&sampleDriverA;return HDF_SUCCESS; }// 驅動卸載函數 void SampleDriverUnload(struct HdfDeviceObject *device) {HDF_LOGI("SampleDriverUnload called");// 在這里釋放資源或執行清理操作 }// 驅動描述符 struct HdfDriverEntry g_sampleDriver = {.moduleVersion = 1,.Bind = SampleDriverBind,.Unload = SampleDriverUnload,.moduleName = "sample_driver", };// 驅動模塊入口 SYSEXPORT_DRIVER(g_sampleDriver);
-
驅動定義消息處理函數中的cmd類型。
#define SAMPLE_WRITE_READ 1 // 讀寫操作碼1
-
用戶態獲取服務接口并發送消息到驅動。
int SendMsg(const char *testMsg) {if (testMsg == NULL) {HDF_LOGE("test msg is null");return HDF_FAILURE;}struct HdfIoService *serv = HdfIoServiceBind("sample_driver");// 綁定到驅動服務if (serv == NULL) {HDF_LOGE("fail to get service");return HDF_FAILURE;}struct HdfSBuf *data = HdfSbufObtainDefaultSize();// 分配數據和響應緩沖區if (data == NULL) {HDF_LOGE("fail to obtain sbuf data");return HDF_FAILURE;}struct HdfSBuf *reply = HdfSbufObtainDefaultSize();if (reply == NULL) {HDF_LOGE("fail to obtain sbuf reply");ret = HDF_DEV_ERR_NO_MEMORY;goto out;}if (!HdfSbufWriteString(data, testMsg)) {// 寫入數據到數據緩沖區HDF_LOGE("fail to write sbuf");ret = HDF_FAILURE;goto out;}int ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply);// 調用服務的 Dispatch 函數發送請求if (ret != HDF_SUCCESS) {HDF_LOGE("fail to send service call");goto out;} out:HdfSbufRecycle(data);HdfSbbufRecycle(reply);HdfIoServiceRecycle(serv);return ret; }
-
用戶態接收該驅動上報的消息。
//用戶態編寫驅動上報消息的處理函數。 static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data) {OsalTimespec time;OsalGetTime(&time);HDF_LOGI("%{public}s received event at %{public}llu.%{public}llu", (char *)priv, time.sec, time.usec);const char *string = HdfSbufReadString(data);if (string == NULL) {HDF_LOGE("fail to read string in event data");return HDF_FAILURE;}HDF_LOGI("%{public}s: dev event received: %{public}d %{public}s", (char *)priv, id, string);return HDF_SUCCESS; } //用戶態注冊接收驅動上報消息的操作方法。 int RegisterListen() {struct HdfIoService *serv = HdfIoServiceBind("sample_driver");if (serv == NULL) {HDF_LOGE("fail to get service");return HDF_FAILURE;}static struct HdfDevEventlistener listener = {.callBack = OnDevEventReceived,.priv ="Service0"};if (HdfDeviceRegisterEventListener(serv, &listener) != 0) {HDF_LOGE("fail to register event listener");return HDF_FAILURE;}......HdfDeviceUnregisterEventListener(serv, &listener);HdfIoServiceRecycle(serv);return HDF_SUCCESS; }