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. Übergangs-HALs sollen nach Möglichkeit ausschließlich AIDL verwenden (wenn Upstream-HALs HIDL verwenden, muss HIDL verwendet werden).

HALs, die AIDL für die Kommunikation zwischen Framework-Komponenten wie denen in system.img und Hardwarekomponenten wie denen in vendor.img verwenden, müssen Stable AIDL verwenden. Für die Kommunikation innerhalb einer Partition, beispielsweise von einem HAL zu einem anderen, gibt es jedoch keine Einschränkung hinsichtlich des 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 nun über Stabilitätsunterstützung verfügt, ist es möglich, einen gesamten Stack mit einer einzigen IPC-Laufzeit zu implementieren. AIDL verfügt außerdem über ein besseres Versionierungssystem als HIDL.

  • Die Verwendung einer einzigen IPC-Sprache bedeutet, dass Sie nur eines lernen, debuggen, optimieren und sichern müssen.
  • AIDL unterstützt die In-Place-Versionierung für die Besitzer einer Schnittstelle:
    • Eigentümer können Methoden am Ende von Schnittstellen oder Felder zu Parcelables hinzufügen. Dies bedeutet, dass es einfacher ist, den Code über die Jahre hinweg 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 und nicht im Typsystem angehängt werden, sodass keine Notwendigkeit besteht, Downstream-Erweiterungen auf neuere Schnittstellenversionen umzubasieren.
  • Eine vorhandene AIDL-Schnittstelle kann direkt verwendet werden, wenn ihr Besitzer sie stabilisieren möchte. 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 annotiert werden.
  • Die Deklaration aidl_interface muss stability: "vintf", enthalten.

Nur der Besitzer 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 damit verbundene 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 Binder-Objekt aufrufen, bevor es gesendet wird zu einem anderen Prozess. Das Herabstufen eines Dienstes auf die Stabilität des Anbieters wird in Java nicht unterstützt, da alle Apps in einem Systemkontext ausgeführt werden.

Deaktivieren Sie außerdem das CPP-Backend, um eine maximale Code-Portabilität zu gewährleisten 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 erklärt, wie Sie das CPP-Backend gezielt auswählen, um es zu deaktivieren.

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

Suche nach 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 anderen hardware/interfaces Unterverzeichnissen in vendor “ oder hardware ablegen.

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-Laufzeitumgebung nicht mit der AOSP-Android-Laufzeitumgebung kompatibel ist. Bei GMS-Geräten ist die Vermeidung einer Änderung dieser Schnittstellen auch der Grund dafür, dass das GSI-Image weiterhin funktionieren kann.

Erweiterungen können auf zwei verschiedene Arten registriert werden:

Wenn jedoch eine Erweiterung registriert ist, besteht keine Möglichkeit eines Zusammenführungskonflikts, wenn herstellerspezifische (d. h. nicht Teil des Upstream-AOSP) Komponenten die Schnittstelle verwenden. Wenn jedoch Downstream-Änderungen an Upstream-AOSP-Komponenten vorgenommen werden, kann es zu Zusammenführungskonflikten kommen, und die folgenden Strategien werden empfohlen:

  • Die Schnittstellenerweiterungen können in der nächsten Version auf AOSP übertragen werden
  • Schnittstellenerweiterungen, die mehr Flexibilität ohne Zusammenführungskonflikte ermöglichen, können in der nächsten Version vorinstalliert 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. Beispiel: Ein Bild, von dem Geräteimplementierer erwarten, dass es ein AOSP-definiertes Parcelable ( AospDefinedParcelable erweitern kann, um ihre Mehrwertfunktionen einzuschließen.

Bisher konnten Geräteimplementierer ohne ParcelableHolder eine AOSP-definierte stabile AIDL-Schnittstelle nicht ä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 vorherigen Code zu sehen ist, ist diese Vorgehensweise fehlerhaft, da die vom Geräteimplementierer hinzugefügten Felder möglicherweise einen Konflikt haben, 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;
}

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

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

Abschließend 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>();

Aufbau gegen die AIDL-Laufzeit

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

Namen der AIDL-HAL-Serverinstanzen

Konventionell haben AIDL-HAL-Dienste einen Instanznamen im Format $package.$type/$instance . Beispielsweise wird eine Instanz des Vibrator-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 deklarieren, zum Beispiel so:

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

