12.07.2015 Views

Programación de módulos kernel en Linux Objetivo de la práctica: 1 ...

Programación de módulos kernel en Linux Objetivo de la práctica: 1 ...

Programación de módulos kernel en Linux Objetivo de la práctica: 1 ...

SHOW MORE
SHOW LESS
  • No tags were found...

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

Diseño <strong>de</strong> Sistemas Operativos5º Curso <strong>de</strong> Ing<strong>en</strong>iería Informáticahttp://lsi.ugr.esGuión <strong>de</strong> Prácticas – Curso 2010-11Práctica 2:Programación <strong>de</strong> módulos <strong>kernel</strong> <strong>en</strong> <strong>Linux</strong><strong>Objetivo</strong> <strong>de</strong> <strong>la</strong> práctica:Programar varios módulos s<strong>en</strong>cillos para el <strong>kernel</strong> <strong>de</strong> <strong>Linux</strong> al objeto <strong>de</strong> estudiar <strong>la</strong>sposibilida<strong>de</strong>s que nos ofrec<strong>en</strong>, y conocer <strong>la</strong>s herrami<strong>en</strong>tas que suministra <strong>Linux</strong> pararealizar <strong>de</strong> forma dinámica <strong>la</strong> carga/<strong>de</strong>scarga <strong>de</strong> módulos.1 IntroducciónUn módulo es un archivo objeto ELF reubicable que resuelve sus símbolos cuando secarga <strong>en</strong> el <strong>kernel</strong>. Una vez cargado, es parte <strong>de</strong>l <strong>kernel</strong> y su invocación ti<strong>en</strong>e los mismos puntos<strong>de</strong> <strong>en</strong>trada que el <strong>kernel</strong>: interrupciones y/o excepciones. Los manejadores <strong>de</strong> dispositivos se<strong>de</strong>sarrol<strong>la</strong>n como módulos. Los módulos utilizan una interfaz c<strong>la</strong>ra <strong>en</strong>tre el <strong>kernel</strong> y el dispositivo,lo que hace que el módulo sea fácil <strong>de</strong> escribir y a <strong>la</strong> vez manti<strong>en</strong>e el código <strong>de</strong>l <strong>kernel</strong> libre <strong>de</strong><strong>de</strong>sor<strong>de</strong>n.El módulo <strong>de</strong>be ser compi<strong>la</strong>do (no <strong>en</strong><strong>la</strong>zado), y se carga <strong>en</strong> el <strong>kernel</strong> <strong>en</strong> ejecución coninsmod, que es un <strong>en</strong><strong>la</strong>zador dinámico, que se utiliza para resolver los símbolos in<strong>de</strong>finidos <strong>de</strong>lmódulo <strong>en</strong> direcciones <strong>de</strong>l <strong>kernel</strong> mediante <strong>la</strong> tab<strong>la</strong> <strong>de</strong> símbolos <strong>de</strong>l <strong>kernel</strong>.Esto significa que po<strong>de</strong>mos escribir un módulo <strong>de</strong> forma muy simi<strong>la</strong>r a un programa C, yque po<strong>de</strong>mos utilizar funciones no <strong>de</strong>finidas por nosotros. La difer<strong>en</strong>cia es que sólo po<strong>de</strong>mosutilizar un conjunto reducido <strong>de</strong> funciones externas, que son <strong>la</strong>s funciones públicas <strong>de</strong>finidas porel <strong>kernel</strong>. Si t<strong>en</strong>emos dudas <strong>de</strong> si una función <strong>kernel</strong> es pública, o no, po<strong>de</strong>mos buscar<strong>la</strong> por sunombre <strong>en</strong> el archivo fu<strong>en</strong>te /usr/src/linux/<strong>kernel</strong>/ksyms.c, o <strong>en</strong> <strong>la</strong> tab<strong>la</strong> <strong>de</strong> ejecución/proc/ksyms. En teoría, po<strong>de</strong>mos escribir cualquier cosa <strong>de</strong>ntro <strong>de</strong> un módulo. En <strong>la</strong> práctica,<strong>de</strong>bemos recordar que un módulo es código <strong>kernel</strong> y <strong>de</strong>be poseer una interfaz bi<strong>en</strong> <strong>de</strong>finida conel resto <strong>de</strong>l sistema.Recordar que Unix transfiere <strong>la</strong> ejecución <strong>de</strong>s<strong>de</strong> modo usuario a modo <strong>kernel</strong> cuando serealiza una l<strong>la</strong>mada al sistema o se produce una interrupción. El código <strong>kernel</strong> que ejecuta unal<strong>la</strong>mada al sistema trabaja <strong>en</strong> el contexto <strong>de</strong>l proceso actual – trabaja <strong>en</strong> b<strong>en</strong>eficio <strong>de</strong>l procesoactual y pue<strong>de</strong> acce<strong>de</strong>r a <strong>la</strong>s estructuras <strong>de</strong> datos <strong>de</strong>l espacio <strong>de</strong> direcciones <strong>de</strong>l proceso. Elcódigo que maneja interrupciones, por otro <strong>la</strong>do, es asíncrono respecto <strong>de</strong> los procesos y no estare<strong>la</strong>cionado con ningún proceso.El objeto <strong>de</strong> un módulo es ext<strong>en</strong><strong>de</strong>r <strong>la</strong> funcionalidad <strong>de</strong>l <strong>kernel</strong>. Un código modu<strong>la</strong>rizadose ejecuta <strong>en</strong> espacio <strong>de</strong>l <strong>kernel</strong>. Normalm<strong>en</strong>te un manejador realiza <strong>la</strong>s dos tareas explicadasanteriorm<strong>en</strong>te: algunas funciones <strong>de</strong>l módulo se ejecutan como parte <strong>de</strong> una l<strong>la</strong>mada al sistema,y otras están a cargo <strong>de</strong>l manejador <strong>de</strong> interrupciones.Cómo el resto <strong>de</strong>l <strong>kernel</strong>, los módulos y manejadores <strong>de</strong> dispositivos <strong>de</strong>b<strong>en</strong> serre<strong>en</strong>trantes, es <strong>de</strong>cir, pue<strong>de</strong>n ser ejecutados <strong>en</strong> más <strong>de</strong> un contexto a <strong>la</strong> vez. Las estructuras <strong>de</strong>


datos <strong>de</strong>b<strong>en</strong> ser diseñadas cuidadosam<strong>en</strong>te para mant<strong>en</strong>er separadas múltiples hebras <strong>de</strong>ejecución, y el código <strong>de</strong>be cuidar el acceso a datos compartidos para evitar <strong>la</strong> corrupción <strong>de</strong> losdatos. Un error común <strong>en</strong>tre programadores <strong>de</strong> manejadores es asumir que <strong>la</strong> concurr<strong>en</strong>cia no esun problema mi<strong>en</strong>tras que un segm<strong>en</strong>to <strong>de</strong> código particu<strong>la</strong>r no se bloquee. Si bi<strong>en</strong> es cierto que<strong>Linux</strong> es no apropiativo hasta <strong>la</strong> versión 2.4 (el 2.6 es apropiativo), <strong>de</strong>bemos t<strong>en</strong>er pres<strong>en</strong>tes queel manejador que diseñemos se pue<strong>de</strong> ejecutar <strong>en</strong> un sistema SMP.La pres<strong>en</strong>te <strong>de</strong>scripción hacer refer<strong>en</strong>cia al <strong>de</strong>sarrollo <strong>de</strong> módulos <strong>en</strong> <strong>la</strong> versión 2.4 <strong>de</strong>l<strong>kernel</strong>, que es <strong>la</strong> que t<strong>en</strong>emos <strong>en</strong> prácticas. Las principales difer<strong>en</strong>cias con su <strong>de</strong>sarrollo <strong>en</strong> <strong>la</strong>versión 2.6 <strong>de</strong>l <strong>kernel</strong> <strong>la</strong>s hemos visto <strong>en</strong> c<strong>la</strong>se <strong>de</strong> teoría.2 Desarrollo <strong>de</strong> <strong>la</strong> prácticaLa práctica consta <strong>de</strong> dos bloques <strong>en</strong> los cuales se van a tratar dos aspectos separados: Bloque I: Realizaremos dos módulos s<strong>en</strong>cillos, re<strong>la</strong>tivam<strong>en</strong>te ais<strong>la</strong>dos <strong>de</strong>l resto <strong>de</strong>l <strong>kernel</strong>,para estudiar <strong>la</strong>s características <strong>de</strong> programación <strong>de</strong> módulos y <strong>la</strong> <strong>de</strong>p<strong>en</strong><strong>de</strong>ncia <strong>de</strong> módulos.En concreto, <strong>la</strong>s tareas a realizar <strong>en</strong> este bloque son dos módulos, <strong>de</strong>nominados acumu<strong>la</strong>dory cli<strong>en</strong>te, y que se ati<strong>en</strong><strong>en</strong> al sigui<strong>en</strong>te comportami<strong>en</strong>to:1. Ambos módulos <strong>de</strong>b<strong>en</strong> mostrar cada vez que se insertan o extraigan el instante <strong>de</strong>tiempo <strong>de</strong> <strong>la</strong> operación. Se mostrará el número <strong>de</strong> segundos <strong>de</strong>s<strong>de</strong> <strong>la</strong> Época (1 <strong>de</strong> <strong>en</strong>ero<strong>de</strong> 1970). Para lo cual, consultaremos <strong>la</strong> variable <strong>de</strong>l <strong>kernel</strong> xtime <strong>de</strong>c<strong>la</strong>rada <strong>en</strong><strong>kernel</strong>/sched.h. Esta variable es <strong>de</strong> tipo struct timeval, tipo que esta <strong>de</strong>finido <strong>en</strong>inclu<strong>de</strong>/linux/time.h y que será necesario incluir <strong>en</strong> los módulos. Recuer<strong>de</strong> que parapo<strong>de</strong>r utilizar esta variable <strong>en</strong> los módulos hay que <strong>de</strong>c<strong>la</strong>rar<strong>la</strong> como extern que elcompi<strong>la</strong>dor no conoce ni su tipo ni su tamaño.2. El módulo acumu<strong>la</strong>dor <strong>de</strong>fine una función void acumu<strong>la</strong>r(int i), que suma <strong>en</strong> unavariable global <strong>de</strong>l módulo el valor que se le pasa como parámetro. También,implem<strong>en</strong>ta <strong>la</strong> función int consultar(void) que permite consultar el valor <strong>de</strong> dichavariable. La variable ti<strong>en</strong>e valor inicial cero, y cuando se extraiga el módulo mostrará elvalor que se haya acumu<strong>la</strong>dor.3. El módulo cli<strong>en</strong>te, al insertarse, l<strong>la</strong>mará a <strong>la</strong> función acumu<strong>la</strong>r() <strong>de</strong>l móduloacumu<strong>la</strong>dor pasándole un valor igual al que le pasamos al módulo al insertarlo. Cuandose extraiga, mostrará cuanto llevamos acumu<strong>la</strong>do hasta el mom<strong>en</strong>to haci<strong>en</strong>do uso <strong>de</strong> <strong>la</strong>función llevamos().Para comprobar el funcionami<strong>en</strong>to, inserta el módulo acumu<strong>la</strong>dor una vez, y el módulo cli<strong>en</strong>tevarias veces, observando que el comportami<strong>en</strong>to es correcto. Finalm<strong>en</strong>te, extrae el móduloacumu<strong>la</strong>dor y observa que el resultado final también es correcto. A continuación, observa quepasa si insertamos el módulo cli<strong>en</strong>te sin haber insertado el acumu<strong>la</strong>dor, o int<strong>en</strong>tamos extraerel acumu<strong>la</strong>dor estando insertado aún el cli<strong>en</strong>te. Explica lo ocurrido <strong>en</strong> ambos casos. Bloque II: Ahora vamos a implem<strong>en</strong>tar un módulo para ver <strong>la</strong> interacción con el resto <strong>de</strong>l<strong>kernel</strong>. El los ejercicios anteriores, los módulos solo realizan su función cuando se insertan oretiran. Como indicábamos <strong>en</strong> <strong>la</strong> introducción, el <strong>kernel</strong> se activa a través <strong>de</strong> una l<strong>la</strong>mada alsistema, o mediante una interrupción. Pues bi<strong>en</strong>, <strong>en</strong> este bloque, vamos a <strong>de</strong>sarrol<strong>la</strong>r unmódulo que intercepte <strong>la</strong>s l<strong>la</strong>madas al sistema geteuid(), getuid(), getgid(), ygetegid() al objeto <strong>de</strong> cambiar el funcionami<strong>en</strong>to <strong>de</strong> <strong>la</strong> or<strong>de</strong>n whoami por un nuevocomportami<strong>en</strong>to <strong>de</strong> tu elección. A<strong>de</strong>más, po<strong>de</strong>mos interceptar <strong>la</strong>s l<strong>la</strong>madas y tras dar unm<strong>en</strong>saje nuestro, <strong>la</strong> or<strong>de</strong>n diga dando también <strong>la</strong> información que ofrecía antes <strong>de</strong>l cambio.3 Material necesarioA continuación mostramos el código completo <strong>de</strong> un módulo “Ho<strong>la</strong>, Mundo”. Este módulo<strong>de</strong>be compi<strong>la</strong>rse y ejecutarse bajo <strong>la</strong>s versiones 2.0 a 2.4 <strong>de</strong>l <strong>kernel</strong> <strong>de</strong> <strong>Linux</strong>:Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 2


