Programación del módulo Linux

https://blog.51cto.com/widder/1956616

I. Introducción:

1. ¿Qué es un módulo del kernel?

1> Los módulos del kernel son programas con funciones independientes. Se puede compilar por separado, pero no se puede ejecutar por separado, su operación debe estar vinculada al kernel como parte del kernel y ejecutarse en el espacio del kernel.

2> La programación del módulo y la versión del kernel están estrechamente vinculadas, porque los nombres de las funciones de algunas funciones cambiarán en diferentes versiones del kernel. Por lo tanto, también se puede decir que la programación de módulos es programación del núcleo.

3> Características:

Los módulos en sí no se compilan en la imagen del kernel, controlando así el tamaño del kernel;
una vez cargados, los módulos son idénticos al resto del kernel.

2. La diferencia entre programación a nivel de usuario y programación de módulos del kernel

 

2. Habiendo dicho todo esto, ¿cómo escribir un programa de módulo del kernel?

1. Veamos primero los dos ejemplos de funciones más simples, que también son programas que casi todos los programadores escribirán cuando aprendan un nuevo idioma: ¡salida hola mundo!

Ahora usamos la programación de módulos para generar ¡hola mundo! y la programación a nivel de usuario para generar ¡hola mundo! . A través de estos dos programas, analizamos cómo escribir un programa de módulo del kernel.

Programación a nivel de usuario: hello.c

#incluir<stdio.h>

int principal (vacío)

{

printf("hola mundo/n");

}

Programación del kernel: module.c

#incluir <linux/init.h>
#incluir <linux/módulo.h>
#incluir <linux/kernel.h>

MODULE_LICENSE("Doble BSD/GPL");

static int hello_init(void)
{ printk(KERN_ALERT "hola, soy edsionte/n"); devolver 0; }


static void hola_exit(void)
{ printk(KERN_ALERT "adiós, kernel/n"); }

module_init(hola_init);
module_exit(hola_salida);
// 可选
MODULE_AUTHOR("Tiger-John");
MODULE_DESCRIPTION("¡Este es un ejemplo sencillo!/n");
MODULE_ALIAS("Un ejemplo más simple");

2. Implementación específica de la programación del módulo del kernel.

Paso 1: Primero, echemos un vistazo al archivo de encabezado del programa.

#incluir<linux/kernel.h>

#incluir<linux/module.h>

#incluir<linux/init.h>

Estos tres archivos de encabezado son necesarios para escribir programas de módulos del kernel.

Descripción de Tiger-John:

1> Dado que las funciones de la biblioteca utilizadas en la programación del kernel y la programación a nivel de usuario son diferentes, sus archivos de encabezado también son diferentes de los que usamos cuando escribimos programas a nivel de usuario.

2> Echemos un vistazo a dónde se almacenan sus archivos de encabezado en Linux.

a. La ubicación del archivo de encabezado del kernel: /usr/src/linux-2.6.x/include/

b. Ubicación de los archivos de encabezado a nivel de usuario: /usr/include/

Ahora lo entendemos. De hecho, los archivos de encabezado y las funciones del sistema que utilizamos cuando escribimos programas de módulos del kernel son diferentes de los que utilizamos cuando utilizamos la programación en capas.

Paso 2: Dos funciones necesarias al escribir un módulo del kernel:

1> Función de carga:

estático int init_fun (vacío)

{

// código de inicialización

}

Ejemplo de función:

static int hello_init(void)// Si no se agrega void, aparecerá una alarma durante la depuración.

{

printk("¡hola mundo!/n");

devolver 0;

}

2> La función de descarga no tiene valor de retorno

vacío estático cleanup_fun (vacío)

{

// código de liberación

}

Ejemplo de función:

static void hello_exit(void)// Si no se agrega void, se producirá una alarma. Si se cambia a static int, también se informará un error, porque la función de salida no puede devolver un valor.

{

printk("adiós,adiós/n");

}

En la programación del módulo, las dos funciones anteriores deben estar disponibles;

Tiger-John agregó:

Hay otra forma de escribir la función de registro y la función de desinstalación:

1>Función de carga del módulo

