Unidad II – El Núcleo o Kernel

Anuncio
Unidad II – El Núcleo o Kernel
Obtención del código fuente
Configuración del kernel
Compilación del código fuente
Arranque del nuevo kernel
Análisis del código fuente
Llamadas al sistema
Módulo del kernel
Aplicando parches
Obtención del código fuente
Introducción
Hay muchas formas diferentes de obtener el código fuente, a continuación se
numeran algunas de ellas. Aunque lo más sencillo sea instalar el código fuente
que viene con su distribución de Linux, se requiere mantenerse al día con las
últimas versiones del código si se desea participar en el desarrollo del kernel.
Formas de obtener el código fuente
•
Descargar el código del kernel mediante ftp a ftp.kernel.org. Se trata de una
operación que consume un gran ancho de banda, así que no debe usarse si
se tiene una conexión lenta.
Conectarse mediante ftp al mirror local, que se llamará:
ftp.<CÓDIGO país del>.kernel.org
Por
ejemplo,
en
el
caso
de
España,
sería:
$ ftp ftp.es.kernel.org
Conectarse, si es necesario, con el nombre de usuario "ftp" o "anonymous".
Cambiar de directorio a pub/linux/kernel/v2.4 y descargar el último kernel. Por
ejemplo, si la última versión es la 2.4.26, descargar el archivo llamado linux2.4.26.tar.gz
Formas de obtener el código fuente
Normalmente hay un archivo llamado LATEST-IS-<version> que indica cual es
la última versión. Se recomienda el archivo comprimido con gzip (el archivo que
tiene la extensión ".gz"), en lugar del comprimido con bzip2 (el que tiene la
extensión ".bz"), ya que bzip2 tarda bastante tiempo en descomprimir.
Descomprimir y extraer el archivo en el directorio en el que se tenga pensado
trabajar. AVISO: El código fuente del kernel se desempaquetará en un
directorio denominado "linux". Si ya tiene un directorio creado con ese nombre,
o un enlace simbólico denominado linux, se sobreescribirá lo que haya en ese
directorio, así que siempre se debe hacer lo
siguiente:
$
rm
(Asumiendo
$
$
$
que
linux
tar
mv
ln
era
linux
un
xzf
linux
-s
enlace
simbólico)
linux-<version>.tar.gz
linux-<version>
linux-<version>
linux
Ahora ya se dispone del código fuente del kernel de Linux.
•
Instalar el código fuente del kernel que viene con los CDs de tu distribución.
Si ya se tiene creado un directorio denominado /usr/src/linux y contiene más
de un subdirectorio, entonces ya se tiene el código fuente instalado. En
caso de que no sea así, continúa leyendo.
Montar el CDROM de instalación. En un sistema basado en RedHat, el RPM
fuente está normalmente en /RPMS/ y se llama kernel-source-..rpm
Una forma de encontrar el paquete del código fuente del kernel es ejecutar
este comando, suponiendo que el CD est&eacuta; montado en /mnt/cdrom:
$
$
find
rpm
-iv
/mnt/cdrom
-name
*kernel-*
<pathname>/kernel-source-<version>.<arch>.rpm
La opción "v" indicará si falla la operación o no.
Hay otras formas diferentes de obtener el código fuente del kernel, usando
CVS o rsync.
Una razón para preferir usar el código original en vez del modificado por algún
distribuidor, es en el caso de que se vaya a crear y enviar parches con
modificaciones a otros desarrolladores del kernel. Si se crea un parche y se envía
a la lista de distribución del kernel para su inclusión en el código del mismo, debe
haber sido creado respecto a la última versión limpia del kernel. Si se crea un
parche para el código fuente de una distribución, es probable que no se pueda
aplicar a el kernel original. El punto clave para usar el código fuente de un
distribuidor es cuando estamos trabajando con arquitecturas distintas a x86. El
código limpio del kernel que se puede descargar casi nunca compila y arranca en
otras arquitecturas que no sean la mencionada x86. A menudo, la mejor forma de
conseguir un kernel que pueda funcionar para una arquitectura de este tipo es el
código fuente de la distribución en particular. Cada plataforma suele tener alguna
forma de añadir el último código fuente al código que ha recibido con tu
distribución.
Arrancando el nuevo kernel con LILO en un X86
El archivo de configuración de LILO está en /etc/lilo.conf. La idea básica es que
copie allí el trozo del archivo de configuración referido al arranque del nuevo
kernel, y que a continuación cambie allí el nombre del archivo en que está el
kernel que desea arrancar. Recuerde que está copiando la información sobre el
kernel actual y modificando la copia, NO el original. Por ejemplo, suponga que se
tiene en /etc/lilo.conf algo parecido a esto:
image=/boot/vmlinuz-2.2.14-5.0
label=linux
initrd=/boot/initrd-2.2.14-5.0.img
read-only
root=/dev/sda1
El campo "image" le dice a LILO dónde encontrar el archivo que contiene el nuevo
kernel. El campo "label" es lo que escribe en el prompt de LILO para arrancar con
ese kernel. Es una buena idea usar como label un nombre corto y sencillo de
teclear. El campo "initrd" es opcional y especifica el ramdisc a cargar en memoria
antes de montar el sistema de archivos raíz. El campo "read-only" dice que
inicialmente hay que montar el sistema de archivos raíz como solo lectura, en
contraposición a permitir lectura y escritura. El campo "root" le indica a LILO qué
dispositivo
contiene
el
sistema
de
ficheros
raíz.
Deberá copiar esta información y cambiar los campos siguientes:
image=<fichero
con
label=<elegir aquí el nombre que quiera>
el
Eliminar el campo "initrd" en el caso de que exista:
initrd=/boot/initrd-2.2.14-5.0.img
kernel
nuevo>
Escriba la etiqueta (label) que haya elegido lo que hayas escrito en el
campo "label") para el nuevo kernel. LILO intentará entonces arrancar
con ese kernel. Para evitar tener que escribir una línea de comandos en
LILO, ejecute esto justo antes de reiniciar:
# lilo -v -R "<LILO command line>"
Esto le indica a LILO que use esa línea de comandos la siguiente vez que reinicie.
A continuación, eliminará esa línea de comandos y volverá a su comportamiento
habitual las siguientes veces que se reinicie. Esto es algo realmente útil con los
nuevos kernel, ya que puede rearrancar, ver que no funciona, reiniciar la máquina
y cargar automáticamente tu kernel original sin necesidad de escribir nada. Si
tienes problemas con LILO, Intenta consultar el LILO mini-HOWTO.
La razón para eliminar el campo "initrd" es que el archivo initrd no
contiene nada que sea útil para el nuevo kernel. En una distribución
normal de Linux, el fichero initrd contiene un montón de módulos del
kernel que solamente pueden ser cargados por el kernel que viene con la
distribución. Las nuevas líneas que se han añadido al archivo de
configuración deberían ser algo parecido al finalizar:
Image=/boot/mynewkernel
label
read-only
root=/dev/sda1
=
new
LILO no se dará cuenta de los cambios que se hayan efectuado en el archivo
/etc/lilo.conf hasta que ejecutes el comando "lilo". Probablemente harás esto más
de una vez: compilar un nuevo kernel, copiarlo a un nuevo destino, olvidarte de
ejecutar LILO y rearrancar el sistema, solo para darte cuenta de que LILO no sabe
absolutamente nada del nuevo kernel. Por eso, cuando modifiques el archivo
/etc/lilo.conf, acuerdate siempre de ejecutar LILO:
#lilo -v
El modificador "-v" significa verbose, y quiere decir que lilo vaya indicando todo lo
que va haciendo. A continuación, reinicie el PC, y verá el prompt de LILO.
Arrancando el nuevo kernel con GRUB en un X86
Necesitará ser root para poder editar /etc/grub.conf. La configuración de GRUB
está almacenada en /etc/grub.conf. La idea básica aquí es copiar el trozo de
configuración de arranque de tu kernel actual y cambiar el nombre del kernel a
arrancar. Recuerda, estás copiando la información sobre tu kernel actual y
modificando la copia, NO el original. Por ejemplo, si tienes en tu /etc/grub.conf
algo
parecido
a
esto:
title
Linux
root
kernel
/vmlinuz-2.4.9-31
initrd /initrd-2.4.9-31.img
ro
(2.4.9-31)
(hd0,0)
root=/dev/hda3
El campo "title" es el título en el menú de GRUB que corresponde al nuevo kernel.
El campo "root" le indica a GRUB dónde está el directorio raiz (el directorio raiz es
lo que está montado en "/"). El campo "kernel" le dice a GRUB dónde encontrar el
archivo que contiene el kernel y, a continuación del nombre del archivo, indica
varias opciones para pasarle al kernel al arrancarlo. NOTA: El nombre del archivo
del kernel está dado de forma relativa a la partición de arranque, lo que
normalmente quiere decir que has de eliminar la parte de "/boot"del nombre del
archivo. El campo "initrd" es opcional y especifica el ramdisk inicial que se ha de
cargar antes de montar el sistema de ficheros raíz. Deberá de copiar esta
información y modificar los siguientes campos:
title <poner aquí el nombre que quiera>
kernel <fichero con tu nuevo kernel> ro root=/dev/hda3
Recuerde eliminar casi con toda probabilidad la parte "/boot" del nombre del
archivo, a menos que sepa bien lo que estás haciendo. Elimine el campo
"initrd" si es que existe:
initrd /initrd-2.4.9-31.img
La razón para eliminar el campo "initrd" es que el archivo initrd no contiene nada
que sea útil para nuestro nuevo kernel. En una distribución normal de Linux, el
archivo initrd contiene un montón de módulos del kernel que solamente pueden
ser cargados por el kernel que viene con la distribución. Las nuevas líneas que se
han añadido al archivo de configuración deberían ser algo parecido a esto al
finalizar:
title
root
kernel /mynewkernel ro root=/dev/hda3
new
(hd0,0)
De nuevo, tener en cuenta que en este caso el nuevo kernel está en
"/boot/mynewkernel", pero, puesto que se tiene una partición diferente montada en
/boot, y puesto que GRUB solamente mirará dentro de esa partición al arrancar,
se requiere eliminar la parte del nombre que corresponde al punto de montaje del
directorio, es decir "/boot". Ahora reinicie el ordenador, y verá aparecer el menú de
GRUB. Seleccione con las teclas de cursor el título que eligió antes y pulse enter.
GRUB intentará a continuación arrancar el nuevo kernel.
Trucos para recompilar el Kernel
A continuación se mencionan un par de trucos para recompilar el código cuando
se está trabajando solamente con uno o dos archivos.
Supóngase que está cambiando el archivo driver /char/foo.c y que está teniendo
problemas para que te compile. Puede escribir simplemente (desde el directorio
principal de arbol del código del kernel) "make drivers/char/foo.o", y "make"
intentará de inmediato compilar ese archivo, en lugar de ir descendiendo por todos
los subdirectorios en el orden correspondiente y buscando qué archivos necesitan
recompilación. Si drivers/char/foo.c es un módulo, puedes entonces usar insmod
con el archivo resultante drivers/char/foo.o cuando haya compilado, en lugar de
ejecutar el comando completo "make modules". Si drivers/char/foo.c no es un
módulo, tendrás que ejecutar de nuevo "make -j2 bzImage" para enlazarlo con el
resto de las partes del kernel.
El operador "&&" en bash es muy útil para la compilación del kernel.
La línea de comandos de bash:
#
cosa1
&&
cosa2
Dice: "Haz cosa1, y si no devuelve un error, haz cosa2". Esto es útil para ejecutar
algo con la condición previa de que la compilación haya funcionado.
Cuando se esta trabajando en un módulo, se usa a menudo el siguiente comando:
# rmmod foo.o && make drivers/char/foo.o && insmod drivers/char/foo.o
Dice: "Elimina el módulo foo.o, y si eso lo ha hecho bien, compila el archivo
drivers/char/foo.c, y si eso también se ha realizado correctamente, entonces carga
en memoria el módulo resultante". De esa forma, si existe un error de compilación,
no se carga el módulo antiguo y es obvio que se requiere arreglar el código. Si no
se determina el error , intentar el comando "make mrproper".
Recompilar el Kernel
Para recompilar el kernel en la mayor parte de los casos, basta con ejecutar
simplemente "make -j2 bzImage" (o el comando apropiado para que compile tu
kernel) para conseguirlo. Si cambia los archivos de configuración, deberá ejecutar
de nuevo "make dep", y posteriormente "make -j2 bzImage". Si ha modificado el
código de un módulo, simplemente ejecuta "make modules" y "make
modules_install" (si es necesario).
A veces, se cambian tantas cosas que "make" no es capaz de averiguar cómo
recompilar los archivos correctamente. "make clean" eliminará todos los archivos
objeto (los que terminan en .o) y algunas otras cosas más. "make mrproper" hace
todo lo que hace "make clean", y además elimina los archivos de configuración,
los de dependencias, y todo el resto de cosas que crea "make config". Asegúrese
de grabar su archivo de configuración en otro archivo antes de ejecutar "make
mrproper". Más tarde, copiar de nuevo el archivo de configuración a ".config" y
comienzar desde el principio, empezando por "make menuconfig". El comando
"make mrproper" a menudo soluciona problemas extraños del kernel que no
tienen sentido, así como errores raros de compilación que tampoco tengan
sentido.
Configuración del Kernel
Introducción
El kernel de Linux viene con diversas herramientas de configuración. Cada una se
ejecuta escribiendo "make <algo>config" en el directorio principal del código
fuente del kernel, normalmente /usr/src/linux. (Todos los comandos "make" se han
de ejecutar desde el directorio principal del código).
Comandos make
•
make config:
Esta es la herramienta de configuración más básica. Preguntará todas y
cada una de las preguntas necesarias para configurar el kernel en su orden
correspondiente. El kernel de Linux tiene MUCHAS opciones de
configuración.
•
make oldconfig:
Esto hará que el sistema tome los datos del archivo ".config" y solamente
pregunte por las opciones cuya selección no esté indicada en ese archivo.
Se usa normalmente cuando se actualiza a una versión más moderna del
kernel.
make menuconfig:
Este es el mecanismo más usado para configurar el kernel. Este comando
hace que salga un sistema de configuración en modo texto basado en
menúes, utilizando la librería ncurses. Se Puede ir entrando en los
submenús que te interesen y cambiar únicamente las opciones de
configuración que se desee.
•
make xconfig:
Si se está usando XWindows, esta es una versión más bonita de
menuconfig, que se puede usar a toque de ratón. Tiende a funcionar en
menos ocasiones que menuconfig, ya que xconfig es más sensible a los
defectos en los archivos de configuración que menuconfig (y tambien debido
a que hay más gente que usa menuconfig).
Consideraciones para configurar el kernel
Puntos a tener en cuenta al configurar un kernel:
•
Activar siempre la opción "Prompt for development... drivers" (pregunte por los
drivers en versiones de desarrollo).
•
Desactivar siempre "module versioning" (versionado de los módulos), pero activar
siempre el "kernel module loader" (cargador de módulos del kernel) y los "kernel
modules" (módulos del kernel).
•
Desactivar todas las características que no sean necesarias
•
Utilizar módulos solamente si se tiene una buena razón para hacerlo. Por
ejemplo, si está trabajando en un driver, querrás poder cargar una nueva versión
del mismo sin necesidad de reiniciar la máquina.
•
Asegurarse muy bien de que ha elegido el tipo correcto de procesador. Puede
averiguar qué procesador tiene con "cat /proc/cpuinfo".
•
Determinar qué dispositivos PCI tiene instalado con "lspci -v".
•
Determinar qué tiene compilado el kernel que está usando ahora mismo con el
comando "dmesg | less". La mayor parte de los drivers de los dispositivos
muestran un mensaje cuando detectan un dispositivo.
Si todo lo demás falla, copie el archivo .config de alguna otra maquina.
Productos finales
Cada uno de los programas de configuración crea estos productos finales:
•
Un enlace simbólico desde include/asm<arch> a /include/asm
•
Un archivo llamado ".config" en el directorio principal del código fuente, que
contiene todas tus opciones de configuración.
•
Un conjuto de archivos de cabecera de C definiendo (o no) los símbolos
CONFIG_* de forma que el preprocesador de C sepa si están activos o no.
Si se cuenta con un archivo ".config" configurado para su máquina, simplemente
se copia al directorio en el que se tenga el código fuente del kernel y ejecuta
"make oldconfig". Debería comprobar a continuación la configuración resultante
con "make menuconfig". Si no dispone de un archivo ".config" con las opciones de
configuración, puede crear uno visitando cada submenú y activando o
desactivando las opciones que necesite. menuconfig y xconfig tienen una opción
de ayuda ("Help") que muestra la entrada de Configure.help correspondiente a
esa opción, lo cual puede ayudar a decidir si debería estar o no activada.
Compilación del kernel
Consideraciones previas
Antes de comenzar a compilar, asegúrate de que /usr/src/linux es un enlace
simbólico al árbol de directorios del código fuente. Si es un enlace simbólico a un
árbol _diferente_, algunas partes de la compilación podrían usar las cabeceras
equivocadas. Si el enlace no existe, algunos pasos de la compilación no
funcionarán.
Pasos para compilar el kernel
Paso1:Primero,
crear
la
información
sobre
las
dependencias
# make dep
Esto ejecuta un script que calcula (la mayor parte de las veces) las dependencias
entre los diferentes archivos. El archivo foo.c depende de baz.h si un cambio en
baz.h afecta al resultado de la compilación de boo.c. Por ejemplo, baz.h define la
estructura "driver_stuff", y foo.c usa una estructura driver_stuff. Si añade un nuevo
elemento a la estructura driver_stuff definida en baz.h, eso hará que cambie el
tamaño de la estructura driver_stuff, y entonces será necesario recompilar foo.c
con el tamaño adecuado de la estructura driver_stuff.
Paso2: Después de calcular las dependencias, se construye el propio kernel:
En un x86:
# make -j<número de procesos> bzImage
En un ppc:
# make -j<número de procesos> zImage
Donde <número de procesos> es dos veces el número de CPUs en tu sistema.
Así, si tienes un ordenador con una única CPU Pentium II, por ejemplo, pondría:
# make -j2 bzImage
Lo que hace el modificador "-j" es decirle al programa make cuántos procesos
(comandos en el Makefile) ha de ejecutar simultaneamente. "make" sabe qué
procesos se pueden realizar de manera simultanea, y cuáles necesitan esperar a
que terminen otros procesos previos antes de poder ejecutarse.
Los procesos de compilación del kernel se pasan tanto tiempo esperando por
operaciones de entrada y salida (I/O), como leer el archivo con el código fuente
desde el disco, que ejecutar dos de los procesos por cada procesador hace que
se obtenga el menor tiempo de compilación.
NOTA: Si tiene un error de compilación, ejecute "make" con un único proceso para
ver con más facilidad dónde ha ocurrido el error.
Paso3: Si ha habilitado los módulos, necesitará compilarlos con el comando:
#
make
modules
Si tiene pensado cargar esos módulos en la misma máquina que los ha
compilado, ejecute el comando que realiza la instalación automática de los
módulos, pero ANTES - grabe una copia de los viejos módulos.
#
#
mv
/lib/modules/`uname
make
-r`
/lib/modules/`uname
r`.bak
modules_install
Esto pondrá todos los módulos nuevos en el directorio /lib/modules/<version>.
Puede averiguar qué <version> es mirando en include/linux/version.h.
Arranque del nuevo kernel
Introducción
La regla número uno a seguir es no borrar nunca el kernel con el que se está
trabajando ahora mismo ni los archivos de configuración del bootloader o
programa iniciador. Nunca sobrescriba su nuevo kernel sobre el original. No es
necesario mantener una copia de todos y cada uno de los kernel que se compile,
pero se deben seleccionar uno o varios kernel "seguros" y mantenerlos a ellos y a
sus archivos de configuración intactos.
Copiar el nuevo kernel a un lugar seguro
Lo primero que se debe hacer es copiar el kernel que acaba de compilar a un
lugar seguro. Se sugiere que use /boot/mynewkernel como destino. Si está
usando un x86, ha de copiar bzImage:
# cp arch/i386/boot/bzImage /boot/mynewkernel
Si está en un PowerPC o Alpha, copia el archivo vmlinux:
# cp vmlinux /boot/mynewkernel
Análisis del código fuente
Introducción
En esta sección se estudia una idea general de dónde se encuentran localizadas
las diferentes partes del kernel en el árbol de directorios, en qué órden se ejecutan
y cómo hay que hacer para buscar un trozo concreto de código.
¿Dónde está todo el código?
Vamos a comenzar por el directorio principal del árbol de directorios del código
fuente de Linux, que normalmente, aunque no siempre, se encuentra en
/usr/src/linux-<version>. No vamos a entrar en demasiados detalles, ya que el
código fuente de Linux cambia constantemente, pero intentaremos dar la
suficiente información para ser capaces de encontrar dónde se encuentra un
driver o una función concreta.
¿Dónde se produce la unión de todo?
El punto central que conecta todo el kernel de Linux es el fichero "init/main.c".
Cada arquitectura ejecuta algunas funciones de inicialización de bajo nivel y
posteriormente ejecuta la función "start_kernel", que se encuentra en "init/main.c".
El orden de ejecución del código se ve como algo así:
Código de inicialización específico de la arquitectura (en arch/<arch>/*)
l
v
La función start_kernel() (en init/main.c)
l
v
La función init() (en init/main.c)
l
v
El programa a nivel de usuario “init”
¿Dónde se produce la unión de todo? - En detalle
En mayor detalle, esto es lo que ocurre:
•
Codigo de inicialización específico de la arquitectura que hace:
1. Descomprime y mueve el propio código del kernel, si es necesario.
2. Inicializa el hardware.
1. Esto puede incluir inicializar la gestión de memoria de bajo nivel.
3. Transferir el control a la función start_kernel()
•
start_kernel() hace, entre otras cosas:
1. Imprime la versión del kernel y la línea de comandos.
2. Da comienzo a la salida de datos por la consola.
3. Habilita las interrupciones.
4. Calibra el delay loop, o bucle de retraso.
5. Arranca los demás
multiprocesador).
procesadores
(en
máquinas
SMP
6. Inicia un hilo de ejecución del kernel para ejecutar la función init()
o
7. Entra en el idle loop, o bucle de espera.
•
init() hace:
1. Inicia los subsistemas de los dispositivos.
2. Monta el directorio raíz.
3. Libera la memoria no utilizada del kernel
4. Ejecuta /sbin/init (o /etc/init, o...)
Llegando a este punto, el programa init está ejecutándose a nivel usuario, lo que
hace cosas como inicializar los servicios de red y ejecutar getty (el programa de
login) en tu(s) consola(s).
Archivos y directorios
Makefile: Este fichero es el Makefile de orden superior de todo el arbol del código.
Define numerosas variables y reglas importantes, como por ejemplo los flags de
compilación por defecto del gcc. Lo que probablemente importe más es que las
primeras líneas definen el número de versión del kernel (por ejemplo 2.4.18-pre7).
Puedes añadir tu propia cadena de caracteres (como tus iniciales) a la línea
EXTRAVERSION para personalizar la cadena que define la versión del kernel.
Documentation/ : Este directorio contiene información util (aunque a menudo
obsoleta) sobre cómo configurar el kernel, utilizar un ramdisk, y cosas similares. El
fichero más importante en este directorio es "Configure.help" - Es el fichero en el
que se encuentran todas las entradas individuales de la ayuda sobre las diferentes
opciones de configuración. (Nota: Esto ha cambiado en la serie 2.5.x del kernel a
ficheros Config.help individuales en cada directorio).
Arch/ : Todo el código específico de cada arquitectura se encuentra en los
directorios include/asm-<arch>. Cada arquitectura tiene su propio directorio. Por
ejemplo, el código para un ordenador basado en PowerPC se encontrarí en
arch/ppc. Encontrarás aquí la gestión de la memoria a bajo nivel, manejo de las
interrupciones, primera inicialización, rutinas en ensamblador y bastantes más
cosas.
kernel/ :El código genérico a nivel del kernel que no encaja en ninguna otra
parte viene a este directorio. El código relativo a las llamadas al sistema de alto
nivel está aquí, así como el código de printk(), el scheduler, el código para
manejar las señales, y muchas más cosas. Los archivos tienen nombres
informativos, así que puedes escribir "ls kernel/" y averiguar con bastante
exactitud lo que hace cada fichero.
Lib/: Aquí vienen las rutinas útiles en general para todo el código del kernel. En
ella se encuentran las operaciones comunes con cadenas de caracteres, rutinas
de depuración de errores y el código de análisis de las líneas de comando.
Mm/ : Aquí viene el código de gestión de la memoria de alto nivel. La memoria virtual (VM,
Virtual Memory) es implementada mediante estas rutinas, conjuntamente con las rutinas de
bajo nivel específicas de cada arquitectura que se encuentran normalmente en
"arch/<arch>/mm/". La gestión temprana de la memoria en el arranque (que es necesaria
antes de que el subsistema de memoria esté totalmente operativo) se realiza aquí, así como
el mapeado en memoria de ficheros, gestión de la caché de las páginas, organización de la
memoria y descarga de las páginas en RAM (entre otras muchas cosas).
net/ : El código de alto nivel de manejo de redes se encuentra en este
directorio. Los drivers de red de bajo nivel pasan los paquetes de información
hacia estos niveles superiores y obtienen de aquí los paquetes a enviar. Desde
aquí se puede pasar los datos a una aplicación de usuario, descartar los datos
o usarlos dentro del propio kernel, según el paquete. El directorio "net/core"
contiene código útil para la mayor parte de los distintos protocolos de red, lo
que también ocurre con varios de los ficheros en el propio directorio "net/". Los
protocolos de red específicos están implementados en subdirectorios de "net/".
Por ejemplo, el código del protocolo TCP/IP (versión 4) se encuentra en el
directorio "net/ipv4".
Scripts/ : Este directorio contiene scripts que son útiles para la construcción del
kernel, pero que no incluyen nada de código que se use en el propio kernel. Las
diferentes herramientas de configuración mantienen aquí sus ficheros, por
ejemplo.
include/: La mayor parte de los archivos de cabecera que se incluyen al inicio
de los archivos .c se encuentran en este directorio. Los ficheros específicos de
una arquitectura se encuentran en "asm-<arch>". Una parte del proceso de
construcción del kernel crea un enlace simbólico desde "asm" a "asm-<arch>",
con lo que "#include <asm/file.h>" incluirá el fichero adecuado para esa
arquitectura sin que haya necesidad de especificar la arquitectura directamente
en el código del archivo .c. Los demás directorios incluyen ficheros de
cabecera que no son específicos de cada arquitectura. Si se usa una misma
estructura, constante o variable en más de un fichero .c, probablemente esté
definida en uno de estos ficheros de cabecera.
Init/ : Este directorio contiene dos ficheros: "main.c" y "version.c". "version.c"
define la cadena de texto con la versión de Linux. "main.c" se puede entender
como la especie de "pegamento" que une el kernel. Hablaremos más sobre
"main.c" en la siguiente sección.
Ipc/ : "IPC" quiere decir "Inter-Process Communication", o comunicación entre
procesos. Contiene el código para la memoria compartida, semáforos y otras
formas de IPC.
drivers/ : Como norma general, el código para usar dispositivos periféricos se
encuentra en subdirectorios dentro de este directorio. Esto incluye drivers de
video, de tarjetas de red, drivers SCSI de bajo nivel y cosas similares. Por
ejemplo, la mayor parte de los drivers de tarjetas de red se encuentran en
drivers/net. El código que actúa de pegamento para unir todos los drivers de un
mismo tipo puede o no estar incluido en el mismo directorio que los mismos
drivers de bajo nivel.
Fs/ : Se encuentra en este directorio tanto el código genérico del sistema de
archivos (conocido como VFS, o Virtual File System - Sistema de archivos virtual)
como el código para cada uno de los diferentes sistemas de archivos. El sistema
de archivos raiz tal vez sea ext2; el código para leer el formato ext2 se encuentra
en fs/ext2. No todos los sistemas de archivos que hay son capaces de compilar y
funcionar, y los sistemas de archivos más oscuros son a menudo un buen
candidato para alguien que busque un proyecto en el kernel.
Encontrando las cosas dentro del arbol de directorios del código
fuente
El problema es, si quiere trabajar, por ejemplo, en el driver USB, ¿Dónde
empiezas
a
mirar
para
buscar
el
código
del
USB?
En primer lugar, puedes intentar usar el comando "find" desde el directorio
principal
del
arbol
del
código
fuente:
$ find . -name *usb*
Este comando imprimirá el nombre de todos los ficheros que contengan la cadena
"usb" en el medio del nombre. Otra cosa que puedes intentar probar es buscar
una cadena de caracteres que sea única. Esta puede ser la salida de una función
printk(), el nombre de un fichero en /proc, o cualquier otra cadena de caracteres
que se encuentre únicamente el en código fuente de ese driver. Por ejemplo, USB
imprime el mensaje:
usb-ohci.c:
USB
OHCI
at
membase
0xcd030000,
IRQ
27
Así que puede intentar un grep recursivo para encontrar el trozo de ese printk que
no es un caracter de conversión de formatos como %d.
$grep -r “USB OHCI at”
Otra forma que tiene para intentar localizar dónde está el código fuente de USB es
mirando en /proc. Si escribe "find /proc -name usb", probablemente encuentre que
existe un directorio llamado "/proc/bus/usb". Es probable que encuentre entre las
entradas de ese directorio una cadena única, que no se repita en ningún otro lugar
del código, con la que pueda hacer un grep. Si todo lo demás falla, intenta
descender a los directorios individuales y hacer un listado de
Otra forma que tiene para intentar localizar dónde está el código fuente de USB es
mirando en /proc. Si escribe "find /proc -name usb", probablemente encuentre que
existe un directorio llamado "/proc/bus/usb". Es probable que encuentre entre las
entradas de ese directorio una cadena única, que no se repita en ningún otro lugar
del código, con la que pueda hacer un grep. Si todo lo demás falla, intenta
descender a los directorios individuales y hacer un listado de los archivos, o
mirando en la salida de "ls -lR".
Llamadas al sistemas
Introducción
En el sentido más literal, una llamada al sistema (también llamada "syscall") es
una instrucción, similar a las instrucciones "add" o "jump". En un nivel más alto, las
llamadas al sistema son la forma en que un programa de usuario le pide al
sistema que haga algo.
Las llamadas al sistema en detalle
Así es cómo funciona una llamada al sistema: En primer lugar, el programa de
usuario prepara los parámetros para la llamada al sistema. Uno de los parámetros
es el número de llamada al sistema (system call number), del que hablaremos
más adelante. Se debe tener en cuenta que ésto es algo que se realiza de forma
automática por las librerías de funciones, a menos que esté programando en
ensamblador. Una vez que los parámetros están preparados, el programa ejecuta
la instrucción de llamada al sistema (system call). Esta instrucción causa una
excepción: un suceso que hace que el procesador salte a una nueva dirección y
comience a ejecutar el código que haya allí.
Las instrucciones en la nueva dirección graban el estado del programa de usuario
en el momento en que se ha invocado la llamada al sistema, averigua qué llamada
al sistema es la que quieres realizar, llama a la función del kernel que implementa
esa llamada al sistema, restaura el estado del programa de usuario y devuelve el
control de nuevo a éste. Una llamada al sistema es uno de los mecanismos por el
que se llama a las funciones definidas en un driver de dispositivo.
Un ejemplo de llamada al sistema
Este es un buen lugar para empezar a mostrar código que acompañe a la teoría.
Seguiremos desarrollo de la invocación de la llamada a read(), comenzando
desde el momento en que se ejecuta la instrucción de llamada al sistema. Se
usará la arquitectura PowerPC como ejemplo para la parte del código específica
de la arquitectura. En el PowerPC, cuando se ejecuta una llamada al sistema, el
procesador salta a la dirección 0xc00. El código en esa posición está definido en
el fichero arch/ppc/kernel/head.S
El código es algo parecido a lo siguiente:
/*
.
SystemCall:
EXCEPTION_PROLOG
stw
li
rlwimi
r20,r23,0,16,16
bl
System
call
=
/*
copiar
bit
EE
del
*/
0xc00
r3,ORIG_GPR3(r21)
r20,MSR_KERNEL
MSR
almacenado*/
transfer_to_handler
.long
.long ret_from_except
DoSyscall
Lo que hace este código es grabar el estado y pasar el control a otra función llamada "DoSyscall".
Módulos del kernel
Introducción
Escribir tu propio módulo te permite escribir código independiente del kernel,
aprender a usar los módulos y descubrir algunas reglas sobre cómo los enlaza el
kernel. Nota: Estas instrucciones han sido escritas para los kernel 2.4.x y tal vez
no funcionen con diferentes versiones del kernel.
¿Soporta módulos tu kernel?
Para esta lección, tu kernel ha de estar compilado con las siguientes opciones:
Loadable
module
[*]
Enable
loadable
[
]
Set
version
information
[*] Kernel module loader
on
support
module
all
module
--->
support
symbols
Si has compilado tu kernel de acuerdo con estas instrucciones en las primeras
lecciones sobre el kernel, deberís tener ya estas opciones activadas. En caso
contrario, cambia estas opciones, recompila el kernel y arranca con el nuevo
kernel.
El esqueleto de un módulo sencillo
En primer lugar, busca el código fuente sobre el que fue compilado el kernel de
Linux que usas actualmente. Cambia de directorio al directorio principal del código
fuente de Linux. Copia y pega entonces el siguiente código en un fichero llamado
"drivers/misc/mymodule.c":
#define MODULE
#include
<linux/config.h>
#include
#include
#include
static
int
printk
("Mi
return
}
static
void
printk
("Descargando
return;
}
module_init(mymodule_init);
module_exit(mymodule_exit);
Grabe
el
fichero
<linux/module.h>
<linux/init.h>
<linux/kernel.h>
mymodule_init(void){
funciona!n");
0;
__init
modulo
__exit
mymodule_exit(void){
modulo.n");
mi
y
compila
tu
módulo:
# make drivers/misc/mymodule.o
Cargue
el
módulo:
# insmod ./drivers/misc/mymodule.o
Y
compruebe
que
tu
mensaje
ha
sido
impreso:
del
texto:
# dmesg | tail
Debería
poder
ver,
al
final
Mi modulo funciona!
Ahora
descarga
el
módulo
del
kernel:
# rmmod mymodule
Comprueba de nuevo la salida de dmesg. Deberías poder leer: Descargando mi
modulo.
Acabas de escribir y ejecutar un nuevo módulo del kernel.
El interfaz módulo/kernel
Vamos a hacer ahora cosas más interesantes con tu módulo. Una de las cosas
claves de las que te tienes que dar cuenta es que los módulos solamente pueden
"ver" las funciones y las variables que el kernel haga deliberadamente visibles
para los módulos. Para comenzar, hagamos las cosas de la forma equivocada.
Edita el fichero "init/main.c" y añade esta línea después de todos los ficheros
incluidos, y cerca de las demás declaraciones de variables globales (pero fuera de
las funciones):
int my_variable = 0;
Ahora recompila tu kernel y arrácalo. A continuación, añade ésto al inicio de la
función mymodule_init de tu módulo, antes del resto del código:
extern int my_variable;
printk ("my_variable es %dn", my_variable);
my_variable++;
Graba tus cambios y recompila tu módulo:
# make drivers/misc/mymodule.o
Y carga el módulo (esta operación fallará):
# insmod ./drivers/misc/mymodule.o
La carga del módulo debería haber fallado con el mensaje:
./drivers/misc/mymodule.o: unresolved symbol my_variable
Lo que ésto está indicando es que el kernel no permite a los módulos ver esa
variable. Cuando el módulo es cargado, tiene que resolver todas sus referencias
externas, como los nombres de las funciones o de las variables. Si no es capaz de
encontrar todos los nombres no resueltos en la lísta de símbolos exportados por el
kernel, el módulo no podrá escribir en esa variable, ni llamar a esa función. La
variable "my_variable" tiene reservado espacio en algún lugar en el kernel, pero el
módulo es incapaz de averiguar dónde.
Para solucionar ésto, añadiremos "my_variable" a la lista de símbolos exportados
por el kernel. Muchos directorios del kernel tienen un fichero específico para
exportar los símbolos definidos en ese directorio. Abre el fichero "kernel/ksyms.c"
y añade estas líneas justo antes de la primera línea "EXPORT_SYMBOL()":
extern
EXPORT_SYMBOL(my_variable);
int
my_variable;
Recompila y arranca tu nuevo kernel. Ahora intenta cargar de nuevo tu módulo.:
# insmod ./drivers/misc/mymodule.o
En esta ocasión, cuando compruebes con dmesg, deberías ver:
my_variable
Mi modulo funciona!
es
0
Cada vez que recargues el módulo, my_variable debería incrementarse en uno.
Estás leyendo y escribiendo en una variable definida en el kernel. Tu módulo
puede acceder a cualquier variable o función definida en el kernel principal,
siempre que sea exportada a través de la declaración "EXPORT_SYMBOL()". Por
ejemplo, la función "printk()" está definida en el kernel y es exportada en el fichero
"kernel/printk.c".
Ejemplo
Puedes usar un módulo para activar o desacivar un printk, definiendo una variable
"do_print" en el kernel, cuyo valor inicialmente sea cero. Esto har&aacuta; todos
tus printk dependientes de "do_print":
if
printk ("Big long obnoxious messagen");
(do_print)
Y activar "do_print" solamente cuando se cargue tu módulo. Puedes añadir una
función definida en tu módulo a la lista de funciones a ser llamadas cuando el
kernel recibe una interrupción determinada (usa "cat /proc/interrupts" para ver qué
interrupciones están en uso). La función request_irq() añade tu función a la lista
de handlers para una línea de irq seleccionada, lo que puedes usar para imprimir
un mensaje cada vez que se recibe una interrupción en esa línea. Puedes
investigar el valor actual de cualquier variable exportada cargando un módulo que
lea esa variable y termine inmediatamente (devuelva un valor distinto de cero en
la función module_init()). La variable "jiffies", que se incrementa cada centésima
de segundo (en casi todas las plataformas), es una buena candidata para un
módulo de este tipo.
Aplicando Parches
Introducción
Crear y aplicar parches puede ser delicado - se deben aprender un cúmulo de
reglas y evitar numerosos errores habituales. Enviar un parche también requiere
cierto trabajo. A primera vista, enviar parches puede parecer la parte más sencilla
del desarrollo del kernel. Después de todo, no puede ser tan complicado como
solucionar un error en el driver de ethernet. A menudo es más sencillo arreglar un
problema en el kernel que conseguir que te acepten un parche en la línea principal
del desarrollo del kernel.
Sencillez de aplicación
Si tu parche es difícil de aplicar, es casi seguro que no será aceptado. Además de
crear el parche con el nivel adecuado de directorios, deberás crearlo con un kernel
que sea idéntico (o casi) al que el resto de la gente va a aplicarle el parche. Así, si
quieres que la persona XYZ aplique tu parche, averigua qué versión del kernel
utiliza e intenta aproximarte a ella lo más posible. Habitualmente es la versión
limpia del último release publicado por el mantenedor del kernel. Por ejemplo, si
tienes un parche contra 2.4.18-pre3, y la última versión liberada es 2.4.18-pre7,
deberías crearlo de nuevo contra la versión 2.4.18-pre7, La forma más sencilla de
hacer ésto es aplicar el parche de la versión 2.4.18-pre3 a la versión 2.4.18-pre7 y
arreglar los cambios que haya entre las dos versiones, para usar de nuevo diff
contra 2.4.18-pre7.
¿Cómo funcionan los parches?
Un "parche" es un archivos que describe las diferencias existentes entre dos
versiones de un archivo. El programa "diff" compara el archivos original y el nuevo
línea a línea e imprime el resultado en la salida estándar en un formato específico.
El programa "patch" es capaz de leer la salida de "diff" y aplicar esos cambios a
otra copia del archivos original. (Fíjate que la palabra "parche" se refiere tanto a la
salida del comando "diff" como a el comando que aplica el parche).
Ejemplo
val@evilcat
<~>$
cat
old/file.txt
This
is
a
simple
file.
val@evilcat
<~>$
cat
new/file.txt
This
is
a
slightly
more
complex
file.
val@evilcat
<~>$
diff
-uNr
old
new
diff
-uNr
old/file.txt
new/file.txt
--old/file.txt
Tue
May
28
23:00:21
2002
+++
new/file.txt
Tue
May
@@
-1,5
This is a -simple +slightly more complex file.
28
+1,5
23:01:01
2002
@@
Como puedes observar, los dos archivos se diferencian únicamente en una línea.
La línea del primer archivos listado en la línea de comandos se muestra con un "-"
delante, seguida de la línea del segundo archivo, en este caso con un "+" delante.
De forma intuitiva, estás "restando" o eliminando la línea del archivo antiguo y
"sumando" o añadiendo la línea del archivo nuevo. Recuerda, la línea del archivo
antiguo siempre aparece en primero, y la del archivo nuevo en segundo lugar.
Aplicación de parches
Una de las razones habituales para tener que usar parches es para poder obtener
una versión del kernel que no esté disponible como un fichero comprimido a
descargar desde ftp.kernel.org, o para actualizar un kernel mediante parches
incrementales y evitar tener que descargar un kernel nuevo completo, cuando la
mayor parte de los ficheros del kernel siguen siendo los mismos.
Los estándares de creación y nombres de los parches del kernel no son
particularmente sencillos. Imagínate que quieres obtener el kernel 2.4.18-pre3 por
alguna razón, y actualmente solo dispones de la versión completa descargable del
kernel 2.4.15. Necesitarás los siguientes parches para pasar desde la versión
2.4.15 hasta la 2.4.18-pre3:
•
2.4.15 a 2.4.16
•
2.4.16 a 2.4.17
•
2.4.17 a 2.4.18-pre3
Cada preparche (los parches intermedios entre releases principales, normalmente
llamados patch-2.4.x-preN, y que se encuentran habitualmente en un diectorio del
ftp denominado "testing") es creado mediante el uso de diff con el release principal
anterior. Un error habitual es descargar la versión del kernel 2.4.18 e intentar
aplicar el preparche 2.4.18-pre3 sobre ella. Si quieres la versión del kernel 2.4.18pre3, debes descargarte el kernel 2.4.17 y aplicar el preparche 2.4.18-pre3 sobre
él. NOTA: la convención de nombres y ubicación de los preparches del kernel
tiende a cambiar frecuentemente. Tal vez tengas que leerte la lista de correo linuxkernel para averiguar dónde están los últimos parches y cómo se llaman.
Los parches oficiales del kernel están hechos de tal forma que lo único que
necesitas
hacer
es:
cd
<arbol
patch -p1 < ../patchfile
de
codigo
fuente
de
linux>
En donde la opción "-p1" en en comando patch quiere decir "Elimina la parte del
path en el nombre hasta la primera barra e intenta aplicar el parche con el nombre
reducido de esa forma".
Ahora vamos a aplicar el parche que acabamos de crear. Un parche actualiza una
versión antigua del archivo para transformarlo en una versión más moderna del
mismo, por lo que queremos aplicar el parche a la versión antigua del archivo.
val@evilcat
<~>$
diff
-uNr
val@evilcat
<~>$
val@evilcat
<~/old>$
patch
patching
file
val@evilcat
<~/old>$
This is a slightly more complex file.
old
new
cd
-p1
>
<
cat
patchfile
old
../patchfile
file.txt
file.txt
Tras aplicar la salida del comando diff usando patch, el archivo "antiguo" es ahora
igual que el "nuevo".
Envío de un parche
Una vez que hayas creado el parche, probablemente quieras compartirlo con otras
personas. Lo ideal es que pruebes el parche tú misma, dárselo a otras personas
para que también lo prueben, y que otras personas lean el propio parche. En
resumen, que tu parche esté libre de errores, bien escrito, y sea sencillo de
aplicar.
Pruebas
Compila y comprueba los parches tú mismo. Verás que hay gente que publica
parches totalmente sin probar en la lista linux-kernel, pero no hagas caso. Un
parche sin probar es muy probablemente un parche inutil. Incluso los
mantenedores del kernel han liberado más de una vez parches que ni siquiera
llegan a compilar. Nadie es perfecto, así que comprueba los parches que hagas.
Estilo de programación
Asegúrate de que tu código encaja con el que lo rodea y que sigue las
convenciones de estilo de programación del kernel. Consulta el fichero
"Documentation/CodingStyle" para tener una guía específica, pero muchas veces
mirar a otros ficheros del código fuente es la mejor forma de averiguar cuales son
las convenciones actuales.
Creación de un parche
Lo primero que has de recordar es tener siempre una versión sin modificar del
código fuente del kernel en alguna parte. No compiles en ella, ni edites ningún
fichero allí, simplemente copialo para obtener una versión de trabajo del arbol de
directorios del código fuente. El código fuente original del kernel debería estar en
un directorio llamado "linux.vanilla" o "linux.orig", y tu directorio de trabajo debería
estar en el mismo directorio que el del código original. Por ejemplo, si el código
fuente original sin modificar está en "/usr/src/linux.vanilla", el código fuente de
trabajo debería estar también en "/usr/src/".
Una vez que hayas realizado los cambios en la copia de trabajo del código,
crearás un parche usando diff. Asumiendo que tu directorio de trabajo se llame
"linux.new",
ejecutarías:
val@evilcat <~>$ diff -uNr linux.vanilla linux.new > patchfile
Todas las diferencias entre el código fuente original y el nuevo están ahora ne
"patchfile". NOTA: No crees parches con directorios no equilibrados. Por ejemplo,
no hagas ésto:
val@evilcat <~>$ diff -uNr linux.vanilla working/usb/thing1/linux > patchfile
Esto no creará un parche con el formato estándar y nadie intentará probar tu
parche debido a la complicación para aplicarlo.
Una vez que has creado un parche, ¡leelo! Es casi seguro que tu parche incluye
ficheros que no deseas que formen parte de él, como copias viejas de seguridad
creadas por el editor de textos, ficheros objeto, o cosillas varias que hayas creado
durante el desarrollo. Para librarse de esos ficheros, le puedes indicar a diff que
ignore ciertos tipos de fichero, puedes elmininarlos o incluso puedes editar el
parche directamente. Asegúrate de que entiendes el formato antes de modificar
un parche manualmente, ya que es facil que crees un parche que no sea posible
aplicar. Un comando util para deshacerse de la mayor parte de los ficheros extra
creados durante la construcción de un kernel es:
make mrproper
Pero recuerda, este comando eliminará los ficheros .config y te obligará a hacer
una recompilación completa del kernel. Además, asegúrate de que tu parche
funciona en la dirección apropiada. ¿Tienen las líneas nuevas un "+" delante? Y,
asegúrate de que esos son los cambios que quieres enviar. Es
sorprendentemente facil realizar un diff con un directorio completamente
equivocado.
Una vez que creas tener la versión final del parche, aplícala a una copia del arbol
original del código fuente (no estropees la única copia limpia que tienes). Si no se
aplica sin errores, rehaz el parche.
Consideraciones políticas
Si al distribuir tu parche no es aceptado, escucha lo que dice otra gente sobre él y
trata de solucionar los problemas que pueda haber. Los parches más rechazados
son aquellos que añaden características nuevas - añadir una característica nueva
es considerado de mal gusto por los otros mantenedores. Si hay suficiente gente
que encuentra útil tu parche, irás ganando una buena reputación entre la gente
que lo descargue y lo use. A veces, un mantenedor no es capaz de aceptar un
parche debido únicamente a su ego. Cuando esto ocurre, la única opción es
mantener una versión mejorada del código independiente del kernel principal. A
menudo, código mantenido externamente que demuestra ser mejor acaba
reemplazando el código presente en el kernel tras un tiempo, el cual es un camino
para llegar a ser mantenedor.
Descargar