Curso básico de Linux - Kernel: el núcleo de Linux
4 - Kernel: el núcleo de Linux
Introducción
La compilación del kernel constituye uno de los pasos que afianzan a cualquier usuario de Linux en el entorno en que se desenvuelve. Este proceso de refinamiento marca la diferencia fundamental entre los sistemas operativos libres y los propietarios: ``Tenemos el código, podemos verlo, podemos tocarlo, podemos modificarlo y aprender de él. Y si nosotros no lo hacemos ¿ Entonces quién ?''. A lo largo de este capítulo se introducirá al lector en el proceso de compilación e instalación de un nuevo núcleo de Linux, también se tratarán temas de desarrollo de módulos y gestión de los mismos. Sin embargo el objetivo principal consiste en lograr que el núcleo se comprenda y no constituya un problema a la hora de implantar Linux en un entorno determinado.
Este documento no pretende sustituir a ninguno de los ya existentes acerca de la compilación del kernel. Humildemente, aún tenemos mucho que aprender de los famosos HOWTO 3.1de cualquier distribución y el dedicado a esta tarea es uno de los más antiguos y completos que existen.
No pretendemos proporcionar una sección que trate con absoluto rigor y detalle cada uno de los subprocesos involucrados en la compilación del kernel, el objetivo básico consiste en mostrar la comodidad con que puede ser llevado a cabo y las ventajas que reporta al administrador. Al mismo tiempo se mostrará la necesidad de la compilación del núcleo para obtener funcionalidades que no vienen en los kernels precompilados como el acceso a particiones NTFS o la configuración de grabadoras de CD.
Este capítulo está estructurado del siguiente modo: Esta primera sección proporciona el contexto necesario para la adecuada comprensión del proceso. En la segunda sección se mostrará una descripción del kernel de Linux, proporcionando información sobre dónde conseguirlo y dónde obtener información relevante. En la tercera sección se detalla el proceso de compilación paso a paso y en el detalle necesario. En la cuarta sección se introduce al lector en la gestión de módulos, proporcionando unas pequeñas nociones sobre desarrollo.
Definición de kernel
Según la teoría clásica de sistemas operativos entendemos un S.O. como un intermediario entre los programas de usuario y el hardware, es decir el software encargado de proporcionar el entorno necesario dónde será posible ejecutar otros programas. Un símil adecuado podrían ser los mandos del salpicadero de un coche a través de los cuales podremos manejarlo con la soltura suficiente. Cada aplicación de usuario se ejecuta sobre la máquina utilizando lo que llamamos proceso (que podría entenderse como un programa en ejecución). Pero es necesario ser consciente de que existe mucho bajo del concepto de proceso. No es necesario decir que nuestra máquina, realmente (como norma general) no posee más que un microprocesador, y este no es capaz de ejecutar más de una instrucción por ciclo 3.2. Sin embargo todo el que ha trabajado con cualquier sistema operativo medianamente serio ha sido capaz de simultanear entre varias aplicaciones y tenerlas ejecutando en paralelo. ¿ Cómo es esto posible ? o mejor dicho ¿ Quién es el responsable de que esto ocurra ?.
Imaginemos por un momento una carrera de tortugas con varios carriles por dónde circularán para intentar obtener la victoria. Ahora, sin embargo, sólo existe un carril por dónde, evidentemente, sólo puede circular una tortuga en un momento determinado. En esta extraña competición se nos encarga la ardua tarea de gestionar ese único carril para que las tortugas puedan correr en él y efectuar una competición digna. Podríamos hacer lo siguiente: tomamos una tortuga al azar y la colocamos en la línea de partida, esta comenzará a moverse; tras un período fijado de tiempo anotamos su posición, la sacamos y tomamos de nuevo otra aleatoriamente para colocarla en la línea de partida. Si utilizamos el mismo intervalo de tiempo y procuramos que todas las tortugas puedan acceder a la pista por igual (colocándolas haciendo cola por ejemplo) podríamos simular una carrera con un sólo carril.
Esta pequeña metáfora refleja una de las funciones del núcleo de un sistema operativo, el planificador de procesos. Pretendemos mostrar al sistema operativo como el software encargado de proporcionar el soporte necesario para habilitar la ejecución de aplicaciones y compartición de recursos como la memoria o la CPU (el único carril de nuestro circuito).
Podemos definir un kernel entonces como el software que constituye el núcleo del sistema operativo, dónde se realizan las funcionalidades básicas como la gestión de procesos, la gestión de memoria y de entrada salida. Pero entonces, ¿un kernel es en sí un sistema operativo? No realmente, depende de la definición de sistema operativo. Hay quién afirma que el shell también forma parte de él. Desde nuestro punto de vista la pregunta anterior no es cierta, dado que existen otros conceptos que iremos comprendiendo a lo largo del artículo y que constituyen, en sí mismos piezas fundamentales del sistema operativo.
Tipos de kernel
En función del tamaño y de las funcionalidades que posea el kernel podemos clasificarlo. Realmente, y pese a seguidores incondicionales en un modelo u otro, existe una tendencia básica a reducir el tamaño del núcleo proporcionando menos funcionalidades, que son desplazadas a módulos que se cargan en tiempo de ejecución.
En función a esta idea tenemos tres tipos fundamentales de kernel:
Kernel monolítico.
Todas las funcionalidades posibles están integradas en el sistema. Se trata de un programa de tamaño considerable que deberemos recompilar al completo cada vez que queramos añadir una nueva posibilidad. Esta es la estructura original de Linux. Por tratarse de una técnica clásica y desfasada el creador de Linux fue muy criticado.
Kernel modular.
Se trata de la tendencia actual de desarrollo. En el kernel se centran las funcionalidades esenciales como la administración de memoria, la planificación de procesos, etc. Sin embargo no tiene sentido que el núcleo de un sistema operativo englobe toda la parafernalia para comunicarse con todas las posibles de tarjetas de vídeo o de sonido. En otros sistemas operativos esto se soluciona con unos ficheros proporcionados por el fabricante llamados drivers. En Linux se creó un interfaz adecuado para posibilitar el desarrollo de módulos que cumplieran esas funcionalidades. Esos módulos pueden ser compilados por separado y añadidos al kernel en tiempo de ejecución.
Estructura de microkernel.
Esta técnica pretende reducir a su mínima expresión el kernel, dejando a los niveles superiores el resto de las funcionalidades. Existen algunos kernels que lo utilizan, si bien el que centra nuestra atención es Hurd. Se trata del último kernel GNU llamado a sustituir a Linux como núcleo del sistema operativo. Aunque esta estrategia de diseño es tan antigua como la modular, no ha sido tenida en cuenta hasta ahora debido a las limitaciones de rendimiento que tenía.
El kernel de Linux
Origen y filosofía
Todos conocemos hoy día Linux, pero quizá lo que, personalmente más me atrae de todo es que fue un estudiante, exactamente con los mismos años que yo tengo ahora, el que tuvo la mayor idea jamas contada, que seguramente estaba basada en las fábulas que le contaron en la guardería: Decidió compartir el código, liberarlo para que el que quisiera lo viera y lo modificara a su antojo. Este hecho junto con la ambiciosa idea de potenciar un sistema operativo Unix bajo la plataforma Intel logro lo que todos hoy conocen como Linux, el sistema operativo de GNU. Pero, ¿qué fue lo que compartió Linus? el kernel, evidentemente. Cuento esto por que cuando uno habla del kernel de Linux no debe hacerlo como la molestia que debe ser tomada sino como la razón de que esté trabajando en este sistema operativo.
Dónde conseguirlo
Para conseguir el kernel no hay que esforzarse demasiado, en cualquier distribución de Linux viene alguna versión, algunas como Debian traen hasta 5 versiones diferentes. De todas formas la mejor fuente de la que nutrirse para obtener el kernel es www.kernel.org. Es importante darse cuenta de cuál es el kernel con el que estamos trabajando, dado que el proceso de compilación, como luego veremos, pese a ser extremadamente sencillo, resulta también crítico y puede tener consecuencias desastrosas.
Una vez hayamos conseguido el paquete será necesario descomprimirlo para obtener las fuentes. Si el paquete que instalemos es .deb o .rpm utilizaremos el mismo proceso de siempre (dpkg -i linux-2.6.9.deb ó rpm -i linux-2.6.9.rpm). Dejarán el código en el directorio /usr/src/linux Si, por el contrario tenemos el paquete en modo .tgz será necesario usar el mandato tar, sería válido por ejemplo:
cp linux-2.6.9.tar.gz /usr/src
tar xvzf /usr/src/linux-2.6.9.tar.gz
También es bastante común encontrarlo en modo .tar.bz2. En este caso:
cp linux-2.6.9.tar.bz2 /usr/src
tar xvjf /usr/src/linux-2.6.9.tar.bz2
Numeración de versiones
El tipo de desarrollo que afronta el kernel (como todo proyecto software se debería someter a algún proceso de desarrollo) podría denominarse como evolutivo, en cierto modo. Esta en continuo cambio, siempre hay alguien trabajando en él, añadiendo dispositivos, adaptándolo a otras arquitecturas, etc. Sin embargo, cuando el número de usuarios creció considerablemente, fue necesario establecer de algún modo ciertas reglas de convivencia entre los desarrolladores y los usuarios de Linux. Para ello se dotó a cada línea base del kernel de una numeración determinada que lo clasifica. Esta numeración se compone de tres dígitos, por ejemplo, el último kernel liberado mientras se escribía este capítulo era 2.6.9. De todos estos dígitos debemos prestar atención al segundo, dado que si se trata de un número impar (2.3.13, 2.5.72 ...) se tratará de un kernel en desarrollo y, consecuentemente, inestable. Por otro lado, un número par confirma el kernel como estable y apto para su compilación y uso (2.0.36, 2.2.17, 2.4.9, 2.6.7 ...). No hay razón para no utilizar un kernel estable a no ser que estemos experimentando con él, por que seamos desarrolladores del kernel de Linux, por ejemplo.
Módulos. Estructura del kernel de Linux
Originalmente Linux era monolítico, es decir, todas las funcionalidades estaban incluidas en el código del núcleo y era necesario recompilarlo para soportar un nuevo dispositivo, etc. Sin embargo, esta idea no encaja con la enorme diversidad de componentes hardware que existen. Raro es que todo el mundo posea los mismos componentes en su ordenador y Linux, como buen sistema operativo Unix pretende obtener todo el partido de la máquina en la que se está ejecutando. Debido a todo esto, el diseño fue migrando paulatinamente a un modelo basado en módulos. Se procura así que el núcleo sea lo más ligero posible y cuando sea necesario añadir una nueva funcionalidad como soportar una nueva tarjeta de sonido, sólo haya que compilar el módulo y añadirlo al núcleo.
Otros núcleos de GNU
Actualmente está en desarrollo otro núcleo, llamado a ser el verdadero kernel de GNU. Esto es por que ha sido desarrollado y arropado desde su concepción por la FSF3.3. Se trata de HURD, un microkernel, descendiente del clásico Mach creado a gusto de los puristas, a conciencia y bien hecho. No olvidemos que el kernel de Linux, ahí dónde está, ha sido muy criticado por su falta de documentación y recursos para lograr que más gente colabore. Lo mejor de todo es que los desarrolladores de HURD están logrando que gran parte del software que funciona bajo Linux lo haga también con su núcleo.
Compilación del kernel
Una vez que sabemos qué es el kernel y cuál es su importancia, vamos a entrar en detalle en el proceso de compilación. Es necesario evitar dos grandes errores a la hora de realizar este proceso, tener miedo de las consecuencias y tomárselo a la ligera (para aquel que esté acostumbrado a pulsar intro antes de leer, entre los que me incluyo, será mejor que pierda el hábito).
Introducción teórica.
Puede que aún alguien dude de porqué es necesario compilar el kernel, propongo este ejemplo: comparemos el kernel de Linux con un coche. Podemos ir al concesionario y comprar un modelo estándar (con complementos de serie) proporcionado por el fabricante. Sin embargo, nosotros somos muy selectivos y tenemos absolutamente claro qué es lo que queremos y qué no. Para ello, nos acercamos al concesionario y solicitamos, de una lista de propiedades, cuales debería tener nuestro futuro vehículo. Tras un tiempo tendremos un coche a la medida en casa. Pues bien, este proceso es exactamente el mismo que realizamos cuando compilamos el kernel. Debemos ser consciente de que el mundo es muy grande y la gente posee máquinas muy dispares, como ingenieros preferimos tomar una idea general y adaptarla a nuestras necesidades que tomar algo muy particular que luego no nos sirva. Este proceso es conocido como ajuste o tunning y consiste en adaptar el software al hardware sobre el que se ejecuta.
Configuración previa a la compilación
Realmente, este el paso decisivo a la hora de compilar el kernel. Esencialmente se trata de un programa como otro cualquiera y debe ser compilado. Supongamos que estamos desarrollando una maravillosa práctica que, por cierto, nos trae de cabeza. Para una correcta depuración añadimos unas cuantas sentencias que imprimen por pantalla mensajes indicativos del punto por el que va el programa. Una vez logramos que la práctica satisfaga todos los requisitos propuestos en el enunciado decidimos entregarla, pero, pese a que no queremos que en la ejecución final aparezcan los mensajes de depuración tampoco queremos quitarlos (nadie nos ha aprobado aún). Podríamos tener una constante en nuestro programa que distinguiera entre una versión de entrega y una de evaluación (por ejemplo DEBUG_MODE = 1). Las funciones que imprimen los mensajes consultarían esa constante antes de soltar el mensaje por pantalla. Para la entrega final colocamos el modo de depuración en modo entrega (DEBUG_MODE = 0) y obtenemos una versión oficial de la práctica. Un ejemplo del código necesario para esto sería:
#define DEBUG_MODE
#ifdef DEBUG_MODE
printf(``Salgo del bucle'');
#endif
Pues bien, el proceso de configuración previo sobre el código del kernel es exactamente el mismo que sobre nuestra hipotética práctica. Podemos ver un fragmento del código del fichero main.c situado en el subdirectorio init dónde se muestra el paralelismo con nuestra práctica:
#ifdef CONFIG_ATARIMOUSE
extern void atari_mouse_setup (char *str,int *ints);
#endif
#ifdef CONFIG_DMASOUND
extern void dmasound_setup (char *str, int *ints);
#endif
#ifdef CONFIG_ATARI_SCSI
extern void atari_scsi_setup (char *str, int *ints);
#endif
extern void wd33c93_setup (char *str, int *ints);
extern void gvp11_setup (char *str, int *ints);
Sin embargo, los desarrolladores han logrado evitar que todo el mundo deba editar el código del kernel y cambiar las constantes a mano. Para ello han creado una serie de programas (que en entornos mas amigables son llamados wizards) que nos ayudarán en nuestra tarea, los más conocidos son tres:
Modo texto.
Es el más antiguo y rudimentario. Consiste en una serie de preguntas relacionadas con los distintos componentes. En cada punto podremos decidir si incluimos o no la funcionalidad solicitada en el kernel. Aunque originalmente era imprescindible hoy es el último recurso por ser muy tedioso. Además, pese a poseer respuestas por defecto a todas las preguntas no es apto para no iniciados en la materia.
Para ejecutar este programa sólo es necesario ejecutar en el directorio dónde hemos descomprimido el kernel (generalmente /usr/src/linux) lo siguiente: make config.
Figura 3.1: Ejemplo de configuración del kernel en modo texto. make config
Ventanas en modo texto.
Mucho más sofisticado y cómodo de manejar. Consiste en una serie de ventanas que en modo texto nos permiten configurar todas las opciones. El hecho de que se aislen las opciones relacionadas logra que si queremos configurar nuestra tarjeta de sonido o la grabadora no haya que tomar decisiones sobre el driver de la tarjeta de red o el soporte para DMA. Para poder usar esta aplicación es necesario tener las librerías ncurses (en Debian por ejemplo los paquetes son: libncurses5 y ncurses3). Una vez tengamos estas librerías instaladas invocaremos a la aplicación mediante make menuconfig.
Figura 3.2: Ejemplo de configuración del kernel con ventanas en modo texto. make menuconfig
Modo X Window.
Se trata de la misma aplicación anterior pero con las posibilidades que ofrece una interfaz gráfica basada en Xwindow. Para su ejecución, en este caso será necesario tener instaladas las librerías QT o GTK dependiendo del front-end que queramos usar (en Gentoo por ejemplo nos bastaría con un emerge qt o emerge gtk+). Para su ejecución deberemos invocar desde un terminal X (en el directorio origen del código) el mandato: make xconfig para el front-end QT y make gconfig para su versión GTK.
Figura 3.3: Ejemplo de configuración del kernel utilizando la versión para QT. make xconfig
Podríamos extendernos páginas completas sobre cada una de las opciones pero lo cierto es que, la mayoría de ellas no nos interesan. Instamos al lector a que se de un paseo por todas ellas comparando las posibilidades ofrecidas con la configuración de su máquina. La mayoría de las veces será necesario cambiar alguna opción o añadir un módulo concreto y el documento dónde se solicite expondrá sus razones coherentemente. De este modo hemos decidido no extendernos con las opciones. Desde la versión 2.6 de Linux no es necesario crear las dependencias necesarias con la orden make dep. Si por algún motivo el kernel que vamos a compilar es un Linux 2.4 o anterior debemos ejecutar make dep en este momento.
El proceso de compilación
El siguiente paso consiste en la compilación propiamente dicha del código. Es necesario aclarar algunos detalles en este punto. Como todos sabemos los procesadores Intel se han visto obligados, para mantener la compatibilidad con versiones anteriores a iniciar la carga de los sistemas operativos en modo real3.4. A causa de esto, el kernel de Linux se encuentra, al arrancar la máquina con sólo 640 Kb de memoria disponible donde cargarse para comenzar a ejecutar. Los desarrolladores tomaron la decisión de comprimir el kernel al cargarlo en la memoria para, más tarde, una vez se ha saltado a modo protegido y se dispone de toda la memoria, descomprimirlo. Sin embargo, al añadir diferentes dispositivos ha llegado un momento en que no es posible comprimir el núcleo para lograr cargarlo en la memoria, se ha utilizado otras estrategias. ¿En que no afecta esto a nosotros?, enseguida lo veremos.
Una vez hemos terminado de configura las diferentes opciones pasamos a compilar mediante la orden:
make zImage
Esto genera una imagen del kernel en el directorio /usr/src/linux/arch/i386/boot. Sin embargo podemos obtener un mensaje de error indicándonos que el kernel que estamos compilando no podrá ser cargado en el arranque mediante el método clásico de comprimirlo. La solución a esto es muy sencilla, tan sólo cambiaremos la orden de compilación, que será make bzImage.
Con el kernel ya configurado y compilado y para terminar el proceso de compilación sólo resta compilar los módulos. Es aconsejable que cualquier opción de la que no estemos completamente seguros de añadir o no sea añadida como módulo (sí es posible). Esto suele reflejarse en los programas de configuración con una M sobre la opción. Para compilar los módulos utilizaremos la siguiente orden: make modules.
Desde Linux 2.6 esto es más cómodo ejecutando directamente make all o simplemente make. Para ver exactamente que tareas se realizan mediante un make all basta con ejecutar un make help | grep "
*"
Instalación tras la compilación
Se trata del último paso, los detalles finales antes de tener nuestro kernel instalado en el sistema. Primero copiaremos los ficheros binarios producto de la etapa anterior en los lugares adecuados mediante las siguientes órdenes:
make install
make modules_install
Debemos ahora configurar el gestor de arranque, esto es, el primer programa que se ejecuta al arrancar un ordenador. El se ocupa de cargar y dar el control al núcleo del sistema operativo para que este a su vez arranque el resto del sistema. Los principales gestores de arranque son Lilo y Grub.
Vamos a configurarlo primero para realizar un arranque con Lilo del nuevo kernel y más tarde para Grub. Es importante habilitar una nueva etiqueta para lograr un arranque con el kernel antiguo (y evitar riesgos innecesarios). La orden make install ha llevado a cabo gran parte de este proceso, sin embargo es necesario completarlo del siguiente modo. Primero editaremos el fichero /etc/lilo.conf dónde podremos distinguir un fragmento similar a este:
image=/vmlinuz label=linux read-only root=/dev/hda1
image=/boot/vmlinuz.old labe=linux.old read-only root=/dev/hda1
Deberemos anotar el fichero referido en image=/vmlinuz. Generalmente este fichero es un enlace que apunta a la verdadera imagen del kernel (situada en el directorio /boot). Nos deberemos asegurar de qué el fichero apuntado es el nuevo recién copiado en el directorio. Podemos listar los ficheros con la opción ls -l /boot para comprobar cual ha sido la nueva imagen recién copiada. Una vez estemos absolutamente seguros de cual es podremos eliminar el enlace y colocarlo apuntando a la nueva imagen o (esto no es aconsejable) configurar la entrada del fichero /etc/lilo.conf. En resumen, lo realmente importante de este último paso es que, en el fichero lilo.conf se acceda a la imagen correcta del kernel. Un ejemplo podría ser el siguiente:
mihrab boot # ls -l
total 4.6M
-rw-r--r-- 1 root root 41K Jul 20 19:22 initrd-1024x768
drwx
2 root root 12K Jul 18 02:45 lost+found
lrwxr-xr-x 1 root root 24 Oct 27 03:00 vmlinuz -> vmlinuz-2.6.7
-rw-r--r-- 1 root root 1.4M Aug 11 20:40 vmlinuz-2.6.7
-rw-r--r-- 1 root root 1.5M Oct 20 15:15 vmlinuz-2.6.9 ...
mihrab boot # rm vmlinuz
mihrab boot # ln -s vmlinuz-2.6.9 vmlinuz
mihrab boot # ln -s vmlinuz-2.6.7 vmlinuz.old
mihrab boot # ls -l vm*
lrwxr-xr-x 1 root root 24 Oct 27 03:01 vmlinuz -> vmlinuz-2.6.9
De este modo configuramos lilo para que arranque utilizando el nuevo kernel y, no menos importante, ofrecemos la posibilidad de cargar la versión anterior por si hubiera problemas.
Ahora veremos como configurar el arranque usando Grub en lugar de Lilo. Grub es un gestor de arranque muy potente y tremendamente flexible, además entiende sistemas de ficheros y formatos ejecutables del núcleo, así que te permite arrancar un sistema operativo cualquiera, de la manera que quieras, sin necesidad de saber la posición física del núcleo en el disco.
Debemos editar el fichero /boot/grub/grub.conf dónde podremos ver una estructura similar a esta:
mihrab grub # cat grub.conf
timeout 10
default 0
title GNU/Linux 2.6.7
root (hd0,0) kernel /vmlinuz-2.6.7 root=/dev/hda3
Añadiremos ahora una nueva entrada para nuestro nuevo kernel, justo encima de la que tenemos actualmente para el antiguo. Esto no es totalmente necesario con Grub ya que si surge algún problema con el nuevo kernel y no podemos arrancarlo, podemos modificar los parametros del kernel a arrancar desde el propio Grub al iniciar el sistema y hacerle usar la imagen del kernel antiguo.
Nuestra nueva entrada podría quedar más o menos así:
title GNU/Linux 2.6.9
root (hd0,0) kernel /vmlinuz-2.6.9 root=/dev/hda3
Si observamos la segunda linea: root (hd0,0), podemos apreciar que la sintáxis de dispositivos usada en Grub no nos es familiar. Para empezar, Grub requiere que se encierre el nombre del dispositivo entre paréntesis `(' y `)'. Lo que `hd' significa es que se trata de un disco duro (hard disk). El primer entero indica el número de la unidad, es decir, que se trata del primer disco duro, mientras que el segundo entero indica el número de la partición. Vamos, lo que se suele conocer como hda1 en Grub se traduciría a (hd0,0).
Las opciones que vemos al principio del fichero grub.conf son para indicar cuanto tiempo se ha de esperar hasta iniciar el sistema por defecto y cual sería este.
Ya sólo queda rearrancar la máquina para probar el nuevo kernel. Una vez ha vuelto a cargar el sistema operativo y estamos sobre la consola, lo primero será cerciorarse de que nuestro nuevo kernel ha sido cargado correctamente, viendo su número de versión. Esto podemos hacerlo de varias formas, entre ellas, por ejemplo, utilizando dmesg, /proc o uname como podemos ver a continuación:
jorge@mihrab ~ $ dmesg | head -n
Linux version 2.6.9 (root@mihrab) (gcc version 3.4.2) #12 Wed Oct 20 15:15:26
CEST 2004
jorge@mihrab ~ $ cat /proc/version
Linux version 2.6.9 (root@mihrab) (gcc version 3.4.2) #12 Wed Oct 20 15:15:26
CEST 2004
jorge@mihrab~ $ uname -a
Linux mihrab 2.6.9 #12 Wed Oct 20 15:15:26 CEST 2004 i686
Intel(R) Pentium(R) M processor 1500MHz GenuineIntel GNU/Linux
El primer método utiliza el mandato dmesg cuya misión es reproducir toda la salida que el kernel produjo durante el arranque (utilizamos head -n 1 por que lo primero que hace todo kernel al arrancar es mostrar un mensaje con su número de versión y el compilador que utilizado y la fecha en que fue compilado). Esta aplicación resulta interesante para observar en detalle la evolución de la carga del kernel en memoria dado que se detalla todo el hardware hallado o posibles problemas. Un ejemplo de la salida típica de dmesg sería:
Linux version 2.6.7 (root@mihrab) (gcc version 3.4.2))
#12 Wed Aug 11 20:40:26 CEST 2004
BIOS-provided physical RAM map:
BIOS-e820: 0000000000000000 - 000000000009f800 (usable)
BIOS-e820: 000000000009f800 - 00000000000a0000 (reserved)
BIOS-e820: 00000000000d0000 - 00000000000d4000 (reserved)
BIOS-e820: 00000000000e0000 - 0000000000100000 (reserved)
BIOS-e820: 0000000000100000 - 000000001ff60000 (usable)
BIOS-e820: 000000001ff60000 - 000000001ff6a000 (ACPI data)
BIOS-e820: 000000001ff6a000 - 000000001ff6b000 (reserved)
BIOS-e820: 000000001ff6b000 - 000000001ff70000 (ACPI NVS)
BIOS-e820: 000000001ff70000 - 0000000020000000 (reserved)
BIOS-e820: 00000000ffb80000 - 00000000ffc00000 (reserved)
BIOS-e820: 00000000fff80000 - 0000000100000000 (reserved)
511MB LOWMEM available.
On node 0 totalpages: 130912
DMA zone: 4096 pages, LIFO batch:1
Normal zone: 126816 pages, LIFO batch:16
HighMem zone: 0 pages, LIFO batch:1
DMI present.
ACPI: RSDP (v000 TOSHIB ) @ 0x000f7a00
ACPI: RSDT (v001 TOSHIB 750 0x00970814 MASM 0x06110000) @ 0x1ff63fd8
ACPI: FADT (v002 TOSHIB 750 0x20030101 MASM 0x61100000) @ 0x1ff69d03
ACPI: SSDT (v001 TOSHIB A0007 0x00970814 MSFT 0x0100000e) @ 0x1ff69d87
ACPI: DBGP (v001 TOSHIB 750 0x00970814 MASM 0x61100000) @ 0x1ff69fa4
ACPI: BOOT (v001 TOSHIB 750 0x00970814 MASM 0x06110000) @ 0x1ff69fd8
ACPI: DSDT (v001 TOSHIB A0007 0x20030806 MSFT 0x0100000e) @ 0x00000000
Built 1 zonelists
Kernel command line: root=/dev/hda3 video=mtrr,vesa:1024x768 vga=0x317
splash=silent
bootsplash: silent mode.
Local APIC disabled by BIOS -- reenabling.
Found and enabled local APIC!
Initializing CPU#0
CPU 0 irqstacks, hard=c03dc000 soft=c03db000
PID hash table entries: 2048 (order 11: 16384 bytes)
Detected 1496.685 MHz processor.
Using tsc for high-res timesource
Console: colour dummy device 80x25
Memory: 515544k/523648k available (1994k kernel code, 7316k reserved,
761k data, 152k init, 0k highmem)
Checking if this processor honours the WP bit even in supervisor mode...
Ok.
Calibrating delay loop... 2957.31 BogoMIPS
Dentry cache hash table entries: 65536 (order: 6, 262144 bytes)
Inode-cache hash table entries: 32768 (order: 5, 131072 bytes)
Mount-cache hash table entries: 512 (order: 0, 4096 bytes)
CPU: After generic identify, caps: a7e9fbbf 00000000 00000000 00000000
CPU: After vendor identify, caps: a7e9fbbf 00000000 00000000 00000000
CPU: L1 I cache: 32K, L1 D cache: 32K
CPU: L2 cache: 1024K
CPU: After all inits, caps: a7e9fbbf 00000000 00000000 00000040
Intel machine check architecture supported.
Intel machine check reporting enabled on CPU#0.
CPU: Intel(R) Pentium(R) M processor 1500MHz stepping 05
Enabling fast FPU save and restore... done.
Enabling unmasked SIMD FPU exception support... done.
Checking 'hlt' instruction... OK.
enabled ExtINT on CPU#0
ESR value before enabling vector: 00000000
ESR value after enabling vector: 00000000
Using local APIC timer interrupts.
calibrating APIC timer ...
..... CPU clock speed is 1495.0998 MHz.
..... host bus clock speed is 99.0733 MHz.
checking if image is initramfs...it isn't (ungzip failed);
looks like an initrd
Freeing initrd memory: 40k freed
NET: Registered protocol family 16
PCI: PCI BIOS revision 2.10 entry at 0xfd981, last bus=4
PCI: Using configuration type 1
mtrr: v2.0 (20020519)
ACPI: Subsystem revision 20040326
ACPI: IRQ9 SCI: Edge set to Level Trigger.
ACPI: Interpreter enabled
ACPI: Using PIC for interrupt routing
ACPI: PCI Interrupt Link [LNKA] (IRQs *3 4 5 7 11)
ACPI: PCI Interrupt Link [LNKB] (IRQs 3 *4 5 7 11)
ACPI: PCI Interrupt Link [LNKC] (IRQs 3 4 5 7 11) *0, disabled.
ACPI: PCI Interrupt Link [LNKD] (IRQs 3 4 5 7 *11)
ACPI: PCI Interrupt Link [LNKE] (IRQs 3 4 5 7 *11)
ACPI: PCI Interrupt Link [LNKF] (IRQs *3 4 5 7 11)
ACPI: PCI Interrupt Link [LNKG] (IRQs *10)
ACPI: PCI Interrupt Link [LNKH] (IRQs 3 4 5 *7 11)
ACPI: PCI Root Bridge [PCI0] (00:00)
PCI: Probing PCI hardware (bus 00)
PCI: Ignoring BAR0-3 of IDE controller 0000:00:1f.1
PCI: Transparent bridge - 0000:00:1e.0
ACPI: PCI Interrupt Routing Table [\_SB_.PCI0._PRT]
ACPI: PCI Interrupt Routing Table [\_SB_.PCI0.PCI1._PRT]
ACPI: PCI Interrupt Routing Table [\_SB_.PCI0.PCIB._PRT]
ACPI: Power Resource [PFAN] (off)
Toshiba System Managment Mode driver v1.11 26/9/2001
SCSI subsystem initialized
ACPI: PCI Interrupt Link [LNKA] enabled at IRQ 3
ACPI: PCI Interrupt Link [LNKD] enabled at IRQ 11
ACPI: PCI Interrupt Link [LNKH] enabled at IRQ 7
ACPI: PCI Interrupt Link [LNKC] enabled at IRQ 11
ACPI: PCI Interrupt Link [LNKB] enabled at IRQ 4
ACPI: PCI Interrupt Link [LNKG] enabled at IRQ 10
ACPI: PCI Interrupt Link [LNKF] enabled at IRQ 3
ACPI: PCI Interrupt Link [LNKE] enabled at IRQ 11
PCI: Using ACPI for IRQ routing
vesafb: framebuffer at 0xe0000000, mapped to 0xe080e000, size 3072k
vesafb: mode is 1024x768x16, linelength=2048, pages=1
vesafb: protected mode interface info at c000:d0e0
vesafb: scrolling: redraw
vesafb: directcolor: size=0:5:6:5, shift=0:11:5:0
fb0: VESA VGA frame buffer device
Simple Boot Flag at 0x36 set to 0x1
Machine check exception polling timer started.
devfs: 2004-01-31 Richard Gooch (rgooch@atnf.csiro.au)
devfs: boot_options: 0x1
udf: registering filesystem
Initializing Cryptographic API
ACPI: AC Adapter [ADP1] (off-line)
ACPI: Battery Slot [BAT1] (battery present)
ACPI: Power Button (FF) [PWRF]
ACPI: Lid Switch [LID]
ACPI: Fan [FAN] (off)
ACPI: Processor [CPU0] (supports C1 C2 C3)
ACPI: Thermal Zone [THRM] (28 C)
toshiba_acpi: Toshiba Laptop ACPI Extras version 0.18
toshiba_acpi: HCI method: \_SB_.VALZ.GHCI
mice: PS/2 mouse device common for all mice
serio: i8042 AUX port at 0x60,0x64 irq 12
input: PS/2 Generic Mouse on isa0060/serio1
serio: i8042 KBD port at 0x60,0x64 irq 1
input: AT Translated Set 2 keyboard on isa0060/serio0
bootsplash 3.1.4-2004/02/19-spock-0.1: looking for picture....
silentjpeg size 17594 bytes, found (1024x768, 23422 bytes, v3).
Console: switching to colour frame buffer device 122x40
RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize
loop: loaded (max 8 devices)
Uniform Multi-Platform E-IDE driver Revision: 7.00alpha2
ide: Assuming 33MHz system bus speed for PIO modes;
override with idebus=xx
ICH4: IDE controller at PCI slot 0000:00:1f.1
PCI: Enabling device 0000:00:1f.1 (0005 -> 0007)
ICH4: chipset revision 3
ICH4: not 100% native mode: will probe irqs later
ide0: BM-DMA at 0x1840-0x1847, BIOS settings: hda:DMA, hdb:pio
ide1: BM-DMA at 0x1848-0x184f, BIOS settings: hdc:DMA, hdd:pio
hda: TOSHIBA MK4021GAS, ATA DISK drive
Using anticipatory io scheduler
ide0 at 0x1f0-0x1f7,0x3f6 on irq 14
hdc: UJDA750 DVD/CDRW, ATAPI CD/DVD-ROM drive
ide1 at 0x170-0x177,0x376 on irq 15
hda: max request size: 128KiB
hda: 78140160 sectors (40007 MB), CHS=65535/16/63, UDMA(100)
/dev/ide/host0/bus0/target0/lun0: p1 p2 p3
hdc: ATAPI 24X DVD-ROM CD-R/RW drive, 2048kB Cache, UDMA(33)
Uniform CD-ROM driver Revision: 3.20
oprofile: using NMI interrupt.
NET: Registered protocol family 2
IP: routing cache hash table of 4096 buckets, 32Kbytes
TCP: Hash tables configured (established 32768 bind 65536)
ip_conntrack version 2.1 (4091 buckets, 32728 max) -
296 bytes per conntrack
ip_tables: (C) 2000-2002 Netfilter core team
ipt_recent v0.3.1: Stephen Frost <sfrost@snowman.net>.
http://snowman.net/projects/ipt_recent/
arp_tables: (C) 2002 David S. Miller
NET: Registered protocol family 1
NET: Registered protocol family 17
speedstep-centrino: found "Intel(R) Pentium(R) M processor 1500MHz":
max frequency: 1500000kHz
ACPI: (supports S0 S3 S4 S5)
RAMDISK: Couldn't find valid RAM disk image starting at 0.
ReiserFS: hda3: found reiserfs format "3.6" with standard journal
ReiserFS: hda3: using ordered data mode
ReiserFS: hda3: journal params: device hda3, size 8192,
journal first block 18, max trans len 1024, max batch 900,
max commit age 30, max trans age 30
ReiserFS: hda3: checking transaction log (hda3)
ReiserFS: hda3: Using r5 hash to sort names
VFS: Mounted root (reiserfs filesystem) readonly.
Mounted devfs on /dev
Freeing unused kernel memory: 152k freed
Adding 1004052k swap on /dev/hda2. Priority:-1 extents:1
nvidia: module license 'NVIDIA' taints kernel.
NVRM: loading NVIDIA Linux x86 NVIDIA Kernel Module 1.0-6111
Tue Jul 27 07:55:38 PDT 2004
PCI: Setting latency timer of device 0000:00:1f.5 to 64
intel8x0_measure_ac97_clock: measured 49395 usecs
intel8x0: clocking to 48000
eepro100.c:v1.09j-t 9/29/99 Donald Becker
http://www.scyld.com/network/eepro100.html
eepro100.c: $Revision: 1.36 $ 2000/11/17
Modified by Andrey V. Savochkin <saw@saw.sw.com.sg> and others
eth0: OEM i82557/i82558 10/100 Ethernet, 00:08:0D:1C:00:F3, IRQ 11.
Board assembly 000000-000, Physical connectors present: RJ45
Primary interface chip i82555 PHY #1.
General self-test: passed.
Serial sub-system self-test: passed.
Internal registers self-test: passed.
ROM checksum self-test: passed (0x04f4518b).
usbcore: registered new driver usbfs
usbcore: registered new driver hub
USB Universal Host Controller Interface driver v2.2
uhci_hcd 0000:00:1d.0: Intel Corp. 82801DB (ICH4) USB UHCI #1
PCI: Setting latency timer of device 0000:00:1d.0 to 64
uhci_hcd 0000:00:1d.0: irq 3, io base 00001800
uhci_hcd 0000:00:1d.0: new USB bus registered, assigned bus number 1
hub 1-0:1.0: USB hub found
hub 1-0:1.0: 2 ports detected
uhci_hcd 0000:00:1d.1: Intel Corp. 82801DB (ICH4) USB UHCI #2
PCI: Setting latency timer of device 0000:00:1d.1 to 64
uhci_hcd 0000:00:1d.1: irq 11, io base 00001820
uhci_hcd 0000:00:1d.1: new USB bus registered, assigned bus number 2
hub 2-0:1.0: USB hub found
hub 2-0:1.0: 2 ports detected
ehci_hcd 0000:00:1d.7: Intel Corp. 82801DB (ICH4) USB2 EHCI Controller
PCI: Setting latency timer of device 0000:00:1d.7 to 64
ehci_hcd 0000:00:1d.7: irq 7, pci mem e0c85000
ehci_hcd 0000:00:1d.7: new USB bus registered, assigned bus number 3
PCI: cache line size of 32 is not supported by device 0000:00:1d.7
ehci_hcd 0000:00:1d.7: USB 2.0 enabled, EHCI 1.00, driver 2004-May-10
hub 3-0:1.0: USB hub found
hub 3-0:1.0: 4 ports detected
Initializing USB Mass Storage driver...
usbcore: registered new driver usb-storage
USB Mass Storage support registered.
usbcore: registered new driver usbhid
drivers/usb/input/hid-core.c: v2.0:USB HID core driver
Real Time Clock Driver v1.12
bootsplash 3.1.4-2004/02/19-spock-0.1: looking for picture....
found (1024x768, 23422 bytes, v3).
bootsplash: status on console 0 changed to on
bootsplash 3.1.4-2004/02/19-spock-0.1: looking for picture....
found (1024x768, 23422 bytes, v3).
bootsplash: status on console 1 changed to on
bootsplash 3.1.4-2004/02/19-spock-0.1: looking for picture....
found (1024x768, 23422 bytes, v3).
bootsplash: status on console 2 changed to on
bootsplash 3.1.4-2004/02/19-spock-0.1: looking for picture....
found (1024x768, 23422 bytes, v3).
bootsplash: status on console 3 changed to on
bootsplash 3.1.4-2004/02/19-spock-0.1: looking for picture....
found (1024x768, 23422 bytes, v3).
bootsplash: status on console 4 changed to on
bootsplash 3.1.4-2004/02/19-spock-0.1: looking for picture....
found (1024x768, 23422 bytes, v3).
bootsplash: status on console 5 changed to on
ehci_hcd 0000:00:1d.7: remove, state 1
El segundo método es quizá más rudimentario. Sin embargo resulta mucho más útil de cara a la obtención de información. Es conveniente, de hecho, que todo usuario se habitúe a buscar los datos que precisa sobre los parámetros del sistema operativo entre las entradas del directorio /proc. Este directorio es un tanto especial dado que sus entradas no son ficheros como tales y se crean cuando arranca el kernel. Al listar un fichero, por ejemplo /proc/ioports, no se muestra su contenido sino que se realizan las oportunas llamadas al sistema operativo consultando la información necesaria para luego mostrarla por pantalla. Si no se lo cree intente buscar el tamaño del fichero /proc/vmstat o cualquier otro fichero situado en /proc.
Los módulos en detalle
Pese a tener nuestro kernel ya compilado y funcionado, en más de una ocasión será necesario añadirle un módulo. Por ejemplo, como veremos más adelante, para lograr utilizar un dispositivo de almacenamiento externo usb deberemos añadir el módulo usb_storage entre otros. En esta sección proporcionaremos la información básica para la gestión de módulos.
Introducción a los módulos de un núcleo
Un módulo no es más que un programa C común que incluye ciertos ficheros de cabecera específicos del núcleo. Si nos paramos a ver el código en detalle, no encontramos una función main sino dos, llamadas init_module y clenaup_module que son ejecutadas al cargar y al descargar el módulo de memoria respectivamente, aunque desde Linux 2.6 hay algo más de libertad en este aspecto y podemos llamarlas como queramos e indicarlo mediante dos macros: module_init y module_exit. El proceso de compilación de un módulo es muy sencillo, tan sólo lo compilaremos como cualquier otro programa, aunque no lo enlazaremos (con la opción -c del compilador gcc obtenemos el fichero compilado, no el ejecutable final tras enlazarlo con las librerías pertinentes). Una vez esto ha sido efectuado con éxito lo podremos cargar en el núcleo mediante el mandato modprobe y descargarlo con rmmod como veremos más adelante.
El fin último del uso de módulos es poder añadir/eliminar nuevas funcionalidades a un kernel sin necesidad de recompilarlo y rearrancar la máquina. Es conveniente que la mayoría de los dispositivos no esenciales como la gestión de sistemas de ficheros foráneos como fat32, el dispositivo encargado de gestionar la tarjeta de sonido, etc. sean compilados como módulos. Además, existe un demonio llamado kerneld encargado de cargar el módulo automáticamente cuando este es requerido (aunque este método no es eficaz dado que en ocasiones nos interesará cargarlos con condiciones específicas como ahora veremos).
Gestión de módulos
Trataremos en este apartado la inserción y borrado de módulos en el kernel. Detallaremos el uso de cuatro mandatos: insmod, rmmod, lsmod y modprobe. No consiste en proporcionar una lista completa de los parámetros que acepta cada uno, dado que conocer esto resulta muy sencillo consultando el manual (man modprobe), dónde además será posible conocer otras herramientas relacionadas.
insmod
Carga un módulo en el kernel.
rmmod
Borra un módulo del kernel.
lsmod
Lista los módulos cargados actualmente. El resultado es similar a listar el contenido del fichero /proc/modules.
modprobe
Carga un módulo aunque previamente resuelve todas las dependencias cargando otros módulos si fuera necesario. Es la forma aconsejada de cargar modulos.
Notas al pie
... HOWTO3.1
Los HOWTO son un compendio de documentos que muestran como realizar ciertas tareas específicas dentro de un campo determinado, en este caso nos referimos a los referidos a Linux, que pueden encontrarse en el directorio /usr/doc/HOWTO
... ciclo3.2
Esto no es realmente cierto, véase procesadores superescalares, sin embargo para el objetivo del artículo es más que suficiente
... FSF3.3
Free Software Fundation
... real3.4
Consiste en dotar al procesador de dos modos: modo real, utilizado para mantener la compatibilidad con software creado para versiones anteriores, y modo protegido dónde es posible explotar las nuevas capacidades del procesador
Autor y licencia de 'Curso básico de Linux'
Curso gratis de ACM Capítulo de Estudiantes - Facultad de Informática UPM . Extraido de: http://acm.asoc.fi.upm.es/documentacion/linux2004/