AIDL für HALs

Android 11 führt die Möglichkeit ein, AIDL für HALs in Android zu verwenden. Dadurch ist es möglich, Teile von Android ohne HIDL zu implementieren. Übergang von HALs zur ausschließlichen Verwendung von AIDL, sofern möglich (wenn Upstream-HALs HIDL verwenden, muss HIDL verwendet werden).

HALs, die AIDL verwenden, um zwischen Framework-Komponenten, wie z. B. in system.img , und Hardwarekomponenten, wie z. B. in vendor.img , zu kommunizieren, müssen Stable AIDL verwenden. Um jedoch innerhalb einer Partition zu kommunizieren, beispielsweise von einer HAL zu einer anderen, gibt es keine Beschränkung für den zu verwendenden IPC-Mechanismus.

Motivation

AIDL gibt es schon länger als HIDL und wird an vielen anderen Stellen verwendet, beispielsweise zwischen Android-Framework-Komponenten oder in Apps. Da AIDL jetzt Stabilitätsunterstützung bietet, ist es möglich, einen ganzen Stack mit einer einzigen IPC-Laufzeitumgebung zu implementieren. AIDL hat auch ein besseres Versionierungssystem als HIDL.

  • Die Verwendung einer einzigen IPC-Sprache bedeutet, dass Sie nur eine Sache lernen, debuggen, optimieren und sichern müssen.
  • AIDL unterstützt die direkte Versionierung für die Eigentümer einer Schnittstelle:
    • Eigentümer können Methoden am Ende von Schnittstellen oder Felder zu Parcelables hinzufügen. Dies bedeutet, dass es im Laufe der Jahre einfacher ist, den Code zu versionieren, und auch die jährlichen Kosten geringer sind (Typen können vor Ort geändert werden, und es sind keine zusätzlichen Bibliotheken für jede Schnittstellenversion erforderlich).
    • Erweiterungsschnittstellen können zur Laufzeit anstatt im Typsystem angefügt werden, sodass nachgelagerte Erweiterungen nicht auf neuere Schnittstellenversionen umbasiert werden müssen.
  • Eine vorhandene AIDL-Schnittstelle kann direkt verwendet werden, wenn ihr Besitzer sich entscheidet, sie zu stabilisieren. Zuvor musste eine vollständige Kopie der Schnittstelle in HIDL erstellt werden.

Schreiben einer AIDL-HAL-Schnittstelle

Damit eine AIDL-Schnittstelle zwischen System und Anbieter verwendet werden kann, sind zwei Änderungen an der Schnittstelle erforderlich:

  • Jede Typdefinition muss mit @VintfStability kommentiert werden.
  • Die aidl_interface Deklaration muss stability: "vintf", .

Nur der Eigentümer einer Schnittstelle kann diese Änderungen vornehmen.

Wenn Sie diese Änderungen vornehmen, muss sich die Schnittstelle im VINTF-Manifest befinden , damit sie funktioniert. Testen Sie dies (und verwandte Anforderungen, z. B. die Überprüfung, ob freigegebene Schnittstellen eingefroren sind) mit dem VTS-Test vts_treble_vintf_vendor_test . Sie können eine @VintfStability Schnittstelle ohne diese Anforderungen verwenden, indem Sie entweder AIBinder_forceDowngradeToLocalStability im NDK-Backend, android::Stability::forceDowngradeToLocalStability im C++-Backend oder android.os.Binder#forceDowngradeToSystemStability im Java-Backend für ein Binderobjekt aufrufen, bevor es gesendet wird zu einem anderen Prozess. Das Herunterstufen eines Dienstes auf Anbieterstabilität wird in Java nicht unterstützt, da alle Apps in einem Systemkontext ausgeführt werden.

Deaktivieren Sie außerdem das CPP-Back-End, um eine maximale Codeportabilität zu erreichen und potenzielle Probleme wie unnötige zusätzliche Bibliotheken zu vermeiden.

