HAL 的 AIDL

Android 11 引入了在 Android 中將 AIDL 用於 HAL 的功能。這使得在沒有 HIDL 的情況下實現部分 Android 成為可能。盡可能將 HAL 轉換為僅使用 AIDL(當上游 HAL 使用 HIDL 時,必須使用 HIDL)。

使用 AIDL 在框架組件(例如system.img中的組件)和硬件組件(例如vendor.img中的組件)之間進行通信的 HAL 必須使用穩定的 AIDL。但是,要在一個分區內進行通信,例如從一個 HAL 到另一個,對 IPC 機制的使用沒有限制。

動機

AIDL 比 HIDL 存在的時間更長,並且在許多其他地方使用,例如在 Android 框架組件之間或在應用程序中。現在 AIDL 具有穩定性支持,可以使用單個 IPC 運行時實現整個堆棧。 AIDL 還具有比 HIDL 更好的版本控制系統。

  • 使用單一 IPC 語言意味著只需學習、調試、優化和保護一件事。
  • AIDL 支持接口所有者的就地版本控制:
    • 所有者可以將方法添加到接口的末尾,或將字段添加到 parcelables。這意味著多年來版本代碼更容易,而且每年的成本也更小(類型可以就地修改,每個接口版本都不需要額外的庫)。
    • 擴展接口可以在運行時而不是在類型系統中附加,因此無需將下游擴展重新定位到較新版本的接口上。
  • 現有的 AIDL 接口可以在其所有者選擇穩定它時直接使用。以前,必須在 HIDL 中創建接口的完整副本。

編寫 AIDL HAL 接口

對於要在系統和供應商之間使用的 AIDL 接口,該接口需要進行兩處更改:

  • 每個類型定義都必須使用@VintfStability進行註釋。
  • aidl_interface聲明需要包括stability: "vintf",

只有接口的所有者才能進行這些更改。

進行這些更改時,接口必須在VINTF 清單中才能工作。使用 VTS 測試vts_treble_vintf_vendor_test進行測試(以及相關要求,例如驗證已發布的接口是否已凍結)。您可以通過調用 NDK 後端中的AIBinder_forceDowngradeToLocalStability 、C++ 後端中的android::Stability::forceDowngradeToLocalStability或 Java 後端中的android.os.Binder#forceDowngradeToSystemStability來使用@VintfStability接口,而不需要這些要求。到另一個進程。 Java 不支持將服務降級到供應商穩定性,因為所有應用程序都在系統上下文中運行。

此外,為了最大限度地提高代碼可移植性並避免不必要的附加庫等潛在問題,請禁用 CPP 後端。

請注意,在下面的代碼示例中使用backends是正確的,因為有三個後端(Java、NDK 和 CPP)。下面的代碼說明瞭如何專門選擇 CPP 後端以禁用它。

    aidl_interface: {
        ...
        backends: {
            cpp: {
                enabled: false,
            },
        },
    }

查找 AIDL HAL 接口

用於 HAL 的 AOSP 穩定 AIDL 接口與 HIDL 接口位於相同的基本目錄中,位於aidl文件夾中。

  • 硬件/接口
  • 框架/硬件/接口
  • 系統/硬件/接口

您應該將擴展接口放入vendorhardware的其他hardware/interfaces子目錄中。

擴展接口

Android 在每個版本中都有一套官方的 AOSP 接口。當 Android 合作夥伴想要為這些接口添加功能時,他們不應該直接更改這些,因為這意味著他們的 Android 運行時與 AOSP Android 運行時不兼容。對於 GMS 設備,避免更改這些接口也是確保 GSI 映像能夠繼續工作的原因。

擴展可以通過兩種不同的方式註冊:

  • 在運行時,請參閱附加的擴展
  • 獨立,在全球註冊並在 VINTF 中註冊。

