start_kernel
Am 4.2, start_kernel
ab init/main.c
ist ein erheblicher Initialisierungsprozess und könnte mit einem main
verglichen werden Funktion.
Es ist der erste Arch-unabhängige Code, der ausgeführt wird, und richtet einen großen Teil des Kernels ein. So ähnlich wie main
, start_kernel
wird ein Setup-Code auf niedrigerer Ebene vorangestellt (ausgeführt in crt*
Objekte im Userland main
), wonach der "hauptsächliche" generische C-Code ausgeführt wird.
Wie start_kernel
wird in x86_64 aufgerufen
arch/x86/kernel/vmlinux.lds.S
, ein Linker-Skript, legt Folgendes fest:
ENTRY(phys_startup_64)
und
phys_startup_64 = startup_64 - LOAD_OFFSET;
und:
#define LOAD_OFFSET __START_KERNEL_map
arch/x86/include/asm/page_64_types.h
definiert __START_KERNEL_map
als:
#define __START_KERNEL_map _AC(0xffffffff80000000, UL)
das ist die Kernel-Eingabeadresse. TODO Wie wird diese Adresse genau erreicht? Ich muss die Schnittstelle verstehen, die Linux für Bootloader bereitstellt.
arch/x86/kernel/vmlinux.lds.S
setzt den allerersten Bootloader-Abschnitt als:
.text : AT(ADDR(.text) - LOAD_OFFSET) {
_text = .;
/* bootstrapping code */
HEAD_TEXT
include/asm-generic/vmlinux.lds.h
definiert HEAD_TEXT
:
#define HEAD_TEXT *(.head.text)
arch/x86/kernel/head_64.S
definiert startup_64
. Das ist der allererste x86-Kernelcode, der ausgeführt wird. Es macht viel von Low-Level-Setup, einschließlich Segmentierung und Paging.
Das ist dann das erste, was läuft, denn die Datei beginnt mit:
.text
__HEAD
.code64
.globl startup_64
und include/linux/init.h
definiert __HEAD
als:
#define __HEAD .section ".head.text","ax"
also dasselbe wie das Allererste des Linker-Skripts.
Am Ende ruft es x86_64_start_kernel
auf etwas umständlich mit und lretq
:
movq initial_code(%rip),%rax
pushq $0 # fake return address to stop unwinder
pushq $__KERNEL_CS # set correct cs
pushq %rax # target address in negative space
lretq
und:
.balign 8
GLOBAL(initial_code)
.quad x86_64_start_kernel
arch/x86/kernel/head64.c
definiert x86_64_start_kernel
was x86_64_start_reservations
aufruft die start_kernel
aufruft .
arm64-Einstiegspunkt
Das allererste arm64, das auf einem unkomprimierten v5.7-Kernel läuft, ist unter https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L72 definiert, sodass entweder der add x13, x18, #0x16
oder b stext
abhängig von CONFIG_EFI
:
__HEAD
_head:
/*
* DO NOT MODIFY. Image header expected by Linux boot-loaders.
*/
#ifdef CONFIG_EFI
/*
* This add instruction has no meaningful effect except that
* its opcode forms the magic "MZ" signature required by UEFI.
*/
add x13, x18, #0x16
b stext
#else
b stext // branch to kernel start, magic
.long 0 // reserved
#endif
le64sym _kernel_offset_le // Image load offset from start of RAM, little-endian
le64sym _kernel_size_le // Effective size of kernel image, little-endian
le64sym _kernel_flags_le // Informative flags, little-endian
.quad 0 // reserved
.quad 0 // reserved
.quad 0 // reserved
.ascii ARM64_IMAGE_MAGIC // Magic number
#ifdef CONFIG_EFI
.long pe_header - _head // Offset to the PE header.
Dies ist auch das allererste Byte eines unkomprimierten Kernel-Images.
Beide Fälle springen zu stext
was die "echte" Aktion startet.
Wie im Kommentar erwähnt, sind diese beiden Anweisungen die ersten 64 Bytes eines dokumentierten Headers, der unter https://github.com/cirosantilli/linux/blob/v5.7/Documentation/arm64/booting.rst#4-call beschrieben wird -das-kernel-image
arm64 erste MMU-aktivierte Anweisung:__primary_switched
Ich denke, es ist __primary_switched
in Kopf.S:
/*
* The following fragment of code is executed with the MMU enabled.
*
* x0 = __PHYS_OFFSET
*/
__primary_switched:
An diesem Punkt scheint der Kernel Seitentabellen zu erstellen + sich möglicherweise so zu verlagern, dass die PC-Adressen mit den Symbolen der vmlinux ELF-Datei übereinstimmen. Daher sollten Sie an dieser Stelle in der Lage sein, aussagekräftige Funktionsnamen in GDB ohne zusätzliche Magie zu sehen.
sekundärer Arm64-CPU-Einstiegspunkt
secondary_holding_pen
definiert unter:https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691
Eingabeverfahren weiter beschrieben unter:https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691
Mit main()
du meinst wahrscheinlich was main()
zu einem Programm ist, nämlich sein "Einstiegspunkt".
Für ein Modul, das init_module()
ist .
Aus der 2. Ausgabe des Linux-Gerätetreibers:
Während eine Anwendung eine einzelne Aufgabe von Anfang bis Ende durchführt, registriert sich ein Modul selbst, um zukünftige Anforderungen zu bedienen, und seine "Haupt"-Funktion wird sofort beendet. Mit anderen Worten, die Aufgabe der Funktion init_module (der Einstiegspunkt des Moduls) besteht darin, den späteren Aufruf der Funktionen des Moduls vorzubereiten; es ist, als würde das Modul sagen:"Hier bin ich, und das kann ich tun." Der zweite Einstiegspunkt eines Moduls, cleanup_module, wird kurz vor dem Entladen des Moduls aufgerufen. Es sollte dem Kernel sagen:"Ich bin nicht mehr da; bitte mich nicht, irgendetwas anderes zu tun."
Grundsätzlich ist nichts Besonderes daran, dass eine Routine main()
heißt . Wie oben angedeutet, main()
dient als Einstiegspunkt für ein ausführbares Lademodul. Sie können jedoch unterschiedliche Einstiegspunkte für ein Lademodul definieren. Tatsächlich können Sie mehr als einen Einstiegspunkt definieren, zum Beispiel auf Ihre bevorzugte DLL verweisen.
Aus der Sicht des Betriebssystems (OS) benötigt es eigentlich nur die Adresse des Einstiegspunkts des Codes, der als Gerätetreiber fungieren wird. Das Betriebssystem übergibt die Steuerung an diesen Einstiegspunkt, wenn der Gerätetreiber E/A für das Gerät ausführen muss.
Ein Systemprogrammierer definiert (jedes Betriebssystem hat seine eigene Methode) die Verbindung zwischen einem Gerät, einem Lademodul, das als Gerätetreiber fungiert, und dem Namen des Einstiegspunkts im Lademodul.
Jedes Betriebssystem hat (offensichtlich) seinen eigenen Kernel und einige könnten/vielleicht mit main()
beginnen aber ich wäre überrascht, einen Kernel zu finden, der main()
verwendet außer in einem einfachen wie UNIX! Wenn Sie Kernel-Code schreiben, sind Sie längst über die Anforderung hinausgegangen, jedes von Ihnen geschriebene Modul als main()
zu benennen .
Hoffe das hilft?
Dieses Code-Snippet aus dem Kernel für Unix Version 6 gefunden. Wie Sie sehen können main()
ist nur ein weiteres Programm, das versucht, loszulegen!
main()
{
extern schar;
register i, *p;
/*
* zero and free all of core
*/
updlock = 0;
i = *ka6 + USIZE;
UISD->r[0] = 077406;
for(;;) {
if(fuibyte(0) < 0) break;
clearsig(i);
maxmem++;
mfree(coremap, 1, i);
i++;
}
if(cputype == 70)
for(i=0; i<62; i=+2) {
UBMAP->r[i] = i<<12;
UBMAP->r[i+1] = 0;
}
// etc. etc. etc.
Mehrere Sichtweisen:
-
Gerätetreiber sind keine Programme. Sie sind Module, die in ein anderes Programm (den Kernel) geladen werden. Als solche haben sie keinen
main()
Funktion. -
Die Tatsache, dass alle Programme einen
main()
haben müssen Funktion gilt nur für Userspace-Anwendungen. Es gilt weder für den Kernel noch für Gerätetreiber.