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