Uso de archivos de secuencia de kernel de Linux

Un rasgo característico de la programación moderna es el uso de la red global como fuente de información de referencia, en particular, una fuente de patrones para resolver problemas desconocidos o poco conocidos para un programador específico. Tal enfoque ahorra mucho tiempo y a menudo da resultados bastante cualitativos. Sin embargo, las soluciones establecidas en la red, aunque generalmente son correctas, no siempre tienen en cuenta todas las sutilezas de la resolución de un problema, lo que lleva a la aparición en el código fuente de secciones que generalmente funcionan correctamente, pero en circunstancias no bastante estándar fuentes de sorpresas desagradables.

Considere el tema del uso de archivos de secuencia en el núcleo de Linux, tales archivos se consideran el mecanismo más conveniente para imprimir desde el modo de núcleo. Pero en la práctica, usarlos correctamente es mucho más difícil de lo que piensas.

Muchos materiales sobre este tema están disponibles en línea. Lo mejor es el código fuente del núcleo en sí, que tiene comentarios bastante detallados. El problema con esta fuente de información es su volumen. Si no sabe exactamente qué buscar, es mejor si solo tiene un tiempo limitado, para no intentarlo en absoluto. Para mí, cuando me interesé en el tema, Google proporcionó varias fuentes de información aparentemente excelentes relacionadas con mi búsqueda: el famoso libro The Linux Kernel Module Programming Guide y una serie de artículos de Rob Day . Estas fuentes no son nuevas, pero son muy sólidas.

Consideremos primero con más detalle cuando es natural usar archivos de secuencia. La situación más común es crear su propio archivo en el sistema de archivos / proc. Al leer los archivos de este sistema, puede obtener una variedad de información sobre el equipo utilizado, sus controladores, RAM, procesos, etc.

Parece que la impresión de cualquier cosa es la tarea más simple en la programación. Pero al trabajar en modo kernel, el sistema operativo impone muchas restricciones que pueden parecer completamente inimaginables para el desarrollador del software de la aplicación. En modo kernel, el tamaño del búfer de impresión está limitado por el tamaño de una página de memoria virtual. Para la arquitectura x86 es de cuatro kilobytes. Por lo tanto, un buen programa al imprimir grandes cantidades de datos primero debe lograr llenar el búfer al máximo, luego imprimirlo y luego repetir esta iteración hasta que los datos para imprimir se agoten por completo. Por supuesto, puede imprimir carácter por carácter, lo que simplificaría mucho todo, pero estamos hablando de buenos programas.

Las fuentes mencionadas anteriormente fueron ligeramente peores de lo esperado. En el libro, por ejemplo, parte de la información resultó ser generalmente incorrecta y eso es lo que me llevó a escribir esta nota. Es común considerar que la información dada en forma de esquema es la más fácil de entender y usar. Sin embargo, en este libro la imagen relacionada con el tema es incorrecta. El uso de tal esquema puede conducir a errores graves, aunque el ejemplo en el libro funciona correctamente y sigue este mismo esquema. Esto se debe al hecho de que en este ejemplo, solo se imprimen unos pocos bytes cada vez que se accede a / proc / iter. Si lo usa como plantilla para imprimir textos más grandes que una página de memoria, habrá sorpresas. La serie de artículos mencionada anteriormente no contiene errores obvios, pero no informa sobre algunos detalles que son importantes para comprender el tema.

Entonces, consideremos primero el esquema correcto de cómo trabajar con un archivo de secuencia.



Para trabajar con dicho archivo, debe crear las funciones start (), stop (), next () y show (). Los nombres de estas funciones pueden ser cualquiera, elegí las palabras más cortas que corresponden en significado a las acciones de las funciones. Cuando tales funciones están presentes y correctamente conectadas a los sistemas del núcleo, comienzan a funcionar automáticamente al acceder al archivo asociado con ellas en el directorio / proc. Lo más confuso es el uso de la función stop (), que se puede llamar en tres contextos diferentes. Llamarlo después de start () significa finalizar el trabajo de impresión. Llamarlo después de show () significa que la última operación de impresión en el búfer (generalmente se utiliza la función seq_printf para esto) desbordó el búfer de página y esta operación de impresión se canceló. Su llamada después de next () es el caso más interesante que ocurre cuando finaliza la impresión de algunos datos en el búfer y necesita finalizar el trabajo o utilizar nuevos datos. Por ejemplo, suponga que nuestro archivo en el directorio / proc, al acceder a él, primero produce información en dispositivos de bloque y luego en los de carácter. En primer lugar, la función start () inicializa la impresión para dispositivos de bloque, y las funciones next () y, posiblemente, show () usan estos datos de inicialización para imprimir información paso a paso sobre los dispositivos de bloque. Cuando todo esté listo, después de la última llamada a next (), se realizará la llamada considerada para detener (), después de lo cual se llama a start (), que esta vez ya debería iniciar la impresión adicional para dispositivos de caracteres.

