GNU/Linux >> Linux Esercitazione >  >> Linux

Il kernel Linux ha la funzione principale?

start_kernel

Su 4.2, start_kernel da init/main.c è un processo di inizializzazione considerevole e potrebbe essere paragonato a un main funzione.

È il primo codice indipendente dall'architettura da eseguire e imposta gran parte del kernel. Proprio come main , start_kernel è preceduto da un codice di configurazione di livello inferiore (fatto nel crt* oggetti in userland main ), dopo di che viene eseguito il codice C generico "principale".

Come start_kernel viene chiamato in x86_64

arch/x86/kernel/vmlinux.lds.S , uno script linker, imposta:

ENTRY(phys_startup_64)

e

phys_startup_64 = startup_64 - LOAD_OFFSET;

e:

#define LOAD_OFFSET __START_KERNEL_map

arch/x86/include/asm/page_64_types.h definisce __START_KERNEL_map come:

#define __START_KERNEL_map  _AC(0xffffffff80000000, UL)

che è l'indirizzo di ingresso del kernel. TODO come si raggiunge esattamente quell'indirizzo? Devo capire l'interfaccia che Linux espone ai bootloader.

arch/x86/kernel/vmlinux.lds.S imposta la primissima sezione del bootloader come:

.text :  AT(ADDR(.text) - LOAD_OFFSET) {
    _text = .;
    /* bootstrapping code */
    HEAD_TEXT

include/asm-generic/vmlinux.lds.h definisce HEAD_TEXT :

#define HEAD_TEXT  *(.head.text)

arch/x86/kernel/head_64.S definisce startup_64 . Questo è il primissimo codice del kernel x86 che viene eseguito. Fa molto di configurazione di basso livello, inclusa la segmentazione e il paging.

Questa è quindi la prima cosa che viene eseguita perché il file inizia con:

.text
__HEAD
.code64
.globl startup_64

e include/linux/init.h definisce __HEAD come:

#define __HEAD      .section    ".head.text","ax"

così come la prima cosa dello script del linker.

Alla fine chiama x86_64_start_kernel un po' goffamente con e 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

e:

.balign 8
GLOBAL(initial_code)
.quad   x86_64_start_kernel

arch/x86/kernel/head64.c definisce x86_64_start_kernel che chiama x86_64_start_reservations che chiama start_kernel .

punto di ingresso arm64

Il primissimo arm64 che gira su un kernel v5.7 non compresso è definito su https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L72 quindi sia il add x13, x18, #0x16 o b stext a seconda di 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.

Questo è anche il primissimo byte di un'immagine del kernel non compressa.

Entrambi questi casi passano a stext che avvia l'azione "reale".

Come accennato nel commento, queste due istruzioni sono i primi 64 byte di un'intestazione documentata descritta in:https://github.com/cirosantilli/linux/blob/v5.7/Documentation/arm64/booting.rst#4-call -l'immagine-del-kernel

arm64 prima istruzione abilitata per MMU:__primary_switched

Penso che sia __primary_switched in testa.S:

/*
 * The following fragment of code is executed with the MMU enabled.
 *
 *   x0 = __PHYS_OFFSET
 */
__primary_switched:

A questo punto, il kernel sembra creare tabelle di pagine + forse riposizionarsi in modo tale che gli indirizzi del PC corrispondano ai simboli del file ELF di vmlinux. Pertanto a questo punto dovresti essere in grado di vedere nomi di funzioni significative in GDB senza ulteriori magie.

punto di ingresso della CPU secondaria arm64

secondary_holding_pen definito su:https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691

Procedura di accesso ulteriormente descritta su:https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691


Con main() probabilmente intendi cosa main() è per un programma, vale a dire il suo "punto di ingresso".

Per un modulo che è init_module() .

Dalla seconda edizione di Linux Device Driver:

Mentre un'applicazione esegue una singola attività dall'inizio alla fine, un modulo si registra per soddisfare le richieste future e la sua funzione "principale" termina immediatamente. In altre parole, il compito della funzione init_module (il punto di ingresso del modulo) è preparare la successiva invocazione delle funzioni del modulo; è come se il modulo dicesse:"Eccomi, e questo è quello che posso fare". Il secondo punto di ingresso di un modulo, cleanup_module, viene richiamato appena prima che il modulo venga scaricato. Dovrebbe dire al kernel:"Non ci sono più; non chiedermi di fare nient'altro".


Fondamentalmente, non c'è niente di speciale nel fatto che una routine si chiami main() . Come accennato in precedenza, main() funge da punto di ingresso per un modulo di caricamento eseguibile. Tuttavia, è possibile definire diversi punti di ingresso per un modulo di caricamento. Infatti, puoi definire più di un punto di ingresso, ad esempio fare riferimento alla tua dll preferita.

Dal punto di vista del sistema operativo (OS), tutto ciò di cui ha veramente bisogno è l'indirizzo del punto di ingresso del codice che funzionerà come driver di dispositivo. Il sistema operativo passerà il controllo a quel punto di ingresso quando il driver del dispositivo è tenuto a eseguire l'I/O al dispositivo.

Un programmatore di sistema definisce (ogni sistema operativo ha il proprio metodo) la connessione tra un dispositivo, un modulo di caricamento che funge da driver del dispositivo e il nome del punto di ingresso nel modulo di caricamento.

Ogni sistema operativo ha il proprio kernel (ovviamente) e alcuni potrebbero/forse iniziare con main() ma sarei sorpreso di trovare un kernel che usa main() altro che in uno semplice, come UNIX! Nel momento in cui stai scrivendo il codice del kernel hai superato da tempo il requisito di nominare ogni modulo che scrivi come main() .

Spero che questo aiuti?

Ho trovato questo frammento di codice dal kernel per Unix versione 6. Come puoi vedere main() è solo un altro programma, che cerca di iniziare!

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.

Diversi modi per vederlo:

  1. I driver di dispositivo non sono programmi. Sono moduli che vengono caricati in un altro programma (il kernel). In quanto tali, non hanno un main() funzione.

  2. Il fatto che tutti i programmi devono avere un main() la funzione è valida solo per le applicazioni in spazio utente. Non si applica al kernel, né ai driver di dispositivo.


Linux
  1. Abbiamo un annullamento in Linux?

  2. Linux:perché Linux mostra sia più che meno memoria di quella che ho installato fisicamente?

  3. Perché una funzione principale senza un'istruzione return restituisce il valore 12?

  4. Cosa significa dire che il kernel Linux è preventivo?

  5. In che modo il kernel Linux determina l'ordine delle chiamate __init?

Un elenco di controllo per l'invio della prima patch del kernel Linux

Comando Dmesg in Linux

Comando Sysctl in Linux

Linux è un sistema operativo o un kernel?

Kernel Linux vs. Kernel Mac

In che modo Linux carica l'immagine "initrd"?