ecordar los nombres reservados y pue<strong>de</strong> acarrean problemas que pue<strong>de</strong>n ir <strong>de</strong>s<strong>de</strong> <strong>la</strong> carga<strong>de</strong>l módulo hasta fallos raros. La mejor forma <strong>de</strong> evitar este problema es <strong>de</strong>c<strong>la</strong>rar todosnuestros símbolos como static 1 y utilizar un prefijo único <strong>de</strong>ntro <strong>de</strong>l <strong>kernel</strong> para lossímbolos que sean globales. Como escritores <strong>de</strong> módulos, po<strong>de</strong>mos contro<strong>la</strong>r <strong>la</strong> visibilida<strong>de</strong>xterna <strong>de</strong> nuestros símbolos como <strong>de</strong>scribiremos <strong>en</strong> el apartado “La tab<strong>la</strong> <strong>de</strong> símbolos <strong>de</strong>l<strong>kernel</strong>”.Figura 1.- En<strong>la</strong>zando un módulo con el <strong>kernel</strong>.5. La última difer<strong>en</strong>cia es cómo cada <strong>en</strong>torno gestiona <strong>la</strong>s faltas: mi<strong>en</strong>tras que una falta <strong>de</strong>segm<strong>en</strong>tación <strong>en</strong> una aplicación es inof<strong>en</strong>siva durante el <strong>de</strong>sarrollo <strong>de</strong> una aplicación ypo<strong>de</strong>mos utilizar un <strong>de</strong>purador para <strong>de</strong>tectar el error, una falta <strong>kernel</strong> es fatal al m<strong>en</strong>os parael proceso actual, si no para el sistema completo.El proceso actualEl código <strong>de</strong>l <strong>kernel</strong> pue<strong>de</strong> conocer el proceso actual (<strong>en</strong> el contexto <strong>de</strong>l cual se ejecuta)accedi<strong>en</strong>do al item curr<strong>en</strong>t, un puntero a una struct task_struct, que <strong>en</strong> <strong>la</strong> versión 2.4<strong>de</strong>l <strong>kernel</strong> se <strong>de</strong>c<strong>la</strong>ra <strong>en</strong> , incluida por . El punterocurr<strong>en</strong>t refer<strong>en</strong>cia al proceso <strong>de</strong> usuario ejecutándose actualm<strong>en</strong>te y nos pue<strong>de</strong> servir paraobt<strong>en</strong>er información específica <strong>de</strong>l proceso, si <strong>la</strong> necesitamos. Un ejemplo <strong>de</strong> esta técnica loveremos <strong>en</strong> el Capítulo 5, “Control <strong>de</strong> acceso a un archivo <strong>de</strong> dispositivo”. Por ejemplo, <strong>la</strong>sigui<strong>en</strong>te <strong>de</strong>c<strong>la</strong>ración imprime el ID <strong>de</strong>l proceso y el nombre <strong>de</strong> <strong>la</strong> or<strong>de</strong>n <strong>de</strong>l proceso actual:printk(“El proceso es \”%s\” (pid %i)\n”, curr<strong>en</strong>t->comm, curr<strong>en</strong>t->pid);Compi<strong>la</strong>ción y cargaVamos a <strong>de</strong>dicar el apartado a escribir un módulo completo aunque sin tipos, es <strong>de</strong>cir, un móduloque no pert<strong>en</strong>ece a ninguna <strong>de</strong> <strong>la</strong>s c<strong>la</strong>ses sigui<strong>en</strong>tes: <strong>de</strong> caracteres, <strong>de</strong> bloques, o <strong>de</strong> red. El1La mayoría <strong>de</strong> <strong>la</strong>s versiones <strong>de</strong> insmod (pero no todas) exportan todos los símbolos como no-static si no<strong>en</strong>cu<strong>en</strong>tra instrucciones específicas <strong>en</strong> el módulo; por ello es sabio <strong>de</strong>c<strong>la</strong>rar como static todos los símbolosque no <strong>de</strong>seemos exportar.Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 4


módulo que veremos <strong>de</strong> <strong>de</strong>nomina skull (Simple Kernel Utility for Loading Localities). Po<strong>de</strong>mosreutiliza el módulo para nuestros propios módulos o manejadores para lo cual solo <strong>de</strong>beremoseliminar el código que no nos sirva.Antes <strong>de</strong> com<strong>en</strong>zar con <strong>la</strong>s funciones <strong>de</strong> inicialización y limpieza <strong>de</strong>l módulo,construiremos un makefile para construir el código objeto que <strong>de</strong>be cargar el <strong>kernel</strong>.Primero, <strong>de</strong>bemos <strong>de</strong>finir el símbolo __KERNEL__ <strong>en</strong> el procesador antes <strong>de</strong> incluiralguna cabecera. Como m<strong>en</strong>cionábamos antes, gran parte <strong>de</strong> los cont<strong>en</strong>idos específicos <strong>de</strong>l<strong>kernel</strong> <strong>en</strong> los archivos <strong>de</strong> cabecera no estarían disponibles sin este símbolo.Otro símbolo importantes es MODULE, que <strong>de</strong>be <strong>de</strong>finirse antes <strong>de</strong> incluir (excepto para los manejadores que están <strong>en</strong><strong>la</strong>zados directam<strong>en</strong>te <strong>en</strong> el<strong>kernel</strong>, pero estos no están cubiertos <strong>en</strong> estas notas).Si estamos compi<strong>la</strong>ndo para una máquina SMP, necesitamos <strong>de</strong>finir __SMP__ antes <strong>de</strong>incluir <strong>la</strong>s cabeceras <strong>kernel</strong>. En <strong>la</strong> versión 2.2 <strong>la</strong> opción multiprocesador o uniprocesador fueasc<strong>en</strong>dida a un item <strong>de</strong> configuración propio, así utilizando estas líneas muy al principio <strong>de</strong>lmódulo realizaremos <strong>la</strong> tarea:#inclu<strong>de</strong> #if<strong>de</strong>f CONFIG_SMP# <strong>de</strong>fine __SMP__#<strong>en</strong>difEn <strong>la</strong> compi<strong>la</strong>ción <strong>de</strong>bemos utilizar el indicador –O, ya que muchas funciones están <strong>de</strong>c<strong>la</strong>radas <strong>en</strong>línea 2 <strong>en</strong> los archivos <strong>de</strong> cabecera. gcc no expan<strong>de</strong> <strong>la</strong>s funciones <strong>en</strong> línea salvo que se habilite <strong>la</strong>optimización, pero pue<strong>de</strong> aceptar <strong>la</strong>s opciones –g y -O, permitiéndonos <strong>de</strong>purar código que utilizafunciones <strong>en</strong> línea. Es importante <strong>la</strong> expansión a<strong>de</strong>cuada <strong>de</strong> estas funciones pues el <strong>kernel</strong> haceun uso int<strong>en</strong>sivo <strong>de</strong> el<strong>la</strong>s.Debemos comprobar que el compi<strong>la</strong>dor que utilizamos igua<strong>la</strong> al <strong>kernel</strong> para el queestamos compi<strong>la</strong>ndo, ver el archivo Docum<strong>en</strong>tation/Changes <strong>en</strong> el árbol <strong>de</strong> fu<strong>en</strong>tes <strong>de</strong>l <strong>kernel</strong>. El<strong>kernel</strong> y el compi<strong>la</strong>dor se <strong>de</strong>sarrol<strong>la</strong>n a <strong>la</strong> vez por difer<strong>en</strong>tes grupos, así algunas veces loscambios <strong>en</strong> una herrami<strong>en</strong>ta reve<strong>la</strong>n errores <strong>en</strong> otra. Algunas distribuciones <strong>en</strong>tregan una versión<strong>de</strong>l compi<strong>la</strong>dor <strong>de</strong>masiado nueva para construir el <strong>kernel</strong> <strong>de</strong> forma fiable. En este caso, estassuel<strong>en</strong> suministrar un paquete separado (a m<strong>en</strong>udo <strong>de</strong>nominado kgcc) con un compi<strong>la</strong>dorp<strong>en</strong>sado para <strong>la</strong> compi<strong>la</strong>ción <strong>de</strong>l <strong>kernel</strong>.Finalm<strong>en</strong>te, al objeto <strong>de</strong> evitar errores <strong>de</strong>sagradables, es recom<strong>en</strong>dable utilizar elindicador –Wall (todos los avisos), y también fijar todas <strong>la</strong>s características <strong>de</strong> nuestro código queprovocan avisos <strong>de</strong>l compi<strong>la</strong>dor, incluso si esto requiere cambios <strong>en</strong> nuestro estilo <strong>de</strong>programación. Cuando se escribe código <strong>kernel</strong>, el estilo <strong>de</strong> codificación preferido es el propio <strong>de</strong>Linus. En Docum<strong>en</strong>tación/CodingStyle <strong>en</strong>contramos una lectura am<strong>en</strong>a y una lección obligatoriapara cualquier persona interesada <strong>en</strong> los <strong>de</strong>talles <strong>de</strong>l <strong>kernel</strong>.Todas <strong>la</strong>s <strong>de</strong>finiciones e indicadores que hemos introducido hasta ahora ti<strong>en</strong><strong>en</strong> su mejorubicación <strong>en</strong> <strong>la</strong> variable CFLAGS utilizada por make.Si el módulo que vamos a construir esta dividido <strong>en</strong> difer<strong>en</strong>tes archivos fu<strong>en</strong>tes, lo cual esusual, el makefile necesita una reg<strong>la</strong> para unir los difer<strong>en</strong>tes archivos objetos. Los archivos objetose un<strong>en</strong> con <strong>la</strong> or<strong>de</strong>n ld –r, que realm<strong>en</strong>te no es una operación <strong>de</strong> <strong>en</strong><strong>la</strong>zado aunque <strong>la</strong> realiceel <strong>en</strong><strong>la</strong>zador. La salida <strong>de</strong> esta or<strong>de</strong>n es otro objeto módulo que incorpora todo el código <strong>de</strong> losarchivos <strong>de</strong> <strong>en</strong>trada. La opción –r significa que el archivo <strong>de</strong> salida es reubicable.El sigui<strong>en</strong>te makefile es un ejemplo mínimo <strong>de</strong> cómo se construye un módulo compuesto<strong>de</strong> dos archivos fu<strong>en</strong>tes. Si nuestro módulo solo ti<strong>en</strong>e un único archivo fu<strong>en</strong>te, <strong>de</strong>bemos saltar <strong>la</strong><strong>en</strong>trada que conti<strong>en</strong>e ld –r:2Sin embargo, es arriesgado el uso <strong>de</strong> una optimización mayor que –O2, dado que el compi<strong>la</strong>dor podríaponer funciones <strong>en</strong> línea que no esta <strong>de</strong>c<strong>la</strong>radas como inline <strong>en</strong> el código. Esto pue<strong>de</strong> ser un problemacon el código <strong>kernel</strong>, dado que algunas funciones esperan <strong>en</strong>contrar una estructura <strong>de</strong> pi<strong>la</strong> estándarcuando son invocadas.Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 5