estático int __init init_fun(vacío)

{

// código de inicialización

}

Ejemplo de función:

estático int __init hola_init (vacío)

{

printk("hola tigre/n");

devolver 0;

}

2> La función de descarga no tiene valor de retorno

vacío estático __salida cleaup_fun(vacío)

{

// código de liberación

}

Ejemplo de función:

vacío estático __salida salida (vacío)

{

printk("¡adiós!/n");

}

En comparación, podemos encontrar que la principal diferencia entre el método de escritura de la segunda función y la primera función es la adición de los prefijos __init y __exit. (init y exit están precedidos por dos guiones bajos)

Entonces, ¿cuáles son las ventajas del segundo método sobre el primero?

_init y __exit son definiciones de macros del kernel de Linux que permiten al sistema liberar funciones y liberar la memoria que ocupa una vez completada la inicialización. Por tanto sus ventajas son obvias. Por lo tanto, se recomienda que todos adopten el segundo método al escribir funciones de entrada y funciones de salida.

(1) En el kernel de Linux, todas las funciones marcadas como __init se colocan en la sección .init.text al conectarse. Además, todas las funciones __init también guardan un puntero de función, el kernel llamará a estas funciones __init a través de estos punteros de función durante la inicialización. y libere la sección de inicio (incluidos .init.text, .initcall.init, etc.) una vez completada la inicialización.

(2) Al igual que __init, __exit también puede habilitar la función correspondiente para recuperar memoria automáticamente una vez completada.

3 > Ahora echemos un vistazo a la función printk()

R. Como se mencionó anteriormente, las funciones de la biblioteca que usamos en la programación del kernel son diferentes de las del modo de usuario. Printk es una función de impresión de información en modo kernel y su función es similar a printf de la biblioteca C estándar. printk también tiene niveles de impresión de información.

b. Ahora echemos un vistazo al prototipo de la función printk():

int printk(const char *fmt, ...)

Nivel de impresión de mensajes:

fmt---- Nivel de mensaje:

#define KERN_EMERG "<0>" /* Mensaje de emergencia, que aparece antes de que el sistema falle, indicando que el sistema no está disponible*/

#define KERN_ALERT "<1>" /* Mensaje de informe que indica que se deben tomar medidas inmediatas*/

#define KERN_CRIT "<2>" /* Condiciones críticas, que generalmente involucran fallas graves de operación de hardware o software*/

#define KERN_ERR "<3>" /* Condiciones de error, los controladores suelen utilizar KERN_ERR para informar errores de hardware*/

#define KERN_WARNING "<4>" /* Condiciones de advertencia, advertencia de posibles problemas*/

#define KERN_NOTICE "<5>" /* Condiciones normales pero importantes, utilizadas para recordatorios. Comúnmente utilizado para mensajes relacionados con la seguridad*/

#define KERN_INFO "<6>" /* Información solicitada, como imprimir información del hardware cuando se inicia el controlador*/

#define KERN_DEBUG "<7>" /* Mensaje de nivel de depuración*/

C. ¿Por qué el modo kernel usa la función printk(), mientras que el modo usuario usa la función printf()?

La función printk() utiliza directamente la función tty_write() para escribir en la terminal. La función printf() llama a la función de llamada del sistema write() para escribir en el dispositivo de salida estándar. Por lo tanto, la función printk () no se puede usar directamente en el modo de usuario (como el proceso 0). En el modo kernel, dado que ya es un nivel privilegiado, no es necesario realizar una llamada al sistema para cambiar el nivel de privilegio, por lo que la función printk() se puede utilizar directamente. printk es la salida del kernel y es invisible en la terminal. Podemos echar un vistazo al registro del sistema.

Pero podemos usar el comando: cat /var/log/messages, o usar el comando dmesg para ver la información de salida.

Paso 3: cargar y descargar módulos

1>módulo_init(hola_init)

a.Dígale al kernel dónde comenzar a ejecutar el programa del módulo que escribió.

El parámetro en la función b.module_init() es el nombre de la función registrada.

2>módulo_salida(hola_salida)

a.Dígale al núcleo dónde dejar el programa del módulo que escribió.

