1
Lowlevel-Coding / Eigener mehrstufiger Bootloader
« am: 09. September 2012, 11:24 »
Als erstes möchte ich sagen, ja ich will einen eigenen Bootloader schreiben (bzw. habe dies ohnehin schon gemacht) und nicht GRUB und Co nutzen.
Da ich jetzt endlich wieder etwas mehr Zeit habe und mich mal wieder mit OSDev beschäftigen kann, wollte ich meinen Kernel (und dann auch das gesamte OS) auf andere Architekturen portieren. Da mit dem Raspberry Pi auch eine sehr gute Möglichkeit dafür zur Verfügung steht, habe ich mit der ARM-Archiktektur angefangen.
Bevor ich den Kernel portieren kann, muss dieser erstmal geladen werden können (und bei mir auch noch zusätzlich Programme). Nein ich will die Programme nicht in eine initrd packen (warum das eher unschön ist, sollte sich weiter unten ergeben).
Problem, vorallem auch auf der ARM Architektur, ist es, dass es keine einheitliche Platform, für die man programmieren kann, gibt. Jedes Board ist im Endeffekt wieder eine andere Platform, selbst wenn mir ein flexibler Bootloader zur Verfügung stehen sollte. Es ist von uboot, über Openfirmware (mein momentaner Favorit), fastboot bis bald EFI alles mögliche vorhanden.
Dann kommt noch hinzu, dass es keine, mir bekannte, Möglichkeit gibt, zur Laufzeit die vorhandene Hardware herauszufinden und vorallem auch wo im Adressbereich sie sich befindet. Also muss es auf jeden Fall auf der ARM-Architektur immer was Board-spezifisches geben.
Dazu kam mir dann, mal wieder (wie einige jetzt denken werden ), hoch komplizierte Idee.
Ich möchte meinen Bootloader in mehrere Stages aufteilen (bisher nix neues):
Der "boot"-Ordner von meinem System könnte für die x86-Architektur z.B. so aussehen:
Mit EFI habe ich mich noch nicht weiter beschäftigt, aber Openfirmware z.B. kann Dateien von unterstützten Dateisystemen laden. Problem ist hier das BIOS, denn dort habe ich nur 512byte (bzw. genug wenn ich direkt von CD boote) zur Verfügung. Der Bootsector müsste nun aber die beiden Dateien "/boot/stage2/bios" und "/boot/osloader" laden, dass das nicht geht ist mir klar. Also war mein erster (und bisher leider einziger) Gedanke, ich brauche eine Stage 1.5. Der Bootsektor würde also entweder ein paar mehr Sektoren laden, keine Ahnung wo ich die abzwacken soll oder (was zwar gehen würde, aber unschön wäre) ich müsste noch eine Datei z.B. "stage_1_5_bios" laden und die lädt dann die anderen beiden Dateien.
Problem bei dieser zusätzlichen Datei wäre, wo soll diese gespeichert werden. Um so flexibel wie möglich zu sein, müsste sie in "/", was aber nicht so schön in eine aufgeräumte Ordnerstruktur passen würde. Eine andere Möglichkeit wäre, im Bootsektor einen direkten Verweis auf z.B. den FAT-Dateieintrag zu speichern. Da sehe ich aber das Problem, was passiert beim Defragmentieren? Der FAT-Dateieintrag könnte ja an eine andere Position verschoben werden und dann findet der Bootsektor den Eintrag nicht mehr. Ich weiß auch nicht wie es mit anderen Dateisystemen ist, könnte es da ein ähnliches Problem geben?
Meine Frage also, wie könnte man obiges Problem sinnvoll lösen?
[Jetzt sollte man nur weiterlesen wenn man das Konzept interessant findet oder darüber diskutieren möchte, hat nämlich nix mehr mit dem obigen Problem zu tun]
Warum eigentlich so kompliziert? Mein Problem ist einfach, dass ich nicht meinen kompletten Bootloader für jedes ARM-Board anpassen wollte. Als ich mich dann aber mal näher mit der Doku zum Raspberry Pi auseinander gesetzt habe, viel mir auf, dass sich das mit dem bisherigen Konzept nicht machen lässt. Deswegen jetzt mehrere Stufen.
Um einfach mal zu verdeutlichen wie das ganze dann für die ARM-Architektur aussehen könnte:
Bei openfirmware bin ich mir auch nicht ganz 100%ig sicher, denn ich weiß dass es möglich ist, so einiges über die Hardware herauszufinden und auch bestimmte Funktionen (ähnlich wie beim BIOS) nutzen zu können, aber ob ich auch direkt herausfinde welcher UART verwendet wird und wo dieser hingemappt ist, keine Ahnung. Kann also gut sein, dass ich auch für Boards mit openfirmware eine spezialisierte Stage2 haben muss.
Die Idee ist es dann, dass Stage2 die Hardware initialisiert und einen HAL für Stage3 zur Verfügung stellt. Dabei kann Stage2 im einfachen binär-Format vorliegen (z.B. beim BIOS nötig) oder als ELF-Datei (für Bootloader wie uboot oder auch GRUB). Stage3 ist dann auf jeden Fall im ELF-Format (es sei denn mir kann noch jemand, ein einfacheres, weniger komplexes, Dateiformat nennen, wo ich die Datei an einer beliebigen Stelle ausführen kann und nur die Symbole auflösen muss, welches auch noch von aktuellen GCC-Versionen unterstützt wird?), damit es an einer beliebigen, von Stage2 festgelegten, Stelle ausgeführt werden kann.
Von Stage2 werden dann auch ein paar nötige hardware-dependent Sachen an Stage3 weitergegeben (z.B. Memory-Map und eindeutiger Name des Boards/derPlatform). Denn Stage3 muss dann auch eine Board-/Platformspezifische Konfigurationsdatei laden, in dem die zu ladenden Programme (was auch Treiber sein können bzw. müssen) stehen. Bei dieser Sache bin ich mir auch noch nicht ganz sicher ob ich das über eine Konfigurationsdatei mache oder ob die zu ladenden Programme nicht direkt in Stage2 gespeichert werden und Stage3 dann zur Verfügung gestellt werden. So wäre ich nicht ganz so flexibel, aber es wäre weniger aufwendig. Das einzige was nämlich in so einer Datei stehen würde, wäre der zu ladende Kernel (für den Fall es könnte mehrere Versionen geben, was ich im Moment nicht plane) und viel wichtige, die zu ladenden Programme und Treiber, damit das OS von sich aus die restlichen Treiber und Programme laden kann.
Am Beispiel des x86er, bräuchte ich z.B. einen IDE-, einen AHCI, einen USB1-3, einen ATAPI usw. -Treiber und noch mind. der Device- und Storage-Server. Für die x86er Architektur würde sich wahrscheinlich eine Datei anbieten, damit man die Treiber auswechseln bzw erweitern kann. Auf den ARM-Boards ist das eher unnötigt, da es sich um SOCs handelt, wo es keine austauschbare Hardware gibt.
Was also machen?
Da ich jetzt endlich wieder etwas mehr Zeit habe und mich mal wieder mit OSDev beschäftigen kann, wollte ich meinen Kernel (und dann auch das gesamte OS) auf andere Architekturen portieren. Da mit dem Raspberry Pi auch eine sehr gute Möglichkeit dafür zur Verfügung steht, habe ich mit der ARM-Archiktektur angefangen.
Bevor ich den Kernel portieren kann, muss dieser erstmal geladen werden können (und bei mir auch noch zusätzlich Programme). Nein ich will die Programme nicht in eine initrd packen (warum das eher unschön ist, sollte sich weiter unten ergeben).
Problem, vorallem auch auf der ARM Architektur, ist es, dass es keine einheitliche Platform, für die man programmieren kann, gibt. Jedes Board ist im Endeffekt wieder eine andere Platform, selbst wenn mir ein flexibler Bootloader zur Verfügung stehen sollte. Es ist von uboot, über Openfirmware (mein momentaner Favorit), fastboot bis bald EFI alles mögliche vorhanden.
Dann kommt noch hinzu, dass es keine, mir bekannte, Möglichkeit gibt, zur Laufzeit die vorhandene Hardware herauszufinden und vorallem auch wo im Adressbereich sie sich befindet. Also muss es auf jeden Fall auf der ARM-Architektur immer was Board-spezifisches geben.
Dazu kam mir dann, mal wieder (wie einige jetzt denken werden ), hoch komplizierte Idee.
Ich möchte meinen Bootloader in mehrere Stages aufteilen (bisher nix neues):
- Stage 1 (optional, wenn wir auf dem PC, die Firmware keine Dateien laden kann) -> Bootsektor, lädt Stage 2 und 3
- Stage 1.5 (optinal, nur wenn benötigt) -> lädt Stage 2 und 3
- Stage 2 -> hardware-dependent Teil, stellt ein HAL für die jeweilige Platform zur Verfügung
- Stage 3 -> hardware-independent Teil, nutzt HAL und lädt Kernel und Programme
Der "boot"-Ordner von meinem System könnte für die x86-Architektur z.B. so aussehen:
/boot
osloader
/stage2
bios
efi
openfirmware
Mit EFI habe ich mich noch nicht weiter beschäftigt, aber Openfirmware z.B. kann Dateien von unterstützten Dateisystemen laden. Problem ist hier das BIOS, denn dort habe ich nur 512byte (bzw. genug wenn ich direkt von CD boote) zur Verfügung. Der Bootsector müsste nun aber die beiden Dateien "/boot/stage2/bios" und "/boot/osloader" laden, dass das nicht geht ist mir klar. Also war mein erster (und bisher leider einziger) Gedanke, ich brauche eine Stage 1.5. Der Bootsektor würde also entweder ein paar mehr Sektoren laden, keine Ahnung wo ich die abzwacken soll oder (was zwar gehen würde, aber unschön wäre) ich müsste noch eine Datei z.B. "stage_1_5_bios" laden und die lädt dann die anderen beiden Dateien.
Problem bei dieser zusätzlichen Datei wäre, wo soll diese gespeichert werden. Um so flexibel wie möglich zu sein, müsste sie in "/", was aber nicht so schön in eine aufgeräumte Ordnerstruktur passen würde. Eine andere Möglichkeit wäre, im Bootsektor einen direkten Verweis auf z.B. den FAT-Dateieintrag zu speichern. Da sehe ich aber das Problem, was passiert beim Defragmentieren? Der FAT-Dateieintrag könnte ja an eine andere Position verschoben werden und dann findet der Bootsektor den Eintrag nicht mehr. Ich weiß auch nicht wie es mit anderen Dateisystemen ist, könnte es da ein ähnliches Problem geben?
Meine Frage also, wie könnte man obiges Problem sinnvoll lösen?
[Jetzt sollte man nur weiterlesen wenn man das Konzept interessant findet oder darüber diskutieren möchte, hat nämlich nix mehr mit dem obigen Problem zu tun]
Warum eigentlich so kompliziert? Mein Problem ist einfach, dass ich nicht meinen kompletten Bootloader für jedes ARM-Board anpassen wollte. Als ich mich dann aber mal näher mit der Doku zum Raspberry Pi auseinander gesetzt habe, viel mir auf, dass sich das mit dem bisherigen Konzept nicht machen lässt. Deswegen jetzt mehrere Stufen.
Um einfach mal zu verdeutlichen wie das ganze dann für die ARM-Architektur aussehen könnte:
/boot
osloader
/stage2
bcm2835
tegra2
openfirmware
Bei openfirmware bin ich mir auch nicht ganz 100%ig sicher, denn ich weiß dass es möglich ist, so einiges über die Hardware herauszufinden und auch bestimmte Funktionen (ähnlich wie beim BIOS) nutzen zu können, aber ob ich auch direkt herausfinde welcher UART verwendet wird und wo dieser hingemappt ist, keine Ahnung. Kann also gut sein, dass ich auch für Boards mit openfirmware eine spezialisierte Stage2 haben muss.
Die Idee ist es dann, dass Stage2 die Hardware initialisiert und einen HAL für Stage3 zur Verfügung stellt. Dabei kann Stage2 im einfachen binär-Format vorliegen (z.B. beim BIOS nötig) oder als ELF-Datei (für Bootloader wie uboot oder auch GRUB). Stage3 ist dann auf jeden Fall im ELF-Format (es sei denn mir kann noch jemand, ein einfacheres, weniger komplexes, Dateiformat nennen, wo ich die Datei an einer beliebigen Stelle ausführen kann und nur die Symbole auflösen muss, welches auch noch von aktuellen GCC-Versionen unterstützt wird?), damit es an einer beliebigen, von Stage2 festgelegten, Stelle ausgeführt werden kann.
Von Stage2 werden dann auch ein paar nötige hardware-dependent Sachen an Stage3 weitergegeben (z.B. Memory-Map und eindeutiger Name des Boards/derPlatform). Denn Stage3 muss dann auch eine Board-/Platformspezifische Konfigurationsdatei laden, in dem die zu ladenden Programme (was auch Treiber sein können bzw. müssen) stehen. Bei dieser Sache bin ich mir auch noch nicht ganz sicher ob ich das über eine Konfigurationsdatei mache oder ob die zu ladenden Programme nicht direkt in Stage2 gespeichert werden und Stage3 dann zur Verfügung gestellt werden. So wäre ich nicht ganz so flexibel, aber es wäre weniger aufwendig. Das einzige was nämlich in so einer Datei stehen würde, wäre der zu ladende Kernel (für den Fall es könnte mehrere Versionen geben, was ich im Moment nicht plane) und viel wichtige, die zu ladenden Programme und Treiber, damit das OS von sich aus die restlichen Treiber und Programme laden kann.
Am Beispiel des x86er, bräuchte ich z.B. einen IDE-, einen AHCI, einen USB1-3, einen ATAPI usw. -Treiber und noch mind. der Device- und Storage-Server. Für die x86er Architektur würde sich wahrscheinlich eine Datei anbieten, damit man die Treiber auswechseln bzw erweitern kann. Auf den ARM-Boards ist das eher unnötigt, da es sich um SOCs handelt, wo es keine austauschbare Hardware gibt.
Was also machen?