Después <strong>de</strong> construir el módulo, el sigui<strong>en</strong>te paso es cargarlo <strong>en</strong> el <strong>kernel</strong>. Como hemoscom<strong>en</strong>tado insmod realiza este trabajo. Este programa es simi<strong>la</strong>r a ld, <strong>en</strong><strong>la</strong>za los símbolos noresueltos <strong>en</strong> el módulo con los <strong>de</strong> <strong>la</strong> tab<strong>la</strong> <strong>de</strong> símbolos <strong>de</strong>l <strong>kernel</strong> <strong>en</strong> ejecución. A difer<strong>en</strong>cia <strong>de</strong>l<strong>en</strong><strong>la</strong>zador, esta no modifica el archivo <strong>en</strong> disco sino una copia <strong>en</strong> memoria <strong>de</strong>l mismo. insmodacepta cierto número <strong>de</strong> opciones <strong>en</strong> <strong>la</strong> línea <strong>de</strong> ór<strong>de</strong>nes (ver <strong>la</strong> página <strong>de</strong> manual), y pue<strong>de</strong>asignar valores a variables <strong>en</strong>teras o ca<strong>de</strong>nas <strong>en</strong> el módulo antes <strong>de</strong> ser <strong>en</strong><strong>la</strong>zado con el <strong>kernel</strong>.Así, si se diseña correctam<strong>en</strong>te un módulo, pue<strong>de</strong> configurase <strong>en</strong> tiempo <strong>de</strong> carga; esto da mayorflexibilidad al usuario que <strong>la</strong> configuración <strong>en</strong> compi<strong>la</strong>ción. La configuración <strong>en</strong> tiempo <strong>de</strong> carga <strong>la</strong>explicaremos <strong>en</strong> el apartado “Configuración manual y automática”.Dep<strong>en</strong><strong>de</strong>ncias <strong>de</strong> versiónT<strong>en</strong>i<strong>en</strong>do <strong>en</strong> m<strong>en</strong>te que el código <strong>de</strong> nuestro módulo <strong>de</strong>be recompi<strong>la</strong>rse para cada versión <strong>de</strong>l<strong>kernel</strong> con <strong>la</strong> que <strong>de</strong>be ser <strong>en</strong><strong>la</strong>zado. Cada módulo <strong>de</strong>fine un símbolo <strong>de</strong>nominado__module_<strong>kernel</strong>_version__, que insmod igua<strong>la</strong> con el número <strong>de</strong> versión <strong>de</strong>l <strong>kernel</strong> actual.Este símbolo se situa <strong>en</strong> <strong>la</strong> sección .modinfo <strong>de</strong>l ELF. Esta <strong>de</strong>scripción se aplica sólo a <strong>la</strong>sversiones 2.2 y 2.4 <strong>de</strong>l <strong>kernel</strong>; <strong>Linux</strong> 2.0 hace lo mismo pero <strong>de</strong> otra forma.El compi<strong>la</strong>dor <strong>de</strong>fine este símbolo por nosotros cuando incluimos .Esto significa que si nuestro módulo esta compuesto <strong>de</strong> varios archivos, solo <strong>de</strong>bemos incluirlo <strong>en</strong>uno <strong>de</strong> ellos (salvo que utilicemos __NO_VERSION__, que introduciremos <strong>en</strong> breve).Si <strong>la</strong>s versiones no coinci<strong>de</strong>n, po<strong>de</strong>mos carga el módulo especificando el conmutador –f(forzado), pero esta operación no es segura y pue<strong>de</strong> fal<strong>la</strong>r. La carga pue<strong>de</strong> fal<strong>la</strong>r porquesimplem<strong>en</strong>te no coincidan los símbolos, lo que provoca un m<strong>en</strong>saje <strong>de</strong> error, o <strong>de</strong>bido a cambiosinternos <strong>de</strong>l <strong>kernel</strong>, <strong>en</strong> cuyo caso po<strong>de</strong>mos provocar un error serio y posiblem<strong>en</strong>te un pánico <strong>de</strong>sistema. La no coinci<strong>de</strong>ncia <strong>de</strong> versiones se pue<strong>de</strong> tratar <strong>de</strong> una manera más elegante utilizandoversiones <strong>en</strong> el <strong>kernel</strong>. Esta práctica no trataremos <strong>en</strong> profundidad <strong>en</strong> control <strong>de</strong> versiones.Si <strong>de</strong>seamos compi<strong>la</strong>r el módulo para una versión particu<strong>la</strong>r <strong>de</strong>l <strong>kernel</strong>, <strong>de</strong>bemos incluir losarchivos <strong>de</strong> cabecera específicos para ese <strong>kernel</strong> (por ejemplo, <strong>de</strong>c<strong>la</strong>rando un KERNELDIRdifer<strong>en</strong>te) <strong>en</strong> el makefile anterior.Cuando solicitamos cargar un módulo, insmod sigue su propio camino <strong>de</strong> búsqueda para<strong>en</strong>contrar el archivo objeto, buscando <strong>en</strong> los directorios <strong>de</strong>p<strong>en</strong>di<strong>en</strong>tes <strong>de</strong> <strong>la</strong> versión bajo/lib/modules. Aunque versiones antiguas buscaban <strong>en</strong> el directorio actual, este comportami<strong>en</strong>toesta ahora <strong>de</strong>shabilitado por seguridad (el mismo problema que ocurre con <strong>la</strong> variable <strong>de</strong> <strong>en</strong>tornoDiseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 6


PATH). Así, si necesitamos cargar un módulo <strong>de</strong>s<strong>de</strong> nuestro directorio <strong>de</strong> trabajo <strong>de</strong>bemos utilizar./module.o, que funciona con todas <strong>la</strong>s versiones <strong>de</strong> <strong>la</strong> herrami<strong>en</strong>ta.A veces, <strong>en</strong>contraremos interfaces <strong>kernel</strong> con un comportami<strong>en</strong>to difer<strong>en</strong>te <strong>en</strong>tre <strong>la</strong>sversiones 2.0.x y 2.4.x. En este caso, necesitamos recurrir a <strong>la</strong>s macros que <strong>de</strong>fin<strong>en</strong> el número <strong>de</strong>versión <strong>de</strong>l árbol <strong>de</strong> fu<strong>en</strong>tes actual, que están <strong>de</strong>finidas <strong>en</strong> . En lugar <strong>de</strong>iniciar una <strong>la</strong>rga discusión específica <strong>de</strong> <strong>la</strong> versión 2.4, apuntaremos algunos cambios <strong>en</strong> losejemplos que lo requieran.La cabecera, automáticam<strong>en</strong>te incluida por linux/module.h, <strong>de</strong>fine <strong>la</strong>s sigui<strong>en</strong>tes macros:UTS_RELEASE La macro se expan<strong>de</strong> por una ca<strong>de</strong>na que <strong>de</strong>scribe al versión <strong>de</strong> este árbol<strong>de</strong>l <strong>kernel</strong>. Por ejemplo, “2.3.48”.LINUX_VERSION_CODE La macro expan<strong>de</strong> a <strong>la</strong> repres<strong>en</strong>tación binaria <strong>de</strong> <strong>la</strong> versión <strong>de</strong>l<strong>kernel</strong>, un byte por cada parte <strong>de</strong>l número <strong>de</strong> liberación <strong>de</strong> <strong>la</strong> versión. Porejemplo, el código para 2.3.48 es 131888 (esto es, 0x020330). Con estainformación, po<strong>de</strong>mos <strong>de</strong>terminar (casi) fácilm<strong>en</strong>te <strong>la</strong> versión <strong>de</strong>l <strong>kernel</strong> con <strong>la</strong> quetratamos.KERNEL_VERSION(major, minor, release) Esta es <strong>la</strong> macro utilizada para construir un“código_versión_<strong>kernel</strong>” a partir <strong>de</strong> los números individuales <strong>de</strong> constituy<strong>en</strong> elnúmero <strong>de</strong> versión. Por ejemplo, KERNEL_VERSION(2, 3, 48) se expan<strong>de</strong> por131888. Esta macro es muy útil cuando necesitamos comparar <strong>la</strong> versión actual yun punto <strong>de</strong> comprobación conocido.El archivo version.h esta incluido por el módulo module.h, por lo que no t<strong>en</strong>emos que incluirloexplícitam<strong>en</strong>te. Por otro <strong>la</strong>do, po<strong>de</strong>mos evitar esta inclusión <strong>de</strong>c<strong>la</strong>rando antes __NO_VERSION__.Utilizaremos __NO_VERSION__ si necesitamos incluir <strong>en</strong> varios archivosfu<strong>en</strong>tes que serán <strong>en</strong><strong>la</strong>zados juntos para formar un único módulo, por ejemplo, si necesitamospreprocesar macros <strong>de</strong>c<strong>la</strong>radas <strong>en</strong> module.h. La <strong>de</strong>c<strong>la</strong>ración __NO_VERSION__ <strong>de</strong> antes <strong>de</strong>incluir module.h evita <strong>la</strong> <strong>de</strong>c<strong>la</strong>ración automática <strong>de</strong> <strong>la</strong> ca<strong>de</strong>na __module_<strong>kernel</strong>_version__ osu equival<strong>en</strong>te <strong>en</strong> los archivos fu<strong>en</strong>tes don<strong>de</strong> no lo <strong>de</strong>seemos (ld –r podría quejarse sobre <strong>la</strong>smúltiples <strong>de</strong>finiciones <strong>de</strong>l símbolo). Los módulos ejemplos que vamos a utilizar lo hac<strong>en</strong> así.La mayoría <strong>de</strong> <strong>la</strong>s <strong>de</strong>p<strong>en</strong><strong>de</strong>ncias basadas <strong>en</strong> <strong>la</strong> versión <strong>de</strong>l <strong>kernel</strong> pue<strong>de</strong>n solv<strong>en</strong>tarse concondicionales <strong>de</strong>l preprocesador explotando KERNEL_VERSION y LINUX_VERSION. Las<strong>de</strong>p<strong>en</strong><strong>de</strong>ncias <strong>de</strong> versión <strong>de</strong>berían, sin embargo, confundir el código <strong>de</strong>l manejador con multitud<strong>de</strong> #if<strong>de</strong>f; <strong>la</strong> mejor forma <strong>de</strong> tratar con incompatibilida<strong>de</strong>s es confinar<strong>la</strong>s <strong>en</strong> una cabeceraespecífica.La tab<strong>la</strong> <strong>de</strong> símbolos <strong>de</strong>l <strong>kernel</strong>Com<strong>en</strong>tábamos cómo insmod resuelve los símbolos no <strong>de</strong>finidos contra <strong>la</strong> ta<strong>la</strong> <strong>de</strong> símbolospúblicos <strong>de</strong>l <strong>kernel</strong>. La tab<strong>la</strong> conti<strong>en</strong>e <strong>la</strong>s direcciones <strong>de</strong> los ítems globales <strong>de</strong>l <strong>kernel</strong> –funciones yvariables- que son necesitados para implem<strong>en</strong>tar un manejador modu<strong>la</strong>rizado. La tab<strong>la</strong> <strong>de</strong>símbolos pue<strong>de</strong> leerse <strong>en</strong> formato texto <strong>de</strong>s<strong>de</strong> el archivo /proc/ksyms.Cuando se carga un módulo, cualquier símbolo exportado por el módulo se convierte <strong>en</strong>parte <strong>de</strong> <strong>la</strong> tab<strong>la</strong> <strong>de</strong> símbolos <strong>de</strong>l <strong>kernel</strong>, y po<strong>de</strong>mos verlo <strong>en</strong> /proc/ksyms o <strong>en</strong> <strong>la</strong> salida <strong>de</strong> <strong>la</strong>or<strong>de</strong>n ksyms.Nuevos módulos pue<strong>de</strong>n utilizar símbolos exportados por nuestro módulo, y po<strong>de</strong>mosapi<strong>la</strong>r nuevos módulos sobre otros módulos. El api<strong>la</strong>mi<strong>en</strong>to <strong>de</strong> módulos es ampliam<strong>en</strong>teimplem<strong>en</strong>tado <strong>en</strong> el <strong>kernel</strong>: el sistema <strong>de</strong> archivos msdos <strong>de</strong>scansa sobre los símbolos exportadospor el módulo fat, y cada <strong>en</strong>trada al módulo <strong>de</strong> dispositivo <strong>de</strong> USB se api<strong>la</strong> sobre los módulosusbcore e input.El api<strong>la</strong>mi<strong>en</strong>to <strong>de</strong> módulos es útil <strong>en</strong> proyectos complejos. Si se implem<strong>en</strong>ta una nuevaabstracción <strong>en</strong> <strong>la</strong> forma <strong>de</strong> manejador <strong>de</strong> dispositivos, esta podría ofrecer un <strong>en</strong>chufe paraimplem<strong>en</strong>taciones específicas <strong>de</strong> hardware. Por ejemplo, el conjunto <strong>de</strong> manejadores vi<strong>de</strong>o-forlinuxesta dividido <strong>en</strong> un módulo g<strong>en</strong>érico que exporta símbolos utilizados por los manejadores <strong>de</strong>dispositivos <strong>de</strong> más bajo nivel para hardware específico. En función <strong>de</strong> nuestra configuración,Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 7