Beachten Sie, dass die Verwendung von backends im folgenden Codebeispiel korrekt ist, da es drei Backends gibt (Java, NDK und CPP). Der folgende Code zeigt, wie das CPP-Backend speziell ausgewählt wird, um es zu deaktivieren.

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

Suchen von AIDL HAL-Schnittstellen

AOSP Stable AIDL-Schnittstellen für HALs befinden sich in denselben Basisverzeichnissen wie HIDL-Schnittstellen, in aidl Ordnern.

  • Hardware/Schnittstellen
  • Frameworks/Hardware/Schnittstellen
  • System/Hardware/Schnittstellen

Sie sollten Erweiterungsschnittstellen in andere hardware/interfaces Unterverzeichnisse im vendor oder hardware legen.

Erweiterungsschnittstellen

Android verfügt mit jeder Version über eine Reihe offizieller AOSP-Schnittstellen. Wenn Android-Partner diesen Schnittstellen Funktionen hinzufügen möchten, sollten sie diese nicht direkt ändern, da dies bedeuten würde, dass ihre Android-Laufzeit nicht mit der AOSP-Android-Laufzeit kompatibel ist. Bei GSM-Geräten stellt die Vermeidung der Änderung dieser Schnittstellen auch sicher, dass das GSI-Image weiterhin funktionieren kann.

Erweiterungen können auf zwei verschiedene Arten registriert werden:

Wenn jedoch eine Erweiterung registriert wird, wenn herstellerspezifische Komponenten (d. h. nicht Teil des Upstream-AOSP) die Schnittstelle verwenden, besteht keine Möglichkeit eines Zusammenführungskonflikts. Wenn jedoch Downstream-Änderungen an Upstream-AOSP-Komponenten vorgenommen werden, können Zusammenführungskonflikte entstehen, und die folgenden Strategien werden empfohlen:

  • die Interface-Ergänzungen können im nächsten Release auf AOSP hochgeladen werden
  • Schnittstellenergänzungen, die weitere Flexibilität ohne Zusammenführungskonflikte ermöglichen, können in der nächsten Version hochgeladen werden

Erweiterung Parcelables: ParcelableHolder

ParcelableHolder ist ein Parcelable , das ein anderes Parcelable enthalten kann. Der Hauptanwendungsfall von ParcelableHolder besteht darin, ein Parcelable erweiterbar zu machen. Zum Beispiel Bilder, von denen Geräteimplementierer erwarten, dass sie in der Lage sein werden, ein AOSP-definiertes Parcelable , AospDefinedParcelable , um ihre Mehrwertfunktionen zu erweitern.

Zuvor konnten Geräteimplementierer ohne ParcelableHolder keine AOSP-definierte stabile AIDL-Schnittstelle ändern, da es ein Fehler wäre, weitere Felder hinzuzufügen:

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

Wie im vorangehenden Code zu sehen ist, wird diese Praxis gebrochen, da die vom Geräteimplementierer hinzugefügten Felder möglicherweise einen Konflikt verursachen, wenn Parcelable in den nächsten Versionen von Android überarbeitet wird.

Mit ParcelableHolder kann der Eigentümer eines Parcelable einen Erweiterungspunkt in einem Parcelable definieren.

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

Dann können die Geräteimplementierer ihr eigenes Parcelable für ihre Erweiterung definieren.

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

Schließlich kann das neue Parcelable über das Feld ParcelableHolder an das ursprüngliche Parcelable angehängt werden.


// 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>();

Erstellen gegen die AIDL-Laufzeit

AIDL hat drei verschiedene Backends: Java, NDK, CPP. Um Stable AIDL zu verwenden, müssen Sie immer die Systemkopie von libbinder unter system/lib*/libbinder.so und auf /dev/binder sprechen. Für Code auf dem Anbieter-Image bedeutet dies, dass libbinder (vom VNDK) nicht verwendet werden kann: Diese Bibliothek hat eine instabile C++-API und instabile Interna. Stattdessen muss nativer Anbietercode das NDK-Backend von AIDL verwenden, mit libbinder_ndk (das vom System libbinder.so unterstützt wird) und mit den von aidl_interface Einträgen erstellten -ndk_platform Bibliotheken verknüpfen.

