Android führt mit deutlichem Abstand die Hitparade der Smartphone-Betriebssysteme an. Die Kern-Technik beleuchtet, welche Änderungen Google für den Mobileinsatz am Linux-Kernel vorgenommen hat und was es mit Wakelocks, Binder und dem anonymen Shared Memory auf sich hat.
Rund 136 Millionen verkaufte Android-Geräte zählten die Marktforscher von IDC im dritten Quartal 2012 [1]. Jeden Tag kommen 1,3 Millionen Geräte hinzu. Mit 75 Prozent Marktanteil dominiert Android die Statistiken und ruft den 1999 von Linus Torvalds nicht ernst gemeinten Aufruf zur World Domination wieder in Erinnerung. Linux-Geräte sind in der Statistik interessanterweise mit 1,5 Prozent gesondert ausgewiesen. Wissen die Marktstrategen von IDC etwa nicht, dass ein Android-Smartphone auch ein Linux-Device ist?
Wie die 1,5 extra gezählten Prozent ist Android allerdings kein reinrassiges Linux. Insbesondere das Userland stellt mit der C-Bibliothek Bionic und der virtuellen Maschine Dalvik eine Neuentwicklung dar. Der Kern jedoch ist in wesentlichen Teilen ein Standardkernel, für den mobilen Einsatz modifiziert. Wie man den Quellcode bekommt, beschreibt der Kasten “Quelltext des Android-Kernels”.
Quelltext des Android-Kernels
Der Quellcode zu Android ist auf der Website http://source.android.com zusammen mit Informationen zur Installation zu finden. Das Userland installiert ein Google-Tool namens Repo. Der Quellcode zum Kernel lässt sich per Git herunterladen. Abbildung 2 zeigt die hierzu notwendigen Kommandos. Das Repository »master« ist allerdings leer, die Entwickler haben es nur aus technischen Gründen angelegt. Beim Auschecken ist daher die gewünschte Kernelversion explizit anzugeben.

Abbildung 2: Der Quellcode für den Android-Kernel kommt per Git auf die Platte. Der Master-Zweig ist allerdings leer.
Für die unterschiedlichen Hersteller und deren Geräte existieren bei Android jeweils eigene Kernelquellen, die per Git aus den Verzeichnissen
https://android.googlesource.com/device/Hersteller/Gerät
zu beziehen sind. Für Testzwecke empfiehlt sich der Goldfish-Kernel. Goldfish ist die Bezeichnung für eine virtuelle CPU, die Google für den Android-Emulator ausgewählt hat und die ARM926T-Befehle verarbeitet. Dieser Quellcode enthält zusätzliche Treiber für die Ein- und Ausgabe im Emulator, die für ein reales Gerät nicht erforderlich sind. Weitere Informationen finden sich unter http://source.android.com/source/building-kernels.html.
Bei Android sind die Modifikationen jedoch so bedeutend, dass Googles Userland zunächst mit einem Standardkernel gar nicht zusammenarbeitete. Die Linux-Entwickler weigerten sich anfangs auch, Patches zu übernehmen. Ihr Argument: Die neuen Programmier-Interfaces würden auf den unbeliebten I/O-Controls basieren, außerdem biete der Standardkernel die gewünschten Funktionalitäten bereits, wenn auch in etwas anderer Form.
Bei diesen Funktionalitäten handelt es sich vor allem um effiziente Synchronisations-, Kommunikations- und Logging-Mechanismen. Hinzu kommen noch Eigenschaften des Memory-Managements, der Sicherheit, des Powermanagements sowie der Ein- und Ausgabe (siehe Abbildung 1).