cargamos el módulo <strong>de</strong> vi<strong>de</strong>o g<strong>en</strong>érico y el módulo específico para el hardware insta<strong>la</strong>do. Elsoporte para puertos paralelos y una amplia variedad <strong>de</strong> dispositivos ligables se gestionan <strong>de</strong> <strong>la</strong>misma manera, como es el subsistema <strong>kernel</strong> USB. La Figura 2 muestra el api<strong>la</strong>mi<strong>en</strong>to <strong>de</strong>lsubsistema <strong>de</strong>l puerto paralelo; <strong>la</strong>s fechas muestran <strong>la</strong>s comunicaciones <strong>en</strong>tre los módulos y con<strong>la</strong> interfaz <strong>de</strong> programación <strong>de</strong>l <strong>kernel</strong>.Cuando se utilizan módulos api<strong>la</strong>bles, es útil <strong>la</strong> utilidad modprobe. Esta funciona <strong>de</strong> forma muysimi<strong>la</strong>r a insmod, pero también carga cualesquiera otros módulos que sean necesarios por elmódulo que <strong>de</strong>seamos cargar. Así, una or<strong>de</strong>n modprobe pue<strong>de</strong> a veces reemp<strong>la</strong>zar variasinvocaciones <strong>de</strong> insmod (si bi<strong>en</strong> necesitamos aún insmod cuando cargamos nuestros módulos<strong>de</strong>s<strong>de</strong> el directorio actual, dado que modprobe solo mira <strong>en</strong> el árbol <strong>de</strong> módulos insta<strong>la</strong>dos).La modu<strong>la</strong>rización por capas pue<strong>de</strong> ayudar a reducir el tiempo <strong>de</strong> <strong>de</strong>sarrollo al simplificarcada capa. Esto es simi<strong>la</strong>r a <strong>la</strong> separación <strong>en</strong>tre mecanismo y política ampliam<strong>en</strong>te utilizado.En el caso usual, un módulo implem<strong>en</strong>ta su propia funcionalidad sin necesidad <strong>de</strong>exportar ningún símbolo. Sin embargo, necesitaremos exportar los símbolos cuando queramosque otros módulos puedan b<strong>en</strong>eficiarse <strong>de</strong> ellos. También, necesitaremos incluir instruccionesespecíficas para evitar exportar todos los símbolos no-static, como <strong>la</strong> mayoría <strong>de</strong> <strong>la</strong>s versiones <strong>de</strong>modutils, pero no todas, exporta todos por <strong>de</strong>fecto.Los archivos <strong>de</strong> cabecera <strong>de</strong>l <strong>kernel</strong> <strong>de</strong> <strong>Linux</strong> suministran una forma conv<strong>en</strong>i<strong>en</strong>te <strong>de</strong>manejar <strong>la</strong> visibilidad <strong>de</strong> nuestros símbolos, reduci<strong>en</strong>do así <strong>la</strong> polución <strong>de</strong>l espacio <strong>de</strong> nombres ypromover el ocultami<strong>en</strong>to a<strong>de</strong>cuado <strong>de</strong> <strong>la</strong> información. Este mecanismo funciona con los <strong>kernel</strong>2.1.18 y posteriores; el <strong>kernel</strong> 2.0 ti<strong>en</strong>e un mecanismo completam<strong>en</strong>te difer<strong>en</strong>te, que<strong>de</strong>scribiremos al final.Si nuestro módulo no exporta ningun símbolo, po<strong>de</strong>mos <strong>de</strong>sear hacerlo explícitocolocando una línea con <strong>la</strong> l<strong>la</strong>mada a <strong>la</strong> macro:EXPORT_NO_SYMBOLS;La macro se expan<strong>de</strong> <strong>en</strong> una directiva <strong>en</strong> <strong>en</strong>samb<strong>la</strong>dor y pue<strong>de</strong> aparecer <strong>en</strong> cualquier lugar <strong>de</strong>lmódulo. Sin embargo, el código portable <strong>de</strong>be situar<strong>la</strong> <strong>en</strong> <strong>la</strong> inicialización <strong>de</strong>l módulo(init_module), dado que <strong>la</strong> versión <strong>de</strong> esta macro <strong>de</strong>finida <strong>en</strong> sys<strong>de</strong>p.h para viejos <strong>kernel</strong>s solofuncionará allí.Si, por <strong>de</strong> otro <strong>la</strong>do, necesitamos exportar un subconjunto <strong>de</strong> símbolos <strong>de</strong>s<strong>de</strong> nuestromódulo, el primer paso es <strong>de</strong>finir <strong>la</strong> macro <strong>de</strong>l preprocesador EXPORT_SYMTAB. Esta macro <strong>de</strong>beestar <strong>de</strong>finida antes <strong>de</strong> incluir module.h. Es común <strong>de</strong>finir<strong>la</strong> <strong>en</strong> tiempo <strong>de</strong> compi<strong>la</strong>ción con elindicador –D <strong>de</strong>l compi<strong>la</strong>dor <strong>en</strong> el Makefile.Si esta <strong>de</strong>finido EXPORT_SYMTAB, los símbolos individuales se exportan con <strong>la</strong> pareja <strong>de</strong>macros:EXPORT_SYMBOL (nombre);EXPORT_SYMBOL_NOVERS (nombre);Cualquiera <strong>de</strong> <strong>la</strong>s dos versiones <strong>de</strong> <strong>la</strong> macro hará que el símbolo dado este disponible fuera <strong>de</strong>lmódulo; <strong>la</strong> segunda versión exporta el símbolo sin información <strong>de</strong> versión. Los símbolos <strong>de</strong>b<strong>en</strong>Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 8


exportarse fuera <strong>de</strong> cualquier función ya que <strong>la</strong>s macros expan<strong>de</strong>n por <strong>la</strong> <strong>de</strong>c<strong>la</strong>ración <strong>de</strong> <strong>la</strong>variable.Inicialización y cierreComo ya hemos m<strong>en</strong>cionado, init_module registra <strong>la</strong> funcionalidad ofrecida por el módulo.Para cada funcionalidad, existe una función específica <strong>de</strong>l <strong>kernel</strong> que realiza <strong>la</strong> operación <strong>de</strong>registro. Los argum<strong>en</strong>tos pasados a <strong>la</strong>s funciones <strong>de</strong> registro <strong>de</strong>l <strong>kernel</strong> son normalm<strong>en</strong>te unpuntero a una estructura <strong>de</strong> datos que <strong>de</strong>scribe <strong>la</strong> funcionalidad y el nombre <strong>de</strong> <strong>la</strong> funciónregistrada. La estructura <strong>de</strong> datos suele empotrar punteros a <strong>la</strong>s funciones <strong>de</strong>l módulo, que escomo <strong>la</strong>s funciones <strong>de</strong>l cuerpo <strong>de</strong>l módulo son invocadas. Entre los ítems que se pue<strong>de</strong>n registrarpo<strong>de</strong>mos <strong>en</strong>contrar manejadores <strong>de</strong> dispositivos, archivos /proc, dominios ejecutables, ydisciplinas <strong>de</strong> línea. Muchos <strong>de</strong> estos ítems registrables soportan funciones que no estándirectam<strong>en</strong>te re<strong>la</strong>cionadas con el hardware si no que son abstracciones software. Estos ítemspue<strong>de</strong>n registrarse dado que se pue<strong>de</strong>n integras <strong>de</strong> cualquier forma <strong>en</strong> <strong>la</strong> funcionalidad <strong>de</strong> losmanejadotes.Manejo <strong>de</strong> errores <strong>en</strong> init_moduleSi se produce un error cuando registramos <strong>la</strong> utilidad, <strong>de</strong>bemos <strong>de</strong>shacer cualquier operación <strong>de</strong>registro realizada antes <strong>de</strong>l fallo. Se pue<strong>de</strong> producir un error, por ejemplo, si no hay sufici<strong>en</strong>tememoria <strong>en</strong> el sistema para asignar una nueva estructura <strong>de</strong> datos o porque un recurso que estasi<strong>en</strong>do solicitado es utilizado por otro manejador.<strong>Linux</strong> no manti<strong>en</strong>e un registro <strong>de</strong> funcionalidad por módulo que ha sido registrado, así elmódulo <strong>de</strong>be <strong>de</strong>shacer todo por él mismo si init_module fal<strong>la</strong>. Si fal<strong>la</strong>mos <strong>en</strong> <strong>la</strong> operación <strong>de</strong> <strong>de</strong>sregistrarel módulo, se <strong>de</strong>ja al <strong>kernel</strong> <strong>en</strong> un estado inseguro: no poe<strong>de</strong>mos volver a registrarnuestra función recargando el módulo dado que aparecería como ocupado, y no po<strong>de</strong>mos <strong>de</strong>sregistrarloporque necesitamos el mismo puntero que utilizamos para registrarlo. La recuperación<strong>de</strong> esta situación es complicada, y normalm<strong>en</strong>te nos veremos forzados a re-arrancar el sistema.La recuperación <strong>de</strong> errores es a m<strong>en</strong>udo bi<strong>en</strong> gestionada por <strong>la</strong> <strong>de</strong>c<strong>la</strong>ración goto.Normalm<strong>en</strong>te, odiamos el goto, pero <strong>en</strong> situación es útil. En el <strong>kernel</strong>, goto se utiliza a m<strong>en</strong>udopara tratar con los errores.El sigui<strong>en</strong>te código ejemplo, que utiliza funciones <strong>de</strong> registro y <strong>de</strong>s-registro ficticias, secomporta correctam<strong>en</strong>te si fal<strong>la</strong> <strong>la</strong> inicialización <strong>en</strong> cualquier punto.int init_module(void){/* el registro toma un puntero y un nombre */err = register_esto(ptr1, “skull”);if (err) goto fallo_esto;err = register_eso(ptr2, “skull”);if (err) goto fallo_eso;err = register_aquello(ptr3, “skull”);if (err) goto fallo_aquello;return 0; /* exito */}fallo_aquello: unregister_eso(ptr2, “skull”);fallo_eso: unregister_esto(ptr1, “skull”);fallo_esto: return err; /* propaga el error */El código int<strong>en</strong>ta registrar tres funciones ficticias. La <strong>de</strong>c<strong>la</strong>ración goto se utiliza <strong>en</strong> caso <strong>de</strong> fallopara provocar <strong>la</strong> <strong>de</strong>s-registración sólo <strong>de</strong> <strong>la</strong>s funciones que han sido registradas con éxito antes<strong>de</strong> que <strong>la</strong>s cosas fues<strong>en</strong> mal.Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 9


Otra opción, que no requiere <strong>la</strong> <strong>de</strong>c<strong>la</strong>ración goto, es mant<strong>en</strong>er <strong>la</strong> pista <strong>de</strong> lo que se haregistrado con éxito y l<strong>la</strong>mar a cleanup_module <strong>en</strong> caso <strong>de</strong> cualquier error. La función <strong>de</strong> limpiezasólo <strong>de</strong>shará los pasos que se han finalizado con éxito. Sin embargo, esta alternativa requieremás código. El valor <strong>de</strong>vuelto por init_module, err, es un código <strong>de</strong> error. En el <strong>kernel</strong> <strong>de</strong> <strong>Linux</strong>,los códigos <strong>de</strong> error son número negativos que pert<strong>en</strong>ec<strong>en</strong> al conjunto <strong>de</strong>finido <strong>en</strong>. So <strong>de</strong>seamos g<strong>en</strong>erar nuestros propios códigos <strong>de</strong> error <strong>en</strong> lugar <strong>de</strong> retornar loque obt<strong>en</strong>emos <strong>de</strong> otras funciones, <strong>de</strong>beremos incluir al objeto <strong>de</strong> utilizar losvalores simbólicos tales como –ENODEV, -ENOMEM, etc. Siempre es bu<strong>en</strong>a práctica retornar loscódigos <strong>de</strong> error a<strong>de</strong>cuados, ya que los usuarios <strong>de</strong> los programas pue<strong>de</strong>n traducirlos a ca<strong>de</strong>nacon s<strong>en</strong>tido utilizando <strong>la</strong> función perror o simi<strong>la</strong>r.Obviam<strong>en</strong>te, cleanup_module <strong>de</strong>shace cualquier operación <strong>de</strong> registro realizada porinit_module, y es costumbre (no obligación) <strong>de</strong>s-registrar funciones <strong>en</strong> el or<strong>de</strong>n inverso usadopara registrar<strong>la</strong>s (como vimos <strong>en</strong> el fragm<strong>en</strong>to <strong>de</strong> código anterior).Si nuestras inicializaciones y limpiezas son más complejas que tratar unos cuantos ítems,el <strong>en</strong>foque <strong>de</strong> gotos pue<strong>de</strong> no se práctico. Para evitar <strong>la</strong> duplicidad <strong>de</strong> código, po<strong>de</strong>mos l<strong>la</strong>mar acleanup_module <strong>de</strong>s<strong>de</strong> <strong>de</strong>ntro <strong>de</strong> init_module si se produce un error.El contador <strong>de</strong> usoEl sistema manti<strong>en</strong>e un contador <strong>de</strong> uso para cada módulo para po<strong>de</strong>r <strong>de</strong>terminar cuando estepue<strong>de</strong> ser eliminado <strong>de</strong> forma segura. En los <strong>kernel</strong>s mo<strong>de</strong>rnos, el sistema manti<strong>en</strong>eautomáticam<strong>en</strong>te este contador, utilizando un mecanismo que veremos <strong>en</strong> el próximo capítulo.Sin embargo, hay veces que necesitamos ajustar manualm<strong>en</strong>te el contador <strong>de</strong> uso. El códigoportable para <strong>kernel</strong>s más antiguos <strong>de</strong>be utilizar un contador <strong>de</strong> uso manual. Para trabajar con elcontador <strong>de</strong> uso, utilizamos <strong>la</strong>s tres macros:MOD_INC_USE_COUNT Increm<strong>en</strong>ta el contador para el módulo actual.MOD_DEC_USE_COUNT Decrem<strong>en</strong>ta el contador.MOD_IN_USESe evalúa a cierto si el contador no es cero.Las macros están <strong>de</strong>finidas <strong>en</strong> y actúan cono estructuras <strong>de</strong> datos internasque no <strong>de</strong>b<strong>en</strong> ser accedidas directam<strong>en</strong>te por el programador.Observar que no hay que comprobar MOD_IN_USE <strong>de</strong>ntro <strong>de</strong> cleanup_module, ya que <strong>la</strong>comprobación <strong>la</strong> realiza <strong>la</strong> l<strong>la</strong>mada al sistema sys_<strong>de</strong>lete_module.La gestión a<strong>de</strong>cuada <strong>de</strong>l contador <strong>de</strong> uso es crítica para <strong>la</strong> estabilidad <strong>de</strong>l sistema.Recordar que el <strong>kernel</strong> pue<strong>de</strong> <strong>de</strong>cidir int<strong>en</strong>tar <strong>de</strong>scargar nuestro módulo <strong>en</strong> cualquier instante. Unerror común <strong>de</strong> <strong>la</strong> programación <strong>de</strong> módulos es iniciar una serie <strong>de</strong> operaciones (por ejemplo, <strong>en</strong>respuesta <strong>de</strong> una solicitud a op<strong>en</strong>) e increm<strong>en</strong>tar <strong>de</strong>spués el contador <strong>de</strong> uso. Si el <strong>kernel</strong><strong>de</strong>scarga el módulo <strong>en</strong>tre <strong>la</strong>s operaciones, el caos esta asegurado. Para evitar esta c<strong>la</strong>se <strong>de</strong>problemas, <strong>de</strong>bemos l<strong>la</strong>mar a MOD_INC_USE_COUNT antes <strong>de</strong> hacer cualquier otra cosa <strong>en</strong> elmódulo.No seremos capaces <strong>de</strong> <strong>de</strong>scargar un módulo si per<strong>de</strong>mos <strong>la</strong> pista <strong>de</strong>l contador <strong>de</strong> uso.Esta situación pue<strong>de</strong> ocurrir con frecu<strong>en</strong>cia durante el <strong>de</strong>sarrollo, por lo que <strong>de</strong>bemos mant<strong>en</strong>erlo<strong>en</strong> m<strong>en</strong>te. Por ejemplo, si se <strong>de</strong>struye un proceso porque nuestro manejador <strong>de</strong>srefer<strong>en</strong>cia unpuntero NULL, el manejador no será capaz <strong>de</strong> cerrar el dispositivo y el contador <strong>de</strong> uso novolverá a cero. Una posible solución es <strong>de</strong>shabilitar completam<strong>en</strong>te el contador <strong>de</strong> uso durante elciclo <strong>de</strong> <strong>de</strong>puración re<strong>de</strong>fini<strong>en</strong>do tanto MOD_INC_USE_COUNT como MOD_DEC_USE_COUNT a noops.Otra solución es el uso <strong>de</strong> cualquier otro método para forzar el contador a cero. Lascomprobaciones razonables nunca <strong>de</strong>b<strong>en</strong> sos<strong>la</strong>yarse <strong>en</strong> un módulo <strong>en</strong> producción. Para<strong>de</strong>puración, sin embargo, el uso <strong>de</strong> <strong>la</strong> fuerza bruta <strong>en</strong> ocasiones ayuda a ahorrar esfuerzo ytiempo <strong>de</strong> <strong>de</strong>sarrollo.El valor actual <strong>de</strong>l contador <strong>de</strong> uso se <strong>en</strong>cu<strong>en</strong>tra <strong>en</strong> el tercer campo <strong>de</strong> cada <strong>en</strong>trada <strong>de</strong>/proc/modules. Este archivo muestra los módulos cargados actualm<strong>en</strong>te <strong>en</strong> el sistema, con unaDiseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 10


