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.
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.
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.
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:
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.
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.
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 |
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).