Dienstleistungen & Datentransfer

In diesem Abschnitt wird beschrieben, wie Sie Dienste registrieren und erkennen und wie Sie Daten an einen Dienst senden, indem Sie Methoden aufrufen, die in Schnittstellen in .hal Dateien definiert sind.

Registrierung von Diensten

HIDL-Schnittstellenserver (Objekte, die die Schnittstelle implementieren) können als benannte Dienste registriert werden. Der registrierte Name muss nicht mit dem Schnittstellen- oder Paketnamen in Zusammenhang stehen. Wenn kein Name angegeben ist, wird der Name „default“ verwendet; Dies sollte für HALs verwendet werden, die nicht zwei Implementierungen derselben Schnittstelle registrieren müssen. Der in jeder Schnittstelle definierte C++-Aufruf für die Dienstregistrierung lautet beispielsweise:

status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService("another_foo_service");  // if needed

Die Version einer HIDL-Schnittstelle ist in der Schnittstelle selbst enthalten. Es wird automatisch mit der Dienstregistrierung verknüpft und kann über einen Methodenaufruf ( android::hardware::IInterface::getInterfaceVersion() ) auf jeder HIDL-Schnittstelle abgerufen werden. Serverobjekte müssen nicht registriert werden und können über HIDL-Methodenparameter an einen anderen Prozess übergeben werden, der HIDL-Methodenaufrufe an den Server durchführt.

Dienstleistungen entdecken

Anforderungen per Client-Code werden für eine bestimmte Schnittstelle nach Name und Version gestellt, indem getService für die gewünschte HAL-Klasse aufgerufen wird:

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

Jede Version einer HIDL-Schnittstelle wird als separate Schnittstelle behandelt. Somit können sowohl IFooService Version 1.1 als auch IFooService Version 2.2 als „foo_service“ registriert werden und getService("foo_service") auf jeder Schnittstelle ruft den registrierten Dienst für diese Schnittstelle ab. Aus diesem Grund muss in den meisten Fällen kein Namensparameter für die Registrierung oder Erkennung angegeben werden (d. h. Name „Standard“).

Das Vendor Interface Object spielt auch eine Rolle bei der Transportmethode der zurückgegebenen Schnittstelle. Für eine Schnittstelle IFoo im Paket android.hardware.foo@1.0 verwendet die von IFoo::getService zurückgegebene Schnittstelle immer die für android.hardware.foo im Gerätemanifest deklarierte Transportmethode, wenn der Eintrag vorhanden ist; und wenn die Transportmethode nicht verfügbar ist, wird nullptr zurückgegeben.

In manchen Fällen kann es erforderlich sein, sofort fortzufahren, auch ohne den Service in Anspruch zu nehmen. Dies kann beispielsweise passieren, wenn ein Client Dienstbenachrichtigungen selbst oder in einem Diagnoseprogramm (z. B. atrace ) verwalten möchte, das alle HW-Dienste abrufen und abrufen muss. In diesem Fall werden zusätzliche APIs bereitgestellt, z. B. tryGetService in C++ oder getService("instance-name", false) in Java. Die in Java bereitgestellte Legacy-API getService muss auch für Dienstbenachrichtigungen verwendet werden. Die Verwendung dieser API vermeidet nicht die Race-Bedingung, bei der sich ein Server selbst registriert, nachdem der Client dies mit einer dieser No-Retry-APIs angefordert hat.

Benachrichtigungen über Diensttodesfälle

Kunden, die benachrichtigt werden möchten, wenn ein Dienst ausfällt, können vom Framework bereitgestellte Ausfallbenachrichtigungen erhalten. Um Benachrichtigungen zu erhalten, muss der Kunde:

  1. Unterklasse der HIDL-Klasse/Schnittstelle hidl_death_recipient (in C++-Code, nicht in HIDL).
  2. Überschreiben Sie die Methode serviceDied() .
  3. Instanziieren Sie ein Objekt der Unterklasse hidl_death_recipient .
  4. Rufen Sie die Methode linkToDeath() für den zu überwachenden Dienst auf und übergeben Sie dabei das Schnittstellenobjekt von IDeathRecipient . Beachten Sie, dass diese Methode nicht den Besitz des Sterbeempfängers oder des Proxys übernimmt, auf dem sie aufgerufen wird.