<strong>en</strong>trada para cada módulo. Los campos son el nombre <strong>de</strong>l módulo, el número <strong>de</strong> bytes <strong>de</strong>memoria que utiliza, <strong>en</strong> el valor actual <strong>de</strong> contador <strong>de</strong> uso. Un archivo /proc/modules típico:autofs 11264 1 (autoclean)pcnet32 12048 1 (autoclean)ipchains 38976 0 (unused)usb-uhci 20720 0 (unused)usbcore 49664 1 [usb-uhci]BusLogic 89696 3sd_mod 11680 3scsi_mod 95072 2 [BusLogic sd_mod]Aquí vemos, <strong>en</strong>tre otras cosas, que el módulo <strong>de</strong>l puerto paralelo se a cargado <strong>de</strong> forma api<strong>la</strong>da.El marcador (autoclean) i<strong>de</strong>ntifica a los módulos gestionados por kmod o <strong>kernel</strong>d; el marcador(unused) significa exactam<strong>en</strong>te eso. En <strong>Linux</strong> 2.0, el tamaño esta expresado <strong>en</strong> páginas (4 KB).DescargaPara <strong>de</strong>scargar un módulo utilizamos <strong>la</strong> or<strong>de</strong>n rmmod. Su tarea es mucho más simple que <strong>la</strong>carga, ya que no se realiza <strong>en</strong><strong>la</strong>zado. La or<strong>de</strong>n invoca <strong>la</strong> l<strong>la</strong>mada al sistema <strong>de</strong>lete_module, queinvoca a cleanup_module <strong>en</strong> el propio módulo si el contador <strong>de</strong> uso es cero o, <strong>en</strong> otro caso,<strong>de</strong>vuelve error.La implem<strong>en</strong>tación <strong>de</strong> cleanup_module ti<strong>en</strong>e a su cargo <strong>de</strong>s-registrar cada item que fueregistrado por el módulo. Sólo los símbolos exportados se eliminan <strong>de</strong> forma automática.Funciones explícitas <strong>de</strong> inicialización y limpiezaComo hemos visto, el <strong>kernel</strong> invoca a init_module y cleanup_module para realizar <strong>la</strong>s <strong>la</strong>bores <strong>de</strong>inicialización y limpieza, respectivam<strong>en</strong>te. En <strong>kernel</strong>s más mo<strong>de</strong>rnos, sin embargo, estasfunciones ti<strong>en</strong><strong>en</strong> a m<strong>en</strong>udo nombres difer<strong>en</strong>tes. En el <strong>kernel</strong> 2.3.13, existe un mecanismo para <strong>la</strong><strong>de</strong>signación explícita <strong>de</strong> estas rutinas; el uso <strong>de</strong> este mecanismo sigue el estilo <strong>de</strong> programaciónpreferido.Por ejemplo, si <strong>de</strong>seamos <strong>de</strong>nominar a estas funciones como my_init y my_cleanup,bastaría con hacermodule_init(mi_init);module_exit(mi_limpieza);Observar que nuestro código <strong>de</strong>be incluir para utilizar module_init y module_exit.La v<strong>en</strong>taja <strong>de</strong> realizar esto es que cada función <strong>de</strong> inicialización y limpieza ti<strong>en</strong>e su propio nombre<strong>en</strong> el <strong>kernel</strong>, lo que ayuda a <strong>la</strong> <strong>de</strong>puración.Si escarbamos <strong>en</strong> los fu<strong>en</strong>tes <strong>de</strong>l <strong>kernel</strong> (versiones 2.2 y posteriores), probablem<strong>en</strong>teveremos una forma difer<strong>en</strong>te <strong>de</strong> <strong>de</strong>c<strong>la</strong>ración para <strong>la</strong> inicialización y limpieza <strong>de</strong> módulos, que separece a:static int __init mi_init(void){…}static void __exit mi_limpieza(void){…}El atributo __init (e initdata, para los datos), cuando se utiliza <strong>de</strong> esta forma, provoca quese <strong>de</strong>scarte <strong>la</strong> función <strong>de</strong> inicialización, y se rec<strong>la</strong>me su memoria, <strong>de</strong>spués <strong>la</strong> inicialización. Sinembargo, sólo funciona para manejadote empotrados; no ti<strong>en</strong>e efecto <strong>en</strong> módulos.En vez <strong>de</strong> eso, __exit provoca <strong>la</strong> omisión <strong>de</strong> <strong>la</strong> función marcada cuando el manejador no estaconstruido como un módulo; <strong>de</strong> nuevo, no ti<strong>en</strong>e efecto <strong>en</strong> módulos. El uso <strong>de</strong> __init pue<strong>de</strong>reducir <strong>la</strong> cantidad <strong>de</strong> memoria utilizada por el <strong>kernel</strong>. No existe daño <strong>en</strong> marcar <strong>la</strong> función <strong>de</strong>Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 11


inicialización <strong>de</strong>l módulo con __init, incluso p<strong>en</strong>sando que no hay tampoco b<strong>en</strong>eficio. Lagestión <strong>de</strong> <strong>la</strong> inicialización <strong>de</strong> módulos no ha sido implem<strong>en</strong>tada aún, pero existe una posiblemejora <strong>en</strong> el futuro.Utilizando recursosUn módulo no pue<strong>de</strong> realizar su tarea sin el uso <strong>de</strong> recursos <strong>de</strong>l sistema tales como memoria,puertos <strong>de</strong> E/S, memoria para E/S, y líneas <strong>de</strong> interrupción, a<strong>de</strong>más <strong>de</strong> canales DMA si utilizamoslos viejos contro<strong>la</strong>dores DMA como ISA.Nuestros programas obti<strong>en</strong><strong>en</strong> memoria utilizando kmalloc y <strong>la</strong> liberan utilizando kfree.Estas funciones son simi<strong>la</strong>res a malloc y free, excepto que kmalloc ti<strong>en</strong>e un argum<strong>en</strong>to adicional,<strong>la</strong> prioridad. Normalm<strong>en</strong>te, una prioridad <strong>de</strong> GFP_KERNEL o GPF_SUER bastará. El acrónimo GFPprovi<strong>en</strong>e <strong>de</strong> “get free page”.Puertos <strong>de</strong> E/S y memoria E/SEl papel <strong>de</strong> un manejador típico es, <strong>en</strong> <strong>la</strong> mayoría <strong>de</strong> los casos, es leer y escribir <strong>en</strong> los puertos<strong>de</strong> E/S y memoria <strong>de</strong> E/S. El acceso a los puertos <strong>de</strong> E/S y memoria <strong>de</strong> E/S 3 (colectivam<strong>en</strong>te<strong>de</strong>nominado regiones <strong>de</strong> E/S) se produce <strong>en</strong> <strong>la</strong> inicialización y durante el funcionami<strong>en</strong>to normal.Desafortunadam<strong>en</strong>te, no todas <strong>la</strong>s arquitecturas <strong>de</strong> bus ofrec<strong>en</strong> una forma c<strong>la</strong>ra <strong>de</strong>i<strong>de</strong>ntificar <strong>la</strong>s regiones <strong>de</strong> E/S que pert<strong>en</strong>ec<strong>en</strong> a cada dispositivo, y a veces el manejador <strong>de</strong>beadivinar dón<strong>de</strong> habita su región <strong>de</strong> E/S, o incluso probar el dispositivo ley<strong>en</strong>do o escribi<strong>en</strong>do <strong>en</strong>los “posibles” rangos <strong>de</strong> direcciones. Este problema es especialm<strong>en</strong>te cierto <strong>en</strong> el bus ISA.A pesar <strong>de</strong> <strong>la</strong>s características (o falta <strong>de</strong> características) <strong>de</strong>l bus que utiliza el dispositivohardware, el manejador <strong>de</strong>l dispositivo <strong>de</strong>be garantizar el acceso exclusivo a sus regiones <strong>de</strong> E/Sal objeto <strong>de</strong> evitar <strong>la</strong>s interfer<strong>en</strong>cias con otros manejadores.Los diseñadores <strong>de</strong> <strong>Linux</strong> eligieron implem<strong>en</strong>tar un mecanismo <strong>de</strong> solicitud/liberación para<strong>la</strong>s regiones <strong>de</strong> memoria, principalm<strong>en</strong>te como medio para evitar colisiones <strong>en</strong>tre difer<strong>en</strong>tesdispositivos. El mecanismo ha sido ampliam<strong>en</strong>te utilizado para puertos <strong>de</strong> E/S y se g<strong>en</strong>eralizóreci<strong>en</strong>tem<strong>en</strong>te para <strong>la</strong> gestión g<strong>en</strong>eral <strong>de</strong> asignación <strong>de</strong> recursos. Observar que este mecanismoes sólo una abstracción software que ayuda al mant<strong>en</strong>imi<strong>en</strong>to <strong>de</strong>l sistema, y pue<strong>de</strong>, o no, seraplicada por <strong>la</strong>s características hardware. Por ejemplo, el acceso no autorizado a un puerto <strong>de</strong>E/S no produce ninguna condición <strong>de</strong> error equival<strong>en</strong>te a una “falta <strong>de</strong> segm<strong>en</strong>tación” –elhardware no pue<strong>de</strong> asegurar el registrado <strong>de</strong> puertos.Po<strong>de</strong>mos <strong>en</strong>contrar información sobre recursos registrados <strong>en</strong> los archivos /proc/ioports y/proc/iomem. PuertosUn archivo /proc/ioports típico sobre PC con el <strong>kernel</strong> 2.4 ti<strong>en</strong>e el sigui<strong>en</strong>te aspecto:…3D<strong>en</strong>ominamos memoria <strong>de</strong> E/S a <strong>la</strong>s áreas <strong>de</strong> memoria que resi<strong>de</strong>n <strong>en</strong> el dispositivo periférico paradifer<strong>en</strong>ciar<strong>la</strong>s <strong>de</strong> <strong>la</strong> RAM <strong>de</strong>l sistema.Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 12