AIDL HAL-Serverinstanznamen

Gemäß Konvention haben AIDL-HAL-Dienste einen Instanznamen im Format $package.$type/$instance . Beispielsweise wird eine Instanz des Vibrators HAL als android.hardware.vibrator.IVibrator/default registriert.

Schreiben eines AIDL-HAL-Servers

@VintfStability AIDL-Server müssen im VINTF-Manifest deklariert werden, zum Beispiel so:

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

Andernfalls sollten sie einen AIDL-Dienst normal registrieren. Beim Ausführen von VTS-Tests wird erwartet, dass alle deklarierten AIDL-HALs verfügbar sind.

Schreiben eines AIDL-Clients

AIDL-Clients müssen sich in der Kompatibilitätsmatrix beispielsweise so deklarieren:

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

Konvertieren einer vorhandenen HAL von HIDL in AIDL

Verwenden Sie das Tool hidl2aidl , um eine HIDL-Schnittstelle in AIDL zu konvertieren.

hidl2aidl Funktionen:

  • Erstellen Sie .aidl Dateien basierend auf den .hal Dateien für das angegebene Paket
  • Erstellen Sie Build-Regeln für das neu erstellte AIDL-Paket mit allen aktivierten Back-Ends
  • Erstellen Sie Übersetzungsmethoden in den Java-, CPP- und NDK-Backends zum Übersetzen von den HIDL-Typen in die AIDL-Typen
  • Erstellen Sie Build-Regeln für Übersetzungsbibliotheken mit erforderlichen Abhängigkeiten
  • Erstellen Sie statische Asserts, um sicherzustellen, dass HIDL- und AIDL-Enumeratoren dieselben Werte in den CPP- und NDK-Back-Ends haben

Befolgen Sie diese Schritte, um ein Paket von .hal-Dateien in .aidl-Dateien zu konvertieren:

  1. Erstellen Sie das Tool, das sich in system/tools/hidl/hidl2aidl .

    Das Erstellen dieses Tools aus der neuesten Quelle bietet die umfassendste Erfahrung. Sie können die neueste Version verwenden, um Schnittstellen auf älteren Zweigen aus früheren Versionen zu konvertieren.

    m hidl2aidl
    
  2. Führen Sie das Tool mit einem Ausgabeverzeichnis gefolgt von dem zu konvertierenden Paket aus.

    hidl2aidl -o <output directory> <package>
    

    Beispielsweise:

    hidl2aidl -o . android.hardware.nfc@1.2
    
  3. Lesen Sie die generierten Dateien durch und beheben Sie alle Probleme mit der Konvertierung.

    • conversion.log enthält alle unbehandelten Probleme, die zuerst behoben werden müssen.
    • Die generierten .aidl Dateien enthalten möglicherweise Warnungen und Vorschläge, die möglicherweise Maßnahmen erfordern. Diese Kommentare beginnen mit // .
    • Nutzen Sie die Gelegenheit, das Paket aufzuräumen und zu verbessern.
  4. Erstellen Sie nur die Ziele, die Sie benötigen.

    • Deaktivieren Sie Backends, die nicht verwendet werden. Bevorzugen Sie das NDK-Backend gegenüber dem CPP-Backend, siehe Laufzeit auswählen .
    • Entfernen Sie Übersetzungsbibliotheken oder ihren generierten Code, der nicht verwendet wird.
  5. Siehe Hauptunterschiede zwischen AIDL und HIDL .

    • Die Verwendung des integrierten Status und der Ausnahmen von AIDL verbessert in der Regel die Schnittstelle und beseitigt die Notwendigkeit für einen anderen schnittstellenspezifischen Statustyp.

