Anatomía del kernel Linux. El propósito de esta sección es entender la arquitectura fundamental del sistema de memoria en los sistemas operativos basados en el kernel Linux y describir las principales capas que lo integran. En concreto, se abordan aspectos técnicos que ayudarán
a comprender mejor cómo el kernel desempeña las
funciones para las que ha sido diseñado: gestión de
memoria física y virtual, acceso a dispositivos, planificación de tareas, almacenamiento en sistemas de
ficheros y networking.
Ciertamente, el kernel Linux es el corazón de un
gran y complejo sistema operativo, sin embargo, éste también se caracteriza por estar bien organizado
internamente en términos de capas y subsistemas.
Tal y como se muestra en la Figura 1, en el sistema
de memoria de GNU/Linux se identifican dos regiones: el espacio de usuario y el espacio del kernel.

Espacio de usuario y espacio del kernel
El espacio de usuario (user space) o espacio de aplicación comprende la región de memoria donde se
ejecutan las aplicaciones de usuario propiamente dichas. Un componente esencial del espacio de usuario
es la librería GNU C (glibc), la cual proporciona una
interfaz entre las aplicaciones y el kernel mediante
un conjunto de llamadas al sistema (syscalls) y funciones básicas, tales como open, malloc, printf o exit.
La librería GNU C está disponible para diferentes
núcleos y arquitecturas de procesador, aunque su
uso más extendido son los sistemas Linux sobre
plataformas x86.
El kernel y las aplicaciones de usuario ocupan diferentes espacios protegidos de memoria. Mientras
que el espacio de usuario mantiene su propio espacio virtual de direcciones, el kernel ocupa un espacio único reservado desde el que provee todos sus servicios (kernel space). El espacio del kernel únicamente puede ser accedido por los procesos de usuario
a través de la interfaz de llamadas al sistema. A di
ferencia de lo que ocurre en el espacio de usuario,
las rutinas ejecutadas en el espacio del kernel tienen
acceso completo a los recursos del sistema. Habitualmente, este modo privilegiado de funcionamiento se conoce como ejecución en modo kernel (kernel
mode).
El espacio del kernel se divide en tres niveles. En
el nivel superior, la interfaz de llamadas al sistema
o SCI (System Call Interface) proporciona los mecanismos necesarios para realizar llamadas a funciones desde el espacio de usuario al kernel. En esencia, se trata de un servicio multiplexador y demultiplexador de llamadas al sistema. Esta interfaz puede ser
dependiente de la arquitectura e incluso diferenciarse
dentro de la misma familia de procesadores. Por poner un ejemplo, en el mercado existen procesadores
x86 con soporte para instrucciones de virtualización,
mientras que los antiguos procesadores x86 carecen
de estas extensiones.