Cada <strong>en</strong>trada <strong>en</strong> el archivo especifica (<strong>en</strong> hexa<strong>de</strong>ximal) un rango <strong>de</strong> los puertos bloqueados porel manejador o propiedad <strong>de</strong>l dispositivo hardware. Este archivo se pue<strong>de</strong> utilizar para evitarcolisiones cuando se aña<strong>de</strong> un dispositivo al sistema y se <strong>de</strong>be seleccionar un rango <strong>de</strong> E/Sa<strong>de</strong>cuando los pu<strong>en</strong>tes (jumpers). Aunque el hardware más mo<strong>de</strong>rno no utiliza pu<strong>en</strong>tes, el temaes aún relevante para dispositivos específicos o compon<strong>en</strong>tes industriales.Pero lo que es más importante <strong>de</strong> este archivo es <strong>la</strong> estructura <strong>de</strong> datos que hay tras él.Cuando el manejador software para un dispositivo se inicializa, este pue<strong>de</strong> conocer que rango <strong>de</strong>puertos están <strong>en</strong> uso; si el manejador necesita probar los puertos <strong>de</strong> E/S para <strong>de</strong>tectar el nuevodispositivo, será capaz <strong>de</strong> evitar probar los puertos que están ya <strong>en</strong> uso por otro dispositivo.La prueba <strong>de</strong> ISA es una tarea arriesgada, varios manejadores distribuidos con el <strong>kernel</strong>oficial <strong>de</strong> <strong>Linux</strong> rechazan realizar pruebas cuando se cargan como módulos, para evitar el riesgo<strong>de</strong> <strong>de</strong>struir <strong>en</strong> sistema <strong>en</strong> ejecución al meti<strong>en</strong>do algo <strong>en</strong> los puertos don<strong>de</strong> pue<strong>de</strong> existirhardware <strong>de</strong>sconocido. Afortunadam<strong>en</strong>te, <strong>la</strong>s arquitecturas <strong>de</strong> bus mo<strong>de</strong>rnas (y algunasantiguas) son inmunes a estos problemas.La interfaz <strong>de</strong> programación utilizada para acce<strong>de</strong>r a los registros <strong>de</strong> E/S se realiza contres funciones:La función check_region pue<strong>de</strong> l<strong>la</strong>marse para ver si un rango <strong>de</strong> puertos esta disponible paraasignarlo; retorna un código <strong>de</strong> error negativo (tal como –EBUSY o –EINVAL) si <strong>la</strong> respuesta esno. request_region asignará realm<strong>en</strong>te el rango <strong>de</strong> puertos, <strong>de</strong>volvi<strong>en</strong>do un puntero no nulo si <strong>la</strong>asignación ti<strong>en</strong>e éxito. Los manejadores no necesitan usar o salvar el puntero real 4 <strong>de</strong>vuelto –todo lo que necesitamos comprobar es que no es NULL. El código que sólo funciona con <strong>kernel</strong>s2.4 no necesitan para nada <strong>la</strong> función check_region; <strong>de</strong> hecho, es mejor no hacerlo, pues <strong>la</strong>cosas pue<strong>de</strong>n cambiar <strong>en</strong>tre <strong>la</strong>s l<strong>la</strong>madas chech_region y request_region. Si <strong>de</strong>seamos g<strong>en</strong>erarcódigo portable con viejos <strong>kernel</strong>s <strong>de</strong>bemos utilizar chech_region dado que request_region<strong>de</strong>vuelve un void <strong>en</strong> versiones anteriores a 2.4. Nuestro manejador <strong>de</strong>berá l<strong>la</strong>mar arelease_region para liberar los puertos cuando finalice con ellos. Las tres funciones son realm<strong>en</strong>temacros, y están <strong>de</strong>finidas <strong>en</strong> .La secu<strong>en</strong>cia típica <strong>de</strong> registrado <strong>de</strong> puertos es <strong>la</strong> sigui<strong>en</strong>te, como aparece <strong>en</strong> el ejemplo<strong>de</strong>l manejador skull (<strong>la</strong> función skull_probe_hw no se muestra ya que conti<strong>en</strong>e código específico<strong>de</strong>l dispositivo):Este código mira primer si el rango solicitado <strong>de</strong> puertos esta disponible; si no pue<strong>de</strong>n serasignados, no ti<strong>en</strong>e s<strong>en</strong>tido buscar el hardware. La asignación real <strong>de</strong> los puertos se difiere hastaque se conoce <strong>la</strong> exist<strong>en</strong>cia <strong>de</strong>l dispositivo. La l<strong>la</strong>mada request_region no <strong>de</strong>bería fal<strong>la</strong>r nunca; el<strong>kernel</strong> sólo carga un único módulo a <strong>la</strong> vez, por lo que no <strong>de</strong>bería haber problemas con otrosmódulos “slipping in” y robando los puertos durante <strong>la</strong> fase <strong>de</strong> <strong>de</strong>tección.4El puntero actual sólo es utilizado cuando <strong>la</strong> función se invoca internam<strong>en</strong>te por el subsistema gestor <strong>de</strong> recursos <strong>de</strong>l<strong>kernel</strong>.Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 13


Cualquier puerto <strong>de</strong> E/S asignado al manejador <strong>de</strong>be finalm<strong>en</strong>te ser liberado; skull realizaesto <strong>de</strong>ntro <strong>de</strong> cleanup_module:El <strong>en</strong>foque solicitud/liberación <strong>de</strong> recursos es simi<strong>la</strong>r a <strong>la</strong> secu<strong>en</strong>cia registro/<strong>de</strong>sregistro <strong>de</strong>scrita<strong>en</strong> <strong>la</strong>s funciones anteriores y <strong>en</strong>caja bi<strong>en</strong> <strong>en</strong> el esquema <strong>de</strong> implem<strong>en</strong>tación basado <strong>en</strong> gotoantes esbozado.MemoriaLa información <strong>de</strong> memoria <strong>de</strong> E/S esta disponible <strong>en</strong> el archivo /proc/iomem. Acontinuación se muestra una porción <strong>de</strong> este archivo tal como aparec<strong>en</strong> <strong>en</strong> un PC:…De nuevo, los valores aparec<strong>en</strong> <strong>en</strong> rangos <strong>en</strong> hexa<strong>de</strong>cimal, y <strong>la</strong> ca<strong>de</strong>na tras los dos puntos es elnombre <strong>de</strong>l propietario <strong>de</strong> <strong>la</strong> región <strong>de</strong> E/S.El registro <strong>de</strong> memoria <strong>de</strong> E/S se realiza <strong>de</strong> <strong>la</strong> misma forma que los puertos <strong>de</strong> E/S, dadoque están realm<strong>en</strong>te basados <strong>en</strong> el mismo mecanismo interno.Para obt<strong>en</strong>er y liberar el acceso a ciertas regiones <strong>de</strong> memoria <strong>de</strong> E/S, el manejador <strong>de</strong>berealizar <strong>la</strong>s sigui<strong>en</strong>tes l<strong>la</strong>madas:Un manejador típico podrá conocer su propio rango <strong>de</strong> memoria <strong>de</strong> E/S, y <strong>la</strong> secu<strong>en</strong>cia mostradacon anterioridad para puertos <strong>de</strong> E/S se reducirá a lo sigui<strong>en</strong>te:Asignación <strong>de</strong> recursos <strong>en</strong> <strong>Linux</strong> 2.4El mecanismo actual <strong>de</strong> asignación <strong>de</strong> recursos se introdujo <strong>en</strong> <strong>Linux</strong> 2.3.11 y suministra unaforma flexible <strong>de</strong> contro<strong>la</strong>r los recursos <strong>de</strong>l sistema. Esta sección <strong>de</strong>scribe brevem<strong>en</strong>te. Sinembargo, <strong>la</strong>s funciones básicas para <strong>la</strong> asignación <strong>de</strong> recursos (request_region y otras) seimplem<strong>en</strong>tan aún (mediante macros) y son utilizadas por razones <strong>de</strong> compatibilidad hacia atrás.La mayoría <strong>de</strong> programadores <strong>de</strong> módulos no necesitan conocer sobre lo que realm<strong>en</strong>te estaocurri<strong>en</strong>do <strong>de</strong>bajo, pero será necesario <strong>en</strong> el <strong>de</strong>sarrollo <strong>de</strong> manejadores más complejos.La gestión <strong>de</strong> recursos es capaz <strong>de</strong> contro<strong>la</strong>r recursos arbitrarios, y pue<strong>de</strong> realizarlo <strong>de</strong>manera jerárquica. Los recursos conocidos globalm<strong>en</strong>te (el rango <strong>de</strong> puertos <strong>de</strong> E/S, porejemplo) pue<strong>de</strong> subdividirse <strong>en</strong> subconjuntos más pequeños –por ejemplo, los recursos asociadoscon una ranura particu<strong>la</strong>r. Los manejadores individuales pue<strong>de</strong>n a su vez subdivididas más.Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 14


Los rangos <strong>de</strong> recursos están <strong>de</strong>scritos mediante una estructura resource, <strong>de</strong>c<strong>la</strong>rada <strong>en</strong>:Los recursos <strong>de</strong>l nivel más alto (raíz) se crean <strong>en</strong> tiempo <strong>de</strong> arranque. Por ejemplo, <strong>la</strong> estructura<strong>de</strong> recursos <strong>de</strong>scrib<strong>en</strong> el rango <strong>de</strong> puertos <strong>de</strong> E/S se crea como sigue:Así, el nombre <strong>de</strong>l recurso es PCI IO, y cubre un rango <strong>de</strong>s<strong>de</strong> cero a IO_SPACE_LIMIT, que <strong>de</strong>acuerdo a <strong>la</strong> p<strong>la</strong>taforma hardware <strong>en</strong> <strong>la</strong> que se ejecuta, pue<strong>de</strong> ser 0xffff (16 bits <strong>de</strong>l espacio <strong>de</strong>direcciones, como ocurre <strong>en</strong> el x86, IA-64, Alpha, M68k, y MIPS), 0xffffffff (32 bits: SPARC,PPC, SH) ó 0xffffffffffffffff (64 bits: SPARC64) .Los subrangos <strong>de</strong> un recurso dado pue<strong>de</strong>n crearse con allocate_resource. Por ejemplo,durante <strong>la</strong> inicialización <strong>de</strong> PCI se crea un nuevo recurso para una región que esta realm<strong>en</strong>teasignada a un dispositivo físico. Cuando el código PCI lee esos puertos o asignaciones <strong>de</strong>memoria crea un nuevo recurso para estas regiones, y <strong>la</strong>s asigna bajo ioport_resource óiomem_resource.Un manejador pue<strong>de</strong> solicitar un subconjunto <strong>de</strong> un recurso particu<strong>la</strong>r (realm<strong>en</strong>te unsubrango <strong>de</strong>l recurso global) y lo marca como ocupado l<strong>la</strong>mando a __request_region, que<strong>de</strong>vuelve un puntero a una nueva estructura <strong>de</strong> datos struct resource que <strong>de</strong>scribe elrecurso que se esta solicitando (o <strong>de</strong>vuelve NULL <strong>en</strong> cado <strong>de</strong> error). La estructura <strong>en</strong> siempreparte ya <strong>de</strong>l árbol global <strong>de</strong> recursos, y el manejador no pue<strong>de</strong> utilizarlo a voluntad.Un lector interesado pue<strong>de</strong> mirar los <strong>de</strong>talles <strong>en</strong> el código <strong>kernel</strong>/resource.c. La mayoría<strong>de</strong> los escritores <strong>de</strong> manejadores, sin embargo, están más que servidos con request_region y <strong>la</strong>sotras funciones introducidas con anterioridad.El mecanismo <strong>de</strong> capas produce un par <strong>de</strong> b<strong>en</strong>eficios. Uno es que hace <strong>la</strong> estructura <strong>de</strong>E/S <strong>de</strong>l sistema evi<strong>de</strong>nte <strong>de</strong>ntro <strong>de</strong> <strong>la</strong>s estructuras <strong>de</strong> datos <strong>de</strong>l <strong>kernel</strong>. El resultado se muestra <strong>en</strong>/proa/ioports, por ejemplo:El rango e800-e8ff esta asignado a una tarjeta Adaptec, que se ha i<strong>de</strong>ntificado el<strong>la</strong> misma almanejador <strong>de</strong>l bus PCI. El manejador aic7xxx ha solicitado gran parte <strong>de</strong>l rango –<strong>en</strong> este caso,<strong>la</strong> parte correspondi<strong>en</strong>te a los puertos reales <strong>de</strong> <strong>la</strong> tarjeta.La otra v<strong>en</strong>taja <strong>de</strong> contro<strong>la</strong>r los recursos <strong>de</strong> esta forma es que particiona el espacio <strong>de</strong>puertos <strong>en</strong> distintos subrangos que reflejan el hardware <strong>de</strong>l sistema subyac<strong>en</strong>te. Dado que eldistribuidor <strong>de</strong> recursos no permite una asignación que cruce subrangos, pue<strong>de</strong> bloquear a unmanejador erróneo (o uno que busque hardware que no existe <strong>en</strong> el sistema) <strong>de</strong> <strong>la</strong> asignación <strong>de</strong>puertos que pert<strong>en</strong>ec<strong>en</strong> a más rangos –incluso so algunos <strong>de</strong> estos puertos están <strong>de</strong>sasignados<strong>en</strong> aquel mom<strong>en</strong>to.Configuración automática y manualAlgunos parámetros que un manejador necesita pue<strong>de</strong>n cambiar <strong>de</strong> un sistema a otro. Porejemplo, el manejador <strong>de</strong>be conocer <strong>la</strong>s direcciones <strong>de</strong> E/S reales <strong>de</strong>l hardware, o los rangos <strong>de</strong>memoria (esto no es un problema con <strong>la</strong>s interfaces <strong>de</strong> bus bi<strong>en</strong> diseñadas y sólo se aplica adispositivos ISA). A veces necesitamos pasar parámetros al manejador para ayudarlo a <strong>en</strong>contrarsu propio dispositivo o para habilitar/<strong>de</strong>shabilitar ciertas características especiales.Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 15


