Wie ein PC bootet

Hintergrund

Ich hatte Anfang 2021 einen PC via eBay-Kleinanzeigen gekauft, den der Verkäufer (Gruß an René) zunächst als "bootet nicht" klassifiziert hatte. Im Gespräch stellte sich heraus, dass die zum Testen verwendete Bootdiskette nicht okay war; mit einer neu erstellten Diskette hatte es funktioniert. Im Verlauf entstand das Interesse daran, was beim Booten eines PCs genau abläuft. Das ist wieder so ein typischer Prozess, den man gar nicht bewusst wahrnimmt, außer wenn er mal nicht funktioniert. Dabei ist es ein ausgesprochen eleganter Ansatz in Bezug auf Flexibilität und Erweiterbarkeit, dessen Grundprinzip man auch in anderen Szenarien anwenden kann.

PCs: damals und heute

In diesem Artikel schreibe ich immer von PCs, meine damit aber eigentlich "alte" PCs mit BIOS und im späteren Verlauf MS-DOS. Ich habe ganz ehrlich keine Ahnung wie das mit UEFI läuft und bin auch immer wieder zwischen Faszination und Abscheu hin- und hergerissen, wenn ich in ein vollgrafisches "BIOS" komme, in dem ich die Bootreihenfolge mit der Maus via Drag&Drop einrichte. Nicht meine Welt. ;-)

Was ich hier beschreibe ist der Boot-Vorgang des Ur-PCs, dem IBM PC 5150. Wie es mit de-facto-Standards so ist haben ihn alle IBM-kompatiblen PCs übernommen, sodass wir so ziemlich das gleiche Vorgehen bis in die Pentium-Klasse hinein sehen können. Das einzige, das wirklich anders ist, betrifft die physikalische Speicheradresse, an der die CPU mit der Abarbeitung beginnt. Aber dazu später mehr.

Am Anfang war das *Klack*

Bei einem IBM-PC beginnt der Startvorgang mit der Betätigung des Big Red Switches, dem Power-Schalter hinten rechts am Netzteil. Aus Sicht eines Informatikers sind Netzteile gruselige Orte, an denen Magnetfelder und elektrische Spannungsfelder ihr Unwesen treiben um aus der Spannung, die Menschen tot macht, die Spannungen zu erzeugen, die elektronische Bauteile antreiben.

Innerhalb des Netzteils gelten die Regeln der analogen Welt: Spannungen nähern sich ihren Sollwerten kontinuierlich an, Oszillatoren schwingen sich ein. Mit den aus digitaler Sicht undefinierten Zwischenwerten kann der Rest des PCs nicht viel anfangen, weshalb das Netzteil beim Starten etwas Vorsprung bekommt. Erst wenn die Spannungen stabil sind darf der Rest des PCs loslaufen, gesteuert durch das Signal "Power Good". Bei der AT-Steckerbelegung befindet sich das Signal auf Pin 1 des P8-Steckers und trägt meistens die Farbe orange.

Die CPU läuft los

Für den nächsten Schritt schauen wir uns die Intel-8088-CPU an, die bekanntlich den ersten PC angetrieben hat. Wenn diese mit Spannung versorgt wird (bzw. einen Reset hinlegt) beginnt sie die Ausführung von Code an der Adresse FFFF0H. Das ist der Anfang der höchsten 16 Byte des gesamten Adressbereichs, der sich von 00000H bis FFFFFH erstreckt (20 Bit => 1 MByte).

Warum ausgerechnet dort, anstatt "vorne", bei 0?
Weil sich im Bereich von 0H bis 3FFH die Interrupt-Sprungtabelle befindet, also jene Sprünge die bei der Ausführung eines Interrupts benötigt werden. Warum man das jetzt so herum angeordnet hat entzieht sich meiner Kenntnis, aber wahrscheinlich wurde dadurch irgendetwas effizienter oder eleganter (kürzere Sprünge in den Programmcode?). Und weil das Booten sehr viel seltener stattfindet als das Ausführen eines Interrupts zur Laufzeit, ergibt es vermutlich schon Sinn, den häufigeren Use-case zu optimieren.

Bei späteren CPUs mit größerem Adressbereich wurde das Schema beibehalten, die Ausführung beginnt in den letzten 16 Byte. Das heißt ein 80286 beginnt mit der Ausführung an Adresse FFFFF0H (24 Bit) und ein 80386 an Adresse FFFFFFF0H (32 Bit). Aber was kann man mit 16 Byte Programmcode anfangen?
Nicht viel, daher steht dort nur ein Sprungbefehl zum Einstiegspunkt des BIOS-Programms, z.B. FE05BH am Beispiel des IBM PCs mit 8088-CPU.

