GNU/Linux >> Tutoriales Linux >  >> Linux

¿El kernel de Linux tiene una función principal?

start_kernel

En 4.2, start_kernel de init/main.c es un proceso de inicialización considerable y podría compararse con un main función.

Es el primer código independiente del arco que se ejecuta y configura una gran parte del kernel. Tanto como main , start_kernel está precedido por algún código de configuración de nivel inferior (hecho en el crt* objetos en la zona de usuario main ), después de lo cual se ejecuta el código C genérico "principal".

Cómo start_kernel recibe una llamada en x86_64

arch/x86/kernel/vmlinux.lds.S , un script de vinculación, establece:

ENTRY(phys_startup_64)

y

phys_startup_64 = startup_64 - LOAD_OFFSET;

y:

#define LOAD_OFFSET __START_KERNEL_map

arch/x86/include/asm/page_64_types.h define __START_KERNEL_map como:

#define __START_KERNEL_map  _AC(0xffffffff80000000, UL)

que es la dirección de entrada del kernel. TODO ¿cómo se llega exactamente a esa dirección? Tengo que entender la interfaz que Linux expone a los gestores de arranque.

arch/x86/kernel/vmlinux.lds.S establece la primera sección del gestor de arranque como:

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

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

#define HEAD_TEXT  *(.head.text)

arch/x86/kernel/head_64.S define startup_64 . Ese es el primer código del kernel x86 que se ejecuta. Hace mucho de configuración de bajo nivel, incluida la segmentación y la paginación.

Eso es lo primero que se ejecuta porque el archivo comienza con:

.text
__HEAD
.code64
.globl startup_64

y include/linux/init.h define __HEAD como:

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

por lo que es lo mismo que lo primero de la secuencia de comandos del enlazador.

Al final llama a x86_64_start_kernel un poco incómodo con y 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

y:

.balign 8
GLOBAL(initial_code)
.quad   x86_64_start_kernel

arch/x86/kernel/head64.c define x86_64_start_kernel que llama x86_64_start_reservations que llama start_kernel .

punto de entrada arm64

El primer arm64 que se ejecuta en un kernel v5.7 sin comprimir se define en https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L72 por lo que el add x13, x18, #0x16 o b stext dependiendo de 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.

Este es también el primer byte de una imagen del núcleo sin comprimir.

Ambos casos saltan a stext que inicia la acción "real".

Como se menciona en el comentario, estas dos instrucciones son los primeros 64 bytes de un encabezado documentado descrito en:https://github.com/cirosantilli/linux/blob/v5.7/Documentation/arm64/booting.rst#4-call -la-imagen-del-kernel

arm64 primera instrucción MMU habilitada:__primary_switched

Creo que es __primary_switched en cabeza.S:

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

En este punto, el núcleo parece crear tablas de páginas y tal vez se reubica de manera que las direcciones de la PC coincidan con los símbolos del archivo ELF de vmlinux. Por lo tanto, en este punto, debería poder ver nombres de funciones significativos en GDB sin magia adicional.

punto de entrada de la CPU secundaria arm64

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

El procedimiento de entrada se describe con más detalle en:https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691


Con main() probablemente te refieres a lo que main() es a un programa, es decir, su "punto de entrada".

Para un módulo que es init_module() .

De la segunda edición de Linux Device Driver:

Mientras que una aplicación realiza una sola tarea de principio a fin, un módulo se registra a sí mismo para atender solicitudes futuras y su función "principal" finaliza de inmediato. En otras palabras, la tarea de la función init_module (el punto de entrada del módulo) es preparar la invocación posterior de las funciones del módulo; es como si el módulo dijera:"Aquí estoy, y esto es lo que puedo hacer". El segundo punto de entrada de un módulo, cleanup_module, se invoca justo antes de que se descargue el módulo. Debería decirle al kernel:"Ya no estoy allí; no me pidas que haga nada más".


Fundamentalmente, no hay nada especial en que una rutina se llame main() . Como se mencionó anteriormente, main() sirve como punto de entrada para un módulo de carga ejecutable. Sin embargo, puede definir diferentes puntos de entrada para un módulo de carga. De hecho, puede definir más de un punto de entrada, por ejemplo, consulte su dll favorita.

Desde el punto de vista del sistema operativo (SO), todo lo que realmente necesita es la dirección del punto de entrada del código que funcionará como un controlador de dispositivo. El sistema operativo pasará el control a ese punto de entrada cuando se requiera que el controlador del dispositivo realice operaciones de E/S en el dispositivo.

Un programador del sistema define (cada sistema operativo tiene su propio método) la conexión entre un dispositivo, un módulo de carga que funciona como controlador del dispositivo y el nombre del punto de entrada en el módulo de carga.

Cada sistema operativo tiene su propio kernel (obviamente) y algunos podrían comenzar con main() pero me sorprendería encontrar un kernel que usara main() que no sea en uno simple, como UNIX! En el momento en que está escribiendo el código del kernel, hace tiempo que ha superado el requisito de nombrar cada módulo que escribe como main() .

¿Espero que esto ayude?

Encontré este fragmento de código del kernel para la versión 6 de Unix. Como puede ver, main() es solo otro programa, ¡intentando comenzar!

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.

Varias formas de verlo:

  1. Los controladores de dispositivos no son programas. Son módulos que se cargan en otro programa (el kernel). Como tal, no tienen un main() función.

  2. El hecho de que todos los programas deben tener un main() La función solo es verdadera para aplicaciones de espacio de usuario. No se aplica al kernel ni a los controladores de dispositivos.


Linux
  1. ¿Tenemos un deshacer en Linux?

  2. Linux – Kernel:¿Soporte de espacios de nombres?

  3. Linux – ¿Reenvío de IP del kernel?

  4. ¿Por qué una función principal sin declaración de retorno devuelve el valor 12?

  5. ¿Qué significa decir que el kernel de Linux es preventivo?

Una lista de verificación para enviar su primer parche del kernel de Linux

Comando Dmesg en Linux

Comando Sysctl en Linux

¿Linux es un sistema operativo o un kernel?

Núcleo de Linux vs. Núcleo de Mac

¿Cómo carga Linux la imagen 'initrd'?