Dep<strong>en</strong>di<strong>en</strong>do <strong>de</strong>l dispositivo, exist<strong>en</strong> otros parámetros a<strong>de</strong>más <strong>de</strong> <strong>la</strong>s direcciones <strong>de</strong> E/Sque afectan al comportami<strong>en</strong>to <strong>de</strong> manejador, tales como <strong>la</strong> marca y versión <strong>de</strong>l dispositivo.Ajustar el manejador con los valores correctos, es <strong>de</strong>cir, configurarlo, es una <strong>de</strong> <strong>la</strong>s tareascomplicadas que necesitamos realizar durante <strong>la</strong> inicialización <strong>de</strong>l dispositivo.Básicam<strong>en</strong>te hay dos formas <strong>de</strong> obt<strong>en</strong>er los valores correctos: bi<strong>en</strong> el usuario losespecifica explícitam<strong>en</strong>te o el manejador los auto<strong>de</strong>tecta. Si bi<strong>en</strong>, <strong>la</strong> auto<strong>de</strong>tección esindudablem<strong>en</strong>te el m<strong>en</strong>or <strong>en</strong>foque, <strong>la</strong> configuración por el usuario es el más fácil <strong>de</strong> implem<strong>en</strong>tar.Un equilibrio a<strong>de</strong>cuado para un escritor <strong>de</strong> dispositivos es implem<strong>en</strong>tar <strong>la</strong> configuraciónautomática cuando sea posible, mi<strong>en</strong>tras que permite <strong>la</strong> configuración <strong>de</strong> usuario como unaopción para sobrescribir <strong>la</strong> auto<strong>de</strong>tección. Una v<strong>en</strong>taja adicional <strong>de</strong> este <strong>en</strong>foque es que el<strong>de</strong>sarrollo inicial se pue<strong>de</strong> realizar sin auto<strong>de</strong>tección, especificando los parámetros <strong>en</strong> tiempo <strong>de</strong>carga, y se pue<strong>de</strong> implem<strong>en</strong>tar <strong>la</strong> auto<strong>de</strong>tección más tar<strong>de</strong>.Muchos manejadores ti<strong>en</strong><strong>en</strong> también opciones <strong>de</strong> configuración que contro<strong>la</strong>s aspectos <strong>de</strong>su funcionami<strong>en</strong>to. Por ejemplo, un manejador para adaptadores SCSI ti<strong>en</strong>e a m<strong>en</strong>udo opcionespara contro<strong>la</strong>r el uso “tagged command queuing”, y los manejadores IDE permit<strong>en</strong> el control <strong>de</strong>usuario <strong>de</strong> <strong>la</strong>s operaciones DMA. Así, incluso si nuestro manejador <strong>de</strong>scansa <strong>en</strong>teram<strong>en</strong>te <strong>en</strong> <strong>la</strong>auto<strong>de</strong>tección para localizar el harware, po<strong>de</strong>mos querer poner a disposición <strong>de</strong>l usuario otrasopciones <strong>de</strong> configuración.Los valores <strong>de</strong> los parámetros pue<strong>de</strong>n asignarse <strong>en</strong> tiempo <strong>de</strong> cargo con insmod omodprobe; <strong>la</strong> última pue<strong>de</strong> también leer los parámetros asignados <strong>en</strong> un archivo <strong>de</strong> configuración(típicam<strong>en</strong>te /etc/modules.conf). Las ór<strong>de</strong>nes aceptan <strong>la</strong> especificación <strong>de</strong> valores <strong>en</strong>teros oca<strong>de</strong>nas <strong>en</strong> <strong>la</strong> línea <strong>de</strong> ór<strong>de</strong>nes. Así, si nuestro módulo pue<strong>de</strong> soportar un parámetro <strong>en</strong>tero<strong>de</strong>nominado skull_ival y un parámetro skull_sval, los parámetros pue<strong>de</strong>n ajustarse <strong>en</strong> tiempo <strong>de</strong>carga <strong>de</strong>l módulo <strong>de</strong> <strong>la</strong> forma:insmod skull skull_ival=666 skull_sval=”el mejor”Sin embargo, antes <strong>de</strong> que insmod pueda cambiar los parámetros <strong>de</strong>l módulo, este <strong>de</strong>be hacerlosdisponibles. Los parámetros se <strong>de</strong>c<strong>la</strong>ran con <strong>la</strong> macro MODULE_PARM, que esta <strong>de</strong>finida <strong>en</strong>module.h. MODULE_PARM toma dos parámetros: el nombre <strong>de</strong> <strong>la</strong> variable, y una ca<strong>de</strong>na que<strong>de</strong>scribe el tipo. La macro <strong>de</strong>be situarse fuera <strong>de</strong> cualquier función y se <strong>en</strong>cu<strong>en</strong>tra típicam<strong>en</strong>tecerca <strong>de</strong> <strong>la</strong> cabecera <strong>de</strong>l archivo fu<strong>en</strong>te. Los dos parámetros m<strong>en</strong>cionados anteriorm<strong>en</strong>te pue<strong>de</strong>n<strong>de</strong>c<strong>la</strong>rarse con <strong>la</strong>s sigui<strong>en</strong>tes líneas:Actualm<strong>en</strong>te, hay cinco tipos soportados para parámetros <strong>de</strong>l módulo: b, un byte; h, dos bytes(short); i, un <strong>en</strong>tero; l, un long; y s, una ca<strong>de</strong>na. En el caso <strong>de</strong> valores ca<strong>de</strong>na, se pue<strong>de</strong> <strong>de</strong>finiruna variable puntero; insmod pue<strong>de</strong> asignar <strong>la</strong> memoria para el parámetro suministrado por elusuario y ajustar a<strong>de</strong>cuadam<strong>en</strong>te <strong>la</strong> variable. Un valor <strong>en</strong>tero precedi<strong>en</strong>do el tipo indica unamatriz <strong>de</strong> una longitud dada; dos números, separados por un guión, dan los valores mínimo ymáximo. Un ejemplo, po<strong>de</strong>mos <strong>de</strong>c<strong>la</strong>rar una matriz que <strong>de</strong>ba t<strong>en</strong>er al m<strong>en</strong>os dos, y no más <strong>de</strong>cuatro, como:int skull_array[4];MODULE_PARM (skull_array, “2-4i”);También existe <strong>la</strong> macro MODULE_PARM_DESC, que permite al programador suministrar una<strong>de</strong>scripción para un parámetro <strong>de</strong>l módulo. Esta <strong>de</strong>scripción se almac<strong>en</strong>a <strong>en</strong> el archivo objeto;pue<strong>de</strong> verse como una herrami<strong>en</strong>ta como objdump, y pue<strong>de</strong> visualizarse mediante herrami<strong>en</strong>tas<strong>de</strong> administración <strong>de</strong> sistemas automáticas. Un ejemplo:int base_port = 0x300;MODULE_PARM (base_port, “i”);Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 16


MODULE_PARM_DESC (base_port,“Puerto E/S base (<strong>de</strong>fecto 0x300)”);Todos los parámetros <strong>de</strong>l módulo <strong>de</strong>b<strong>en</strong> <strong>de</strong> t<strong>en</strong>er un valor por <strong>de</strong>fecto; insmod podrá cambiar losvalores sólo si se lo indica el usuario explícitam<strong>en</strong>te. El módulo pue<strong>de</strong> comprobar los parámetrosexplícitos probando los parámetros fr<strong>en</strong>te a los valores por <strong>de</strong>fecto. La configuración automática,<strong>en</strong>tonces, pue<strong>de</strong> diseñarse para funcionar <strong>de</strong> esta forma: si <strong>la</strong>s variables <strong>de</strong> configuración ti<strong>en</strong><strong>en</strong>sus valores por <strong>de</strong>fecto, realizan auto<strong>de</strong>tección; <strong>en</strong> otro caso, manti<strong>en</strong>e el valor actual. Al objeto<strong>de</strong> que funciones esta técnica, el valor “por <strong>de</strong>fecto” <strong>de</strong>ber ser tal que el usuario nunca <strong>de</strong>seerealm<strong>en</strong>te especificarlo <strong>en</strong> tiempo <strong>de</strong> carga.El sigui<strong>en</strong>te código muestra cómo skull auto<strong>de</strong>tecta <strong>la</strong> dirección <strong>de</strong>l puerto <strong>de</strong> undispositivo. En este ejemplo, <strong>la</strong> auto<strong>de</strong>tección se utiliza para mirar múltiples dispositivos, mi<strong>en</strong>trasque <strong>la</strong> configuración manual esta restringida a un único dispositivo. La función skull_<strong>de</strong>tect <strong>la</strong>vimos <strong>en</strong> “Puertos”, mi<strong>en</strong>tras que skull_init_board esta a cargo <strong>de</strong> <strong>la</strong> inicialización específica <strong>de</strong>ldispositivo y no se muestra:Si <strong>la</strong>s variables <strong>de</strong> configuración son utilizadas sólo <strong>de</strong>ntro <strong>de</strong>l manejador (no son publicadas <strong>en</strong><strong>la</strong> tab<strong>la</strong> <strong>de</strong> símbolos <strong>de</strong>l <strong>kernel</strong>), el escritor <strong>de</strong>l manejador pue<strong>de</strong> hacer <strong>la</strong> vida un poco más fácilpara el usuario eliminado los prefijos <strong>de</strong> los nombres <strong>de</strong> <strong>la</strong>s variables. Los prefijos no ti<strong>en</strong><strong>en</strong>mucho s<strong>en</strong>tido para el usuario excepto tecleado extra.Por completitud, exist<strong>en</strong> otras tres macros que sitúan docum<strong>en</strong>tación <strong>de</strong>ntro <strong>de</strong>l archivoobjeto. Estas son:MODULE_AUTHOR (nombre) Pone el nombre <strong>de</strong>l autor <strong>en</strong> el archivo objeto.Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 17


