Vom Bootloader zum KernelWenn Sie die vorherigen
Artikel lesen, kennen Sie mein neues Hobby für Low-Level-Programmierung. Ich schrieb mehrere Artikel über Assembler-Programmierung für
x86_64
Linux und begann gleichzeitig, in den Quellcode des Linux-Kernels einzutauchen.
Ich bin sehr daran interessiert zu verstehen, wie Dinge auf niedriger Ebene funktionieren: wie Programme auf meinem Computer ausgeführt werden, wie sie sich im Speicher befinden, wie der Kernel Prozesse und Speicher verwaltet, wie der Netzwerkstapel auf niedriger Ebene funktioniert und vieles mehr. Deshalb habe ich beschlossen, eine weitere Artikelserie über den Linux-Kernel für die
x86_64-Architektur zu schreiben.
Bitte beachten Sie, dass ich kein professioneller Kernel-Entwickler bin und bei der Arbeit keinen Kernel-Code schreibe. Dies ist nur ein Hobby. Ich mag nur Dinge auf niedriger Ebene und es ist interessant, mich mit ihnen zu beschäftigen. Wenn Sie also Verwirrung oder Fragen / Kommentare bemerken, kontaktieren Sie mich
auf Twitter , per
E-Mail oder erstellen Sie einfach ein
Ticket . Ich wäre dankbar.
Alle Artikel werden im
GitHub-Repository veröffentlicht. Wenn etwas mit meinem Englisch oder dem Inhalt des Artikels nicht stimmt, zögern Sie nicht, eine Pull-Anfrage zu senden.
Bitte beachten Sie, dass dies keine offizielle Dokumentation ist, sondern lediglich Schulung und Wissensaustausch.Erforderliche Kenntnisse- C-Code verstehen
- Grundlegendes zum Assembler-Code (AT & T-Syntax)
In jedem Fall werde ich versuchen, in diesem und den folgenden Artikeln etwas zu erklären, wenn Sie gerade erst anfangen, solche Werkzeuge zu lernen. Okay, wenn die Einführung abgeschlossen ist, ist es Zeit, sich mit dem Linux-Kernel und den Dingen auf niedriger Ebene zu beschäftigen.
Ich habe in den Tagen des Linux 3.18-Kernels angefangen, dieses Buch zu schreiben, und seitdem hat sich viel geändert. Bei Änderungen werde ich die Artikel entsprechend aktualisieren.
Magischer Netzschalter, wie geht es weiter?
Obwohl dies Artikel über den Linux-Kernel sind, haben wir ihn - zumindest in diesem Abschnitt - noch nicht erreicht. Sobald Sie den magischen Netzschalter auf Ihrem Laptop oder Desktop-Computer drücken, funktioniert es. Das Motherboard sendet ein Signal an die
Stromversorgung . Nach dem Empfang des Signals versorgt es den Computer mit der erforderlichen Strommenge. Sobald das Motherboard ein
"Power OK" -Signal empfängt, versucht es, die CPU zu starten. Er speichert alle verbleibenden Daten in seinen Registern und legt für jeden von ihnen vordefinierte Werte fest.
Prozessoren
80386 und spätere Versionen sollten nach einem Neustart die folgenden Werte in den CPU-Registern haben:
IP 0xfff0
CS-Selektor 0xf000
CS-Basis 0xffff0000
Der Prozessor beginnt im
Real-Modus zu arbeiten . Gehen wir etwas zurück und versuchen,
die Speichersegmentierung in diesem Modus zu verstehen. Der Real-Modus wird von allen x86-kompatiblen Prozessoren unterstützt: von
8086 bis zu modernen 64-Bit-Intel-Prozessoren. Der 8086-Prozessor verwendet einen 20-Bit-Adressbus,
0-0xFFFFF
er kann mit einem Adressraum von
0-0xFFFFF
oder
1
. Es gibt jedoch nur 16-Bit-Register mit einer maximalen Adresse von
2^16-1
oder
0xffff
(64 Kilobyte).
Eine Speichersegmentierung ist erforderlich, um den gesamten verfügbaren Adressraum zu nutzen. Der gesamte Speicher ist in kleine Segmente mit einer festen Größe von
65536
Byte (64 KB) unterteilt. Da wir mit 16-Bit-Registern nicht auf Speicher über 64 KB zugreifen können, wurde eine alternative Methode entwickelt.
Die Adresse besteht aus zwei Teilen: 1) einem Segmentwähler mit einer Basisadresse; 2) Versatz von der Basisadresse. Im Real-Modus ist die Basisadresse des Segmentwählers
* 16
. Um die physikalische Adresse im Speicher zu erhalten, müssen Sie einen Teil des Segmentwählers mit 16 multiplizieren und den Offset hinzufügen:
= * 16 +
Wenn das
CS:IP
Register beispielsweise den Wert
0x2000:0x0010
,
0x2000:0x0010
die entsprechende physikalische Adresse wie folgt:
>>> hex((0x2000 << 4) + 0x0010) '0x20010'
Wenn Sie jedoch den Selektor des größten Segments und den Offset
0xffff:0xffff
, erhalten Sie die Adresse:
>>> hex((0xffff << 4) + 0xffff) '0x10ffef'
das sind
65520
Bytes nach dem ersten Megabyte. Da im Real-Modus nur ein Megabyte verfügbar ist, wird
0x00ffef
bei deaktivierter
A20-Leitung zu
0x00ffef
.
Nun wissen wir etwas über den Real-Modus und die Speicheradressierung in diesem Modus. Kehren wir zur Diskussion der Registerwerte nach dem Zurücksetzen zurück.
Das
CS
Register besteht aus zwei Teilen: einem sichtbaren Segmentwähler und einer versteckten Basisadresse. Obwohl die Basisadresse normalerweise durch Multiplizieren des Werts des Segmentwählers mit 16 gebildet wird, ist während eines Hardware-Resets der Segmentwähler im CS-Register
0xf000
und die Basisadresse
0xffff0000
. Der Prozessor verwendet diese spezielle Basisadresse, bis sich die CS ändert.
Die Startadresse wird durch Hinzufügen der Basisadresse zum Wert im EIP-Register gebildet:
>>> 0xffff0000 + 0xfff0 '0xfffffff0'
Wir erhalten
0xfffffff0
, was 16 Bytes unter 4 GB liegt. Dieser Punkt wird als
Rücksetzvektor bezeichnet . Dies ist die Stelle im Speicher, an der die CPU nach einem Zurücksetzen auf die Ausführung des ersten Befehls wartet: eine Sprungoperation (
jmp ), die normalerweise den BIOS-Einstiegspunkt angibt. Wenn Sie sich beispielsweise den Quellcode von
coreboot (
src/cpu/x86/16bit/reset16.inc
)
src/cpu/x86/16bit/reset16.inc
, werden wir sehen:
.section ".reset", "ax", %progbits .code16 .globl _start _start: .byte 0xe9 .int _start16bit - ( . + 2 ) ...
Hier sehen wir den Operationscode (
jmp
)
jmp
, nämlich
0xe9
, und die Zieladresse
_start16bit - ( . + 2)
.
Wir sehen auch, dass der
reset
Abschnitt 16 Bytes umfasst und kompiliert wird, um von der Adresse
0xfffff0
(
src/cpu/x86/16bit/reset16.ld
) ausgeführt zu werden:
SECTIONS { _bogus = ASSERT(_start16bit >= 0xffff0000, "_start16bit too low. Please report."); _ROMTOP = 0xfffffff0; . = _ROMTOP; .reset . : { *(.reset); . = 15; BYTE(0x00); } }
Das BIOS wird jetzt gestartet. Nach dem Initialisieren und Überprüfen der BIOS-Hardware müssen Sie das Startgerät finden. Die Startreihenfolge wird in der BIOS-Konfiguration gespeichert. Beim Versuch, von der Festplatte zu booten, versucht das BIOS, den Bootsektor zu finden. Auf
MBR-partitionierten Festplatten wird der Startsektor in den ersten 446 Bytes des ersten Sektors gespeichert, wobei jeder Sektor 512 Bytes umfasst. Die letzten beiden Bytes des ersten Sektors sind
0x55
und
0xaa
. Sie zeigen dem BIOS, dass es sich um ein Startgerät handelt.
Zum Beispiel:
; ; : Intel x86 ; [BITS 16] boot: mov al, '!' mov ah, 0x0e mov bh, 0x00 mov bl, 0x07 int 0x10 jmp $ times 510-($-$$) db 0 db 0x55 db 0xaa
Wir sammeln und betreiben:
nasm -f bin boot.nasm && qemu-system-x86_64 boot
QEMU erhält den Befehl, die soeben erstellte
boot
Binärdatei als Disk-Image zu verwenden. Da die oben generierte Binärdatei die Anforderungen des Bootsektors erfüllt (beginnend bei
0x7c00
und endend mit einer magischen Sequenz), betrachtet QEMU die Binärdatei als Master Boot Record (MBR) des Disk-Images.
Sie werden sehen:

In diesem Beispiel sehen wir, dass der Code im 16-Bit-Real-Modus ausgeführt wird und an der Adresse
0x7c00
im Speicher beginnt. Nach dem Start wird ein
0x10- Interrupt
ausgelöst , der einfach ein Zeichen druckt
!
;; Füllt die verbleibenden 510 Bytes mit Nullen und endet mit zwei magischen Bytes
0xaa
und
0x55
.
Sie können den binären Speicherauszug mit dem Dienstprogramm
objdump
anzeigen:
nasm -f bin boot.nasm
objdump -D -b binary -mi386 -Maddr16,data16,intel boot
Natürlich gibt es im realen Bootsektor Code, um den Bootvorgang fortzusetzen, und eine Partitionstabelle anstelle einer Reihe von Nullen und einem Ausrufezeichen :). Ab diesem Moment überträgt das BIOS die Kontrolle an den Bootloader.
Hinweis : Wie oben erläutert, befindet sich die CPU im Real-Modus. wobei die Berechnung der physikalischen Adresse im Speicher wie folgt ist:
= * 16 +
Wir haben nur 16-Bit-Allzweckregister, und der Maximalwert des 16-Bit-Registers ist
0xffff
. Bei den größten Werten ist das Ergebnis also:
>>> hex((0xffff * 16) + 0xffff) '0x10ffef'
Dabei ist
0x10ffef
1 + 64 - 16
. Der
8086-Prozessor (der erste Prozessor mit Real-Modus) verfügt über eine 20-Bit-Adressleitung. Da
2^20 = 1048576
, beträgt der tatsächlich verfügbare Speicher 1 MB.
Im Allgemeinen lautet die Speicheradressierung im Real-Modus wie folgt:
0x00000000 - 0x000003FF - Tabelle der Interruptvektoren des Realmodus
0x00000400 - 0x000004FF - BIOS-Datenbereich
0x00000500 - 0x00007BFF - wird nicht verwendet
0x00007C00 - 0x00007DFF - unser Bootloader
0x00007E00 - 0x0009FFFF - wird nicht verwendet
0x000A0000 - 0x000BFFFF - Video-RAM (VRAM)
0x000B0000 - 0x000B7777 - monochromer Videospeicher
0x000B8000 - 0x000BFFFF - Videospeicher im Farbmodus
0x000C0000 - 0x000C7FFF - Video-ROM-BIOS
0x000C8000 - 0x000EFFFF - Schattenbereich (BIOS Shadow)
0x000F0000 - 0x000FFFFF - System-BIOS
Am Anfang des Artikels steht, dass sich der erste Befehl für den Prozessor bei
0xFFFFFFF0
, was viel mehr als
0xFFFFF
(1 MB) ist. Wie kann die CPU im Real-Modus auf diese Adresse zugreifen? Antwort in der
Coreboot- Dokumentation:
0xFFFE_0000 - 0xFFFF_FFFF: 128 ROM
Zu Beginn der Ausführung befindet sich das BIOS nicht im RAM, sondern im ROM.
Bootloader
Der Linux-Kernel kann mit verschiedenen Bootloadern wie
GRUB 2 und
Syslinux geladen werden . Der Kernel verfügt über ein Boot-Protokoll, das die Bootloader-Anforderungen für die Implementierung der Linux-Unterstützung definiert. In diesem Beispiel arbeiten wir mit GRUB 2.
Das BIOS setzte den Startvorgang fort, wählte das Startgerät aus und übertrug die Kontrolle auf den
Startsektor . Die Ausführung beginnt mit
boot.img . Aufgrund seiner begrenzten Größe ist dies ein sehr einfacher Code. Es enthält einen Zeiger, um zum Haupt-GRUB 2-Image zu gelangen. Es beginnt mit
diskboot.img und wird normalerweise unmittelbar nach dem ersten Sektor in nicht verwendetem Speicherplatz vor der ersten Partition gespeichert. Der obige Code lädt den Rest des Images in den Speicher, der den GRUB 2-Kernel und Treiber für die Verarbeitung von Dateisystemen enthält. Danach wird die Funktion
grub_main ausgeführt .
Die Funktion
grub_main
initialisiert die Konsole, gibt die Basisadresse für die Module zurück, legt das Root-Gerät fest, lädt / analysiert die Grub-Konfigurationsdatei, lädt die Module usw. Am Ende der Ausführung wird grub in den normalen Modus versetzt. Die Funktion
grub_normal_execute
(aus der
grub_normal_execute
grub-core/normal/main.c
) schließt die letzten Vorbereitungen ab und zeigt ein Menü zur Auswahl des Betriebssystems an. Wenn wir einen der Grub-Menüpunkte auswählen, wird die Funktion
grub_menu_execute_entry
, die den Befehl grub
boot
ausführt und das ausgewählte Betriebssystem lädt.
Wie im Kernel-Boot-Protokoll angegeben, muss der Bootloader einige Felder des Kernel-Installations-Headers lesen und ausfüllen, der mit dem Offset
0x01f1
vom Kernel-Installationscode beginnt. Dieser Offset wird im
Linker-Skript angegeben . Der Kernel-Header
arch / x86 / boot / header.S beginnt mit:
.globl hdr hdr: setup_sects: .byte 0 root_flags: .word ROOT_RDONLY syssize: .long 0 ram_size: .word 0 vid_mode: .word SVGA_MODE root_dev: .word 0 boot_flag: .word 0xAA55
Der Bootloader sollte diesen und andere Header (die im Linux-Bootprotokoll wie in diesem Beispiel nur als Typ
write
gekennzeichnet sind) mit Werten füllen, die von der Befehlszeile empfangen oder beim Boot berechnet werden. Jetzt werden wir nicht auf die Beschreibungen und Erklärungen für alle Headerfelder eingehen. Wir werden später diskutieren, wie der Kernel sie verwendet. Eine Beschreibung aller Felder finden Sie
im Download-Protokoll .
Wie Sie im Kernel-Boot-Protokoll sehen können, wird der Speicher wie folgt angezeigt:
| Geschützter Kernel-Modus |
100000 + ------------------------ +
| E / A-Zuordnung |
0A0000 + ------------------------ +
| Reserve für BIOS | Lass so viel wie möglich frei
~ ~
| Befehlszeile | (kann auch unter X + 10000 liegen)
X + 10000 + ------------------------ +
| Stapel / Haufen | Um echten Kernel-Modus-Code zu verwenden
X + 08000 + ------------------------ +
| Kernel-Installation | Kernel Real Mode Code
| Kernel Boot Sektor | Legacy-Kernel-Bootsektor
X + ------------------------ +
| Lader | <- Einstiegspunkt 0x7C00 Bootsektor
001000 + ------------------------ +
| Reserve für MBR / BIOS |
000800 + ------------------------ +
| Normalerweise verwenden MBR |
000600 + ------------------------ +
| Gebraucht Nur BIOS |
000000 + ------------------------ +
Wenn der Loader die Kontrolle an den Kernel überträgt, beginnt er mit der Adresse:
X + sizeof (KernelBootSector) + 1
Dabei ist
X
die Adresse des Kernel-Bootsektors. In unserem Fall ist
X
0x10000
, wie im Speicherauszug zu sehen ist:

Der Bootloader hat den Linux-Kernel in den Speicher verschoben, die Header-Felder ausgefüllt und dann an die entsprechende Speicheradresse verschoben. Jetzt können wir direkt zum Kernel-Installationscode gehen.
Beginn der Kernel-Installationsphase
Endlich sind wir im Kern! Obwohl technisch läuft es noch nicht. Zunächst muss der Kernel-Installationsteil etwas konfigurieren, einschließlich eines Dekomprimierers und einiger Dinge mit Speicherverwaltung. Nach all dem wird sie den wahren Kern auspacken und dorthin gehen. Die Installation beginnt in
arch / x86 / boot / header.S mit dem Zeichen
_start .
Auf den ersten Blick mag dies etwas seltsam erscheinen, da mehrere Anweisungen vor ihm liegen. Vor langer Zeit hatte der Linux-Kernel einen eigenen Bootloader. Wenn Sie jetzt zum Beispiel ausführen,
qemu-system-x86_64 vmlinuz-3.18-generic
Sie werden sehen:

Tatsächlich beginnt die Datei
header.S
mit der magischen Nummer
MZ (siehe Screenshot des Dumps oben), dem Text der Fehlermeldung und dem
PE- Header:
#ifdef CONFIG_EFI_STUB # "MZ", MS-DOS header .byte 0x4d .byte 0x5a #endif ... ... ... pe_header: .ascii "PE" .word 0
Es wird benötigt, um ein Betriebssystem mit
UEFI- Unterstützung zu laden. Wir werden das Gerät in den folgenden Kapiteln betrachten.
Tatsächlicher Einstiegspunkt für die Installation des Kernels:
Der Bootloader (grub2 und andere) kennt diesen Punkt (Offset
0x200
von
MZ
) und geht direkt zu diesem Punkt, obwohl
header.S
vom Abschnitt
.bstext
, in dem sich der Text der Fehlermeldung befindet:
// // arch/x86/boot/setup.ld // . = 0; // current position .bstext : { *(.bstext) } // put .bstext section to position 0 .bsdata : { *(.bsdata) }
Einstiegspunkt für die Kernelinstallation:
.globl _start _start: .byte 0xeb .byte start_of_setup-1f 1:
Hier sehen wir den Operationscode
jmp
(
0xeb
), der zum Punkt
start_of_setup-1f
. In der
Nf
Notation bezieht sich
2f
beispielsweise auf das lokale Label
2:
In unserem Fall ist dies die Bezeichnung
1
, die unmittelbar nach dem Übergang vorhanden ist und den Rest des Setup-Headers enthält. Unmittelbar nach dem Installationsheader sehen wir den Abschnitt
.entrytext
, der mit der Bezeichnung
start_of_setup
beginnt.
Dies ist der erste tatsächlich ausgeführte Code (außer natürlich den vorherigen Sprunganweisungen). Nachdem ein Teil der
jmp
die Kontrolle vom Loader erhalten hat, befindet sich der erste
jmp
Befehl am Offset
0x200
ab dem Beginn des realen Kernelmodus,
0x200
nach den ersten 512 Bytes. Dies ist sowohl im Linux-Kernel-Boot-Protokoll als auch im grub2-Quellcode zu sehen:
segment = grub_linux_real_target >> 4; state.gs = state.fs = state.es = state.ds = state.ss = segment; state.cs = segment + 0x20;
In unserem Fall
0x10000
der Kernel unter der Adresse
0x10000
. Dies bedeutet, dass die Segmentregister nach dem Start der Kernelinstallation die folgenden Werte haben:
gs = fs = es = ds = ss = 0x10000
cs = 0x10200
Nach dem
start_of_setup
von
start_of_setup
Kernel Folgendes tun:
Mal sehen, wie das umgesetzt wird.
Segmentfallausrichtung
Zunächst prüft der Kernel, ob die Segmentregister
ds
und
es
auf dieselbe Adresse verweisen. Anschließend wird das Richtungsflag mit der
cld
:
movw %ds, %ax movw %ax, %es cld
Wie ich bereits geschrieben habe, lädt grub2 standardmäßig den Kernel-Installationscode bei
0x10000
und
cs
bei
0x10200
, da die Ausführung nicht am Anfang der Datei beginnt, sondern beim Übergang hier:
_start: .byte 0xeb .byte start_of_setup-1f
Dies ist ein Versatz von
512
Byte von
4d 5a . Wie bei allen anderen Segmentregistern ist es auch erforderlich,
cs
von
0x10200
bis
0x10000
auszurichten. Danach installieren Sie den Stack:
pushw %ds pushw $6f lretw
Dieser Befehl schiebt den
ds
Wert auf den Stapel, gefolgt von der Adresse des Etiketts
6 und dem
lretw
, der die Adresse des Etiketts
6
in das Register des
Befehlszählers lädt und
cs
mit dem Wert
ds
lädt. Danach haben
ds
und
cs
die gleichen Werte.
Stack-Setup
Fast der gesamte Code ist Teil des Prozesses zur Vorbereitung der C-Umgebung im Real-Modus. Der nächste Schritt besteht darin, den
ss
Registerwert zu überprüfen und den richtigen Stapel zu erstellen, wenn der
ss
Wert falsch ist:
movw %ss, %dx cmpw %ax, %dx movw %sp, %dx je 2f
Dies kann drei verschiedene Szenarien auslösen:
ss
gültigen Wert von 0x1000
(wie bei allen anderen Registern außer cs
)ss
ungültigen Wert und das CAN_USE_HEAP
Flag CAN_USE_HEAP
gesetzt (siehe unten)ss
ungültigen Wert und das CAN_USE_HEAP
Flag CAN_USE_HEAP
nicht gesetzt (siehe unten)
Betrachten Sie alle Szenarien der Reihe nach:
ss
gültigen Wert ( 0x1000
). In diesem Fall gehen wir zu Label 2:
2: andw $~3, %dx jnz 3f movw $0xfffc, %dx 3: movw %ax, %ss movzwl %dx, %esp sti
Hier setzen wir die Ausrichtung des
dx
Registers (das den vom Bootloader angegebenen
sp
Wert enthält) auf
4
Bytes und prüfen auf Null. Wenn es Null ist, geben wir den Wert
0xfffc
dx
(
4
Byte-ausgerichtete Adresse vor der maximalen Segmentgröße von 64 KB). Wenn es nicht gleich Null ist, verwenden wir weiterhin den vom Bootloader angegebenen
sp
Wert (in unserem Fall
0xf7f4
). Dann setzen wir den
ax
Wert in
ss
, wodurch die richtige Segmentadresse
0x1000
und der richtige
sp
. Jetzt haben wir den richtigen Stapel:

- Im zweiten Szenario ist
ss != ds
. Zuerst setzen wir den Wert _end (die Adresse des Endes des Installationscodes) in dx
und überprüfen die loadflags
des Headerfelds. Verwenden Sie die Anweisung testb
um zu überprüfen, ob der Heap verwendet werden kann. loadflags ist ein Bitmasken-Header, der wie folgt definiert ist:
#define LOADED_HIGH (1<<0) #define QUIET_FLAG (1<<5) #define KEEP_SEGMENTS (1<<6) #define CAN_USE_HEAP (1<<7)
und wie im Boot-Protokoll angegeben:
: loadflags
.
7 (): CAN_USE_HEAP
1, ,
heap_end_ptr . ,
.
Wenn das
CAN_USE_HEAP
Bit gesetzt ist,
CAN_USE_HEAP
wir in
dx
den Wert
heap_end_ptr
(der auf
_end
) und fügen
STACK_SIZE
(die minimale
STACK_SIZE
beträgt
1024
Bytes). Gehen Sie danach zu Etikett
2
(wie im vorherigen Fall) und erstellen Sie den richtigen Stapel.

- Wenn
CAN_USE_HEAP
nicht festgelegt ist, verwenden Sie einfach den Mindeststapel von _end
bis _end + STACK_SIZE
:

BSS-Setup
Bevor Sie mit dem Haupt-C-Code fortfahren, sind zwei weitere Schritte erforderlich: Hiermit wird der
BSS-Bereich eingerichtet und die „magische“ Signatur überprüft. Überprüfung der Unterschrift zuerst:
cmpl $0x5a5aaa55, setup_sig jne setup_bad
Die Anweisung vergleicht
setup_sig einfach mit der magischen Zahl 0x5a5aaa55. Wenn sie nicht gleich sind, wird ein schwerwiegender Fehler gemeldet.
Wenn die magische Zahl übereinstimmt und wir einen Satz korrekter Segmentregister und einen Stapel haben, müssen Sie nur noch den BSS-Abschnitt konfigurieren, bevor Sie mit dem C-Code fortfahren.
Der BSS-Abschnitt wird zum Speichern statisch zugeordneter nicht initialisierter Daten verwendet. Linux prüft sorgfältig, ob dieser Speicherbereich zurückgesetzt wird:
movw $__bss_start, %di movw $_end+3, %cx xorl %eax, %eax subw %di, %cx shrw $2, %cx rep; stosl
Zunächst wird die Startadresse von
__bss_start nach
di
verschoben. Dann wird die Adresse
_end + 3
(+3 für die Ausrichtung um 4 Bytes) nach
cx
verschoben. Das
eax
Register wird gelöscht (unter Verwendung des
xor
Befehls), die Größe der bss-Partition (
cx-di
) wird berechnet und in
cx
platziert. Dann wird
cx
in vier geteilt (die Größe des „Wortes“) und der
stosl
wird
stosl
, wobei der Wert
(Null) in der auf
di
zeigenden Adresse gespeichert wird,
di
automatisch um vier erhöht wird und dies wiederholt wird, bis
Null erreicht). Der Nettoeffekt dieses Codes besteht darin, dass Nullen in alle Wörter im Speicher von
__bss_start
bis
_end
:

Gehe zur Hauptstraße
Das war's: Wir haben einen Stack und BSS, also können Sie zur
main()
C-Funktion gehen:
calll main
Die Funktion
main()
befindet sich in
arch / x86 / boot / main.c. Wir werden im nächsten Teil über sie sprechen.
Fazit
Dies ist das Ende des ersten Teils über das Linux-Kernelgerät. ,
,
. C, Linux, ,
memset
,
memcpy
,
earlyprintk
, .
Referenzen