Modell-Threading

Methoden, die als oneway gekennzeichnet sind, werden nicht blockiert. Bei Methoden, die nicht als oneway gekennzeichnet sind, wird der Methodenaufruf eines Clients blockiert, bis der Server die Ausführung abgeschlossen oder einen synchronen Rückruf aufgerufen hat (je nachdem, was zuerst eintritt). Servermethodenimplementierungen können maximal einen synchronen Rückruf aufrufen. Zusätzliche Rückrufaufrufe werden verworfen und als Fehler protokolliert. Wenn eine Methode Werte über einen Callback zurückgeben soll und den Callback nicht aufruft, wird dies als Fehler protokolliert und dem Client als Transportfehler gemeldet.

Threads im Passthrough-Modus

Im Passthrough-Modus sind die meisten Aufrufe synchron. Damit oneway-Aufrufe den Client jedoch nicht blockieren, wird für jeden Prozess ein Thread erstellt. Weitere Informationen finden Sie in der HIDL-Übersicht.

Threads in binderisierten HALs

Für eingehende RPC-Aufrufe (einschließlich asynchroner Rückrufe von HALs an HAL-Nutzer) und Todesbenachrichtigungen wird jedem Prozess, der HIDL verwendet, ein Threadpool zugeordnet. Wenn ein einzelner Prozess mehrere HIDL-Schnittstellen und/oder Handler für den Tod von Prozessen implementiert, wird sein Threadpool von allen gemeinsam genutzt. Wenn ein Prozess einen eingehenden Methodenaufruf von einem Client empfängt, wählt er einen kostenlosen Thread aus dem Threadpool aus und führt den Aufruf in diesem Thread aus. Wenn kein kostenloser Thread verfügbar ist, wird der Vorgang blockiert, bis einer verfügbar ist.

Wenn der Server nur einen Thread hat, werden die Aufrufe an den Server in der Reihenfolge ausgeführt. Ein Server mit mehreren Threads führt Anrufe möglicherweise nicht in der richtigen Reihenfolge aus, auch wenn der Client nur einen Thread hat. Bei einem bestimmten Interface-Objekt sind oneway-Aufrufe jedoch garantiert sortiert (siehe Server-Threading-Modell). Bei einem mehrstufigen Server, der mehrere Schnittstellen hostet, können oneway Aufrufe verschiedener Schnittstellen gleichzeitig oder mit anderen blockierenden Aufrufen verarbeitet werden.

Mehrere verschachtelte Aufrufe werden über denselben hwbinder-Thread gesendet. Wenn beispielsweise ein Prozess A einen synchronen Aufruf von einem hwbinder-Thread an Prozess B sendet und dann Prozess B einen synchronen Rückruf an Prozess A sendet, wird der Aufruf im ursprünglichen hwbinder-Thread in A ausgeführt, der durch den ursprünglichen Aufruf blockiert ist. Diese Optimierung ermöglicht es, dass ein einzelner Thread-Server verschachtelte Aufrufe verarbeiten kann. Sie gilt jedoch nicht für Fälle, in denen die Aufrufe durch eine andere Sequenz von IPC-Aufrufen geleitet werden. Wenn beispielsweise Prozess B einen Binder-/VndBinder-Aufruf durchgeführt hat, der einen Prozess C aufgerufen hat, und Prozess C dann zurück zu A ruft, kann er nicht im ursprünglichen Thread in A ausgeführt werden.

Server-Threading-Modell

Mit Ausnahme des Passthrough-Modus laufen Serverimplementierungen von HIDL-Schnittstellen in einem anderen Prozess als der Client und benötigen einen oder mehrere Threads, die auf eingehende Methodenaufrufe warten. Diese Threads sind der Threadpool des Servers. Der Server kann festlegen, wie viele Threads in seinem Threadpool ausgeführt werden sollen, und eine Threadpoolgröße von 1 verwenden, um alle Aufrufe an seinen Schnittstellen zu serialisieren. Wenn der Server mehr als einen Thread im Threadpool hat, kann er gleichzeitig eingehende Aufrufe über eine beliebige seiner Schnittstellen erhalten. In C++ bedeutet das, dass freigegebene Daten sorgfältig gesperrt werden müssen.

Einwegaufrufe an dieselbe Schnittstelle werden serialisiert. Wenn ein mehrstufiger Client method1 und method2 über die Schnittstelle IFoo und method3 über die Schnittstelle IBar aufruft, werden method1 und method2 immer serialisiert, method3 kann jedoch parallel zu method1 und method2 ausgeführt werden.

Ein einzelner Client-Ausführungsthread kann auf zwei Arten zu einer gleichzeitigen Ausführung auf einem Server mit mehreren Threads führen:

  • oneway-Anrufe werden nicht blockiert. Wenn ein oneway-Aufruf ausgeführt und dann ein Nicht-oneway-Aufruf aufgerufen wird, kann der Server den oneway-Aufruf und den Nicht-oneway-Aufruf gleichzeitig ausführen.
  • Servermethoden, die Daten mit synchronen Callbacks zurückgeben, können die Blockierung des Clients aufheben, sobald der Callback vom Server aufgerufen wird.