Sepolicy für AIDL HALs

Ein AIDL-Diensttyp, der für Anbietercode sichtbar ist, muss das Attribut vendor_service haben. Ansonsten ist die sepolicy-Konfiguration dieselbe wie bei jedem anderen AIDL-Dienst (obwohl es spezielle Attribute für HALs gibt). Hier ist eine Beispieldefinition eines HAL-Dienstkontexts:

    type hal_foo_service, service_manager_type, vendor_service;

Für die meisten von der Plattform definierten Dienste wird bereits ein Dienstkontext mit dem richtigen Typ hinzugefügt (z. B. wäre android.hardware.foo.IFoo/default bereits als hal_foo_service markiert). Wenn jedoch ein Framework-Client mehrere Instanznamen unterstützt, müssen zusätzliche Instanznamen in gerätespezifischen service_contexts -Dateien hinzugefügt werden.

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

HAL-Attribute müssen hinzugefügt werden, wenn wir einen neuen HAL-Typ erstellen. Ein bestimmtes HAL-Attribut kann mehreren Diensttypen zugeordnet sein (von denen jeder mehrere Instanzen haben kann, wie wir gerade besprochen haben). Für eine HAL, foo , haben wir hal_attribute(foo) . Dieses Makro definiert die Attribute hal_foo_client und hal_foo_server . Für eine bestimmte Domäne ordnen die Makros hal_client_domain und hal_server_domain eine Domäne einem bestimmten HAL-Attribut zu. Beispielsweise entspricht der Systemserver, der ein Client dieser HAL ist, der Richtlinie hal_client_domain(system_server, hal_foo) . Ein HAL-Server enthält in ähnlicher Weise hal_server_domain(my_hal_domain, hal_foo) . Typischerweise erstellen wir für ein bestimmtes HAL-Attribut auch eine Domäne wie hal_foo_default als Referenz oder Beispiel-HALs. Einige Geräte verwenden diese Domänen jedoch für ihre eigenen Server. Die Unterscheidung zwischen Domänen für mehrere Server ist nur dann von Bedeutung, wenn wir mehrere Server haben, die dieselbe Schnittstelle bedienen und in ihren Implementierungen unterschiedliche Berechtigungssätze benötigen. In all diesen Makros ist hal_foo eigentlich kein sepolicy-Objekt. Stattdessen wird dieses Token von diesen Makros verwendet, um auf die Gruppe von Attributen zu verweisen, die einem Client-Server-Paar zugeordnet sind.

Bisher haben wir jedoch hal_foo_service und hal_foo (das Attributpaar von hal_attribute(foo) ) noch nicht verknüpft. Ein HAL-Attribut wird mithilfe des Makros hal_attribute_service mit AIDL-HAL-Diensten hal_attribute_service (HIDL-HALs verwenden das Makro hal_attribute_hwservice ). Zum Beispiel hal_attribute_service(hal_foo, hal_foo_service) . Das bedeutet, dass hal_foo_client Prozesse die HAL abrufen können und hal_foo_server Prozesse die HAL registrieren können. Die Durchsetzung dieser Registrierungsregeln erfolgt durch den Kontextmanager ( servicemanager ). Beachten Sie, dass Dienstnamen möglicherweise nicht immer HAL-Attributen entsprechen. Zum Beispiel könnten wir hal_attribute_service(hal_foo, hal_foo2_service) . Da dies impliziert, dass die Dienste immer zusammen verwendet werden, könnten wir im Allgemeinen jedoch erwägen, den hal_foo2_service zu entfernen und hal_foo_service für alle unsere Dienstkontexte zu verwenden. Die meisten HALs, die mehrere hal_attribute_service setzen, liegen daran, dass der ursprüngliche HAL-Attributname nicht allgemein genug ist und nicht geändert werden kann.

Alles zusammengenommen sieht ein Beispiel-HAL so aus:

    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)