然而,一個擴展被註冊,當特定供應商(意味著不是上游 AOSP 的一部分)組件使用該接口時,沒有合併衝突的可能性。但是,當對上游 AOSP 組件進行下游修改時,可能會導致合併衝突,建議採用以下策略:

  • 接口添加可以在下一個版本中上傳到 AOSP
  • 接口添加允許進一步的靈活性,沒有合併衝突,可以在下一個版本中上游

擴展 Parcelables:ParcelableHolder

ParcelableHolder是一個Parcelable ,它可以包含另一個ParcelableParcelableHolder的主要用例是使Parcelable可擴展。例如,設備實現者希望能夠擴展 AOSP 定義的ParcelableAospDefinedParcelable以包含其增值功能的圖像。

以前沒有ParcelableHolder ,設備實現者無法修改 AOSP 定義的穩定 AIDL 接口,因為添加更多字段會出錯:

parcelable AospDefinedParcelable {
  int a;
  String b;
  String x; // ERROR: added by a device implementer
  int[] y; // added by a device implementer
}

從前面的代碼中可以看出,這種做法被打破了,因為在 Android 的下一個版本中修訂 Parcelable 時,設備實現者添加的字段可能會發生衝突。

使用ParcelableHolder ,parcelable 的所有者可以在Parcelable中定義擴展點。

parcelable AospDefinedParcelable {
  int a;
  String b;
  ParcelableHolder extension;
}

然後設備實現者可以為他們的擴展定義他們自己的Parcelable

parcelable OemDefinedParcelable {
  String x;
  int[] y;
}

最後,新的Parcelable可以通過ParcelableHolder字段附加到原始Parcelable上。


// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;

ap.extension.setParcelable(op);

...

OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);

// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();

ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);

...

std::shared_ptr<OemDefinedParcelable> op_ptr;

ap.extension.getParcelable(&op_ptr);

// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);

...

std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);

// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });

ap.extension.set_parcelable(Rc::clone(&op));

...

let op = ap.extension.get_parcelable::<OemDefinedParcelable>();

針對 AIDL 運行時構建

AIDL 有三種不同的後端:Java、NDK、CPP。要使用穩定的 AIDL,您必須始終使用位於system/lib*/libbinder.so的 libbinder 的系統副本並在/dev/binder上進行交談。對於供應商映像上的代碼,這意味著無法使用libbinder (來自 VNDK):該庫具有不穩定的 C++ API 和不穩定的內部結構。相反,本機供應商代碼必須使用 AIDL 的 NDK 後端,鏈接到libbinder_ndk (由系統libbinder.so支持),並鏈接到由aidl_interface條目創建的-ndk_platform庫。

AIDL HAL 服務器實例名稱

按照慣例,AIDL HAL 服務的實例名稱格式$package.$type/$instance 。例如,振動器 HAL 的一個實例註冊為android.hardware.vibrator.IVibrator/default

編寫 AIDL HAL 服務器

@VintfStability AIDL 服務器必須在 VINTF 清單中聲明,例如:

    <hal format="aidl">
        <name>android.hardware.vibrator</name>
        <version>1</version>
        <fqname>IVibrator/default</fqname>
    </hal>

否則,他們應該正常註冊 AIDL 服務。運行 VTS 測試時,預計所有聲明的 AIDL HAL 都可用。

編寫 AIDL 客戶端

AIDL 客戶端必須在兼容性矩陣中聲明自己,例如:

    <hal format="aidl" optional="true">
        <name>android.hardware.vibrator</name>
        <version>1-2</version>
        <interface>
            <name>IVibrator</name>
            <instance>default</instance>
        </interface>
    </hal>

將現有 HAL 從 HIDL 轉換為 AIDL

使用hidl2aidl工具將 HIDL 接口轉換為 AIDL。

hidl2aidl特點:

  • 根據給定包的.hal文件創建.aidl文件
  • 為啟用所有後端的新創建的 AIDL 包創建構建規則
  • 在 Java、CPP 和 NDK 後端創建轉換方法,以將 HIDL 類型轉換為 AIDL 類型
  • 為具有所需依賴項的翻譯庫創建構建規則
  • 創建靜態斷言以確保 HIDL 和 AIDL 枚舉器在 CPP 和 NDK 後端具有相同的值

