En esta entrada y en las siguientes de esta misma saga voy a intentar explicar, hasta donde llegan mis conocimientos y experiencia, qué ocurre en Linux desde que se pulsa una tecla hasta que aparece el carácter correspondiente en el terminal.
Esta primera entrada versará sobre lo que ocurre en el entorno de consola de Linux desde que se pulsa la tecla hasta que el carácter llega a la aplicación, posponiendo a otras entradas la explicación del camino de vuelta, lo que ocurre en X-Window y en una consola remota conectada mediante SSH.
El entorno en el que voy a desarrollar mi explicación es el siguiente:
- GNU/Linux Kernel 3.13.11 (LFS 7.5) de 32 bits
- Teclado IBM AT original conectado al puerto PS/2
El software responsable de toda la gestión de la consola es kbd. En mi caso, kbd-2.0.1, descargable desde la web del proyecto: https://www.kernel.org/pub/linux/utils/kbd
Primer nivel: hardware y scancodes
El microcontrolador del teclado se encuentra continuamente escaneando la matriz de las conexiones de las teclas a la búsqueda de una pulsación o de una liberación de tecla. Cuando se produce en evento de este tipo, la información viaja hasta nuestro sistema atravesando diferentes etapas: microcontrolador del teclado, controlador "virtual" 8042, driver del núcleo atkbd, dispositivo del directorio /dev, ...
Nota: por si algún lector no lo recuerda, mi sistema LFS tiene una personalidad un tanto rebelde con las modas: he suprimido a propósito udev y systemd. Me gusta lo clásico. Ya se sabe: "Your distro, your rules"
La información "en crudo" que llega al sistema se puede ver fácilmente con la orden:
hexdump -C /dev/input/event2
(la ruta exacta al dispositivo puede cambiar en el sistema del lector dependiendo de si está gestionando los dispositivos con udev, si el teclado es usb, etc.)
A continuación se muestra la salida de hexdump correspondiente a la pulsación y liberación de la secuencia de teclas: May. Izq., May. Dch., Ñ, Intro, Intro (teclado numérico) (designo por "tecla Ñ" a la tecla que en un teclado español convencional lleva impresa la letra "Ñ"). Para una mejor interpretación, he separado por líneas en blanco las pulsaciones y liberaciones de cada una de las teclas:
00000030 8f 23 f3 53 e7 f2 03 00 04 00 04 00 2a 00 00 00 |.#óSçò......*...|
00000040 8f 23 f3 53 e7 f2 03 00 01 00 2a 00 01 00 00 00 |.#óSçò....*.....|
00000050 8f 23 f3 53 e7 f2 03 00 00 00 00 00 00 00 00 00 |.#óSçò..........|
00000060 8f 23 f3 53 7b ad 05 00 04 00 04 00 2a 00 00 00 |.#óS{......*...|
00000070 8f 23 f3 53 7b ad 05 00 01 00 2a 00 00 00 00 00 |.#óS{....*.....|
00000080 8f 23 f3 53 7b ad 05 00 00 00 00 00 00 00 00 00 |.#óS{..........|
00000090 8f 23 f3 53 75 a0 0d 00 04 00 04 00 36 00 00 00 |.#óSu ......6...|
000000a0 8f 23 f3 53 75 a0 0d 00 01 00 36 00 01 00 00 00 |.#óSu ....6.....|
000000b0 8f 23 f3 53 75 a0 0d 00 00 00 00 00 00 00 00 00 |.#óSu ..........|
000000c0 90 23 f3 53 00 8b 00 00 04 00 04 00 36 00 00 00 |.#óS........6...|
000000d0 90 23 f3 53 00 8b 00 00 01 00 36 00 00 00 00 00 |.#óS......6.....|
000000e0 90 23 f3 53 00 8b 00 00 00 00 00 00 00 00 00 00 |.#óS............|
000000f0 90 23 f3 53 2f 9f 09 00 04 00 04 00 27 00 00 00 |.#óS/.......'...|
00000100 90 23 f3 53 2f 9f 09 00 01 00 27 00 01 00 00 00 |.#óS/.....'.....|
00000110 90 23 f3 53 2f 9f 09 00 00 00 00 00 00 00 00 00 |.#óS/...........|
00000120 90 23 f3 53 4c 33 0b 00 04 00 04 00 27 00 00 00 |.#óSL3......'...|
00000130 90 23 f3 53 4c 33 0b 00 01 00 27 00 00 00 00 00 |.#óSL3....'.....|
00000140 90 23 f3 53 4c 33 0b 00 00 00 00 00 00 00 00 00 |.#óSL3..........|
00000150 90 23 f3 53 c5 3b 0f 00 04 00 04 00 1c 00 00 00 |.#óSÅ;..........|
00000160 90 23 f3 53 c5 3b 0f 00 01 00 1c 00 01 00 00 00 |.#óSÅ;..........|
00000170 90 23 f3 53 c5 3b 0f 00 00 00 00 00 00 00 00 00 |.#óSÅ;..........|
00000180 91 23 f3 53 72 41 01 00 04 00 04 00 1c 00 00 00 |.#óSrA..........|
00000190 91 23 f3 53 72 41 01 00 01 00 1c 00 00 00 00 00 |.#óSrA..........|
000001a0 91 23 f3 53 72 41 01 00 00 00 00 00 00 00 00 00 |.#óSrA..........|
000001b0 91 23 f3 53 70 85 07 00 04 00 04 00 9c 00 00 00 |.#óSp...........|
000001c0 91 23 f3 53 70 85 07 00 01 00 60 00 01 00 00 00 |.#óSp.....`.....|
000001d0 91 23 f3 53 70 85 07 00 00 00 00 00 00 00 00 00 |.#óSp...........|
000001e0 91 23 f3 53 d5 d3 08 00 04 00 04 00 9c 00 00 00 |.#óSÕÓ..........|
000001f0 91 23 f3 53 d5 d3 08 00 01 00 60 00 00 00 00 00 |.#óSÕÓ....`.....|
00000200 91 23 f3 53 d5 d3 08 00 00 00 00 00 00 00 00 00 |.#óSÕÓ..........|
Podemos apreciar que al sistema llega bastante información referente a las pulsaciones de las teclas. Sin embargo, el driver del teclado y las utilidades de kbd nos van a permitir procesar más fácilmente esta información. Gracias a ellos una pulsación del teclado se convierte en una secuencia de bytes llamada scancode. Podemos ver los scancode generados por las pulsaciones de tecla con la utilidad showkey -s ejecutada como root en una consola:
# showkey -s kb mode was XLATE press any key (program terminates 10s after last keypress)... 0x9c 0x2a 0xaa 0x36 0xb6 0x27 0xa7 0x1c 0x9c 0xe0 0x1c 0xe0 0x9c #
El primer código 0x9c corresponde a la liberación de la tecla Intro tras la pulsación que ha lanzado el programa. Los códigos 0x2a y 0xaa corresponden a la pulsación y liberación de la tecla Mayusc. Izq. Los códigos 0x36 y 0xb6 corresponden a la tecla Mayusc. Dcha. Los códigos 0x27 y 0xa7 a la tecla "Ñ". Los códigos 0x1c y 0x9c son de la tecla Intro principal y los códigos 0xe0 0x1c junto con los 0xe0 0x9c corresponden a la pulsación y liberación de la tecla Intro del teclado numérico.
Una ayuda del driver: keycodes
En el camino desde la pulsación de tecla hasta el programa del usuario el driver del teclado nos facilita las cosas en la etapa siguiente traduciendo los scancodes a códigos de tecla llamados keycodes. De esta forma la pulsación y liberación de una tecla quedan unidas en un sólo código. Podemos acceder a los keycodes con la orden showkey -k que ahora nos ofrece los siguientes resultados:
# showkey -k kb mode was XLATE press any key (program terminates 10s after last keypress)... keycode 28 release keycode 42 press keycode 42 release keycode 54 press keycode 54 release keycode 39 press keycode 39 release keycode 28 press keycode 28 release keycode 96 press keycode 96 release #
Como podemos apreciar, los códigos asociados a las primeras teclas son los mismos que los scancodes de las mismas sólo que en notación decimal. Sin embargo, el correspondiente a la tecla Intro del teclado numérico ha cambiado de 0xe0 0x1c a 96. Para saber qué correspondencias existen entre scancodes y keycodes, podemos utilizar la orden getkeycodes:
# getkeycodes Plain scancodes xx (hex) versus keycodes (dec) for 1-83 (0x01-0x53) scancode equals keycode 0x50: 80 81 82 83 99 0 86 87 0x58: 88 117 0 0 95 183 184 185 0x60: 0 0 0 0 0 0 0 0 0x68: 0 0 0 0 0 0 0 0 0x70: 93 0 0 89 0 0 85 91 0x78: 90 92 0 94 0 124 121 0 Escaped scancodes e0 xx (hex) e0 00: 0 0 0 0 0 0 0 0 e0 08: 0 0 0 0 0 0 0 0 e0 10: 165 0 0 0 0 0 0 0 e0 18: 0 163 0 0 96 97 0 0 e0 20: 113 140 164 0 166 0 0 0 e0 28: 0 0 255 0 0 0 114 0 e0 30: 115 0 172 0 0 98 255 99 e0 38: 100 0 0 0 0 0 0 0 e0 40: 0 0 0 0 0 119 119 102 e0 48: 103 104 0 105 112 106 118 107 e0 50: 108 109 110 111 0 0 0 0 e0 58: 0 0 0 125 126 127 116 142 e0 60: 0 0 0 143 0 217 156 173 e0 68: 128 159 158 157 155 226 0 112 e0 70: 0 0 0 0 0 0 0 0 e0 78: 0 0 0 0 0 0 0 0 #
Tercer nivel: del código de tecla al carácter
En el siguiente paso el driver del teclado lleva a cabo una traducción de keycodes (códigos de tecla) a keysyms (símbolos de tecla). Cada keysym es un secuencia de dos bytes. Las secuencias que empiezan por el byte 0x00 corresponden a caracteres. Cada keysym tiene un nombre simbólico que describe de forma aproximada el símbolo impreso en una tecla (p.e.: "a" o "Esc") o una acción determinada (p.e.: "Show_Memory"). Cada combinación de una tecla con las teclas modificadoras (Mayusc., AltrGr., Ctrl., Alt., Mayusc. Izq., Mayusc. Dch., Ctrl Izq., Ctrl. Dcha., Bloq. Mayusc.) puede tener asignado un keysym diferente.
La lista de nombres simbólicos reconocidos por el driver del teclado pueden obtenerse con la orden:
dumpkeys -l
La lista de asociaciones entre keycodes y keysyms activas en un momento determinado puede obtenerse con la orden dumpkeys.
La lista de asociaciones activas puede cambiarse con la orden loadkeys. Esta orden puede tomar como argumento un nombre de fichero con las asociaciones deseadas. Este fichero suele denominarse keymap (cf. man keymaps) . El diseño de loadkeys permite que las definiciones sean acumulativas, por lo que puede jugarse, hasta cierto punto, incluyendo nuevas definiciones a lo largo de una sesión que amplíen o modifiquen las funcionalidades del teclado.
Como ejemplo, vamos a asignar a la tecla F11 el carácter 0xF1:
# loadkeys <<FIN keycode 87 = 0x00F1 FIN #
Esta forma de asignar keysyms mediante el código hexadecimal del carácter (o el código Unicode al estilo U+6566) no es lo más cómodo ni lo más habitual. Lo más frecuente es asignarlos mediante un nombre simbólico (de ahí la designación keysym):
# loadkeys <<FIN keycode 87 = euro FIN #
Puede comprobarse el efecto de esta modificación de la forma siguiente (designo entre corchetes la tecla que es pulsada en cada ocasión):
# hexdump -C [F11][barra espaciadora][barra espaciadora]...[barra espaciadora][Intro] 00000000 a4 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 |. | 00000010 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | #
Es posible que la salida de hexdump -C sea ligeramente diferente:
# hexdump -C [F11][barra espaciadora][barra espaciadora]...[barra espaciadora][Intro] 00000000 c2 a4 20 20 20 20 20 20 20 20 20 20 20 20 20 20 |.. | 00000010 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | #
En breve explicaré a qué es debida esta diferencia. Por el momento centrémonos en que hemos asignado el código hexadecimal 0x00A4 a la tecla F11 utilizando el nombre simbólico euro. Esto no supone ninguna ambigüedad porque el carácter euro tiene asignado el código hexadecimal 0xA4 de forma inequívoca, pero ¿cómo debería interpretarse la asignación siguiente?:
# loadkeys <<FIN keycode 87 = mu FIN #
El carácter mu corresponde al código hexadecimal 0xB5 en los conjuntos de caracteres ISO-8859-1,3,8,9,13,y 15 y al código hexadecimal 0xEC en el ISO-8859-7. ¿Qué código queremos que genere la tecla F11?
Si no especificamos nada, la orden loadkeys asumirá que todos los nombres simbólicos que utilicemos deben interpretarse siguiendo la norma ISO-8859-1. En otro caso, deberemos especificarlo:
# loadkeys <<FIN charset "iso-8859-7" keycode 87 = mu FIN #
Ahora la pulsación de la tecla F11 nos generárá el código 0xEC y no el código 0xB5.
Obsérvese que esta especificación de la codificación de caracteres no tiene otra consecuencia que la forma de interpretar los keysyms que adoptará la orden loadkeys en una ejecución determinada y no tendrá mayores consecuencias en relación con la totalidad del sistema.
El último paso: entregando a la aplicación el carácter.
En este camino desde la pulsación de tecla al momento en que el carácter llega a la aplicación del usuario, hemos llegado a la última etapa. El driver del teclado ya sabe qué debe entregar, bien un carácter si el código asignado a la tecla comienza por 0x00, bien una cadena de caracteres si empieza por 0x01, etc. Si el código del carácter se encuentra entre 0x0000 y 0x007F, no hay ningún problema, pero ¿cómo debe entregar los caracteres con un código superior? ¿como un sólo carácter o como varios caracteres UTF?
Para contestar a esta pregunta debemos utilizar la orden kbd_mode. Con el modificador -a, entregará los caracteres 0x0080 a 0x00FF como un sólo byte. Con el modificador -u, los entregará codificados en UTF. Si suponemos que hemos asignado el código 0x00F1 a la tecla F11, podemos ver el efecto de kbd_mode de la siguiente forma:
# kdb_mode -a # hexdump -C [F11][barra espaciadora][barra espaciadora]...[barra espaciadora][Intro] 00000000 f1 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 |. | 00000010 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | # kdb_mode -u # hexdump -C [F11][barra espaciadora][barra espaciadora]...[barra espaciadora][Intro] 00000000 c3 b1 20 20 20 20 20 20 20 20 20 20 20 20 20 20 |.. | 00000010 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | #
Lo que ocurra a partir de este momento es responsabilidad de la aplicación. Nosotros hemos utilizado para nuestros ejemplos la aplicación hexdump, pero el comportamiento de otras aplicaciones, como el ubicuo bash, puede verse afectado por variables de entorno como LANG, etc.