AA_rennen.eps

© Sergey Kandakov, 123RF

Turbostart

Bootprozess von Lucid Lynx

Ubuntus Leiter der Entwicklung, Scott James Remnant, erklärt, warum Ubuntu 10.04 deutlich schneller bootet.

Im Bootprozess von Ubuntu 10.04 steckt die Arbeit von einem Jahr, in dem sich Ubuntu-Entwickler darauf konzentrierten, die Bootzeit einer Ubuntu-Maschine zu minimieren. Boot-Performance ist ein Feld, das unser Entwicklerteam seit den Anfängen von Ubuntu einige Male beackert hat, insbesondere für Ubuntu 5.04 – das zweite Release.

Der zuletzt erfolgte Versuch war eine Reaktion auf Intels Demonstration eines 5-Sekunden-Bootvorgangs auf der Linux-Plumbers-Konferenz von 2008. Obwohl sich die Demonstration auf ein sehr abgespecktes Software-Image stützte, das direkt auf die darunter liegende Hardware zugeschnitten war, zeigte es uns doch, dass sich Linux so weit verbessert hat, dass schnellere Bootzeiten auch für Distributionen wie Ubuntu möglich sind, die eine große Bandbreite an Hardware- und Softwarekonfigurationen unterstützen.

Die Bootgeschwindigkeit zu reduzieren, ist ein wenig, wie Gewicht zu verlieren: Man kann Sport treiben, für eine Weile eine Diät beginnen und sehen, wie viel Gewicht man los wird. Oder man sucht sich ein Zielgewicht und rechnet aus, wie viel Übungen und Diät nötig sind, um das Gewicht in der verfügbaren Zeit zu erreichen.

In der Entwicklungsphase von Ubuntu 9.10 wussten wir bereits, dass wir eine große Zahl einfacher Probleme zu lösen hatten, um bereits eine spürbare Verbesserung der Bootzeiten zu erreichen – die so genannten "niedrig hängenden Früchte". Daher beschlossen wir, die Probleme zu lösen, von denen wir wussten und das zu akzeptieren, was an Bootzeit dabei heraus sprang.

Für Ubuntu 10.04 gab es wesentlich weniger "niedrig hängende Früchte" und mehr harte Arbeit. Also setzten wir die sehr ambitionierte Zielbootzeit von 10 Sekunden, von der wir nicht wirklich erwarteten, sie zu erreichen. Dann unterteilten wir die Zeit in Abschnitte für die verschiedenen Phasen innerhalb der Bootsequenz (Tabelle 1).

Tabelle 1

Abschnitt Dauer
Kernel und Initramfs 2s
Klempnern 2s
X-Server 2s
Desktop 4s
Insgesamt 10s

Die Zeiten in Tabelle 1 gelten für ein SSD-basiertes Mini 10v Netbook von Dell mit einer auf Intel Diamondville fußenden Hardware-Plattform und einem Atomprozessor.

Um Benchmarks zu nehmen, ist eine einzige Hardware-Plattform extrem wichtig, weil die Zeiten zwischen verschiedenartiger Hardware stark variieren. Wir hatten das Gefühl, dass die gewählte Hardware einen guten Kompromiss darstellt. Die meisten Desktops und Laptops der Anwender haben deutlich schnellere CPUs als den Atom, sie erleben also eine schnellere Bootzeit. Aber die meisten nutzen zugleich traditionelle Festplatten (HDDs) und keine SSDs, was den Bootvorgang wiederum verlangsamt.

Die Unterteilung der Bootzeit in Phasen erfolgt nicht willkürlich. Die Uhr läuft, sobald der Kernel in den Speicher geladen wird und mit der Initialisierung beginnt. Die Zeit, die das BIOS zum Starten braucht, beziehen wir nicht mit ein. Dieser Prozess liegt nicht in unserer Hand und wir können ihn zeitlich nicht akkurat einordnen. Der Kernel bietet hingegen eine zuverlässige Zeitmessung an, also macht es Sinn, ihn als erste Etappe im Prozess der Optimierung zu setzen.

Kernel und InitramFS

Der erste Abschnitt im Bootvorgang umfasst den Zeitraum, um das Linux-System zu initialisieren, Kernkomponenten der Hardware einzurichten wie die PCI- und USB-Busse und das Dateisystem zu lokalisieren und einzuhängen.

Einen großen Anteil an der Beschleunigung bei der Kernel-Initialisierung trägt das Moblin-Projekt und dessen Arbeit an der Boot-Performance. Vorher setzte der Kernel ein Subsystem nach dem anderen in Gang und wartete jeweils, bis eins fertig war, bevor er das nächste anschob. Der Prozess wurde so verändert, dass die Subsysteme nun asynchron starten. Der Kernel wartet nur noch darauf, dass alle fertig sind, bevor er dem User Space die Kontrolle überlässt.

Das Aufspüren und Einhängen des Root-Dateisystems (/) erledigt nicht der Kernel, sondern das "Initial RAM Filesystem", kurz InitramFS, welches der Bootloader entpackt. Am einfachsten kann man sich das InitramFS als ein sehr rudimentäres Linux-System vorstellen, dessen Aufgabe allein darin besteht, das Root-Dateisystem einzuhängen und das restliche Ubuntu zu laden.

Sie wundern sich womöglich, warum wir das alles brauchen und warum der Kernel das Root-Dateisystem nicht selbst lädt. Der Ansatz ist notwendig, um Dinge wie den Standby-Modus und den Ruhezustand, Software-RAID, LVM und eine breite Palette verschiedener SCSI-, IDE- und Serial-ATA-Controller zu unterstützen.