El nombre del parámetro en b.module_exit() es el nombre de la función de desinstalación.

Generalmente realizamos alguna inicialización en la función de registro, como solicitar espacio de memoria y registrar un número de dispositivo, etc. Luego tenemos que liberar los recursos que ocupamos en la función de descarga.

(1) Si la función de carga del módulo registra XXX, la función de descarga del módulo debe cerrar sesión XXX

(2) Si la función de carga del módulo se aplica dinámicamente a la memoria, la función de descarga del módulo debe cerrar sesión XXX

(3) Si la función de carga del módulo se aplica a la ocupación de recursos de hardware (interrupciones, canales DMA), la función de descarga del módulo debe liberar estos recursos de hardware.

(4) Si la función de carga del módulo enciende el hardware, el hardware generalmente se apaga en la función de descarga.

Paso 4: Declaración de permisos

1> Ejemplo de función:

MODULE_LICENSE("BSD/GPL dual") ;

2> Esto es opcional, no puedes agregar el sistema predeterminado (pero sonará una alarma)

La declaración del módulo describe los permisos del módulo del kernel. Si no se declara LICENCIA, recibirá una advertencia del kernel cuando se cargue el módulo.

En el kernel Linux2.6, la LICENCIA aceptable incluye "GPL", "GPL v2", "GPL y derechos adicionales", "Dual BSD/GPL", "Dual MPL/GPL", "Propietario".

Parte 5: Declaración y descripción del módulo (se puede agregar o no)

MODULE_AUTHOR("autor"); // autor

MODULE_DESCRIPTION("descripción");//Descripción

MODULE_VERSION("cadena_versión"); // Versión

MODULE_DEVICE_TABLE("table_info");//Tabla de dispositivos

Para USB, PCI y otros controladores de dispositivos, generalmente se crea un MODULE_DEVICE_TABLE

MODULE_ALIAS("nombre_alternativo"); // Alias

Tiger-John: Resumen

Después de los cinco pasos anteriores (en realidad, solo los primeros cuatro pasos), se completa la programación completa del módulo.

Paso 6: Comandos de programación de módulos de uso común:

1>En sistemas Linux, use el comando lsmod para obtener todos los módulos cargados en el sistema y las dependencias entre módulos.

2>También puedes usar cat /proc/modules para ver la información del módulo cargado

3> La información sobre los módulos cargados en el kernel también existe en el directorio /sys/module. Después de cargar hello.ko, el kernel contendrá el directorio /sys/module/hello, que también contiene un archivo refcnt y un directorio de secciones. Puede ver la relación entre ellos ejecutando tree -a en el directorio /sys/module/hello.

4>Utilice el comando modinfo <nombre del módulo> para obtener información del módulo, incluido el autor del módulo, la descripción del módulo, los parámetros admitidos por un determinado bloque y vermagic.

Sin embargo, esto ya lo hemos dicho antes. Compilación entre programación del kernel y programación a nivel de usuario

Los enlaces también son diferentes. Entonces, ¿cómo compilamos, vinculamos y ejecutamos el programa del módulo?

Ahora sigamos aprendiendo cómo escribir archivos Makefile:

3. El uso de make y la preparación de Makefile.

1. ¿Qué es Makefile?

1>Makefile es un script que se utiliza principalmente para compilar varios archivos.

2> El programa make puede mantener archivos fuente interdependientes, pero cuando algunos archivos cambian, puede identificarlos automáticamente

Y solo se compilan automáticamente los archivos correspondientes.

2. Cómo escribir Makefile

El archivo Makefile consta de cinco partes: reglas de visualización que incluyen definiciones de variables de reglas, indicadores de archivos Makefile y comentarios.

El prototipo de una regla Make es:

Objetivo...: Depende de...

Orden

...

Los comandos de Shell se pueden usar en archivos MAKE, como pwd, uname

Archivo MAKE simple:

obj-m := hola.o

kernel_path=/usr/src/linux-headers-$(shell uname -r)

todo:

hacer -C $(kernel_path) M=$(PWD) módulos

limpio:

hacer -C $(kernel_path) M=$(PWD) limpio

obj-m:= hello.o // Genera el destino del módulo hola