Ein Pseudocode-Beispiel (C++ und Java sind ähnlich):

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

Derselbe Sterbeempfänger kann bei mehreren verschiedenen Diensten registriert sein.

Datentransfer

Daten können an einen Dienst gesendet werden, indem Methoden aufgerufen werden, die in Schnittstellen in .hal Dateien definiert sind. Es gibt zwei Arten von Methoden:

  • Blockierende Methoden warten, bis der Server ein Ergebnis geliefert hat.
  • Oneway- Methoden senden Daten nur in eine Richtung und blockieren nicht. Wenn die Menge der in RPC-Aufrufen übertragenen Daten die Implementierungsgrenzen überschreitet, können die Aufrufe entweder blockieren oder eine Fehleranzeige zurückgeben (das Verhalten ist noch nicht bestimmt).

Eine Methode, die keinen Wert zurückgibt, aber nicht als oneway deklariert ist, blockiert immer noch.

Alle in einer HIDL-Schnittstelle deklarierten Methoden werden in einer einzigen Richtung aufgerufen, entweder von der HAL oder in die HAL. Die Schnittstelle gibt nicht an, in welche Richtung sie aufgerufen wird. Architekturen, die Aufrufe von der HAL benötigen, sollten zwei (oder mehr) Schnittstellen im HAL-Paket bereitstellen und die entsprechende Schnittstelle von jedem Prozess bedienen. Die Wörter „Client“ und „Server“ werden in Bezug auf die Aufrufrichtung der Schnittstelle verwendet (dh der HAL kann ein Server einer Schnittstelle und ein Client einer anderen Schnittstelle sein).

Rückrufe

Das Wort Rückruf bezieht sich auf zwei verschiedene Konzepte, die durch synchronen Rückruf und asynchronen Rückruf unterschieden werden.

Synchrone Rückrufe werden in einigen HIDL-Methoden verwendet, die Daten zurückgeben. Eine HIDL-Methode, die mehr als einen Wert zurückgibt (oder einen Wert eines nicht-primitiven Typs zurückgibt), gibt ihre Ergebnisse über eine Rückruffunktion zurück. Wenn nur ein Wert zurückgegeben wird und es sich um einen primitiven Typ handelt, wird kein Rückruf verwendet und der Wert wird von der Methode zurückgegeben. Der Server implementiert die HIDL-Methoden und der Client implementiert die Rückrufe.

Asynchrone Rückrufe ermöglichen es dem Server einer HIDL-Schnittstelle, Aufrufe zu initiieren. Dies geschieht, indem eine Instanz einer zweiten Schnittstelle über die erste Schnittstelle geleitet wird. Der Client der ersten Schnittstelle muss als Server der zweiten fungieren. Der Server der ersten Schnittstelle kann Methoden für das zweite Schnittstellenobjekt aufrufen. Beispielsweise kann eine HAL-Implementierung Informationen asynchron an den Prozess zurücksenden, der sie verwendet, indem sie Methoden für ein Schnittstellenobjekt aufruft, das von diesem Prozess erstellt und bereitgestellt wird. Methoden in Schnittstellen, die für asynchrone Rückrufe verwendet werden, können blockierend (und möglicherweise Werte an den Aufrufer zurückgeben) oder oneway sein. Ein Beispiel finden Sie unter „Asynchrone Rückrufe“ in HIDL C++ .

Um den Speicherbesitz zu vereinfachen, akzeptieren Methodenaufrufe und Rückrufe nur in Parameter und unterstützen keine out oder inout -Parameter.

Limits pro Transaktion

Für die in HIDL-Methoden und Rückrufen gesendete Datenmenge gibt es keine Beschränkungen pro Transaktion. Allerdings gelten Aufrufe, die 4 KB pro Transaktion überschreiten, als übermäßig. Wenn dies auftritt, wird eine Neuarchitektur der gegebenen HIDL-Schnittstelle empfohlen. Eine weitere Einschränkung sind die Ressourcen, die der HIDL-Infrastruktur zur Abwicklung mehrerer gleichzeitiger Transaktionen zur Verfügung stehen. Mehrere Transaktionen können gleichzeitig ausgeführt werden, da mehrere Threads oder Prozesse Aufrufe an einen Prozess senden oder mehrere oneway vom empfangenden Prozess nicht schnell verarbeitet werden. Der maximal verfügbare Gesamtspeicherplatz für alle gleichzeitigen Transaktionen beträgt standardmäßig 1 MB.

