| Entendiendo el Mecanismo de Inicio (Initcall) del Kernel de Linux: Creando funciones Dinámicas de apuntadores de Llamadas a Tablas | ||
|---|---|---|
| <<< Anterior | Siguiente >>> | |
Armados con la anterior informacion, ahora estamos listos para entender como trabaja el mecanismo de initcall del kernel de Linux. De Hecho si has entendido la mayoría de lo que se ha dicho hasta este punto. ya entiendes como trabaja este; puede que quieras parar de leer y ahora explorar por ti mismo!.
Cuando escribes un driver para el kernel de Linux, sigues una plantilla simple. Siguiendo esta plantilla, junto con algunas entradas en el sistema compilado un usuario puede compilar su driver en el kernel o también como modulo cargable. Todos los drivers, cuando se cargan tiene la oportunidad de ejecutar una función de inicialización de un-tiempo. Despues que se llama a esta función, esta no sera llamara de nuevo sera llamada durante el tiempo que tu driver este cargado. Si tu driver fue compilado como modulo, se llamara a la fución de inicialización de un-tiempo, cada vez que se cargue el modulo. Si tu driver esta compilado dentro del kernel, esta función de un-tiempo, es llamada cada vez que el sistema arranca. Tener un kernel que tiene una cantidad favorable de memoria usada por las funciones que se llaman una-vez cuando la maquina enciende, y no se llamaran de nuevo, es un desperdicio considerable. Por consiguiente los desarrolladores de kernel han acordado que tal código sera puesto en su propio segmento ELF el cual es expulsado "tossed away", una mez que la maquina esta arriba y corriendo (y ha pasado la etapa de inicialización).
Hacer un volcado completo de una parte del código en un segmento separado en tiempo de compilación es una buena idea, pero entonces como llamas a esas funciones en tiempo de ejecución? Las funciones no son todas de la misma longitud, y no seria una idea muy productiva forzar a todas ellas a tenerla. Por lo tanto no es posible avanzar a a través del segmento de código, llamando funciones a lo largo del recorrido. Sin embargo las definiciones de las funciones por si mismas, no tienen la misma longitud, por suerte todos losapuntadores a funciones son de la misma longitud (en el mismo sistema) por lo tanto por eso podemos compilar una tabla de punteros a todas las funciones de inicialización para llamar para llamar y pasar a través de esta tabla llamando a cada una por turno. Desde que esta tabla es también es algo que se necesita solamente en tiempo de incialización, esta también tiene en cuenta de poner la tabla de punteros dentro de su propio segmento entonces esto también puede corregirse despues que se haya completado la etapa de inicializaci&oacte;n.
Date cuenta de que el truco de abajo, el de poner el código de inicialización dentro de un segmento y la la tabla de llamadas de apuntadores a funciones de inicialización en otro segmento (ambos de los cuales pueden pueden ejecutarse, una vez la maquina este arriba y corriendo) solo se usa cuando un driver para un dispositivo esta compilado dentro del kernel. Si el driver para el dispositivo esta compilado como modulo entonces la inicialización se maneja de forma diferente.
La desicion, de si compilar algo dentro del kernel o como modular no se hace en tiempo de escritura del código por quien escribe el driver para el dispositivo, pero si, en tiempo en configuración y compilación, a veces, por algún otro que escribio el driver para el dispositivo. Es importante el tratar de usar el mismo código par ambas situaciones, esto hace las cosas muy fáciles para manejar el c&ocaute;digo para la persona que escribe el driver para el dispositivo. Como se manejan ambas situaciones? Escribiendo un grupo de macros y invitando a los programadores a seguir la siguiente plantilla.
He destilado la plantilla para escritura de drivers para dispositivos para Linux para un sencillo driver en el siguiente código He encontrado y expandido las macros para la situación donde queremos que el driver se compile dentro del kernel Linux. También hay que notar que si quieres escribir tu propios drivers para dispositivos y solo estas aprendiendo, esta no es la forma de como deberia verse tu código mira que los drivers para dispositivos no contienen un main()! Escribi este código de tal forma que usa las mismas ideas y crudamente el mismo código del kernel, pero de tal forma que se pueda usar como usuario normal como código que no es de un driver para dispositivo.
/*
* AUTHOR: Trevor Woerner
* START DATE: 14 August 2003 - 09:58:33 AM
* MODIFIED: 23 September 2003 - 12:33:55 AM
* FILENAME: kernelcalls.c
* PURPOSE: Demonstrates how code works that is meant to be
* compiled into the kernel.
*
* Copyright (C) 2003 Trevor Woerner
*/
#include <stdio.h>
typedef int (*initcall_t)(void);
extern initcall_t __initcall_start, __initcall_end;
#define __initcall(fn) \
static initcall_t __initcall_##fn __init_call = fn
#define __init_call __attribute__ ((unused,__section__ ("function_ptrs")))
#define module_init(x) __initcall(x);
#define __init __attribute__ ((__section__ ("code_segment")))
static int __init
my_init1 (void)
{
printf ("my_init () #1\n");
return 0;
}
static int __init
my_init2 (void)
{
printf ("my_init () #2\n");
return 0;
}
module_init (my_init1);
module_init (my_init2);
void
do_initcalls (void)
{
initcall_t *call_p;
call_p = &__initcall_start;
do {
fprintf (stderr, "call_p: %p\n", call_p);
(*call_p)();
++call_p;
} while (call_p < &__initcall_end);
}
int
main (void)
{
fprintf (stderr, "in main()\n");
do_initcalls ();
return 0;
}
|
Ahora vamos a examinar mas de cerca esos #define.
module_init(x) (calls __initcall(fn))
#define __initcall(fn) \
static initcall_t __initcall_##fn __init_call = fn
#define __init_call __attribute__ ((unused,__section__ ("function_ptrs")))
#define module_init(x) __initcall(x);
|
toma el nombre de la función
Define una variable de la cual su nombre es laconcatenacion de la cadena "__initcall_" mas el nombre de la función
de tipo initcall_t (explico. un apuntador a función)
al cual se le han asignado atributos de la expansion de la macro __init_call (la cual solo dice basicamente ponga este objeto (un apuntador a función) dentro de su propio segmento llamado function_ptrs)
al cual se asigna el valor de la dirección de la funcion
#define module_init(fn) \
static initcall_t __initcall_##fn __attribute__ ((section ("function_ptrs"))) = fn
|
__init
#define __init __attribute__ ((__section__ ("code_segment")))
|
le dice al compilador que ponga todos esos objectos dentro de sus propios segmentos llamados code_segment
Compìlando este código obtenemos...un error:
[trevor]$ gcc -o kernelcalls kernelcalls.c
/tmp/ccVwvr4P.o(.text+0x9): In function `do_initcalls':
: undefined reference to `__initcall_start'
/tmp/ccVwvr4P.o(.text+0x30): In function `do_initcalls':
: undefined reference to `__initcall_end'
collect2: ld returned 1 exit status
[trevor]$
|
Tratar de crear un script válido para enlazador a mano de la nada seria un buen ejercicio, pero no algo para lo que tenga tiempo de investigar. Entoces en vez de eso, conseguire que el enlazador me diga cual es el script por defecto para el enlazador y modificarlo para generar mi script requerido para el enlazador. Puedes obtener el script por defecto, haciendo un gcc -Wl,--verbose en linea de comando, la salida la cual he guardado como linker.lds. Puedes encontrar los contenidos de ese script por defecto en esta sección.
Siguiendo la dirección de los scripts del enlazador del kernel he añadido las siguientes lineas al script del enlazador:
__initcall_start = .;
function_ptrs : { *(function_ptrs) }
__initcall_end = .;
code_segment : { *(code_segment) }
|
Lo cual resulta en la siguiente salida:
[trevor]$ make
gcc -Tlinker.lds -o kernelcalls kernelcalls.c
[trevor]$ ./kernelcalls
in main()
call_p: 0x80482cc
my_init () #1
call_p: 0x80482d0
my_init () #2
[trevor]$
|
el objdump -t se ve algo como:
08048274 g F .init 00000000 _init
08048274 l d .init 00000000
0804828c l d .plt 00000000
0804829c F *UND* 00000023 fprintf@@GLIBC_2.0
080482ac F *UND* 000000fb __libc_start_main@@GLIBC_2.0
080482bc F *UND* 00000039 printf@@GLIBC_2.0
080482cc g *ABS* 00000000 __initcall_start
080482cc l O function_ptrs 00000004 __initcall_my_init1
080482cc l d function_ptrs 00000000
080482d0 l O function_ptrs 00000004 __initcall_my_init2
080482d4 g *ABS* 00000000 __initcall_end
080482d4 l F code_segment 0000001d my_init1
080482d4 l d code_segment 00000000
080482f1 l F code_segment 0000001d my_init2
08048310 g F .text 00000000 _start
|
Date cuenta como si re-organizamos las siguientes lineas en las fuentes:
module_init (my_init2);
module_init (my_init1);
|
[trevor]$ make
gcc -Tlinker.lds -o kernelcalls kernelcalls.c
[trevor]$ ./kernelcalls
in main()
call_p: 0x80482cc
my_init () #2
call_p: 0x80482d0
my_init () #1
[trevor]$
|
08048274 g F .init 00000000 _init
08048274 l d .init 00000000
0804828c l d .plt 00000000
0804829c F *UND* 00000023 fprintf@@GLIBC_2.0
080482ac F *UND* 000000fb __libc_start_main@@GLIBC_2.0
080482bc F *UND* 00000039 printf@@GLIBC_2.0
080482cc g *ABS* 00000000 __initcall_start
080482cc l O function_ptrs 00000004 __initcall_my_init2
080482cc l d function_ptrs 00000000
080482d0 l O function_ptrs 00000004 __initcall_my_init1
080482d4 g *ABS* 00000000 __initcall_end
080482d4 l F code_segment 0000001d my_init1
080482d4 l d code_segment 00000000
080482f1 l F code_segment 0000001d my_init2
08048310 g F .text 00000000 _start
|
| <<< Anterior | Inicio | Siguiente >>> |
| Como poner objetos dentro de sus propias secciones ELF | Del Kernel al código Stand-Alone |