MODULE_DESCRIPTION (<strong>de</strong>s) Pone una <strong>de</strong>scripción <strong>de</strong>l módulo.MODULE_SUPPORTED_DEVICE (<strong>de</strong>v) Sitúa una <strong>en</strong>trada que <strong>de</strong>scribe qué dispositivo estasoportado por este módulo. Los com<strong>en</strong>tarios <strong>en</strong> <strong>la</strong>s fu<strong>en</strong>tes<strong>de</strong>l <strong>kernel</strong> sugier<strong>en</strong> que este parámetro pue<strong>de</strong> utilizarsefinalm<strong>en</strong>te para ayudar con <strong>la</strong> carga automatizada <strong>de</strong>l módulo,pero <strong>en</strong> este instante no se realiza tal uso.Cambios <strong>en</strong> <strong>la</strong> gestión <strong>de</strong> recursosEl nuevo esquema <strong>de</strong> gestión <strong>de</strong> recursos trae unos pocos problemas <strong>de</strong> portabilidad si <strong>de</strong>seamosescribir un manejador que se ejecute <strong>en</strong> versiones anteriores a <strong>la</strong> 2.4. Esta sección discute losproblemas <strong>de</strong> portabilidad que po<strong>de</strong>mos <strong>en</strong>contrar y cómo <strong>la</strong> cabecera sys<strong>de</strong>p.h int<strong>en</strong>taescon<strong>de</strong>rlos.El cambio más visible <strong>en</strong> el nuevo código <strong>de</strong> gestión <strong>de</strong> recursos es <strong>la</strong> inclusión <strong>de</strong> <strong>la</strong>función request_mem_region y re<strong>la</strong>cionadas. Su papel esta limitado al acceso a <strong>la</strong> base <strong>de</strong> datos<strong>de</strong> memoria <strong>de</strong> E/S, si realizar operaciones específicas sobre cualquier hardware. Así, lo quepo<strong>de</strong>mos hacer con <strong>kernel</strong>s anteriores es simplem<strong>en</strong>te no invocar <strong>la</strong>s funciones. La cabecerasys<strong>de</strong>p.h consigue simplem<strong>en</strong>te esto <strong>de</strong>fini<strong>en</strong>do <strong>la</strong>s funciones como macros que retornan 0 paralos <strong>kernel</strong> anteriores al 2.4.Otra difer<strong>en</strong>cias <strong>en</strong>tre el 2.4 y los anteriores es el prototipo real <strong>de</strong> request_region y <strong>la</strong>sfunciones re<strong>la</strong>cionadas.Los <strong>kernel</strong> anteriores al 2.4 <strong>de</strong>c<strong>la</strong>ra request_region y release_region como funciones queretornan void (así fuerza el uso <strong>de</strong> check_region <strong>de</strong> antemano). La nueva implem<strong>en</strong>tación, máscorrectam<strong>en</strong>te, ti<strong>en</strong>e funciones que <strong>de</strong>vuelv<strong>en</strong> un puntero <strong>de</strong> forma que pue<strong>de</strong> seña<strong>la</strong>rse unacondición <strong>de</strong> error (haci<strong>en</strong>do así a check_region bastante inútil). EL valor <strong>de</strong>l puntero real no seránormalm<strong>en</strong>te útil para el código <strong>de</strong> manejador salvo para probar el valor NULL, que significa que<strong>la</strong> solicitud ha fal<strong>la</strong>do.Si <strong>de</strong>seamos ahorrar algunas líneas <strong>de</strong> código y no estamos preocupados por <strong>la</strong>compatibilidad hacia atrás, po<strong>de</strong>mos explotar <strong>la</strong>s nuevas l<strong>la</strong>madas al sistema y evitar utilizarcheck_region, liberando <strong>la</strong> región <strong>de</strong> memoria y <strong>de</strong>volvi<strong>en</strong>do éxito si <strong>la</strong> solicitud se satisface; <strong>la</strong>sobrecarga es <strong>de</strong>spreciable dado que ninguna <strong>de</strong> esas funciones se invoca nunca <strong>de</strong>s<strong>de</strong> unasección <strong>de</strong> código crítica <strong>en</strong> tiempo.Si <strong>de</strong>seamos transportabilidad, po<strong>de</strong>mos poner <strong>la</strong> secu<strong>en</strong>cia <strong>de</strong> l<strong>la</strong>madas que sugerimosanteriorm<strong>en</strong>te <strong>en</strong> el capítulo e ignorar los valores <strong>de</strong> retorno <strong>de</strong> request_region y release_region.De todas formas, sys<strong>de</strong>p.h <strong>de</strong>c<strong>la</strong>ra ambas funciones como macros para <strong>de</strong>volver 0 (éxito), asípo<strong>de</strong>mos hacer tanto que sea portable y comprobar el valor <strong>de</strong> retorno <strong>de</strong> cada función.La última difer<strong>en</strong>cia <strong>en</strong> el registro <strong>de</strong> E/S <strong>en</strong>tre <strong>la</strong> versión 2.4 y anteriores es que los tipos<strong>de</strong> datos utilizados para los argum<strong>en</strong>tos start y l<strong>en</strong>. Mi<strong>en</strong>tras los nuevos <strong>kernel</strong> siempre utilizanunsigned long, los viejos <strong>kernel</strong>s utilizan tipos más cortos. Este cambio no ti<strong>en</strong>e efecto sobre<strong>la</strong> portabilidad <strong>de</strong>l manejador.Sustituy<strong>en</strong>do los printk’sAl inicio <strong>de</strong> esta apartado, veiamos como printk imprime m<strong>en</strong>sajes <strong>en</strong> <strong>la</strong> conso<strong>la</strong> <strong>de</strong>l sistema perono, por ejemplo, <strong>en</strong> una terminal X. En ocasiones, no interesa que un módulo pueda <strong>en</strong>viarm<strong>en</strong>sajes a cualquier tty <strong>de</strong>s<strong>de</strong> <strong>la</strong> que se instale el módulo.La forma <strong>de</strong> hacer esto es utilizar el puntero al proceso actual, curr<strong>en</strong>t, para obt<strong>en</strong>er <strong>la</strong>estructura tty <strong>de</strong>l proceso actual. Después, miramos <strong>en</strong> esta estructura el puntero que apunta a<strong>la</strong> función <strong>de</strong> escritura, que nosostros utilizaremos para escribir una ca<strong>de</strong>na <strong>en</strong> el tty. Un ejemplo:/*print_string.c - S<strong>en</strong>d output to the tty you're running on, regardless ofwhether it's through X11, telnet, etc. We do this by printing the string tothe tty associated with the curr<strong>en</strong>t task.*/#inclu<strong>de</strong> #inclu<strong>de</strong> Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 18


#inclu<strong>de</strong> // For curr<strong>en</strong>t#inclu<strong>de</strong> // For the tty <strong>de</strong>c<strong>la</strong>rationsMODULE_LICENSE("GPL");MODULE_AUTHOR("Peter Jay Salzman");void print_string(char *str){struct tty_struct *my_tty;my_tty = curr<strong>en</strong>t->tty;// The tty for the curr<strong>en</strong>t task/* If my_tty is NULL, the curr<strong>en</strong>t task has no tty you can print to (this ispossible, for example, if it's a daemon). If so, there's nothing we can do.*/if (my_tty != NULL) {/* my_tty->driver is a struct which holds the tty's functions, one ofwhich (write) is used to write strings to the tty. It can be used to take astring either from the user's memory segm<strong>en</strong>t or the <strong>kernel</strong>'s memory segm<strong>en</strong>t.The function's 1st parameter is the tty to write to, because the same functionwould normally be used for all tty's of a certain type. The 2nd parametercontrols whether the function receives a string from <strong>kernel</strong> memory (false, 0)or from user memory (true, non zero). The 3rd parameter is a pointer to astring. The 4th parameter is the l<strong>en</strong>gth of the string.*/(*(my_tty->driver).write)(my_tty,// The tty itself0, // We don't take the string from user spacestr,// Stringstrl<strong>en</strong>(str));// L<strong>en</strong>gth/* ttys were originally hardware <strong>de</strong>vices, which (usually) strictlyfollowed the ASCII standard. In ASCII, to move to a new line you need twocharacters, a carriage return and a line feed. On Unix, the ASCII line feed isused for both purposes - so we can't just use \n, because it wouldn't have acarriage return and the next line will start at the column right after the linefeed. BTW, this is why text files are differ<strong>en</strong>t betwe<strong>en</strong> Unix and MS Windows.In CP/M and its <strong>de</strong>rivatives, like MS-DOS and MS Windows, the ASCII standard wasstrictly adhered to, and therefore a newline requirs both a LF and a CR.*/(*(my_tty->driver).write)(my_tty, 0, "\015\012", 2);}}int print_string_init(void){print_string("The module has be<strong>en</strong> inserted. Hello world!");return 0;}void print_string_exit(void){print_string("The module has be<strong>en</strong> removed. Farewell world!");}module_init(print_string_init);module_exit(print_string_exit);¿Cómo interceptar <strong>la</strong>s l<strong>la</strong>madas al sistema?En este apartado vamos a “abusar” <strong>de</strong>l esquema <strong>de</strong> módulos <strong>kernel</strong>. Normalm<strong>en</strong>te, estosse utilizan para ext<strong>en</strong><strong>de</strong>r <strong>la</strong> funcionalidad, <strong>en</strong> especial <strong>de</strong> los manejadores <strong>de</strong> dispositivos. En esteapartado, vamos a hacer algo difer<strong>en</strong>te como es interceptar una l<strong>la</strong>mada al sistema y modificar<strong>la</strong><strong>de</strong> forma que el sistema cambie el comportami<strong>en</strong>to <strong>de</strong> diversas ór<strong>de</strong>nes o l<strong>la</strong>madas al sistema.Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 19


Las l<strong>la</strong>madas al sistema están <strong>de</strong>finidas <strong>en</strong> el archivo /usr/inclu<strong>de</strong>/sys/syscall.h. Acontinuación mostramos un estracto <strong>de</strong> este archivo:#if<strong>de</strong>f _SYS_CALL_H#<strong>de</strong>fine _SYS_CALL_H#<strong>de</strong>fine SYS_setup 0 /*utilizada solo por init*/#<strong>de</strong>fine SYS_exit1#<strong>de</strong>fine SYS_fork2#<strong>de</strong>fine SYS_read3…#<strong>en</strong>dif /* */Cada l<strong>la</strong>mada esta <strong>de</strong>finida por un número (ver listado) que es el que se utiliza realm<strong>en</strong>te pararealizar <strong>la</strong> l<strong>la</strong>mada. El <strong>kernel</strong> utiliza <strong>la</strong> interrupción software 0x80 para manejar <strong>la</strong>s l<strong>la</strong>madas. Elnúmero <strong>de</strong> <strong>la</strong> l<strong>la</strong>mada y sus argum<strong>en</strong>tos se <strong>de</strong>posita <strong>en</strong> los registros apropiados <strong>de</strong> <strong>la</strong> máquina. Elnúmero <strong>de</strong> <strong>la</strong> l<strong>la</strong>mada se utiliza como índice <strong>en</strong> un vector <strong>de</strong>l <strong>kernel</strong> <strong>de</strong>nominadosys_call_table[]. Esta función hace correspon<strong>de</strong>r a cada número <strong>de</strong> l<strong>la</strong>mada <strong>la</strong>correspondi<strong>en</strong>te función <strong>kernel</strong> que lo realiza.Vamos a ver como se pue<strong>de</strong> interceptar una l<strong>la</strong>mada al sistema. Para ellos lo que t<strong>en</strong>emosque capturar el puntero <strong>de</strong> <strong>la</strong> correspondi<strong>en</strong>te <strong>en</strong>trada <strong>de</strong> sys_call_table[] y ajustarlo por elpuntero a <strong>la</strong> función nueva que sustituye a <strong>la</strong> l<strong>la</strong>mada. El sigui<strong>en</strong>te módulo hace imposible acualquier usuario <strong>de</strong>l sistema don<strong>de</strong> se insta<strong>la</strong> <strong>la</strong> creación <strong>de</strong> un directorio.#<strong>de</strong>fine MODULE#<strong>de</strong>fine __KERNEL__#inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> #inclu<strong>de</strong> extern void* sys_call_table[];/*para acce<strong>de</strong>r a sys_call_table*/int (*orig_mkdir)(const char *path); /*<strong>la</strong> l<strong>la</strong>mada original*/int hacked_mkdir(const char *path){return 0;}/*<strong>la</strong> nueva l<strong>la</strong>mada no hace nada*/int init_module(void)/*iniciar modulo*/{orig_mkdir=sys_call_table[SYS_mkdir];sys_call_table[SYS_mkdir]=hacked_mkdir;return 0;}void cleanup_module(void)/*retirar modulo*/{sys_call_table[SYS_mkdir]=orig_mkdir; /*establece mkdir a <strong>la</strong> original */}Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 20


Consi<strong>de</strong>raciones importantes <strong>de</strong> nuestro sistemaPara <strong>la</strong> realización <strong>de</strong> <strong>la</strong>s prácticas, vamos a usar los equipos <strong>de</strong> <strong>la</strong> Escue<strong>la</strong> queti<strong>en</strong>e insta<strong>la</strong>da distribución Red Hat. Esta distribución, por motivos <strong>de</strong> seguridad, noexporta <strong>la</strong> tab<strong>la</strong> <strong>de</strong> l<strong>la</strong>madas al sistema.Para solv<strong>en</strong>tar este inconv<strong>en</strong>i<strong>en</strong>te <strong>de</strong>beremos exportar<strong>la</strong> nosotros mismos. Lospasos a seguir son:1. Editamos /usr/src/linux-2.4/<strong>kernel</strong>/ksyms.c y añadimos <strong>la</strong> líneaEXPORT_SYMBOL(sys_call_table)2. Recompi<strong>la</strong>mos e insta<strong>la</strong>mos el nuevo <strong>kernel</strong>.3. Arrancamos <strong>en</strong> local con <strong>la</strong> nueva imag<strong>en</strong> <strong>de</strong>l núcleo.A<strong>de</strong>más <strong>en</strong> nuestro sistema, <strong>en</strong> lugar <strong>de</strong> <strong>la</strong>s l<strong>la</strong>madas al sistema getpid, etc. <strong>de</strong>beremosusar <strong>la</strong>s funciones getpid32, etc.4 Bibliografía y refer<strong>en</strong>cias <strong>de</strong> interés• Versión 2.4 <strong>de</strong>l <strong>kernel</strong>:- P. J. Salzamn, y O. Pomerantz, “The <strong>Linux</strong> Kernel Module Programming Gui<strong>de</strong>”, 2001, <strong>en</strong>http://tldp.org/LDP/lkmpg/.- Pragmatic /THC, “LKM – Loadable <strong>Linux</strong> Kernel Modules”, 1999, <strong>en</strong>http://www.thc.org/papers/LKM_HACKING.html.- A. Rubini y J. Corbet, “<strong>Linux</strong> Device Drivers (2 nd ed.)”, O`Reilly, 2001. Po<strong>de</strong>mos verlo <strong>en</strong><strong>la</strong> dirección http://www.xml.com/ldd/chapter/book/.• Versión 2.6 <strong>de</strong>l <strong>kernel</strong>:– B. H<strong>en</strong><strong>de</strong>rson, <strong>Linux</strong> Loadable Kernel Modules HOWTO, 2006-09-24, disponible <strong>en</strong>http://tldp.org/HOWTO/Module-HOWTO/.– A. Rubini, J. Corbet, y G. Kroah-Hartman, <strong>Linux</strong> Device Drivers (3/e), O'Reilly, 2005,disponible <strong>en</strong> http://lwn.net/Kernel/LDD3/.– S. V<strong>en</strong>kateswaran, Ess<strong>en</strong>tial <strong>Linux</strong> Device Drivers, Pr<strong>en</strong>tice Hall, 2008. Disponible <strong>en</strong>ftp://air.zz.com/_manuals/<strong>Linux</strong>/V<strong>en</strong>kateswaran%20-%20Ess<strong>en</strong>tial%20<strong>Linux</strong>%20Device%20Drivers%20%28Pr<strong>en</strong>tice,%202008%29.pdf.Diseño <strong>de</strong> Sistemas Operativos Guión <strong>de</strong> prácticas 21

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!