Binder – oder genauer gesagt das Binder-Framework – ist das wichtigste System für die Interprozesskommunikation in Android. Das Binder-Framework ist älter als das Android-Betriebssystem. Es geht auf das OpenBinder-Projekt zurück, das im Jahr 2005 auf Linux portiert wurde (weitere Informationen finden Sie in den Referenzen). OpenBinder wurde in den frühen Versionen von Android unverändert übernommen, wurde jedoch um das Jahr 2008 herum komplett neu implementiert.
Das Binder-Framework besteht aus vielen verschiedenen Komponenten. Im Folgenden werden der Aufbau des Binder-Frameworks und die Terminologie der Komponenten näher erläutert.
Figure 1: Die Struktur des Binder Frameworks
| Name | Beschreibung |
| Binder-Treiber | Im Kern befindet sich der Binder-Treiber als Teil des Linux-Kernels von Android. Der Binder-Treiber ist für die Übertragung der Daten von einem Prozess zum anderen zuständig. |
| Binder-Protokoll | Das Binder-Protokoll ist ein Low-Level-Protokoll. Es basiert auf ioctl und wird für die Kommunikation mit dem Binder-Treiber verwendet. |
| IBinder-Schnittstelle | Die IBinder-Schnittstelle ist eine Java-/Kotlin-Schnittstelle, die Methoden für die Binder-Objekte definiert. Diese sind erforderlich, damit eine Android-Anwendung tatsächlich eine IPC-Kommunikation durchführen kann. |
| Binder (Objekt) | Das Binder-Objekt ist eine generische Implementierung der IBinder-Schnittstelle. |
| Binder-Token | Ein abstrakter 32-Bit-Ganzzahlwert, der als eindeutige Kennung für ein Binder-Objekt über alle Prozesse hinweg im Android-Betriebssystem verwendet wird. |
| AIDL | Die Android Interface Definition Language (AIDL) wird verwendet, um Geschäftsoperationen auf einer IBinder-Schnittstelle zu beschreiben. |
| Binder-Dienst | Der Binder-Dienst ist eine konkrete Implementierung des Binder-Objekts. Ein Binder-Dienst kann mithilfe von AIDL erstellt werden, indem die definierten Geschäftsoperationen implementiert werden. Der Binder-Dienst ist die Komponente, mit der eine App tatsächlich interagiert. Wenn eine App den Standort benötigt, interagiert sie mit dem Binder-Dienst für den Standort. |
| Binder-Client | Der Binder-Client ist das Objekt, das die von einem Binder-Dienst angebotenen Funktionen nutzen möchte. Zum Beispiel die Komponente der QR-Code-Scanner-App, die die Kamerafunktionen einer installierten Kamera-App anfordert. |
| Binder- Transaktion | Der Vorgang des Aufrufens einer Operation, d. h. einer Methode, an einem entfernten Binder-Objekt, was das Senden und Empfangen von Daten über das Binder-Protokoll beinhalten kann. |
| Parcel | Der Container für eine Nachricht (Daten und Objektreferenzen), die über einen IBinder gesendet werden kann. Eine Einheit transaktionaler Daten – eine für die ausgehende Anfrage und eine weitere für die eingehende Antwort. |
| Marshalling | Das Verfahren zur Konvertierung von Datenstrukturen, die Primitive wie Integer oder Double sowie übergeordnete Datenstrukturen wie Objekte enthalten, in Pakete, um sie in die Binder-Transaktion einzubetten. |
| Unmarshalling | Das Verfahren zur Rekonstruktion von primitiven oder höherwertigen Datenstrukturen aus Paketen, die über eine Binder-Transaktion empfangen wurden. |
| Proxy | Eine Implementierung der AIDL-Schnittstelle, die Daten un/marshalled und Methodenaufrufe auf Transaktionen abbildet, die über eine umschlossene IBinder-Schnittstelle an das Binder-Objekt übermittelt werden. |
| Stub | Eine Teilimplementierung der AIDL-Schnittstelle, die Transaktionen während des Un- und Marshalling von Daten auf Binder-Dienstmethodenaufrufe abbildet. |
| Kontext Manager (servicemanager): | Ein spezielles Binder-Objekt, das als Registrierungs-/Lookup-Dienst für andere Binder-Objekte dient (Zuordnung von Namen zu Handles). Der Context Manager ermöglicht die Erkennung anderer Binder-Dienste im Betriebssystem. |
Wie funktioniert die Kommunikation mit dem Android Binder Framework eigentlich? Wir können diese Frage aus verschiedenen Blickwinkeln betrachten. Jeder weitere Blickwinkel führt uns tiefer in die Interna von Android.
Beginnen wir mit einer QR-Code-Scanner-App als Client. In diesem Zusammenhang ist der Client die Anwendung, die die Funktionen einer anderen Anwendung, beispielsweise der Kamera-App, aufruft. Um einen QR-Code zu scannen, nutzt die QR-Code-Scanner-App eine implementierte Funktion zur Interaktion mit der Kamera. Aus einer vereinfachten, abstrakten Perspektive würde die Kommunikation wie in der folgenden Skizze aussehen.
Figure 2: Interprozess Kommunikation aus der Sicht des Clients
Wie bereits erwähnt, ist eine direkte Kommunikation zwischen Prozessen jedoch nicht möglich. Um Daten mit einem anderen Prozess auszutauschen, muss ein Dritter hinzugezogen werden.
Während der Entwicklung stehen verschiedene übergeordnete APIs und Funktionen zur Verfügung, die die Umsetzung dieser Art der Interprozesskommunikation vereinfachen. Wie bereits erwähnt, können Prozesse aufgrund der Prozessisolierung nicht direkt miteinander interagieren. Anstelle einer direkten Interaktion fungiert daher der Kernel als Vermittler zwischen den Prozessen, wobei der Binder-Treiber als Kommunikationsschnittstelle dient.
Figure 3: Simplifizierte Darstellung der Prozess Kommunikation mithilfe des Binder Treibers
In der Regel registriert sich ein Dienst, auf den Clients zugreifen können, beim Binder-Treiber (1). Dadurch werden spezielle Threads (Thread-Pool) gestartet, die am Binder-Treiber blockiert sind und auf Rückrufe warten, um die Arbeit auszuführen (2). Wenn ein Client also Zugriff auf die Funktionen eines Dienstes benötigt, sendet er eine Nachricht an den Binder-Treiber (3). Der Binder-Treiber sucht dann den entsprechenden, registrierten Dienst, um diese Nachricht zu bearbeiten. Der Binder-Treiber entsperrt einen der Dienst-Threads. Dieser Thread bearbeitet dann die Anfrage (4). Nun wird ein weiterer Thread entsperrt, und der Dienst sendet die Antwort über den neuen entsperrten Thread zurück an den Binder-Treiber (5). Sobald die Antwort gesendet wurde, wird der Thread wieder gesperrt, und der Binder-Treiber sendet die Antwort an den Client (6).
Es ist zu beachten, dass der Dienst möglicherweise gleichzeitige Anfragen von mehreren Clients erhält. Aus diesem Grund muss er den Zugriff auf seine veränderbaren Zustände synchronisieren. Der Binder-Treiber ist über /dev/binder verfügbar und bietet eine relativ einfache API, die auf den Operationen open, release, poll, mmap, flush und ioctl basiert.
Der Großteil der Kommunikation erfolgt über die Funktion ioctl(binderFd, BINDER_WRITE_READ, &bwd) des Binder-Treibers. Die Struktur bwd ist wie folgt definiert:
struct binder_write_read {
signed long write_size; // bytes to write
signed long write_consumed; // bytes consumed by the driver
unsigned long write_buffer; // service response commands
signed long read_size; // bytes to read
signed long read_consumed; // bytes consumed by the driver
unsigned long read_buffer; // client request commands
}
Sobald sich ein Dienst beim Binder-Treiber registriert, wird die Struktur binder_write_read mit einem leeren write_buffer an den Treiber gesendet. Wenn ein Client dann über den Treiber eine Anfrage an den Dienst stellt, kopiert der Treiber die Anfrage des Clients in den read_buffer und fügt die Länge des read_buffer zu read_size hinzu. Der Treiber gibt daraufhin einen Thread frei und sendet die Struktur an den Dienst. Der Dienst liest den read_buffer und führt die Befehle aus. Wenn diese Befehle ein Ergebnis liefern, wird dieses im write_buffer gespeichert und die Länge des Buffers in write_size gesetzt. Anschließend wird die Struktur an den Binder-Treiber zurückgesendet.
Der write_buffer enthält außerdem eine Reihe von Befehlen, mit denen der Treiber Verwaltungsbefehle ausführt, z. B. das Erhöhen/Verringern von Binder-Objektreferenzen, das Anfordern/Löschen von Todesbenachrichtigungen und mehr. Er führt auch Befehle aus, die eine Antwort erfordern, wie z. B. BC_TRANSACTION. Bei der Rückgabe enthält der read_buffer Befehle für den Userspace, um dieselben Verwaltungsbefehle auszuführen. Darüber hinaus führt er einen Befehl aus, um die Verarbeitung der Antwort anzufordern (d. h. BC_REPLY) oder eine Anfrage zur Ausführung einer verschachtelten (rekursiven) Operation. Einfacher ausgedrückt kann die gesamte Interaktion zwischen zwei Diensten auf den Austausch dieser beiden Buffer reduziert werden.
Die gesamte ioctl-Kommunikation findet auf Treiber-Ebene statt, also auf einer sehr niedrigen Ebene. Während der Entwicklung einer Anwendung spielen diese Befehle aufgrund der Abstraktion dieser Ebene durch die native Bibliothek libbinder keine Rolle. Tatsächlich wissen Clients und Dienste nichts über das ioctl-Binder-Protokoll oder gar über libbinder. Dafür kommen der Proxy und die Stubs zum Einsatz.
Figure 4: Interprozess Kommunication mit dem Binder Treiber mithilfe von Proxy und Stub
Was machen die Proxy- und Stub-Komponenten wirklich?
Die meisten Clients sind vordefinierte Java- oder Kotlin-Funktionen, die eine Programmierung auf hoher Ebene vereinfachen sollen. Für Entwicklerinnen und Entwickler geht es im Wesentlichen nur darum, Nachrichten an einen anderen Dienst oder Prozess zu senden und von diesem zu empfangen. Hier kommen Proxy und Stub ins Spiel.
Die Aufgabe des Proxys besteht darin, die hochrangige Java-Anfrage vom Client entgegenzunehmen und sie in ein Parcel umzuwandeln. Ein Parcel ist ein Container für entweder primitive Datentypen oder eine Objektreferenz. Die Parcel-Struktur wird dann in einer ioctl-Transaktion an den Binder-Treiber und den Thread-Block übermittelt.
Der Vorgang der Umwandlung von primitiven Datentypen wie Integer, Double oder Objektreferenzen wie Strings oder einer benutzerdefinierten Klasse wird als Marshalling bezeichnet. Auf der anderen Seite des Binder-Treibers führt die Stub-Komponente den Vorgang namens Unmarshalling durch, der im Grunde die Umkehrung des Marshalling ist. Die Parcel-Struktur wird in eine Datenstruktur umgewandelt, die der Dienst interpretieren kann. Anschließend wird die entsprechende Methode im Dienst aufgerufen.
Tatsächlich wissen die meisten Clients gar nicht, dass sie ein IPC verwenden, geschweige denn einen Binder oder einen Proxy. Während der Entwicklung nutzen Entwicklerinnen und Entwickler definierte Klassen und Methoden, die als Manager fungieren, um all diese Komplexität zu abstrahieren.
Figure 5: Interprozess Kommunication via Binder Treiber mithilfe einer Manager Komponente
Der Einsatz von Managern ist insbesondere bei Systemdiensten wie dem Standortdienst oder dem Akkustatus von Bedeutung. Diese Dienste stellen den Clients in der Regel über ihre Manager einen Teil ihrer APIs zur Verfügung. Die Aufgabe eines Managers besteht darin, als Proxy für einen Proxy zu fungieren. Dieser erleichtert die Erkennung eines Dienstes, mit dem Clients interagieren möchten, und verbirgt die Binder-Interaktion vollständig vor ihnen.
Wenn ein Client mit dem Standortdienst interagieren möchte, verwendet er ein Java/Kotlin-Objekt. Dieses Objekt wandelt dann die Methodenaufrufe des Objekts über den Proxy in Binder-Methodenaufrufe um. Manager sind auch für die Behandlung von Ausnahmen oder Problemen mit verschiedenen Threads zuständig.
Aber wie erhält ein Client einen Zugriff auf den Dienst, mit dem er kommunizieren möchte?
Wie findet eine Anwendung (ein Client) den Dienst, mit dem sie interagieren möchte? Die Antwort lautet: indem sie den servicemanager – auch Context-Manager genannt – befragt.
Der Context-Manager ist ein Dienst, der sich beim Binder Driver registriert (1). Jeder neue Dienst registriert sich beim Binder Driver (2). Der Binder Driver benachrichtigt daraufhin den Context-Manager über den neu registrierten Dienst. Wenn ein Client nun die Funktion eines Dienstes benötigt, initialisiert er dafür in der Regel einen speziellen Manager (3). Im Hintergrund bezieht der Manager den Dienst von einem Proxy. Der Proxy kontaktiert daraufhin den Binder-Treiber, um den registrierten Context-Manager zu erhalten (4). Schließlich fordert der Proxy den gewünschten Dienst vom Context-Manager an (5). Der Proxy sendet den empfangenen Dienst dann zurück an den Manager (6), und der Manager wechselt in den Zustand „initialisiert“ (7).
Nun kann der Client die Funktionen eines Dienstes, wie beispielsweise den Standort, wie zuvor beschrieben nutzen.
Figure 6: Prozess Interaktion des Binder Treibers unter Benutzung des Context-Managers
Laurie Wired
InfoQ
Hextree.io
Medium
Android
OpenBinder