Für alle, die die Eleganz dieses Konzepts überlesen haben:

  1. Die CPU beginnt an einer festen Adresse, die vom Prozessor-Hersteller in die CPU gebrannt wurde.
    Die CPU "weiß" noch nicht, ob sie ein PC, eine Waschmaschine oder eine Ölpumpensteuerung wird.
  2. Der PC-Hersteller (OEM) legt einen ROM-Baustein dort hin, den er selbst definiert.
  3. An der festen Adresse steht ein Sprungbefehl zum OEM-spezifischen Programmanfang.

Das BIOS und der Bootsektor

Das BIOS des Ur-PCs liegt im Bereich von FE000H bis FFFFFH (8192 Byte), wobei der Anfang nicht ausführbar ist und Dinge wie die Teilenummer und Copyright-Angaben enthält. Ab FE05BH geht der Programmcode los, der den PC erst einmal in einen arbeitsfähigen Zustand bringt. Dazu gehört z.B. das Initialisieren der internen und externen Hardware. Irgendwann später kommt der Moment, an dem die Kontrolle vom BIOS zum Betriebssystem übergeht. Doch wo befindet sich das?

Es folgt wieder eine Kombination nach dem Schema "fester Ort, dynamischer Sprung". Wir gehen davon aus, dass die Bootreihenfolge so eingestellt ist, dass vom ersten Diskettenlaufwerk gestartet werden soll. Das BIOS lädt den ersten Sektor (fester Ort) in den Speicher und führt ihn aus. Dabei ist es eine Konvention, dass der Code an die Adresse 7C00H ins RAM geladen wird. Dies ist wichtig für die Systementwickler, damit absolute Sprünge und Daten an der richtigen Stelle gesucht werden (wie das org 100h beim Assemblieren von COM-Dateien).

Der erste Sektor der Diskette, auch Bootsektor genannt, beginnt nun mit dem dynamischen Sprung. Warum das, wenn der Code doch komplett beliebig ist? Weil am Anfang der Diskette auch noch dateisystemspezifische Informationen wie das Dateisystem selbst (Typ, Version), die Geometrie des Datenträgers, Name des Mediums, usw. abgelegt sind. Dies muss nämlich auch jederzeit auffindbar sein und macht daher ebenfalls vom "fester Ort"-Prinzip Gebrauch.

Der vom Anfang des Bootsektors angesprungene Code ist nun betriebssystemspezifisch und erledigt den Rest. Wie dieser Rest aussieht hängt natürlich stark vom Betriebssystem ab. Im Folgenden betrachten wir ein MS-DOS.

MS-DOS startet

Anmerkung: das könnte auch IBM PC-DOS heißen, die Unterschiede sind hier marginal und im Wesentlichen auf die Benennung der Dateien beschränkt.

MS-DOS besteht aus mehreren Dateien. Direkt sichtbar für den Benutzer ist COMMAND.COM, der Befehlsinterpreter. Weiterhin gibt es noch IO.SYS (Gerätetreiber) und MSDOS.SYS (DOS-Kernel), die normalerweise versteckt sind (Attribute S, H und R). Der Code aus dem Bootsektor lädt die ersten drei Sektoren von IO.SYS in den Speicher und führt sie aus. Dieser Anfang von IO.SYS lädt den Rest nach, initialisiert die Geräte und fährt danach mit dem Laden und Ausführen von MSDOS.SYS fort. Danach sind die Funktionen für den "normalen" Zugriff auf das Dateisystem verfügbar und die Abarbeitung von CONFIG.SYS und schließlich COMMAND.COM erfolgen.

Auch hier war übrigens ein "fester Ort" am Werk: der Bootsektor-Code lädt die Datei IO.SYS und diese danach MSDOS.SYS von einem festen Ort: sie müssen die beiden ersten Datei-Einträge des Stammverzeichnisses sein. Darüber hinaus dürfen sie nicht fragmentiert sein (d.h. alle ihre Sektoren müssen direkt aufeinander folgen). Das ist notwendig, weil der "richtige" FAT-Dateisystem-Treiber erst in MSDOS.SYS enthalten ist und der Boot-Code viel primitiver zu Werke geht.