Bei der zweiten Methode kann jeder Code in der Serverfunktion, der nach dem Aufruf des Rückrufs ausgeführt wird, gleichzeitig ausgeführt werden. Der Server verarbeitet dann nachfolgende Aufrufe vom Client. Dazu gehören Code in der Serverfunktion und automatische Destruktoren, die am Ende der Funktion ausgeführt werden. Wenn der Server mehr als einen Thread im Threadpool hat, treten Probleme mit der Parallelität auf, auch wenn nur ein einzelner Client-Thread Aufrufe sendet. Wenn eine HAL, die von einem Prozess bereitgestellt wird, mehrere Threads benötigt, haben alle HALs mehrere Threads, da der Threadpool pro Prozess freigegeben wird.

Sobald der Server den angegebenen Rückruf aufruft, kann der Transport den implementierten Rückruf auf dem Client aufrufen und die Blockierung des Clients aufheben. Der Client führt parallel zu den Aktionen der Serverimplementierung nach dem Aufruf des Rückrufs fort (z. B. das Ausführen von Destruktoren). Code in der Serverfunktion nach dem Rückruf blockiert den Client nicht mehr (solange der Server-Threadpool genügend Threads hat, um eingehende Aufrufe zu verarbeiten), kann aber gleichzeitig mit zukünftigen Aufrufen vom Client ausgeführt werden (es sei denn, der Server-Threadpool hat nur einen Thread).

Zusätzlich zu synchronen Callbacks können oneway-Aufrufe von einem Client mit nur einem Thread von einem Server mit mehreren Threads in seinem Threadpool gleichzeitig verarbeitet werden. Dies ist jedoch nur möglich, wenn diese oneway-Aufrufe auf verschiedenen Schnittstellen ausgeführt werden. oneway-Aufrufe auf derselben Schnittstelle werden immer serialisiert.

Hinweis:Wir empfehlen dringend, dass Serverfunktionen so schnell wie möglich zurückkehren, nachdem sie die Rückruffunktion aufgerufen haben.

Beispiel (in C++):

Return<void> someMethod(someMethod_cb _cb) {
    // Do some processing, then call callback with return data
    hidl_vec<uint32_t> vec = ...
    _cb(vec);
    // At this point, the client's callback is called,
    // and the client resumes execution.
    ...
    return Void(); // is basically a no-op
};

Client-Threading-Modell

Das Threading-Modell auf dem Client unterscheidet zwischen nicht blockierenden Aufrufen (Funktionen, die mit dem Keyword oneway gekennzeichnet sind) und blockierenden Aufrufen (Funktionen, für die das Keyword oneway nicht angegeben ist).

Anrufe blockieren

Bei blockierenden Aufrufen blockiert der Client, bis eines der folgenden Ereignisse eintritt:

  • Es tritt ein Transportfehler auf. Das Return-Objekt enthält einen Fehlerstatus, der mit Return::isOk() abgerufen werden kann.
  • Die Serverimplementierung ruft den Callback auf (falls vorhanden).
  • Die Serverimplementierung gibt einen Wert zurück (wenn kein Rückrufparameter vorhanden war).

Bei Erfolg wird die vom Client als Argument übergebene Callback-Funktion immer vom Server aufgerufen, bevor die Funktion selbst zurückgegeben wird. Der Rückruf wird in demselben Thread ausgeführt, in dem der Funktionsaufruf erfolgt. Daher müssen Entwickler bei der Sperrung von Ressourcen während Funktionsaufrufen vorsichtig sein und sie nach Möglichkeit vermeiden. Eine Funktion ohne generates-Anweisung oder oneway-Keyword wird weiterhin blockiert. Der Client wartet, bis der Server ein Return<void>-Objekt zurückgibt.

Einseitige Anrufe

Wenn eine Funktion mit oneway gekennzeichnet ist, kehrt der Client sofort zurück und wartet nicht, bis der Server den Funktionsaufruf abgeschlossen hat. Auf den ersten Blick (und insgesamt) bedeutet dies, dass der Funktionsaufruf nur noch die Hälfte der Zeit in Anspruch nimmt, da nur noch die Hälfte des Codes ausgeführt wird. Bei der Erstellung von leistungskritischen Implementierungen hat dies jedoch einige Auswirkungen auf die Planung. Normalerweise wird der Aufrufer bei einem einseitigen Aufruf weiterhin geplant, während bei einem normalen synchronen Aufruf der Scheduler sofort vom Aufrufer zum aufgerufenen Prozess übergeht. Dies ist eine Leistungsoptimierung in binder. Bei Diensten, bei denen der unidirektionale Aufruf im Zielprozess mit hoher Priorität ausgeführt werden muss, kann die Planungsrichtlinie des empfangenden Dienstes geändert werden. In C++ wird durch die Verwendung der Methode setMinSchedulerPolicy von libhidltransport mit den in sched.h definierten Planungsprioritäten und ‑richtlinien sichergestellt, dass alle Aufrufe des Dienstes mindestens mit der festgelegten Planungsrichtlinie und Priorität ausgeführt werden.