按照以下步驟將 .hal 文件包轉換為 .aidl 文件:

  1. 構建位於system/tools/hidl/hidl2aidl中的工具。

    從最新來源構建此工具可提供最完整的體驗。您可以使用最新版本從舊版本轉換舊分支上的接口。

    m hidl2aidl
    
  2. 使用輸出目錄執行該工具,然後是要轉換的包。

    hidl2aidl -o <output directory> <package>
    

    例如:

    hidl2aidl -o . android.hardware.nfc@1.2
    
  3. 通讀生成的文件並修復轉換的任何問題。

    • conversion.log包含任何需要首先修復的未處理問題。
    • 生成的.aidl文件可能包含可能需要採取措施的警告和建議。這些註釋以//開頭。
    • 藉此機會清理並改進包。
  4. 僅構建您需要的目標。

    • 禁用不會使用的後端。首選 NDK 後端而不是 CPP 後端,請參閱選擇運行時
    • 刪除不會使用的翻譯庫或其生成的任何代碼。
  5. 請參閱主要 AIDL/HIDL 差異

    • 使用 AIDL 的內置Status和異常通常會改善界面並消除對另一種特定於界面的狀態類型的需求。

AIDL HAL 的 Sepolicy

對供應商代碼可見的 AIDL 服務類型必須具有vendor_service屬性。否則,sepolicy 配置與任何其他 AIDL 服務相同(儘管 HAL 有特殊屬性)。以下是 HAL 服務上下文的示例定義:

    type hal_foo_service, service_manager_type, vendor_service;

對於平台定義的大多數服務,已經添加了具有正確類型的服務上下文(例如, android.hardware.foo.IFoo/default已經被標記為hal_foo_service )。但是,如果框架客戶端支持多個實例名稱,則必須在特定於設備的service_contexts文件中添加其他實例名稱。

    android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0

當我們創建一種新的 HAL 類型時,必須添加 HAL 屬性。一個特定的 HAL 屬性可能與多個服務類型相關聯(正如我們剛剛討論的,每個服務類型都可能有多個實例)。對於 HAL, foo ,我們有hal_attribute(foo) 。此宏定義屬性hal_foo_clienthal_foo_server 。對於給定的域, hal_client_domainhal_server_domain宏將域與給定的 HAL 屬性相關聯。例如,作為此 HAL 客戶端的系統服務器對應於策略hal_client_domain(system_server, hal_foo) 。 HAL 服務器同樣包括hal_server_domain(my_hal_domain, hal_foo) 。通常,對於給定的 HAL 屬性,我們還會創建一個類似hal_foo_default的域以供參考或示例 HAL。但是,某些設備將這些域用於自己的服務器。僅當我們有多個服務器提供相同的接口並且在其實現中需要不同的權限集時,區分多個服務器的域才重要。在所有這些宏中, hal_foo實際上並不是一個 sepolicy 對象。相反,這些宏使用此標記來引用與客戶端服務器對關聯的屬性組。

但是,到目前為止,我們還沒有關聯hal_foo_servicehal_foo (來自hal_attribute(foo)的屬性對)。 HAL 屬性使用hal_attribute_service宏與 AIDL HAL 服務相關聯(HIDL HAL 使用hal_attribute_hwservice宏)。例如, hal_attribute_service(hal_foo, hal_foo_service) 。這意味著hal_foo_client進程可以獲取 HAL,而hal_foo_server進程可以註冊 HAL。這些註冊規則的執行由上下文管理器( servicemanager )完成。請注意,服務名稱可能並不總是對應於 HAL 屬性。例如,我們可能會看到hal_attribute_service(hal_foo, hal_foo2_service) 。不過,一般來說,由於這意味著服務總是一起使用,我們可以考慮刪除hal_foo2_service並將hal_foo_service用於我們所有的服務上下文。大多數設置多個hal_attribute_service的 HAL 是因為原始 HAL 屬性名稱不夠通用,無法更改。