Andere schnell bootende Systeme – etwa Moblin – reduzierten die Bootzeit, indem sie das InitramFS entfernten. Wir wollten hingegen nicht auf die aus ihm resultierende Flexibilität verzichten. Ubuntus Techniker konzentrierten sich stattdessen auf die anderen von Moblin vorgenommenen Änderungen am Kernel und veränderten diesen so, dass er das InitramFS asynchron nutzen kann.

Die Bootgrafik des Kernels (Abbildung 1) zeigt die Folgen der beiden Entwicklungen: Einerseits die von Moblins Technikern entworfene, asynchrone Initialisierung und andererseits die von Ubuntus-Entwicklern realisierte asynchrone Initialisierung des InitramFS (die visualisiert der Prozess populate_rootfs). Die Grafik wird nach dem Booten aus Informationen des Kernel-Protokolls gespeist. Dabei werden spezielle Einträge ausgewertet, welche der Bootparameter initcall_debug hervorruft, um die verschiedenen Kernel-Prozesse voneinander zu unterscheiden.

Zusätzliche Arbeit half den Code im InitramFS selbst zu straffen. Die meiste Zeit wartet dieses in einer Endlosschleife (Loop) auf das Root-Dateisystem, wozu noch die Schwierigkeit kam, das Einhängen von Festplatten über UUIDs und Labels unterstützen zu wollen.

Der Loop wartet darauf, dass der Udev-Daemon untätig wird. Dieser empfängt gewöhnlich Mitteilungen über neue Hardware vom Kernel und legt als Reaktion darauf Geräteverknüpfungen ("Device Nodes") an. Der Loop tut jedoch nichts, bis Udev bestimmte "Device Nodes" oder symbolische Links anlegt und untätig wird. Erst dann testet er, ob hinter der Geräteverknüpfung ein valides Dateisystem steckt. Schlägt einer der Schritte fehl, tut die Endlosschleife kurz nichts und beginnt von Neuem.

Das offensichtliche Problem bei diesem Ansatz besteht darin, dass der Udev-Daemon mehr tut, als einfach nur die Geräteverknüpfungen für das Root-Dateisystem anzulegen. Er lädt zum Beispiel auch andere vom System benötigte Treiber und wartet, bis weitere Hardwarekomponenten einsatzbereit sind. Aus diesem Grund wird er nicht sofort nach dem Anlegen einer Verknüpfung zum Root-Dateisystem untätig und lässt den Loop somit warten. Zusätzlich nimmt das Testen des Dateisystems unnötig Zeit in Anspruch. Da der Udev-Daemon die Existenz des Root-Dateisystems bereits beim Anlegen der Verknüpfung überprüft, entpuppt sich der zweite Test als überflüssig.

Den Loop ersetzt nun ein kleines Programm, das die Nachrichten von Udev belauscht und wartet, bis eine Nachricht signalisiert, dass das Root-Dateisystem da ist.

Klempnern

Das Root-Dateisystem ist nicht das einzige Dateisystem, auf das wir warten müssen. Viele Systeme hängen nicht nur zusätzliche Laufwerke an verschiedenen Punkten im Dateisystem ein, sondern auch eine Reihe verschiedener virtueller Dateisysteme. Zudem muss das Root-Dateisystem überprüft und beschreibbar gemacht werden.

Diese Arbeit erledigten bisher eine kleine Anzahl riesiger und komplizierter Shell-Skripte, die jedes Dateisystem prüfen und dann einhängen. All das wird sequenziell abgearbeitet und kann erst passieren, nachdem sicher ist, dass alle Geräte entdeckt wurden; und der Fall tritt ein, sobald Udev untätig wird.

In Ubuntu 9.10 und 10.04 verbindet sich hingegen ein schlanker Daemon zugleich mit dem Udev-Daemon und dem Init-Daemon von Upstart. Er lauscht, welche Udev-Ereignisse zu den Dateisystemen passen, die in der Datei /etc/fstab stehen und wird nur aktiv, wenn die jeweilige Festplatte bereit ist. Das Überprüfen von Dateisystemen findet parallel statt und die Dateisysteme werden eingehängt, wenn sie fertig sind. Dank der Verbindung zu Upstart startet der Init-Daemon Dienste, sobald die virtuellen, lokalen oder entfernten Dateisysteme eingehängt sind und erlaubt dem System, das Booten abzuschließen, sobald alle Dateisysteme gemountet wurden.

Der neue Daemon umschifft einen großen Teil der kritischen Probleme beim Booten des Systems. Er beendet die sinnlosen Verzögerungen beim Warten auf Dinge wie den Soundkarten-Treiber, nur um zu ermitteln, dass die Festplatten einsatzbereit sind. Der Daemon entfernt zudem das Zeitlimit, so dass nun auch langsamer reagierende Datenträger funktionieren.

Die Veränderungen erlauben es uns, Upstarts Init-Daemon intensiv zu nutzen, der nun mit dem Udev-Daemon kooperiert und so auch Zugang zu Hardware-basierten Ereignissen hat. Das macht einen Großteil der kritische Strecke beim Booten asynchron.

Die Unterschiede sind offensichtlich, wenn man die Bootgrafiken von Ubuntu 9.04 und Ubuntu 10.04 Beta 2 vergleicht: Betrachten Sie die wesentlichen Prozesse auf der Grafik, sehen Sie, dass unter Ubuntu 9.04 (Abbildung 2) die meisten Prozesse erst starten, wenn der Vorgängerprozess beendet wurde, während in 10.04 (Abbildung 3) viel mehr parallel geschieht.

Einem Freund empfehlen