In einer gut gestalteten Schnittstelle sollte es nicht passieren, dass diese Ressourcenbeschränkungen überschritten werden. Wenn dies der Fall ist, kann der Aufruf, der diese überschritten hat, entweder blockieren, bis Ressourcen verfügbar werden, oder einen Transportfehler signalisieren. Jedes Vorkommen einer Überschreitung der Grenzwerte pro Transaktion oder eines Überlaufs der HIDL-Implementierungsressourcen durch aggregierte In-Flight-Transaktionen wird protokolliert, um das Debuggen zu erleichtern.

Methodenimplementierungen

HIDL generiert Header-Dateien, die die erforderlichen Typen, Methoden und Rückrufe in der Zielsprache (C++ oder Java) deklarieren. Der Prototyp der HIDL-definierten Methoden und Rückrufe ist für Client- und Servercode derselbe. Das HIDL-System stellt Proxy- Implementierungen der Methoden auf der Aufruferseite bereit, die die Daten für den IPC-Transport organisieren, und Stub- Code auf der Aufruferseite, der die Daten an Entwicklerimplementierungen der Methoden übergibt.

Der Aufrufer einer Funktion (HIDL-Methode oder Rückruf) ist Eigentümer der an die Funktion übergebenen Datenstrukturen und behält auch nach dem Aufruf den Besitz; In allen Fällen muss der Angerufene den Speicher nicht freigeben oder freigeben.

  • In C++ sind die Daten möglicherweise schreibgeschützt (Schreibversuche können einen Segmentierungsfehler verursachen) und für die Dauer des Aufrufs gültig. Der Client kann die Daten tief kopieren, um sie über den Anruf hinaus weiterzugeben.
  • In Java erhält der Code eine lokale Kopie der Daten (ein normales Java-Objekt), die er behalten und ändern oder der Garbage Collection zulassen kann.

Nicht-RPC-Datenübertragung

HIDL bietet zwei Möglichkeiten, Daten ohne RPC-Aufruf zu übertragen: Shared Memory und Fast Message Queue (FMQ), beide werden nur in C++ unterstützt.

  • Geteilte Erinnerung . Der integrierte memory vom Typ HIDL wird verwendet, um ein Objekt zu übergeben, das den zugewiesenen gemeinsam genutzten Speicher darstellt. Kann in einem Empfangsprozess verwendet werden, um den gemeinsam genutzten Speicher abzubilden.
  • Fast Message Queue (FMQ) . HIDL stellt einen Nachrichtenwarteschlangentyp mit Vorlagen bereit, der die Nachrichtenübermittlung ohne Wartezeit implementiert. Der Kernel oder Scheduler wird nicht im Passthrough- oder Binder-Modus verwendet (die Kommunikation zwischen Geräten verfügt nicht über diese Eigenschaften). Normalerweise richtet die HAL ihr Ende der Warteschlange ein und erstellt ein Objekt, das über RPC über einen Parameter des integrierten HIDL-Typs MQDescriptorSync oder MQDescriptorUnsync übergeben werden kann. Dieses Objekt kann vom empfangenden Prozess verwendet werden, um das andere Ende der Warteschlange einzurichten.
    • Synchronisierungswarteschlangen dürfen nicht überlaufen und können nur einen Leser haben.
    • Nicht synchronisierte Warteschlangen dürfen überlaufen und können viele Leser haben, von denen jeder Daten rechtzeitig lesen muss, sonst gehen sie verloren.
    Keiner der Typen darf einen Unterlauf aufweisen (das Lesen aus einer leeren Warteschlange schlägt fehl), und jeder Typ kann nur einen Schreiber haben.

Weitere Einzelheiten zu FMQ finden Sie unter Fast Message Queue (FMQ) .