Konvertieren eines 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 Backends
  • Erstellen Sie Übersetzungsmethoden in den Java-, CPP- und NDK-Backends für die Übersetzung von HIDL-Typen in AIDL-Typen
  • Erstellen Sie Build-Regeln für Übersetzungsbibliotheken mit erforderlichen Abhängigkeiten
  • Erstellen Sie statische Asserts, um sicherzustellen, dass HIDL- und AIDL-Enumeratoren in den CPP- und NDK-Backends dieselben Werte 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 befindet.

    Die Erstellung dieses Tools aus der neuesten Quelle bietet das umfassendste Erlebnis. 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 vom zu konvertierenden Paket aus.

    Optional können Sie das Argument -l verwenden, um den Inhalt einer neuen Lizenzdatei am Anfang aller generierten Dateien hinzuzufügen. Stellen Sie sicher, dass Sie die richtige Lizenz und das richtige Datum verwenden.

    hidl2aidl -o <output directory> -l <file with license> <package>
    

    Zum Beispiel:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Lesen Sie die generierten Dateien durch und beheben Sie etwaige Probleme bei der Konvertierung.

    • conversion.log enthält alle nicht behandelten 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.
    • Überprüfen Sie die Annotation @JavaDerive auf Funktionen, die möglicherweise benötigt werden, z. B. toString oder equals .
  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 deren generierten Code, der nicht verwendet wird.
  5. Siehe Hauptunterschiede zwischen AIDL und HIDL .

    • Die Verwendung der integrierten Status und Ausnahmefunktionen von AIDL verbessert in der Regel die Schnittstelle und macht einen weiteren schnittstellenspezifischen Statustyp überflüssig.
    • AIDL-Schnittstellenargumente in Methoden sind nicht standardmäßig @nullable , wie dies in HIDL der Fall war.

Separate Richtlinie für AIDL-HALs

Ein AIDL-Diensttyp, der für den Anbietercode sichtbar ist, muss über das Attribut hal_service_type verfügen. 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, hal_service_type;

Für die meisten von der Plattform definierten Dienste ist 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 ein Framework-Client jedoch 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 ein 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 verknüpfen die Makros hal_client_domain und hal_server_domain eine Domäne mit einem bestimmten HAL-Attribut. Wenn beispielsweise der Systemserver ein Client dieser HAL ist, entspricht dies der Richtlinie hal_client_domain(system_server, hal_foo) . Ein HAL-Server umfasst in ähnlicher Weise hal_server_domain(my_hal_domain, hal_foo) . Normalerweise erstellen wir für ein bestimmtes HAL-Attribut auch eine Domäne wie hal_foo_default als Referenz oder als Beispiel-HALs. Einige Geräte nutzen diese Domänen jedoch für ihre eigenen Server. Die Unterscheidung zwischen Domänen für mehrere Server ist nur dann wichtig, wenn wir mehrere Server haben, die dieselbe Schnittstelle bedienen und in ihren Implementierungen einen unterschiedlichen Berechtigungssatz 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 aus hal_attribute(foo) ) noch nicht verknüpft. Ein HAL-Attribut wird AIDL-HAL-Diensten mithilfe des Makros hal_attribute_service zugeordnet (HIDL-HALs verwenden das Makro hal_attribute_hwservice ). Zum Beispiel hal_attribute_service(hal_foo, hal_foo_service) . Dies bedeutet, dass hal_foo_client Prozesse an die HAL gelangen 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. Beispielsweise könnten wir hal_attribute_service(hal_foo, hal_foo2_service) sehen. Da dies jedoch impliziert, dass die Dienste immer zusammen verwendet werden, könnten wir im Allgemeinen 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 festlegen, liegen daran, dass der ursprüngliche HAL-Attributname nicht allgemein genug ist und nicht geändert werden kann.

Alles in allem 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, hal_service_type, 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_use(hal_foo_server)

    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
    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 Ihren Erwartungen entspricht. Erweiterungen können nur über den Prozess festgelegt werden, der einen Binder bedient.

Angehängte Erweiterungen sollten immer dann verwendet werden, wenn eine Erweiterung die Funktionalität eines vorhandenen HAL ändert. Wenn eine völlig neue Funktionalität benötigt wird, muss dieser Mechanismus nicht verwendet werden und eine Erweiterungsschnittstelle kann direkt beim Service-Manager registriert werden. Angehängte Erweiterungsschnittstellen sind am sinnvollsten, wenn sie an Unterschnittstellen angehängt sind, da diese Hierarchien tief sein oder mehrere Instanzen aufweisen können. Die Verwendung einer globalen Erweiterung zum Spiegeln der Binder-Schnittstellenhierarchie eines anderen Dienstes würde eine umfangreiche Buchhaltung erfordern, um gleichwertige Funktionalität wie direkt angehängte 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
  • Im Rust-Backend: binder::Binder::set_extension

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
  • Im Rust-Backend: binder::Binder::get_extension

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

Große 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 ähnelt eher der von Java. Die HIDL-Syntax ähnelt der von C++.
  • Alle AIDL-Schnittstellen verfügen über integrierte Fehlerstatus. Anstatt benutzerdefinierte Statustypen zu erstellen, erstellen Sie konstante Status-Ints in Schnittstellendateien und verwenden Sie 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 Thread-Verwaltung ).
  • 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 Rückrufe“).
  • AIDL verwendet ein fd als primitiven Typ anstelle eines Handles.
  • HIDL verwendet Hauptversionen für inkompatible Änderungen und Nebenversionen für kompatible Änderungen. In AIDL werden abwärtskompatible Änderungen direkt vorgenommen. AIDL hat kein explizites Konzept für Hauptversionen; Stattdessen wird dies in Paketnamen integriert. AIDL könnte beispielsweise den Paketnamen bluetooth2 verwenden.
  • AIDL erbt standardmäßig nicht die Echtzeitpriorität. Die Funktion setInheritRt muss pro Binder verwendet werden, um die Prioritätsvererbung in Echtzeit zu ermöglichen.