Doy un ejemplo ligeramente modificado (el contenido del archivo evens.c) del artículo de Rob Day. Tuve que reemplazar la llamada de una función, que está ausente en los núcleos modernos con su equivalente real. Los comentarios también han cambiado ligeramente.

#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/proc_fs.h> #include <linux/fs.h> #include <linux/seq_file.h> #include <linux/slab.h> static int limit = 10; //default value, it can be changed here or module_param(limit, int, S_IRUGO); //transfered as a module parameter static int* even_ptr; //we will work with dynamic memory /** * start */ static void *ct_seq_start(struct seq_file *s, loff_t *pos) { printk(KERN_INFO "Entering start(), pos = %Ld, seq-file pos = %lu.\n", *pos, s->count); if (*pos >= limit) { // are we done? printk(KERN_INFO "Apparently, we're done.\n"); return NULL; } //Allocate an integer to hold our increasing even value even_ptr = kmalloc(sizeof(int), GFP_KERNEL); if (!even_ptr) // fatal kernel allocation error return NULL; printk(KERN_INFO "In start(), even_ptr = %pX.\n", even_ptr); *even_ptr = (*pos)*2; return even_ptr; } /** * show */ static int ct_seq_show(struct seq_file *s, void *v) { printk(KERN_INFO "In show(), even = %d.\n", *(int*)v); seq_printf(s, "The current value of the even number is %d\n", *(int*)v); return 0; } /** * next */ static void *ct_seq_next(struct seq_file *s, void *v, loff_t *pos) { printk(KERN_INFO "In next(), v = %pX, pos = %Ld, seq-file pos = %lu.\n", v, *pos, s->count); (*pos)++; //increase my position counter if (*pos >= limit) //are we done? return NULL; *(int*)v += 2; //to the next even value return v; } /** * stop */ static void ct_seq_stop(struct seq_file *s, void *v) { printk(KERN_INFO "Entering stop().\n"); if (v) printk(KERN_INFO "v is %pX.\n", v); else printk(KERN_INFO "v is null.\n"); printk(KERN_INFO "In stop(), even_ptr = %pX.\n", even_ptr); if (even_ptr) { printk(KERN_INFO "Freeing and clearing even_ptr.\n"); kfree(even_ptr); even_ptr = NULL; } else printk(KERN_INFO "even_ptr is already null.\n"); } /** * This structure gathers functions which control the sequential reading */ static struct seq_operations ct_seq_ops = { .start = ct_seq_start, .next = ct_seq_next, .stop = ct_seq_stop, .show = ct_seq_show }; /** * This function is called when a file from /proc is opened */ static int ct_open(struct inode *inode, struct file *file) { return seq_open(file, &ct_seq_ops); }; /** * This structure gathers functions for a /proc-file operations */ static struct file_operations ct_file_ops = { .owner = THIS_MODULE, .open = ct_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release }; /** * This function is called when this module is loaded into the kernel */ static int __init ct_init(void) { proc_create("evens", 0, NULL, &ct_file_ops); return 0; } /** * This function is called when this module is removed from the kernel */ static void __exit ct_exit(void) { remove_proc_entry("evens", NULL); } module_init(ct_init); module_exit(ct_exit); MODULE_LICENSE("GPL"); 


Las funciones para trabajar con un archivo de secuencia utilizan dos punteros con funcionalidad superpuesta (esto también es algo confuso). Uno de ellos debe apuntar al objeto actual que se imprimirá en el búfer mediante show (): es el puntero 'v' en el programa. El otro puntero 'pos' se usa generalmente para señalar el contador.

Para aquellos que quieran por primera vez ejecutar su programa en modo kernel, les doy un ejemplo de un Makefile para una compilación exitosa. Por supuesto, para una compilación exitosa, debe tener encabezados de origen del kernel de Linux en el sistema.

 obj-m += evens.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 


La conexión al núcleo se realiza con el comando sudo insmod evens.ko , verificando la funcionalidad del archivo / proc / evens, que apareció después de esto, con el comando cat /proc/evens , leyendo el registro de eventos que explica las operaciones del sistema con el comando sudo cat /var/log/messages .

Para desbordar el búfer de página, establezca el parámetro de límite en un valor mayor, por ejemplo, 200. Este valor puede ingresarse en el texto del programa o usarse al cargar el módulo con un comando sudo insmod events.ko limit=200 .

El análisis de registro puede explicar los puntos restantes que no están claros. Por ejemplo, puede notar que antes de llamar a stop () después de next () o start (), el sistema pone a cero la variable 'v'. También puede notar que antes de llamar a start () después de stop (), el sistema imprime el contenido del búfer.

Estaría agradecido si alguien informara cualquier inexactitud encontrada en mi nota o cualquier otra cosa que deba mencionarse.

El código fuente también está disponible aquí .

Source: https://habr.com/ru/post/444620/


All Articles