La Tabla 1 corresponde a un
ejemplo simple de llamada al sistema utilizando la
interrupción int 80h, característica de los sistemas Unix
basados en arquitectura Intel. En el ejemplo, el servicio
write es invocado para mostrar por salida estándar una
cadena de texto de longitud fija almacenada a partir de
una dirección de memoria previamente conocida.
El nivel central del espacio del kernel corresponde al código del kernel o también denominado
código del kernel independiente de la arquitectura.
Aquí es donde están implementados los principales
subsistemas que dotan de funcionalidad al sistema
operativo. Más adelante describiremos los subsistemas que integran esta capa.
Aunque la mayor parte del código del kernel
es independiente de la arquitectura, existen ciertos El árbol de código del kernel Linux clasifica los drivers en diferentes directorios según la categoría o familia de dispositivos
a la que pertenece.
Módulos del kernel
A la hora de añadir soporte para un determinado dispositivo en
Linux pueden considerarse dos alternativas. La primera consiste en incluir el soporte en el propio archivo binario del kernel,
de tal forma que, cuando el kernel se cargue en el arranque del
sistema, también se inicie el soporte para el dispositivo cuyo
driver fue compilado anteriormente como parte del kernel. La
segunda opción consiste en compilar el driver del dispositivo
por separado y cargarlo en el kernel dinámicamente, es decir, únicamente cuando éste sea requerido. El driver compilado de
esta forma recibe el nombre de módulo. La carga dinámica de
módulos requiere haber activado previamente esta caracterís
tica en el kernel en el momento de su compilación.
Los módulos del kernel no son más que archivos objeto
generados con el compilador C de GNU. Generalmente, el código fuente de los módulos se distribuye junto al código fuente
del kernel, evitando de esta forma su inserción en una versión
incorrecta del kernel. También existen ciertos módulos que,
por cuestiones de licencia principalmente, deben obtenerse
y compilarse por separado. En las distribuciones GNU/Linux,
los módulos del kernel se almacenan por defecto en el directorio /lib/modules/kernelversion, siendo kernelversion la salida
del comando uname -r, por ejemplo, 2.6.34. En este caso
concreto, los módulos (con extensión .ko) estarían ubicados
en /lib/modules/2.6.34/.
Kernel monolítico vs. Kernel modular
Por definición, un kernel es monolítico cuando la opción de
soporte de carga de módulos está desactivada y los drivers
de todos los dispositivos han sido compilados e integrados
originalmente en el kernel. Diremos que un kernel es modular
cuando el soporte de carga de módulos está activado y, por
tanto, es posible añadir o quitar módulos del kernel dinámicamente cuando éste se encuentra cargado en memoria principal.
La decisión de decantarse por un kernel monolítico o modular depende del propósito y el escenario concreto. Ante una
máquina con las funciones muy bien definidas y hardware inalterable en el tiempo, probablemente, lo más recomendable
es utilizar un kernel monolítico. Los kernels monolíticos suelen
caracterizarse por ser más eficientes y seguros, ya que se
evita la carga de módulos que puedan llegar a comprometer
la integridad del sistema. No obstante, el uso de kernels monolíticos también lleva de la mano algunos inconvenientes. El
primero y quizás más relevante reside en que, generalmente,
ocupan más espacio en memoria; muchas veces este recurso
es desaprovechado por cargar características innecesarias
o soporte para dispositivos inexistentes en la máquina física.
Por otro lado, la incorporación de nuevos dispositivos sin
soporte en el kernel monolítico activo también implica la necesidad de recompilarlo nuevamente y tener que reiniciar la
máquina para hacer efectivos los cambios.
En los equipos de propósito general existentes hoy día
es habitual la instalación de nuevos dispositivos, tales como
discos duros externos, webcams, capturadoras, impresoras o pendrives. En este tipo de escenarios donde los cambios en
la configuración de hardware son frecuentes, es mucho más
flexible y práctico recurrir a kernels modulares. Al contrario de
lo que ocurre con los kernels monolíticos, los kernels modulares ocupan menos espacio en memoria y la inserción de módulos adicionales no requiere tener que reiniciar la máquina en
la mayoría de casos.
Manipulación de módulos
La carga de un módulo implica enlazar dinámicamente el
módulo en cuestión con el kernel activo cargado en memoria.
Habitualmente, esta tarea se realiza de forma automática,
sin embargo, hay ocasiones en las que el usuario necesita
o prefiere manipular ciertos módulos por sí mismo. En este
apartado se presentan las herramientas provistas por el paquete module-init-tools de la rama 2.6 del kernel Linux
(equivalente a modutils en la rama 2.4) para manipular y administrar los módulos en tiempo de ejecución.
• Listado de módulos cargados: el comando lsmod muestra
todos los módulos cargados actualmente, espacio ocupado en memoria, recuento de usos (número de módulos
y servicios que están haciendo uso de cada módulo) y módulos referenciados. La sintaxis del comando es lsmod.
• Inserción de módulos: los comandos insmod y modprobe
permiten añadir módulos en el kernel activo. La inserción
y eliminación de módulos requiere privilegios de administración. La sintaxis básica del primer comando es insmod nombre_módulo. A diferencia del anterior, modprobe
carga el módulo indicado incluyendo posibles prerrequisitos y dependencias. Su sintaxis básica es modprobe nom
bre_módulo. Generalmente, se recomienda utilizar siempre
modprobe.
• Eliminación de módulos: los comandos rmmod y modprobe -r permiten la eliminación de módulos cargados en el
kernel. La sintaxis de ambos comandos es idéntica a la inserción de módulos. Si el módulo a desactivar está en uso
o es referenciado por otro módulo, éste no podrá eliminarse. A diferencia de rmmod, con modprobe también se eliminan automáticamente los módulos definidos como prerrequisitos que quedan inutilizados.
• Obtener información sobre módulos: con el comando modinfo es posible obtener información sobre un módulo específico a partir de su archivo objeto. No todos los módulos del kernel proporcionan la misma salida; algunos tan sólo muestran una breve descripción y otros, a diferencia
de los anteriores, presentan gran cantidad de información
con todo lujo de detalles. La sintaxis básica del comando
es modinfo archivo_objeto. La Figura 2 muestra un ejemplo de ejecución de modinfo.

