服務和數據傳輸

本部分介紹瞭如何註冊和發現服務,以及如何通過調用 .hal 文件內的接口中定義的方法將數據發送到服務。

註冊服務

HIDL 接口服務器(實現接口的對象)可註冊爲已命名的服務。註冊的名稱不需要與接口或軟件包名稱相關。如果沒有指定名稱,則使用名稱“默認”;這應該用於不需要註冊同一接口的兩個實現的 HAL。例如,在每個接口中定義的服務註冊的 C++ 調用是:

status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService(“another_foo_service”); // if needed
HIDL 接口的版本包含在接口本身中。版本自動與服務註冊關聯,並可通過每個 HIDL 接口上的方法調用 (android::hardware::IInterface::getInterfaceVersion()) 進行檢索。服務器對象不需要註冊,並可通過 HIDL 方法參數傳遞到其他進程,相應的接收進程會向服務器發送 HIDL 方法調用。

發現服務

客戶端代碼按名稱和版本請求指定的接口,並對所需的 HAL 類調用 getService:

// C++
sp<V1_1::IFooService> service = V1_1::IFooService::getService();
sp<V1_1::IFooService> alternateService = 1_1::IFooService::getService("another_foo_service");
// Java
V1_1.IFooService; service = V1_1.IFooService.getService(true /* retry */);
V1_1.IFooService; alternateService = 1_1.IFooService.getService("another", true /* retry */);

每個版本的 HIDL 接口都會被視爲單獨的接口。因此,IFooService 版本 1.1 和 IFooService 版本 2.2 都可以註冊爲“foo_service”,並且兩個接口上的 getService(“foo_service”) 都可獲取該接口的已註冊服務。因此,在大多數情況下,註冊或發現服務均無需提供名稱參數(也就是說名稱爲“默認”)。

供應商接口對象還會影響所返回接口的傳輸方法。對於軟件包 [email protected] 中的接口 IFoo,IFoo::getService 返回的接口始終使用設備清單中針對 android.hardware.foo 聲明的傳輸方法(如果相應條目存在的話);如果該傳輸方法不存在,則返回 nullptr。

在某些情況下,即使沒有獲得相關服務,也可能需要立即繼續。例如,當客戶端希望自行管理服務通知或者在需要獲取所有 hwservice 並檢索它們的診斷程序(例如 atrace)中時,可能會發生這種情況。在這種情況下,可以使用其他 API,例如 C++ 中的 tryGetService 或 Java 中的 getService(“instance-name”, false)。Java 中提供的舊版 API getService 也必須與服務通知一起使用。使用此 API 不會避免以下競態條件:當客戶端使用某個非重試 API 請求服務器後,該服務器對自身進行了註冊。

服務終止通知

想要在服務終止時收到通知的客戶端會接收到框架傳送的終止通知。要接收通知,客戶端必須:

  1. 將 HIDL 類/接口 hidl_death_recipient(位於 C++ 代碼中,而非 HIDL 中)歸入子類。
  2. 替換其 serviceDied() 方法。
  3. 實例化 hidl_death_recipient 子類的對象。
  4. 在要監控的服務上調用 linkToDeath() 方法,並傳入 IDeathRecipient 的接口對象。

僞代碼示例(C++ 和 Java 類似):

class IMyDeathReceiver : hidl_death_recipient {
  virtual void serviceDied(uint64_t cookie,
                           wp<IBase>& service) override {
    log("RIP service %d!", cookie);  // Cookie should be 42
  }
};
....
IMyDeathReceiver deathReceiver = new IMyDeathReceiver();
m_importantService->linkToDeath(deathReceiver, 42);

同一終止接收方可能已在多個不同的服務上註冊。

數據轉移

可通過調用 .hal 文件內的接口中定義的方法將數據發送到服務。具體方法有兩類:

  • 阻塞方法會等到服務器產生結果。
  • 單向方法僅朝一個方向發送數據且不阻塞。如果 RPC 調用中正在傳輸的數據量超過實現限制,則調用可能會阻塞或返回錯誤指示(具體行爲尚不確定)。

不返回值但未聲明爲 oneway 的方法仍會阻塞。