Abbildung 1: Übersicht der Modifikationen: Fast in jedem Subsystem haben die Android-Entwickler den Linux-Kernel für ihre Zwecke angepasst.
Die Energieverwaltung hat die wesentlichsten Änderungen erfahren. Nur mit einem aggressiven Powermanagement sind die hohen Anforderungen eines Mobiltelefons an niedrigen Stromverbrauch zu erfüllen. Während sich ein Standard-Linux erst nach Aufforderung schlafen legt, versucht Android dies bei jeder sich bietenden Gelegenheit zu tun. Damit das nicht zu Unzeiten passiert, können Treiber und Applikationen im Android-Kernel mit einem “Bleib-Wach-Lock” (Wakelock) das Schlafenlegen verhindern.
Aufgewacht!
Um das einmal schlafende System automatisiert wecken zu können, haben die Android-Entwickler Alarm-Timer implementiert. Anders als die bekannten Hrtimer [2] ermöglichen diese nicht nur das Wecken eines Jobs, sondern das Aufwecken des kompletten Systems aus einem Suspend-Zustand. Dafür ist die in jedem System vorhandene Real Time Clock zuständig, die im Standard-Linux der RTC-Treiber bedient.
Androids Alarm-Timer sind in Abgrenzung zu diesem Treiber deutlich flexibler und genauer und kombinieren die Funktionalität von Hrtimer und RTC. Im Normalbetrieb sind Alarm-Timer durch Hrtimer realisiert. Geht das System aber in den Suspend, programmiert Android die nächste Aufweckzeit in die RTC. In der Kritik der Kernelentwickler steht – wie so oft – das Userland-Interface, das auf die Gerätedatei »/dev/alarm« und auf »ioctls()« setzt.
Remote Object Invocation
Im Bereich Synchronisation und Kommunikation hat Android Binder eingeführt. Binder stellt eine Möglichkeit der Interprozess-Kommunikation dar, bei der eine Applikation Zugriff auf ein Objekt hat, auch wenn dieses zu einem anderen Prozess gehört. Bei dieser so genannten Remote Object Invocation ist ein Objekt mit Hilfe der Android Interface Description Language (AIDL) beschrieben. Aus der Beschreibung wird automatisiert Code erzeugt, der für die Client-Seite – also eine normale App – ein Proxy-Objekt realisiert. Dieses Proxy-Objekt kann sich über einen Prozess, der Content Manager genannt wird, die Adresse des realen Objektes (Server-Seite) holen und auf dieses mit Hilfe eines sehr effizienten Protokolls zugreifen.
Sollen die beiden Prozesse Daten austauschen, also so genannte Transactions durchführen, transferiert ein in den Kernel integrierter Gerätetreiber die Informationen. Der Binder-Treiber, dessen Quellcode sich im Staging-Bereich des Standardkernels befindet, kopiert die Daten aus dem Speicher des einen Prozesses mit dem bekannten Kernelbefehl »copy_from_user()« und transferiert sie in den Speicher des anderen Prozesses mit »copy_to_user()« .
Binder stellt rein synchrone Kommunikation zur Verfügung, wobei der Server aus Effizienzgründen einen Threadpool instanziert, der ankommende Anfragen ohne Zeitverlust bearbeitet. Der Client ruft die Methode eines Objekts auf und arbeitet erst dann weiter, wenn die Methode abgearbeitet ist und das Ergebnis vorliegt. Binder ist die zentrale Komponente der Interprozess-Kommunikation des Android-Systems, mit der Apps und Systemprozesse interagieren. Klassische Interprozess-Kommunikation, insbesondere System-V-IPC, haben die Android-Entwickler aus dem Kernel verbannt.
Speicher anonym
Eine weitere effiziente Möglichkeit der Interprozess-Kommunikation stellt Android mit Anonymous Shared Memory (ASHMEM) zur Verfügung. Anders als beim normalen Shared Memory ist der Kernel berechtigt, einen ASHMEM-Block selbstständig freizugeben. Hierzu kann ein Prozess Speicherseiten als nicht benötigt markieren, die der Kernel – sollte er unter Speicherdruck stehen – freigibt, bevor er den Low Memory Killer aktivieren muss. Ein solcher Einsatz ist beispielsweise im Fall von Caches sinnvoll.
Implementierungstechnisch handelt es sich bei ASHMEM um einen gemeinsamen Speicherbereich, den »mmap()« in den jeweiligen Prozess-Adressraum einblendet. Um einen solchen Speicher erstmals zu reservieren, greift ein Prozess auf die Gerätedatei »/dev/ashmem« zu. Damit andere Prozesse auf den gleichen Speicher zugreifen können, bekommen sie den Dateideskriptor übergeben. Das muss über das erwähnte Binder-Subsystem erfolgen, da Filedeskriptoren prozessspezifisch sind. Binder ist in der Lage, den Filedeskriptor für die anderen Prozesse zu übersetzen.
Speicher reservieren
Eine weitere Möglichkeit, Speicher zu reservieren, besonders für den Zugriff auf die Grafikkarte, stellt der mit Android 4.0 eingeführte Memory Manager ION bereit. Er ersetzt den aus früheren Versionen bekannten Memory Manager PMEM und bietet ein einheitliches API für verschiedene Basisverfahren wie »vmalloc()« , »kmalloc()« oder auch so genannten Carveout-Speicher, also RAM, der bereits beim Booten reserviert ist. Dabei können Speicherbereiche so organisiert werden, dass sich im Fall der Nichtnutzung zur Energieeinsparung einzelne RAM-Riegel abschalten lassen.
ION lässt sich sowohl vom Kernel als auch vom Userland aus verwenden. Für Letzteres stellt er einen Satz von I/O-Controls bereit. Der Quellcode zu ION befindet sich im Android-Kernel unter »kernel/drivers/gpu/ion/« . Der erwähnte und ebenfalls im Staging-Bereich befindliche Low Memory Killer schließlich soll verhindern, dass das System instabil wird, weil der Speicher ausgeht, denn Android verwendet kein Swap.
Der Low Memory Killer überwacht daher ständig die Speicherauslastung und fordert, sobald eine erste Schranke erreicht ist, insbesondere Hintergrundprozesse dazu auf, aktuelle Daten und wichtige Zustandsinformationen zu sichern. Erst wenn sich die Speichersituation weiter verschärft, werden unkritische Hintergrundprozesse beendet – wenn nichts anderes hilft, auch Vordergrundprozesse.
Abschussliste
Die Entscheidung, welche Jobs zuerst dran glauben müssen, orientiert sich an dem im Standardkernel ohnehin vorhandenen Wert »oom_score_adj« . Dieser existiert für jeden Job und liegt beim aktuellen Kernel im Bereich von -1000 bis 1000. Ein Job mit einem »oom_score« von 1000 wird als Erster in die ewigen Jagdgründe geschickt, ein Job mit einem Score von -1000 überhaupt nicht.
Für die Zuordnung der Score-Werte zu den Jobs unterscheidet Android zwischen fünf Prozessarten: Vordergrund-, sichtbare, Service-, Hintergrund- und leere Prozesse (foreground, visible, service, background, empty). Empty-Prozesse sind übrigens Jobs, die sich beendet haben. Android hält diese aber noch im Speicher, sodass sie – sollten sie noch einmal gestartet werden – sehr rasch aktiviert sind (Abbildung 3).
Implementiert ist der Low Memory Killer als Treiber, der sich über die Funktion »register_shrinker()« in das Memory-Management einklinkt. Über das »/sys/« -Dateisystem lässt er sich – wie in seinem Quellcode angegeben – konfigurieren: Steht »0,8« in der Datei
/sys/module/lowmemorykiller/parameters/adj
und »1024,4096« in der Datei
/sys/module/lowmemorykiller/parameters/minfree
bedeutet dies, dass Android alle Jobs mit einem »oom_score« von 8 und mehr terminiert, wenn weniger als 4096 Speicherseiten zur Verfügung stehen. Sind weniger als 1024 Pages frei, beendet das Betriebssystem alle Jobs mit einem Score von 0 und mehr. Für das Scheduling setzt Android im Wesentlichen die im Standardkernel bereits implementierten Mechanismen ein.
Das Linux-Scheduling basiert zunächst auf Prioritätsebenen. Dabei unterscheidet es zwei Bereiche, den der dynamischen und den der statischen Prioritätsebenen. Im Bereich der dynamischen Prioritäten erlaubt es sich der Scheduler, je nach Situation die Prioritäten anzupassen. Hierbei greift er auf den Linux-spezifischen Completely-Fair-Scheduling-Algorithmus (CFS) zurück. Statische Prioritäten dagegen rührt er nicht an.
Prioritäten und Container
Android-Prozesse sind typischerweise einer der 40 Prioritätsebenen aus dem dynamischen Bereich zugeordnet und werden damit per CFS verwaltet. Vereinfacht ausgedrückt teilt der Scheduler eine bestimmte Zeitscheibe (zum Beispiel 10 Millisekunden) gleichmäßig auf die rechenbereiten Prozesse auf. Gibt es zwei lauffähige Jobs, bekommt jeder eine Zeitscheibe von 5 Millisekunden. Gibt es fünf Jobs, erhält jeder eine Zeitscheibe von 2 Millisekunden.
Außerdem stellt der Mechanismus der Container Groups (Cgroups) sicher, dass ein Android-Gerät immer interaktiv bleibt. Dazu hat Android zwei Gruppen gebildet, nämlich »bg_non_interactive« und »default« . Den nicht interaktiven Jobs stehen lediglich 5 Prozent der CPU-Leistung zur Verfügung, die interaktiven Tasks (mit den Typen »foreground« und »visible») bekommen die restlichen 95 Prozent zugeteilt.
Timed GPIO
Android hat das I/O-Subsystem des Linux-Kernels mit Timed GPIO um eine zeitgesteuerte Ein-/Ausgabe erweitert. General Purpose Input Output (GPIO) bezeichnet Ein- und Ausgabeleitungen. Als Input konfiguriert können sie programmgesteuert den Zustand des anliegenden Signals einlesen. Hier sind Taster, Schalter und einfache Sensoren angeschlossen. Als Output schalten sie den Zustand der zugehörigen Leitung programmgesteuert an oder aus. An diesen Leitungen lassen sich LEDs und insbesondere auch der Vibrationsmotor eines Android-Smartphones betreiben.
Einen GPIO-Treiber gibt es auch im Standard-Linux, doch die Android-Variante erweitert diesen um die Zeitsteuerfunktion. Der Timed-GPIO-Treiber ermöglicht es, den Zustand der Ausgabeleitung für eine definierte Zeit, die in Millisekunden angegeben ist, zu setzen. Nach Ablauf der Zeit wird der Ursprungszustand wieder eingenommen.
Die Zeitsteuerfunktion ließe sich relativ einfach auch im Userland unterbringen, also in den Applikationen. Android möchte aber mit der Kernel-Implementierung das Userland entlasten und denkt vor allem an den Fehlerfall beziehungsweise an den Programmabsturz, bei dem die Ausgabeleitung möglicherweise zu lange in einem unerwünschten Zustand verweilt und den Smartphone-Benutzer beispielsweise mit nicht enden wollendem Vibrieren belästigt.
Neben diesen Erweiterungen hat Android auch das Protokollieren von Systeminformationen optimiert. Unter Linux reichen Applikationen typischerweise die zu protokollierenden Informationen per Socket an den Syslog-Daemon weiter, der sie in einer Datei unter »/var/log/« abspeichert. Meldungen des Standardkernels selbst legt Linux in einem Kernelspeicher ab, aus dem sie der »klogd« ebenfalls dem Syslog übergibt. Den Android-Entwicklern sind die Socket-Kommunikation und der Dateizugriff aber zu aufwändig, sie haben das Logging daher komplett in den Kernel verlagert.
Dazu implementierten sie den Treiber »logger« , der vier Ringpuffer im Kernel reserviert. Android stellt sie den Anwendungen über die Gerätedateien »/dev/log/event« (256 KByte), »/dev/log/main« (64 KByte), »/dev/log/radio« (64 KByte) und »/dev/log/system« (64 KByte) zur Verfügung (Abbildung 4). Die Zugriffsrechte der Gerätedateien erlauben jeder Applikation den schreibenden (also loggenden) Zugriff. Lesen ist allerdings nur ausgewählten Applikationen erlaubt, beispielsweise »logcat« , dem Pendant zum Syslog-Daemon. Zur Abstraktion des Zugriffs greift das Programm auf die Bibliothek Liblog zurück, die allen Applikationen zur Verfügung steht.
Wachhunde debuggen
Der Android-Treiber »ram_console« erleichtert Entwicklern das Debugging. Im Fall eines Kernel-Crash landen die Kernelnachrichten in einem RAM-Speicher, sodass sie nach dem Reboot über die Gerätedatei »/proc/last_kmsg« auslesbar sind. Ist das Gerät zwischen Absturz und Reboot aber ohne Strom, sind die Nachrichten verloren. »ram_console« kommt zum Einsatz, um Kernel-Panics und Watchdog-Resets zu debuggen.
Eine aktuelle Neuerung bringt der Treiber »persistent_ram« . Wie der Name andeutet, macht er es möglich, Daten in nicht flüchtigem Speicher abzulegen. Dadurch stehen sie auch dann zur Verfügung, wenn das Android-Gerät zwischendurch stromlos war. Natürlich funktioniert der Treiber nur, wenn auch die geeignete Hardware vorhanden ist.
In Abgrenzung zu einem Standardkernel bietet Android noch ein Paranoid Network genanntes Feature. Es schränkt den Zugriff auf einige Netzwerkfunktionen abhängig von der Gruppenzugehörigkeit der nutzenden Task ein. Die entsprechenden Gruppen-IDs sind in der Kernel-Headerdatei »android_aid.h« zu finden und gehen von 3001 bis 3007.
Linus zeigt Einsicht
Paranoid Network ist bislang nicht im Staging-Bereich des Standardkernels aufgetaucht. Dabei übernimmt Linus Torvalds (Abbildung 5) inzwischen wesentliche Patches, ein aktuelles Android-Userland läuft seit Kernel 3.3 auch auf dem Standardkernel. “Ihr hattet recht, und wir nehmen unsere Kritik zurück. Wenn eure Kernelversion auf Millionen von Geräten weltweit läuft, dann habt ihr ja was richtig gemacht”, sagte der Linux-Erfinder am 7. November auf der Linuxcon Europe 2012 in Barcelona und meinte damit die Android-Entwickler [1].
In einem oder spätestens in zwei Jahren werden sich die auf den Smartphones genutzten Kernel vermutlich nur noch in kleinen Details vom Standardkernel unterscheiden. (mhu)
Infos
- IDC-Pressemeldung, “Android Marks Fourth Anniversary Since Launch with 75.0% Market Share in Third Quarter”: https://www.idc.com/getdoc.jsp?containerId=prUS23771812
- Quade, Kunst: “Linux-Treiber entwickeln”, Dpunkt 2011, S. 174ff.
- Jörg Thoma, “Linus Torvalds: Wir haben uns Android zu Unrecht verweigert”, Golem, 7.11.2012: http://www.golem.de/news/linus-torvalds-wir-haben-uns-android-zu-unrecht-verweigert-1211-95576.html








