GNU/Linux >> Znalost Linux >  >> Linux

Má jádro Linuxu hlavní funkci?

start_kernel

Dne 4.2, start_kernel od init/main.c je značný inicializační proces a lze jej přirovnat k main funkce.

Je to první kód nezávislý na archu, který běží a nastavuje velkou část jádra. Podobně jako main , start_kernel předchází nějaký kód nastavení nižší úrovně (provedený v crt* objektů v uživatelské zemi main ), po kterém běží „hlavní“ generický C kód.

Jak start_kernel je volána v x86_64

arch/x86/kernel/vmlinux.lds.S , skript linkeru, nastaví:

ENTRY(phys_startup_64)

a

phys_startup_64 = startup_64 - LOAD_OFFSET;

a:

#define LOAD_OFFSET __START_KERNEL_map

arch/x86/include/asm/page_64_types.h definuje __START_KERNEL_map jako:

#define __START_KERNEL_map  _AC(0xffffffff80000000, UL)

což je vstupní adresa jádra. TODO jak je přesně dosaženo této adresy? Musím porozumět rozhraní, které Linux vystavuje bootloaderům.

arch/x86/kernel/vmlinux.lds.S nastaví úplně první sekci bootloaderu jako:

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

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

#define HEAD_TEXT  *(.head.text)

arch/x86/kernel/head_64.S definuje startup_64 . To je úplně první kód jádra x86, který běží. Dělá to hodně nízkoúrovňového nastavení, včetně segmentace a stránkování.

To je pak první věc, která se spustí, protože soubor začíná:

.text
__HEAD
.code64
.globl startup_64

a include/linux/init.h definuje __HEAD jako:

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

takže to samé jako úplně první věc skriptu linkeru.

Nakonec zavolá x86_64_start_kernel trochu nešikovně pomocí a 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

a:

.balign 8
GLOBAL(initial_code)
.quad   x86_64_start_kernel

arch/x86/kernel/head64.c definuje x86_64_start_kernel který volá x86_64_start_reservations který volá start_kernel .

vstupní bod arm64

Úplně první arm64, který běží na nekomprimovaném jádře v5.7, je definován na https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L72, takže buď add x13, x18, #0x16 nebo b stext v závislosti na 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.

Toto je také úplně první byte nekomprimovaného obrazu jádra.

Oba tyto případy skočí na stext která zahájí „skutečnou“ akci.

Jak je uvedeno v komentáři, tyto dvě instrukce jsou prvních 64 bajtů zdokumentované hlavičky popsané na:https://github.com/cirosantilli/linux/blob/v5.7/Documentation/arm64/booting.rst#4-call -image-kernel

Instrukce první aktivované MMU pro arm64:__primary_switched

Myslím, že je to __primary_switched v hlavě.S:

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

V tomto okamžiku se zdá, že jádro vytváří tabulky stránek + možná se přemístí tak, aby adresy PC odpovídaly symbolům souboru vmlinux ELF. Proto byste v tomto bodě měli být schopni vidět smysluplné názvy funkcí v GDB bez zvláštní magie.

Vstupní bod sekundárního procesoru arm64

secondary_holding_pen definováno na:https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691

Vstupní postup je dále popsán na:https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691


S main() pravděpodobně máte na mysli to, co main() je k programu, konkrétně jeho "vstupnímu bodu".

Pro modul, který je init_module() .

Z 2. vydání ovladače zařízení Linux:

Zatímco aplikace provádí jeden úkol od začátku do konce, modul se zaregistruje, aby mohl obsluhovat budoucí požadavky, a jeho „hlavní“ funkce okamžitě skončí. Jinými slovy, úkolem funkce init_module (vstupní bod modulu) je připravit se na pozdější vyvolání funkcí modulu; je to, jako by modul řekl:"Tady jsem, a to je to, co mohu udělat." Druhý vstupní bod modulu, cleanup_module, se vyvolá těsně před uvolněním modulu. Mělo by to říct jádru:"Už tam nejsem; nechtějte po mně nic jiného."


Na tom, že se rutina jmenuje main(), není v zásadě nic zvláštního . Jak bylo zmíněno výše, main() slouží jako vstupní bod pro spustitelný zaváděcí modul. Pro zatěžovací modul však můžete definovat různé vstupní body. Ve skutečnosti můžete definovat více než jeden vstupní bod, například odkazovat na svůj oblíbený dll.

Z pohledu operačního systému (OS) opravdu potřebuje pouze adresu vstupního bodu kódu, který bude fungovat jako ovladač zařízení. Operační systém předá řízení tomuto vstupnímu bodu, když je vyžadován ovladač zařízení k provedení I/O do zařízení.

Systémový programátor definuje (každý OS má svou vlastní metodu) spojení mezi zařízením, zaváděcím modulem, který funguje jako ovladač zařízení, a názvem vstupního bodu v zaváděcím modulu.

Každý OS má své vlastní jádro (samozřejmě) a některé mohou/možná začínají na main() ale byl bych překvapen, kdybych našel jádro, které používá main() jiné než v jednoduchém, jako je UNIX! V době, kdy píšete kód jádra, jste již dávno překonali požadavek pojmenovat každý modul, který píšete, jako main() .

Doufám, že to pomůže?

Nalezen tento fragment kódu z jádra pro Unix verze 6. Jak můžete vidět main() je jen další program, který se pokouší začít!

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.

Několik způsobů, jak se na to podívat:

  1. Ovladače zařízení nejsou programy. Jsou to moduly, které se načítají do jiného programu (jádra). Jako takové nemají main() funkce.

  2. Skutečnost, že všechny programy musí mít main() funkce platí pouze pro aplikace v uživatelském prostoru. Nevztahuje se na jádro ani na ovladače zařízení.


Linux
  1. Linux – Kernel IP Forwarding?

  2. Linux – Jak se linuxové jádro srovnává s architekturami mikrokernelu?

  3. Linux – Jsou různá jádra Linux/unix zaměnitelná?

  1. Máme v Linuxu možnost vrátit zpět?

  2. Co to znamená, když se řekne linuxové jádro je preemptivní?

  3. Volání funkce uživatelského prostoru z modulu jádra Linuxu

  1. Linux – Kernel:Podpora jmenných prostorů?

  2. Linux – Proč Linux ukazuje více i méně paměti, než jsem fyzicky nainstaloval?

  3. Proč hlavní funkce bez příkazu return vrací hodnotu 12?