Das Gleiche gilt übrigens auch für MS-DOS-Installationen auf Festplatte und erklärt auch die unbeweglichen Dateien, die man z.B. in der Detailansicht von DEFRAG.EXE sehen kann. Weiterhin sollte an dieser Stelle auch nachvollziehbar sein, warum Bootdisketten nicht einfach dadurch erzeugt werden können, dass man die notwendigen Dateien drauf kopiert. Sie müssen zum einen an einem ganz bestimmten Ort liegen (das ginge vielleicht noch durch das Kopieren in der richtigen Reihenfolge), zum anderen muss der Bootsektor den passenden Code enthalten. Unter MS-DOS erledigt das Programm SYS.COM genau diese Schritte.

Zusammenfassung

Schritt Fester Ort Dynamischer Sprung Flexibilität
Power-On/Reset Speicher-Adresse FFFF0H (ROM) BIOS-Einstieg, z.B. FE05BH CPU kann für beliebige Geräte genutzt werden
BIOS bootet von Datenträger Bootsektor, z.B. auf Laufwerk A: Code weiter hinten im Bootsektor BIOS kann beliebige Betriebssysteme starten
Boot-Code lädt Anfang von IO.SYS 1. FAT-Eintrag Intelligenz kann in IO.SYS ausgelagert werden
IO.SYS lädt Rest von IO.SYS und MSDOS.SYS 1. und 2. FAT-Eintrag Lade-Code in IO.SYS kann relativ primitiv sein
MSDOS.SYS lädt CONFIG.SYS und COMMAND.COM Verwendung "normaler" Dateien im Dateisystem

Ein bisschen was Praktisches

Um die beschriebenen Schritte selbst nachzuvollziehen wird nur ein PC (oder eine virtuelle Maschine) mit MS-DOS bzw. Images der Disketten benötigt. Ich verwende dazu den IBM-PC-Emulator von PCjs (MS-DOS 3.30, 640K RAM).

Als erstes schauen wir uns diesen Sprung an der Adresse FFFF0H an. Dazu starten wir DEBUG.COM und disassemblieren den Speicherbereich F000:FFF0:

Wir sehen den absoluten Sprung, JMP F000:E05B, der 5 Byte umfasst. Danach folgt ein ASCII-String mit dem Produktionsdatum des BIOS. Das ist eine Konvention, aber nicht notwendig. Als nächstes wollen wir schauen, was am Anfang des BIOS-ROMs steht.

Der Anfang ist der Freitext mit Teilenummer und Copyright-Informationen, ab E05BH folgt der BIOS-Code der ausgeführt wird. Das kann nahezu beliebig kompliziert werden. Wir wollen als nächstes lieber einen Blick auf den Bootsektor einer MS-DOS-Diskette werfen. Praktischerweise kann man das Image auch gleich durch einen Klick auf "Save" von PCjs runterladen.

Hier ist die Ausgabe auf meinem Linux-System mit xxd -g 1 -l 512 MSDOS330-DISK1.img:

Hinter den ersten drei Byte verbirgt sich ein Sprung:

An der Adresse 0x36 startet der Boot-Code mit FA (entspricht CLI). Aus den Angaben davor lässt sich ablesen, wo die FAT startet und wie man an das Stammverzeichnis gelangt. Es ist etwas mühsam das abzulesen, aber mit der Tabelle aus dem Wikipedia-Artikel kommt man auf:

Offset Länge Beschreibung Wert im Beispiel
0x0B 2 Bytes pro Sektor 0x0200 = 512
0x0E 2 Anzahl reservierte Sektoren vor FAT 0x0001 = 1
0x10 1 Anzahl der FATs 0x02 = 2
0x16 2 Anzahl der Sektoren pro FAT 0x0002 = 2

Das Stammverzeichnis schließt sich direkt an die zweite FAT an, d.h. es sollte am Offset ({Sektoren vor FAT} + ({Anzahl FATs} * {Sektoren pro FAT})) * {Bytes pro Sektor} = (1 + (2 * 2)) * 512 = 2560 (0xA00) zu finden sein. Und tatsächlich:

Die beiden ersten Einträge im Stammverzeichnis sind IO.SYS und MSDOS.SYS. Über die Verzeichnis-Einträge lassen sich nun noch die Positionen der eigentlichen Daten innerhalb des Images finden, aber das führt etwas weit und ist Stoff für einen weiteren Artikel. Für alle die schon mal etwas auf eigene Faust stöbern möchten ein Software-Tipp aus der c't: Active@ Disk Editor (Freeware, für Windows und Linux).

Quellen


Zurück zur Hauptseite