• No results found

Unidad 2 SO pdf

N/A
N/A
Protected

Academic year: 2020

Share "Unidad 2 SO pdf"

Copied!
28
0
0

Loading.... (view fulltext now)

Full text

(1)

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

(2)

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&OACUTE;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 linux-2.4.26.tar.gz

Formas de obtener el código fuente

(3)

$ rm linux

(Asumiendo que linux era un enlace simbólico)

$ tar xzf linux-<version>.tar.gz

$ mv linux linux-<version>

$ ln -s 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 /mnt/cdrom -name *kernel-*

$ rpm -iv <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.

(4)

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 el kernel nuevo>

label=<elegir aquí el nombre que quiera>

Eliminar el campo "initrd" en el caso de que exista:

(5)

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 = new

read-only root=/dev/sda1

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

(6)

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 (2.4.9-31)

root (hd0,0)

kernel /vmlinuz-2.4.9-31 ro root=/dev/hda3 initrd /initrd-2.4.9-31.img

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

(7)

title new

root (hd0,0)

kernel /mynewkernel ro root=/dev/hda3

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:

(8)

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).

(9)

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:

(10)

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.

(11)

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

(12)

(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 -r` /lib/modules/`uname r`.bak

# make 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

(13)

# 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

(14)

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 procesadores (en máquinas SMP o multiprocesador).

(15)

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

(16)

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

(17)

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*

(18)

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

(19)

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:

/* System call */

. = 0xc00

SystemCall:

EXCEPTION_PROLOG

stw r3,ORIG_GPR3(r21)

li r20,MSR_KERNEL

rlwimi r20,r23,0,16,16 /* copiar bit EE del MSR almacenado*/

(20)

.long DoSyscall .long ret_from_except

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 support --->

[*] Enable loadable module support

[ ] Set version information on all module symbols [*] Kernel module loader

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

(21)

#include <linux/module.h>

#include <linux/init.h>

#include <linux/kernel.h>

static int __init mymodule_init(void){

printk ("Mi modulo funciona!n");

return 0;

}

static void __exit mymodule_exit(void){

printk ("Descargando mi modulo.n");

return; }

module_init(mymodule_init); module_exit(mymodule_exit);

Grabe el fichero 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:

# dmesg | tail

Debería poder ver, al final del texto:

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

(22)

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 int my_variable;

EXPORT_SYMBOL(my_variable);

Recompila y arranca tu nuevo kernel. Ahora intenta cargar de nuevo tu módulo.:

# insmod ./drivers/misc/mymodule.o

(23)

my_variable es 0 Mi modulo funciona!

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 (do_print)

printk ("Big long obnoxious messagen");

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

(24)

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

(25)

+++ new/file.txt Tue May 28 23:01:01 2002

@@ -1,5 +1,5 @@

This is a -simple +slightly more complex file.

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

(26)

Los parches oficiales del kernel están hechos de tal forma que lo único que

necesitas hacer es:

cd <arbol de codigo fuente de linux> patch -p1 < ../patchfile

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 old new > patchfile

val@evilcat <~>$ cd old

val@evilcat <~/old>$ patch -p1 < ../patchfile

patching file file.txt

val@evilcat <~/old>$ cat file.txt

This is a slightly more complex file.

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

(27)

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.

(28)

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

ftp.kernel.org LILO mini-HOWTO

References

Related documents

The fact that these two major research universities in Amsterdam joined forces to create a liberal arts and sciences undergraduate experience is an example of a “local cooperation

Broward College Board of Trustees authorized the second sub-award amendment with Jobs for the Future through the National Science Foundation Grant (Impact of Work-Based Learning

Using the complementary erf function with the templates obtained from the training phase, the algorithm computes the probabilities to belong to each of the 3 states

But I did feel all the time that it was going to be very hard to do anything really competitive there, because it needs a building before you even start to build an

The Erie County Comptroller’s Office (“Office”) has completed its reconciliation of all mortgage taxes collected by Erie County (“County”) in 2010 and has reported its findings

Increase the RS:X Class major events value across all channels - TV, print, venue, online Increase its global media exposure of the sport to attract viewers and sponsors.. Market

The fund would be used to com- pensate federal crime victims, assist state crime victim compensa- tion programs, and help fund public and private victim

Oxy’s ownership of Vintage, last year’s top Operator, and its taking over Venoco’s properties conceivably could lead to its expanding its presence in the Valley although