Angeschlossene Erweiterungsschnittstellen

Eine Erweiterung kann an jede Binder-Schnittstelle angehängt werden, unabhängig davon, ob es sich um eine direkt beim Service Manager registrierte Schnittstelle der obersten Ebene oder um eine Unterschnittstelle handelt. Wenn Sie eine Erweiterung erhalten, müssen Sie bestätigen, dass der Typ der Erweiterung wie erwartet ist. Erweiterungen können nur aus dem Prozess dienenden Binder gesetzt werden.

Angehängte Erweiterungen sollten immer dann verwendet werden, wenn eine Erweiterung die Funktionalität einer vorhandenen HAL modifiziert. Wenn eine völlig neue Funktionalität benötigt wird, muss dieser Mechanismus nicht verwendet werden, und eine Erweiterungsschnittstelle kann direkt beim Dienstmanager registriert werden. Angefügte Erweiterungsschnittstellen sind am sinnvollsten, wenn sie an Unterschnittstellen angefügt sind, da diese Hierarchien tief oder aus mehreren Instanzen bestehen können. Die Verwendung einer globalen Erweiterung zum Spiegeln der Binder-Schnittstellenhierarchie eines anderen Dienstes würde eine umfangreiche Buchhaltung erfordern, um eine gleichwertige Funktionalität wie direkt angeschlossene Erweiterungen bereitzustellen.

Um eine Erweiterung für Binder festzulegen, verwenden Sie die folgenden APIs:

  • Im NDK-Backend: AIBinder_setExtension
  • Im Java-Backend: android.os.Binder.setExtension
  • Im CPP-Backend: android::Binder::setExtension

Um eine Erweiterung für einen Binder zu erhalten, verwenden Sie die folgenden APIs:

  • Im NDK-Backend: AIBinder_getExtension
  • Im Java-Backend: android.os.IBinder.getExtension
  • Im CPP-Backend: android::IBinder::getExtension

Weitere Informationen zu diesen APIs finden Sie in der Dokumentation der Funktion getExtension im entsprechenden Backend. Ein Beispiel zur Verwendung von Erweiterungen finden Sie unter hardware/interfaces/tests/extension/vibrator .

Wesentliche AIDL/HIDL-Unterschiede

Beachten Sie bei der Verwendung von AIDL-HALs oder der Verwendung von AIDL-HAL-Schnittstellen die Unterschiede zum Schreiben von HIDL-HALs.

  • Die Syntax der AIDL-Sprache ist näher an Java. Die HIDL-Syntax ähnelt der von C++.
  • Alle AIDL-Schnittstellen haben eingebaute Fehlerstatus. Anstatt benutzerdefinierte Statustypen zu erstellen, erstellen Sie konstante Statusints in Schnittstellendateien und verwenden EX_SERVICE_SPECIFIC in den CPP/NDK-Backends und ServiceSpecificException im Java-Backend. Siehe Fehlerbehandlung .
  • AIDL startet Threadpools nicht automatisch, wenn Binder-Objekte gesendet werden. Sie müssen manuell gestartet werden (siehe Threadverwaltung ).
  • AIDL bricht bei ungeprüften Transportfehlern nicht ab (HIDL Return bricht bei ungeprüften Fehlern ab).
  • AIDL kann nur einen Typ pro Datei deklarieren.
  • AIDL-Argumente können zusätzlich zum Ausgabeparameter als in/out/inout angegeben werden (es gibt keine "synchronen Callbacks").
  • AIDL verwendet anstelle von handle ein fd als primitiven Typ.
  • HIDL verwendet Hauptversionen für inkompatible Änderungen und Nebenversionen für kompatible Änderungen. In AIDL werden rückwärtskompatible Änderungen vorgenommen. AIDL hat kein explizites Konzept von Hauptversionen; Stattdessen wird dies in Paketnamen integriert. Beispielsweise könnte AIDL den Paketnamen bluetooth2 verwenden.