Arquitectura y Tecnologı́a de Ordenadores Personales (IS37 - II37) Ingenierı́a Técnica en Informática de Sistemas - Ingenierı́a Informática Práctica 1. Módulos del núcleo de Linux. 1. Objetivos Con el desarrollo de esta práctica se podrá ver la estructura de los módulos de Linux –versión 2.6.x del núcleo–, sus caracterı́sticas, su función y la forma de acceder a los dispositivos gestionados por módulos –un módulo actuando de manejador o driver del dispositivo. 2. Introducción El núcleo del sistema operativo Linux presenta una arquitectura monolı́tica, es decir que todos sus componentes –sistema de ficheros, gestión de memoria, manejadores de dispositivos- están enlazados en una sola imagen –normalmente el fichero vmlinuz– que se carga en memoria y ejecuta durante el arranque del sistema. Esta estructura es poco flexible dado que requiere bien que todos los posibles manejadores de los distintos dispositivos –estén o no presentes en el hardware del sistema– estén incluı́dos en el código del núcleo, haciéndolo innecesariamente grande, bien que el núcleo deba ser recompilado cada vez que se produce una modificación en el soporte material del sistema. Para evitar estos inconvenientes, desde la versión 2.0 del núcleo de Linux, se ha incorporado soporte para la carga y descarga dinámica de fragmentos de código del núcleo, los módulos que se van a estudiar en esta práctica. 2.1. Módulos del núcleo Los módulos son fragmentos de código del sistema operativo en un formato objeto especial –.ko– que pueden ser vinculados dinámicamente –esto es, insertados– en el núcleo o eliminados de él durante el funcionamiento normal del sistema. El código del módulo insertado pasa a ser una parte del núcleo del sistema y por lo tanto se ejecuta con el máximo nivel de privilegio –modo supervisor–, tiene acceso a todas las funciones exportadas por el núcleo o por otros módulos cargados anteriormente y a todos los recursos fı́sicos de la máquina. La única diferencia con el código estático –el que se carga al arrancar el sistema– del núcleo es que un módulo se puede descargar, desvinculándose del núcleo, liberando todos los recursos utilizados –la memoria consumida por el propio módulo– e invalidando las referencias que hubiera podido exportar al sistema. 2.2. Estructura y creación de los módulos Los módulos se pueden generar como cualquier otro programa pero deben seguir una serie de normas para adecuarse a la estructura que el núcleo espera. En esta práctica se compilará un fichero fuente en lenguaje C indicando, mediante el fichero Makefile adjunto, las opciones de compilación adecuadas. La primera función que se ejecuta de un módulo es init module(), que tiene por objeto realizar las comprobaciones adecuadas –por ejemplo, detectar un dispositivo– y crear las estructuras de datos necesarias para la ejecución de su código. Cuando un módulo es descargado del sistema se invoca la función cleanup module() cuyo objeto es liberar los recursos consumidos por aquél. Estas dos funciones son invocadas por el núcleo y su ejecución termina tras llevar a cabo las tareas indicadas. El código del módulo que realiza trabajo útil para el sistema se invoca a través de funciones de gestión de ficheros –open, close, read, write, ioctl . . . – para las cuales el módulo incorpora código. Este código es añadido al núcleo al invocar la función register chrdev() y ejecutado cada vez que se realiza la llamada al sistema correspondiente sobre el dispositivo gestionado por el módulo. Otra caracterı́stica importante de la estructura de los módulos es que, siendo código del núcleo, no puede incluir funciones de las librerı́as compartidas –funciones estandarizadas en la programación en C– que utilizan las aplicaciones de usuario. Sı́ pueden utilizar sin embargo llamadas a todas las funciones que el núcleo exporta. En el ejemplo que se va a seguir se verá el uso de la función del núcleo printk() como método habitual de salida de información del módulo. 2.3. Acceso al código de los módulos desde aplicaciones de usuario Las aplicaciones de usuario acceden al código exportado por los módulos mediante llamadas al sistema sobre dispositivos creados en /dev. Al registrar el módulo como se ha indicado anteriormente, el sistema devuelve un entero que constituye el número de referencia del dispositivo, el major number que le corresponde. Si se crea una entrada en /dev mediante la orden del sistema mknod utilizando este número –y el minor number que se desee, consultar la descripción del manual man mknod para ver el uso correcto de la orden – se accederá a las funciones del módulo a través de ella utilizando la semántica de gestión de ficheros que se ha comentado. En estos casos el minor number se utiliza para distinguir entre distintos dispositivos gestionados por el mismo módulo. 2.4. Gestión de módulos y órdenes asociadas Para cargar y descargar el código de un módulo en el núcleo el sistema dispone de un conjunto de órdenes a tal efecto que deben ejecutarse con permisos de superusuario. Estas órdenes son insmod y modprobe para cargar módulos y rmmod para descargarlos. También existe lsmod para consultar los módulos cargados en un momento dado. Estas órdenes residen en el directorio de ejecutables del sistema, /sbin. Si no está incluido en el PATH del superusuario, se ejecutan indicando esa ruta, por ejemplo /sbin/insmod. Para observar los mensajes generados por los módulos y, en general, por el núcleo mediante la función printk() se puede utilizar la orden dmesg. Otra información importante acerca de los módulos se puede observar ejecutando cat /proc/modules y acerca de los sı́mbolos exportados por el núcelo mediante cat /proc/kallsyms 3. Trabajo a desarrollar 1. Descargar la última versión del fichero mi pci.tgz adjunto, descomprimirlo y compilar el módulo mi pci.ko. Instalar el módulo en el núcleo y observar los mensajes generados mediante la orden dmesg. Antes habrá que editar el archivo de cabecera mi pci.h y eliminar el comentario que protege la constante que identifica el chipset de los ordenadores del laboratorio. 2. Crear, como superusuario y con los permisos de lectura y escritura, un dispositivo tipo carácter llamado /dev/mi pci y ejecutar el programa mi pci prueba. 3. Descargar el módulo, recompilarlo para mostrar información de depuración –definiendo DEBUG en el fichero fuente del módulo o en el fichero de cabecera– y observar los mensajes generados. Verficar que el major number no ha cambiado o modificar el punto de acceso en /dev/mi pci adecuadamente. Ejecutar nuevamente la aplicación mi pci prueba y observar los mensajes generados por el módulo. 4. Modificar el módulo para que devuelva información acerca de los recursos del sistema utilizados por cada dispositivo. Consultar las estructuras de datos adecuadas mediante el navegador del código de linux. Programar una aplicación que acceda al módulo modificado y probar su funcionamiento. 4. Bibliografı́a Se puede encontrar más información acerca de los módulos, su creación y la forma de acceder a ellos en el documento The Linux Kernel Module Programming Guide disponible en http://www.tldp.org o en la web de la asignatura.