綜上所述,HAL 示例如下所示:

    public/attributes:
    // define hal_foo, hal_foo_client, hal_foo_server
    hal_attribute(foo)

    public/service.te
    // define hal_foo_service
    type hal_foo_service, vendor_service, protected_service, service_manager_type

    public/hal_foo.te:
    // allow binder connection from client to server
    binder_call(hal_foo_client, hal_foo_server)
    // allow client to find the service, allow server to register the service
    hal_attribute_service(hal_foo, hal_foo_service)
    // allow binder communication from server to service_manager
    binder_call(hal_foo_server, servicemanager)

    private/service_contexts:
    // bind an AIDL service name to the selinux type
    android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0

    private/<some_domain>.te:
    // let this domain use the hal service
    binder_use(some_domain)
    hal_client_domain(some_domain, hal_foo)

    vendor/<some_hal_server_domain>.te
    // let this domain serve the hal service
    binder_use(some_hal_server_domain)
    hal_server_domain(some_hal_server_domain, hal_foo)

附加擴展接口

擴展可以附加到任何綁定器接口,無論是直接向服務管理器註冊的頂級接口還是子接口。獲取擴展名時,您必須確認擴展名的類型是否符合預期。只能從為活頁夾服務的進程中設置擴展。

每當擴展修改現有 HAL 的功能時,都應使用附加的擴展。當需要全新的功能時,不需要使用這種機制,可以直接向服務管理器註冊擴展接口。附加的擴展接口在附加到子接口時最有意義,因為這些層次結構可能很深,也可能是多實例的。使用全局擴展來鏡像另一個服務的綁定器接口層次結構將需要大量的簿記以提供與直接附加的擴展等效的功能。

要在 binder 上設置擴展,請使用以下 API:

  • 在 NDK 後端: AIBinder_setExtension
  • 在 Java 後端: android.os.Binder.setExtension
  • 在 CPP 後端: android::Binder::setExtension

要獲得活頁夾的擴展,請使用以下 API:

  • 在 NDK 後端: AIBinder_getExtension
  • 在 Java 後端: android.os.IBinder.getExtension
  • 在 CPP 後端: android::IBinder::getExtension

您可以在相應後端的getExtension函數的文檔中找到有關這些 API 的更多信息。可以在hardware/interfaces/tests/extension/vibrator中找到如何使用擴展的示例。

AIDL/HIDL 的主要區別

使用 AIDL HAL 或使用 AIDL HAL 接口時,請注意與編寫 HIDL HAL 相比的差異。

  • AIDL 語言的語法更接近於 Java。 HIDL 語法類似於 C++。
  • 所有 AIDL 接口都有內置的錯誤狀態。不要創建自定義狀態類型,而是在接口文件中創建常量狀態整數,並在 CPP/NDK 後端使用EX_SERVICE_SPECIFIC並在 Java 後端使用ServiceSpecificException 。請參閱錯誤處理
  • AIDL 不會在發送 binder 對象時自動啟動線程池。它們必須手動啟動(參見線程管理)。
  • AIDL 不會因未檢查的傳輸錯誤而中止(HIDL Return會因未檢查的錯誤而中止)。
  • AIDL 每個文件只能聲明一種類型。
  • 除了輸出參數之外,AIDL 參數還可以指定為 in/out/inout(沒有“同步回調”)。
  • AIDL 使用 fd 作為原始類型而不是句柄。
  • HIDL 對不兼容的更改使用主要版本,對兼容的更改使用次要版本。在 AIDL 中,向後兼容的更改已經到位。 AIDL 沒有明確的主要版本概念;相反,它被合併到包名稱中。例如,AIDL 可能使用包名bluetooth2