Excepto para el último capítulo, todo lo que hemos hecho hasta ahora en el núcleo ha sido como respuesta a un proceso que lo pide, ya sea tratando con un fichero especial, enviando un ioctl(), o a través de una llamada al sistema. Pero el trabajo del núcleo no es sólo responder a las peticiones de los procesos. Otro trabajo no menos importante es hablar con el hardware conectado a la máquina.
Hay dos tipos de interacción entre la CPU y el resto del hardware de la computadora. El primer tipo es cuando la CPU da órdenes al hardware, el el otro es cuando el hardware necesita decirle algo a la CPU. La segunda, llamada interrupción, es mucho más difícil de implementar porque hay que tratar con ella cuando le conviene al hardware, no a la CPU. Los dispositivos hardware típicamente tienen una pequeña cantidad de RAM, y si no lees su información cuando está disponible, se pierde.
Bajo Linux, las interrupciones hardware se llaman IRQs (abreviatura de Interrupt Requests)11.1. Hay dos tipos de IRQs, cortas y largas. Una IRQ corta es la que se espera que dure un periodo de tiempo muy corto, durante el cual el resto de la máquina estará bloqueado y ninguna otra interrupción será manejada. Una IRQ larga es una que puede durar más tiempo, y durante la cual otras interrupciones pueden ocurrir (pero no interrupciones que vengan del mismo dispositivo). Si es posible, siempre es mejor declarar un manejador de interrupciones como largo.
Cuando la CPU recibe una interrupción, detiene lo que quiera que esté haciendo (a menos que se encuentre procesando una interrupción más prioritaria, en cuyo caso tratará con esta interrupción sólo cuando la más prioritaria se haya acabado), salva ciertos parámetros en la pila y llama al manejador de interrupciones. Esto significa que ciertas cosas no se permiten dentro del propio manejador de interrupciones, porque el sistema se encuentra en un estado desconocido. La solución a este problema es que el manejador de interrupciones haga lo que necesite hacer inmediatamente, normalmente leer algo desde el hardware o enviar algo al hardware, y después planificar el manejo de la nueva información en un tiempo posterior (esto se llama `bottom half') y retorna. El núcleo está garantizado que llamará al bottom half tan pronto como sea posible; y cuando lo haga, todo lo que está permitido en los módulos del núcleo estará permitido.
La forma de implementar esto es llamar a request_irq() para que se llame a tu manejador de interrupciones cuando se reciba la IRQ relevante (hay 15 de ellas, más una que se utiliza para disponer en cascada los controladores de interrupción, en las plataformas Intel). Esta función recibe el número de IRQ, el nombre de la función, banderas, un nombre para /proc/interrupts y un parámetro para pasarle al manejador de interrupciones. Las banderas pueden incluir SA_SHIRQ para indicar que estás permitiendo compartir la IRQ con otro manejador de interrupciones (normalmente porque un número de dispositivos hardware están en la misma IRQ) y SA_INTERRUPT para indicar que esta es una interrupción rápida. Esta función sólo tendrá éxito si no hay ya un manejador para esta IRQ, o si ya la estais compartiendo.
Entonces, desde dentro del manejador de interrupciones, nos comunicamos con el hardware y después usamos queue_task_irq() con tq_immediate() y mark_bh(BH_IMMEDIATE) para planificar el bottom half. El motivo por el que no podemos usar la queue_task estándar en la versión 2.0 es que la interrupción podría producirse en el medio de la queue_task de alguien 11.2. Necesitamos mark_bh porque las versiones anteriores de Linux sólo tenían un array de 32 bottom half's, y ahora uno de ellos (BH_IMMEDIATE) se usa para la lista enlazada de bottom half's para los controladores que no tenían una entrada de bottom half asignada.
Teclados en la arquitectura Intel
El resto de este capítulo es completamente específico de Intel. Si no estás trabajando en una plataforma Intel, no funcionará. Ni siquiera intentes compilar el siguiente código.
Tuve un problema escribiendo el código de ejemplo para este capítulo. Por una parte, para que un ejemplo sea útil tiene que ejecutarse en las computadoras de todo el mundo con resultados significativos. Por otra parte, el núcleo ya incluye controladores de dispositivo para todos los dispositivos comunes, y esos controladores de dispositivo no coexistirán con lo que voy a escribir. La solución que encontré fue escribir algo para la interrupción del teclado, y deshabilitar primero el manejador normal de interrupción del teclado. Como está definido como un símbolo estático en los ficheros fuente del núcleo (concretamente drivers/char/keyboard.c), no hay forma de restaurarlo. Antes de instalar este código, haz en otro terminal sleep 120 ; reboot si es que valoras en algo tu sistema de ficheros.
Este código se registra para la IRQ 1, que es la IRQ controlada por el teclado bajo las arquitecturas Intel. Entonces, cuando recibe una interrupción de teclado, lee el estado del teclado (que es el propósito de inb(0x64)) y el código de barrido (scan code), que es el valor devuelto por el teclado. Tan pronto como el núcleo cree que es factible, ejecuta got_char que da el código de la tecla usada (los siete primeros bits del código de barrido) y si ha sido presionado (si el octavo bit es cero) o soltado (si es uno).
intrpt.c
/* intrpt.c - Un manejador de interrupciones. */
/* Copyright (C) 1998 por Ori Pomerantz */
/* Los ficheros de cabeceras necesarios */
/* Estándar en los módulos del núcleo */
#include <linux/kernel.h> /* Estamos haciendo trabajo del núcleo */
#include <linux/module.h> /* Específicamente, un módulo */
/* Distribuido con CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS
1
#define MODVERSIONS
#include <linux/modversions.h>
#endif
#include <linux/sched.h>
#include <linux/tqueue.h>
/* Queremos una interrupción */
#include <linux/interrupt.h>
#include <asm/io.h>
/* En 2.2.3 /usr/include/linux/version.h se incluye una
* macro para esto, pero 2.0.35 no lo hace - por lo tanto
* lo añado aquí si es necesario. */
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif
/* Bottom Half - esto será llamado por el núcleo
* tan pronto como sea seguro hacer todo lo normalmente
* permitido por los módulos del núcleo. */
static void got_char(void *scancode)
{
printk("Código leído %x %s.\n",
(int) *((char *) scancode) & 0x7F,
*((char *) scancode) & 0x80 ? "Liberado" : "Presionado");
}
/* Esta función sirve para las interrupciones de teclado. Lee
* la información relevante desde el teclado y entonces
* planifica el bottom half para ejecutarse cuando el núcleo
* lo considere seguro. */
void irq_handler(int irq,
void *dev_id,
struct pt_regs *regs)
{
/* Estas variables son estáticas porque necesitan ser
* accesibles (a través de punteros) por la rutina bottom
* half. */
static unsigned char scancode;
static struct tq_struct task =
{NULL, 0, got_char, &scancode};
unsigned char status;
/* Lee el estado del teclado */
status = inb(0x64);
scancode = inb(0x60);
/* Planifica el bottom half para ejecutarse */
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)
queue_task(&task, &tq_immediate);
#else
queue_task_irq(&task, &tq_immediate);
#endif
mark_bh(IMMEDIATE_BH);
}
/* Inicializa el módulo - registra el manejador de IRQs */
int init_module()
{
/* Como el manejador de teclado no coexistirá con
* otro manejador, tal como nosotros, tenemos que deshabilitarlo
* (liberar su IRQ) antes de hacer algo. Ya que nosotros
* no conocemos dónde está, no hay forma de reinstalarlo
* después - por lo tanto la computadora tendrá que ser reiniciada
* cuando halla sido realizado.
*/
free_irq(1, NULL);
/* Petición IRQ 1, la IRQ del teclado, para nuestro
* irq_handler. */
return request_irq(
1, /* El número de la IRQ del teclado en PCs */
irq_handler, /* nuestro manejador */
SA_SHIRQ,
/* SA_SHIRQ significa que queremos tener otro
* manejador en este IRQ.
*
* SA_INTERRUPT puede ser usado para
* manejarla en una interrupción rápida.
*/
"test_keyboard_irq_handler", NULL);
}
/* Limpieza */
void cleanup_module()
{
/* Esto está aquí sólo para completar. Es totalmente
* irrelevante, ya que no tenemos forma de restaurar
* la interrupción normal de teclado, por lo tanto
* la computadora está totalmente inservible y tiene que
* ser reiniciada. */
free_irq(1, NULL);
}