kernel_path // Definir el directorio del archivo fuente del kernel

todo :

hacer -C $(kernel_path) M=$(PWD) módulos

// Los parámetros para generar el módulo del kernel son el directorio del código fuente del kernel y el directorio donde se encuentra el módulo.

limpio:

hacer -C $(kernel_path) M=$(PWD) limpio

// Borrar archivos de módulo generados y archivos intermedios

Tiger-John explica:

1> Las líneas debajo de todo y limpia deben estar separadas por caracteres de tabla antes de crear y no pueden separarse por espacios, de lo contrario se producirá un error de compilación.

2> Después de -C, se especifica el directorio del código fuente del kernel de Linux, y después de M =, se especifica el directorio donde se encuentran hello.c y Makefile.

3.Ejemplo de archivo Make:

1 obj-m:=module.o
2
3
4 CURRENT_PATH :=$(shell pwd)
5 VERSION_NUM :=$(shell uname -r)
6 LINUX_PATH :=/usr/src/linux-headers-$(VERSION_NUM)
7
8 todos:
9 make -C $(LINUX_PATH) M=$(CURRENT_PATH) módulos
10 clean:
11 make -C $(LINUX_PATH) M=$(CURRENT_PATH) clean

-------------------------------------------------- --------------------

Después de la programación del módulo anterior y la programación Makefile, podemos compilar, vincular y ejecutar nuestro programa.

4. Proceso de operación del módulo del kernel.

1> Ingrese make en la consola para compilar y vincular

2> Después de corregir, ingrese sudo insmod module.ko en la consola (cargar módulo)

3> Ingrese dmesg en la consola para ver los resultados

4> Ingrese rmmod tigre (módulo de desinstalación) en la consola

5> Ingrese dmesg para ver los resultados

(O utilice cat /var/log/messages para ver el archivo de registro del sistema)

6>hacer limpieza (eliminar archivos generados intermedios)

-------------------------------------------------- --------------------

Ahora practiquémoslo en general y experimentémoslo. El placer de escribir programas de módulos del kernel

módulo.c

1 #include<linux/kernel.h>
2 #include<linux/init.h>
3 #include<linux/module.h>
4 MODULE_LICENSE("Doble BSD/GPL");
5
6 static int __init hola_init(void)
7 { 8 printk("Hola mundo/n"); 9 devuelve 0; 10 } 11 12 vacío estático __salida hola_salida(void) 13 { 14 printk("Adiós Corne/n"); 15 16 } 17 module_init(hola_init); 18 módulo_salida(hola_salida);










Archivo Make

1 obj-m:=module.o
2
3
4 CURRENT_PATH :=$(shell pwd)
5 VERSION_NUM :=$(shell uname -r)
6 LINUX_PATH :=/usr/src/linux-headers-$(VERSION_NUM)
7
8 todos:
9 make -C $(LINUX_PATH) M=$(CURRENT_PATH) módulos
10 clean:
11 make -C $(LINUX_PATH) M=$(CURRENT_PATH) clean

Escribe marca en la terminal

think@ubuntu:~/work/moudule/mokua_biancheng$ make
make -C /usr/src/linux-headers-2.6.32-25-generic M=/home/think/work/moudule/mokua_biancheng módulos
make[1]: Ingresando al directorio `/usr/src/linux-headers-2.6.32-25-generic'
Construyendo módulos, etapa 2.
Los módulos MODPOST 1
hacen[1]: Saliendo del directorio `/usr/src/linux-headers-2.6.32- 25-generico'
think@ubuntu:~/work/moudule/mokua_biancheng$

think@ubuntu:~/work/module/mokua_biancheng$ sudo insmod module.ko

think@ubuntu:~/work/moudule/mokua_biancheng$ dmesg

[19011.002597] Hola mundo

Cuando el programa no tiene errores, podemos ver los resultados del programa ejecutándose cuando ingresamos dmesg.
----------------------------------
Programación del módulo Linux
https://blog.51cto.com/widder/ 1956616

Supongo que te gusta

Origin blog.csdn.net/gaotihong/article/details/127576142
Recomendado
Clasificación