Configuración de módulos con Modprobe
Inicialmente, la configuración de los módulos del kernel de la
serie 2.4 y anteriores se almacenaba en /etc/modules.conf.
Con la llegada de la versión 2.6, la sintaxis de configuración se
simplificó y este fichero fue reemplazado por /etc/modprobe.
conf. Alternativamente, la configuración también puede ser
almacenada de forma modular en varios ficheros dentro del
directorio /etc/modprobe.d/. Esta opción es utilizada por la
mayoría de distribuciones GNU/Linux actuales.
La configuración de modprobe especifica, entre otras
cosas, las opciones que deben aplicarse a la hora de añadir
o quitar módulos del kernel. El formato de modprobe.conf y los
archivos de modprobe.d son muy simples, un comando por
línea con la posibilidad de añadir líneas en blanco y comentarios. A continuación, se describen las directivas admitidas en
los ficheros de configuración:
• alias nombre_alias nombre_módulo: permite definir un
nombre alternativo nombre_alias para el módulo indicado
en nombre_módulo.
• options nombre_módulo opción1=valor [opción2=valor …]:
permite definir opciones de carga para el módulo nombre_módulo. Las opciones también son aplicadas cuando el
módulo se carga indirectamente por ser una dependencia.
• install nombre_módulo comando: en lugar de insertar
el módulo en el kernel de forma habitual, se ejecuta el
comando indicado. Por ejemplo, si el módulo A funciona
mejor con el módulo B activo (pero B no es una dependencia de A, por lo que no se carga automáticamente),
podría añadirse la línea de configuración "install A /sbin/
modprobe B; /sbin/modprobe --ignore-install A". El parámetro --ignore-install evita que se vuelva a ejecutar el
mismo comando install por recursividad.
• remove nombre_módulo comando: directiva similar a install, salvo que ahora es invocada en la eliminación de módulos. Ejemplo: "remove A /sbin/modprobe -r --ignore-re-
move A && /sbin/modprobe -r B". El parámetro --ignore-
remove evita que se vuelva a ejecutar el mismo comando
remove por recursividad.
• include nombre_fichero: con esta directiva es posible añadir ficheros adicionales a la configuración. Si el nombre
del fichero es un directorio, se incluirán todos los archivos
que cuelguen del mismo.
• blacklist nombre_módulo: en ocasiones, existen dos o más
módulos que dan soporte a los mismos dispositivos, o bien, un módulo soporta de manera incorrecta un dispositivo.
Con la directiva blacklist es posible ignorar la carga de
módulos problemáticos o no deseados.
Dependencias entre módulos
Los módulos del kernel proveen servicios (symbols) que pueden ser usados por otros módulos. Si un determinado módulo
requiere servicios provistos por otro módulo, entonces el primero depende de éste. La idea es simple, sin embargo, las dependencias entre módulos pueden llegar a ser bastante complejas.
Para que modprobe sea capaz de identificar las dependencias de un módulo a la hora de cargarlo, se recurre al fichero de dependencias de módulos modules.dep, ubicado en el
directorio /lib/modules/$(uname -r). Internamente, las dependencias se definen con el siguiente formato: "fichero_objeto_
módulo: fichero_objeto_dependencia1 fichero_objeto_dependencia2…". Todos los módulos del kernel deben aparecer en
modules.dep, incluyendo ruta completa, nombre y extensión.
Aquellos módulos que no tienen dependencias también deben
listarse.El kernel Linux a fondo
Para garantizar el correcto funcionamiento de modprobe,
el fichero modules.dep ha de mantenerse siempre actualizado. Con el fin de prevenir posibles situaciones conflictivas, la
mayoría de distribuciones ejecutan "depmod -a" en el arranque del sistema. Este comando crea y sobrescribe el fichero
modules.dep para el kernel activo. La regeneración del fichero
modules.dep es necesaria cuando se produce cualquier cambio en las dependencias, por ejemplo, después de compilar
y añadir un nuevo módulo en el kernel.
En el caso particular de realizar cambios sobre una máquina con kernel 2.4, también sería necesario regenerar el fichero de configuración modules.conf con el comando update-
modules. El comando update-modules y el fichero modules.
conf pertenecen al paquete modutils de los kernels 2.4. En la
rama 2.6, modutils está obsoleto y es reemplazado por modu-
le-init-tools.
Initial Ram Disk
El disco RAM inicial (Initial RAM Disk), o también llamado
initrd, es un sistema de archivos temporal implicado en el
arranque del kernel Linux. Habitualmente, se emplea para
realizar las tareas necesarias antes de que el sistema de
ficheros raíz pueda ser montado. Con el propósito de habilitar el arranque del sistema operativo desde distintos medios
(incluyendo medios virtuales y transitorios como los provis
tos por una conexión de red), initrd facilita el acceso a todos
los archivos necesarios. El bootloader es el encargado de
indicar la ubicación del disco RAM (initrd) durante el arranque. Para ser más exactos, initrd es montado como parte de
la etapa de arranque 'stage 2'. A continuación, tiene lugar la
carga de los módulos del kernel que habilitarán el sistema de
ficheros raíz y permitirán continuar el proceso de arranque
(proceso init), ver Figura 3.

