GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Hat der Linux-Kernel eine Hauptfunktion?

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:

  1. Gerätetreiber sind keine Programme. Sie sind Module, die in ein anderes Programm (den Kernel) geladen werden. Als solche haben sie keinen main() Funktion.

  2. 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.


Linux
  1. Linux – Kernel:Namespaces-Unterstützung?

  2. Linux – Warum zeigt Linux sowohl mehr als auch weniger Speicher an, als ich physisch installiert habe?

  3. Linux – Wie unterscheidet sich der Linux-Kernel von Mikrokernel-Architekturen?

  4. Linux – Sind verschiedene Linux/Unix-Kernel austauschbar?

  5. Warum gibt eine main-Funktion ohne return-Anweisung den Wert 12 zurück?

Eine Checkliste zum Einreichen Ihres ersten Linux-Kernel-Patches

Dmesg-Befehl unter Linux

Sysctl-Befehl unter Linux

Ist Linux ein Betriebssystem oder ein Kernel?

Linux-Kernel vs. Mac-Kernel

Wie lädt Linux das 'initrd'-Image?