在 HIDL 接口中聲明的所有方法都是單向調用,要麼從 HAL 發出,要麼到 HAL。該接口沒有指定具體調用方向。需要從 HAL 發起調用的架構應該在 HAL 軟件包中提供兩個(或更多個)接口並從每個進程提供相應的接口。我們根據接口的調用方向來取名“客戶端”或“服務器”(即 HAL 可以是一個接口的服務器,也可以是另一個接口的客戶端)。

回調

“回調”一詞可以指代兩個不同的概念,可通過“同步回調”和“異步回調”進行區分。

“同步回調”用於返回數據的一些 HIDL 方法。返回多個值(或返回非基元類型的一個值)的 HIDL 方法會通過回調函數返回其結果。如果只返回一個值且該值是基元類型,則不使用回調且該值從方法中返回。服務器實現 HIDL 方法,而客戶端實現回調。

“異步回調”允許 HIDL 接口的服務器發起調用。通過第一個接口傳遞第二個接口的實例即可完成此操作。第一個接口的客戶端必須作爲第二個接口的服務器。第一個接口的服務器可以在第二個接口對象上調用方法。例如,HAL 實現可以通過在由該進程創建和提供的接口對象上調用方法來將信息異步發送回正在使用它的進程。用於異步回調的接口中的方法可以是阻塞方法(並且可能將值返回到調用程序),也可以是 oneway 方法。要查看相關示例,請參閱 HIDL C++ 中的“異步回調”。

要簡化內存所有權,方法調用和回調只能接受 in 參數,並且不支持 out 或 inout 參數。

每事務限制

每事務限制可能會強制限制在 HIDL 方法和回調中發送的數據量。具體限制尚不確定,但可能小至 4K。超出這些限制的調用會立即返回失敗。另一個限制是可供 HIDL 基礎架構處理多個同時進行的事務的資源。由於多個線程或進程向一個進程發送調用或者接收進程未能快速處理多個 oneway 調用,因此多個事務可能會同時進行。

在設計良好的接口中,不應出現超出這些資源限制的情況;如果超出的話,則超出資源的調用可能會阻塞,直到資源可用或發出傳輸錯誤的信號。每當因正在進行的總事務導致出現超出每事務限制或溢出 HIDL 實現資源的情況時,系統都會記錄下來以方便調試。

方法實現

HIDL 生成以目標語言(C++ 或 Java)聲明必要類型、方法和回調的標頭文件。客戶端和服務器代碼的 HIDL 定義方法和回調的原型是相同的。HIDL 系統提供調用程序端(整理 IPC 傳輸的數據)的方法代理實現,並將代碼存根到被調用程序端(將數據傳遞到方法的開發者實現)。

函數的調用程序(HIDL 方法或回調)擁有對傳遞到該函數的數據結構的所有權,並在調用後保留所有權;被調用程序在所有情況下都無需釋放存儲。

  • 在 C++ 中,數據可能是隻讀的(嘗試寫入可能會導致細分錯誤),並且在調用期間有效。客戶端可以深層複製數據,以在調用期間外傳播。
  • 在 Java 中,代碼會接收數據的本地副本(普通 Java 對象),代碼可以保留和修改此數據或允許垃圾回收器回收。
非 RPC 數據轉移

HIDL 在不使用 RPC 調用的情況下通過兩種方法來轉移數據:共享內存和快速消息隊列 (FMQ),只有 C++ 同時支持這兩種方法。

  • 共享內存。*內置 HIDL 類型 memory 用於傳遞表示已分配的共享內存的對象。 可以在接收進程中使用,以映射共享內存。
  • 快速消息隊列 (FMQ)。 HIDL 提供了一種可實現無等待消息傳遞的模板化消息隊列類型。它在直通式或綁定式模式下不使用內核或調度程序(設備間通信將不具有這些屬性)。通常,HAL 會設置其隊列的末尾,從而創建可以藉助內置 HIDL 類型 MQDescriptorSync 或 MQDescriptorUnsync 的參數通過 RPC 傳遞的對象。接收進程可使用此對象設置隊列的另一端。
    • “已同步”隊列不能溢出,且只能有一個讀取器。
    • “未同步”隊列可以溢出,且可以有多個讀取器;每個讀取器必須及時讀取數據,否則數據就會丟失。
      兩種隊列都不能下溢(從空隊列進行讀取將會失敗),且都只能有一個寫入器。

有關 FMQ 的更多詳情,請參閱快速消息隊列 (FMQ)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章