El sistema de archivos initrd está comprimido con la librería gzip. Crear o editar una imagen de un sistema de archivos
initrd de un disco RAM requiere privilegios de administración.
Por otra parte, los discos RAM suelen ser de tamaño fijo, así que normalmente utilizan más espacio del necesario. A pesar
de estos inconvenientes, initrd sigue teniendo un amplio uso.
Con la llegada del kernel 2.6, initrd ha comenzado a ser reemplazado por initramfs debido a su mayor flexibilidad y eficiencia
en el uso de memoria.
Sistema de archivos /proc
El directorio /proc es un sistema de archivos virtual cargado en
memoria (no se encuentra físicamente en disco), de ahí que
también se denomine pseudo-sistema de archivos. Este pseudo-sistema de archivos permite interactuar con las estructuras
de datos internas del kernel, facilitando el acceso a información útil relacionada con los procesos y permitiendo modificar
en caliente algunos parámetros de funcionamiento. Entender
y saber aplicar cambios en /proc es un punto clave para exprimir al máximo el sistema operativo.
A continuación, se describen algunos de los ficheros
ubicados en la raíz de /proc de los cuales se puede obtener
información utilizando el comando cat:• /proc/cpuinfo: información sobre el procesador (tipo, marca, modelo, familia, frecuencia, etc.).
• /proc/devices: controladores de dispositivos configurados
y ejecutándose en el kernel.
• /proc/dma: canales DMA que están siendo utilizados.
• /proc/filesystem: sistemas de archivos soportados por el
kernel.
• /proc/interrupts: interrupciones y cuántas de cada tipo se
han producido desde el arranque.
• /proc/ioports: direcciones de memoria de los puertos de
entrada y salida.
• /proc/kcore: imagen de la memoria física del sistema (mis-
mo tamaño que la memoria física).
• /proc/kmsg: salida de los mensajes emitidos por el kernel.
• /proc/loadavg: nivel medio de carga del sistema.
• /proc/modules: módulos cargados en el kernel hasta el
momento.
• /proc/meminfo: información detallada sobre el uso de la
memoria física y de intercambio.
• /proc/self: enlace simbólico al directorio de proceso del
programa que esté observando a /proc. Se trata de una
facilidad para que los programas accedan a su directorio
de procesos.
• /proc/stat: estadísticas del sistema.
• /proc/uptime: tiempo de funcionamiento en segundos des-
de el último arranque.
• /proc/version: versión del kernel activo.
• /proc/swaps: puntos de montaje destinados a memoria
swap, tamaño disponible y utilizado.
• /proc/partitions: listado con las particiones del sistema.
• /proc/cmdline: parámetros pasados al kernel durante el
arranque (stage 2).
En el directorio /proc también existen carpetas asociadas a los
procesos que actualmente se encuentran en ejecución. Para
cada proceso, el nombre de la carpeta coincide con el identificador del proceso, es decir, su PID (Process IDentifier). Cada
carpeta contiene ficheros que proporcionan detalles sobre el
estado y el entorno del proceso en cuestión. La descripción
de estos ficheros está disponible con man proc. El PID de un
proceso puede obtenerse ejecutando el comando ps -aef en
consola.
Además de obtener información sobre procesos, a través
de /proc también es posible interactuar con el núcleo y modificar ciertos parámetros que rigen su comportamiento. Todos
los cambios realizados se mantendrán efectivos mientras no
se apague o reinicie el sistema. El fichero /proc/sys/kernel/
hostname, por ejemplo, contiene el nombre de red de la
máquina. Para modificar el valor de este parámetro bastaría
con ejecutar el siguiente comando: echo nuevo_hostname> /proc/sys/kernel/hostname. En /proc/sys también hay multitud
de ficheros configurables, aunque no vamos a enumerarlos.
Un directorio que sí mencionaremos es /proc/sys/net, a través
del cual es posible modificar algunas propiedades de red.
Por ejemplo, para hacer que la máquina local ignore las peticiones de ping, sería tan simple como desactivar echo icmp
ejecutando el siguiente comando: echo 1 > /proc/sys/net/ipv4/
icmp_echo_ignore_all.
Estos ejemplos son tan sólo una muestra de cómo el sistema de ficheros /proc proporciona una potente interfaz para
visualizar y modificar aspectos relacionados con los procesos
en ejecución, el hardware del equipo y el comportamiento general del sistema.
Reconocimiento y gestión
de dispositivos
El kernel Linux puede cargar módulos bajo demanda de forma
automática cuando éstos son requeridos. Básicamente, el kernel recibe peticiones que finalmente son traducidas a nombres
de módulos. Acto seguido, modprobe es quien levanta (carga)
los módulos y sus dependencias asociadas. El objetivo de
este apartado es presentar los principales servicios de descubrimiento y mecanismos de gestión de dispositivos disponibles
en los sistemas GNU/Linux.
Discover: habitualmente, durante la instalación de distribuciones como Debian, la herramienta discover es utilizada
para identificar o descubrir el hardware conectado al equipo.
Discover también incluye soporte para la detección de hardware en tiempo de arranque. Su funcionamiento consiste en
mapear los dispositivos conectados a los buses e identificar
los módulos del kernel que ofrecen soporte para su correcto
funcionamiento.
Hotplug: desde enero de 2001 (kernel 2.4), hotplug fue
adoptado como característica estándar en los sistemas GNU/
Linux. El agente hotplug permite conectar nuevos dispositivos y utilizarlos inmediatamente. En los kernels 2.6, hotplug
reemplaza a discover, sin embargo, ambos pueden coexistir.
En caso de ser así, hotplug se ejecuta una vez que discover
ha finalizado su trabajo. De esta forma, hotplug únicamente
levantaría los módulos adicionales necesarios.
Kmod: cargador de módulos del kernel (reemplazo de kerneld). Cuando se recibe la solicitud de una determinada característica, el kernel ya sabe qué módulo la provee, kmod despierta y ejecuta modprobe mediante una llamada a la función
execve(), pasándole como parámetro de entrada el nombre
del módulo a cargar.
Udev: gestor de dispositivos en el kernel 2.6 (sucesor de
devfs). Provee un servicio, scripts de conexión en caliente y herramientas para manejar eventos. Udev es quien crea y gestiona los ficheros o nodos de dispositivos existentes en /dev.
A diferencia de los sistemas Linux tradicionales (sin udev,
ni devfs), udev mantiene únicamente en /dev las entradas
correspondientes a los dispositivos actualmente conectados,
evitando lo que comúnmente se conoce como superpoblación
de /dev.
Lista estática de módulos: el usuario también puede
definir de forma estática qué módulos adicionales desea cargar en el arranque del sistema. El fichero de configuración se
encuentra en /etc/modules. Su sintaxis es muy simple, cada
línea identifica el nombre de un módulo a cargar.
Versionado del kernel
El versionado del kernel consta de cuatro números que adoptan el siguiente formato: A.B.C[.D], por ejemplo: 2.2.1, 2.4.16
o 2.6.27. El número A identifica identifica la versión del kernel; a lo largo de su historia sólo ha sido modificado dos veces (1.0 en 1994 y 2.0 en 1996). El número B hace referencia a la subversión; antes de la llegada del kernel 2.6.x, los números
pares indicaban versión estable o final, los impares se asignaban a versiones de desarrollo. El número C indica una revisión mayor en el kernel; su valor aumenta a medida que se añade una cantidad significativa de parches de seguridad, bugfixes
y nuevas características o drivers. El número D se añade
cuando se produce un error grave que requiere arreglo inmediato y todavía no existen cambios suficientes para aumentar
C. Los cambios menores, como bugfixes y parches de seguridad, también son manejados actualmente por D.En ocasiones, después del número de versión puede
aparecer un sufijo. Las siglas rc, por ejemplo, hacen alusión
a Release Candidate, es decir, una versión del kernel candidata a convertirse en versión final. Este sufijo también puede utilizarse para identificar bifurcaciones en la rama de desarrollo
oficial del kernel Linux: ck (Con Kolivas), ac (Alan Cox) y mm
(Andrew Morton) son sólo algunos ejemplos.
A día de hoy, Linus Torvalds continua lanzando nuevas versiones del kernel Linux. Las versiones oficiales reciben el nombre de kernels vanilla. Un kernel vanilla es aquél que no ha sufrido ninguna modificación por terceras personas. Actualmente, la mayoría de distribuciones disponibles tienden a tomar el
kernel Linux original, es decir, el kernel vanilla de Kernel.org,
y modificarlo con el propósito de añadir soporte adicional para
dispositivos o herramientas que inicialmente no fueron lanzadas como estables en la versión oficial. Otras distribuciones
como Slackware, en cambio, son más conservadoras y prefieren adoptar el kernel vanilla.
Marcos Blanco Galán |