Funcionamiento de terminales en UNIX

Luis Meléndez cc0luism@uco.es


Este documento pretende ser una introducción al tema del funcionamiento de los diferentes tipos de terminales mediante los que un usuario se puede conectar a un sistema UNIX.

Introducción

Principios básicos

El teclado

La pantalla

Disciplina de línea

Control de flujo

Otras opciones del comando stty

Emuladores de terminal y otros tipos de conexión


Introducción

Conocer cómo funciona la interacción entre un ordenador UNIX y los diferentes tipos de terminales desde los que podemos conectarnos a él no es fundamental para el usuario medio, pero sí es interesante para resolver algunos tipos de problemas habituales, para saber para qué sirve la variable TERM, etc. Este es un tema relativamente complejo y sobre el que existe poca documentación clara y asequble, de ahí el motivo de este documento. Es interesante, en primer lugar, hacer notar la diferencia entre el trabajo en un PC y en UNIX. En un PC el teclado, la pantalla y el ratón forman parte de la máquina misma, de forma que el ordenador controla todos ellos y sabe perfectamente qué tecla se ha pulsado en cada momento o cómo controlar la pantalla. Aunque también en este entorno pueden surgir problemas de configuración, siempre serán menos numerosos que en UNIX. Cuando trabajamos en UNIX o en cualquier otro sistema operativo multiusuario, nuestro teclado y nuestra pantalla no suelen estar conectados fisicamente al ordenador, salvo en el caso de las versiones UNIX de PC o estaciones gráficas; pero incluso en esos casos, si nos conectamos a otros ordenadores, empiezan a surgir los temas de los que vamos a hablar aquí.

Principios básicos

Partamos del caso más simple, que aunque era el más utilizado en los primeros tiempos de UNIX (y por tanto es el que orientó a sus diseñadores en la forma de gestionar terminales), hoy no es el más habitual. Me refiero a un simple terminal "tonto" (también llamados terminales ASCII, terminales VT, dumb terminals, etc) conectado mediante un cable serie RS232 al propio ordenador. Por un cable serie sólo viajan bytes, uno detrás de otro, tanto en dirección del terminal al ordenador (cosa que ocurre cuando pulsamos las teclas) como en el sentido contrario (caso de lo que sale por pantalla). Veamos qué ocurre cuando pulsamos una tecla normal, como la de la letra "a":

  1. El terminal envía por el cable serie el código ASCII de la letra "a" (97) por el cable serie.
  2. El ordenador lo recibe y se lo pasa al driver de terminales. Un driver es una parte del sistema operativo que gestiona un tipo concreto de dispositivos, sean terminales, discos, unidades de cinta, etc. Cada driver controla todos los dispositivos del mismo tipo que tenga el ordenador.
  3. El driver de terminales comprueba de qué terminal concreto ha llegado el carácter, y a continuación qué parámetros están configurados para él (estos parámetros se configuran con el comando stty). Más adelante hablaremos de esto. El caso más normal es que el byte que ha llegado se entregue al programa que se está ejecutando en el terminal y asimismo se devuelva por el cable serie al terminal (eco). Este al ver que llega el byte 97, ve que corresponde al código ASCII de la letra "a" y hace aparecer ese carácter por la pantalla. Un caso en que no ocurre así en cuando al conectarnos se nos pide la clave y vemos que al escribirla no aparece por la pantalla. Es así porque el programa encargado de pedir el nombre de usuario y la clave (el programa login) cuando pide la clave desactiva el parámetro eco del terminal.
  4. El programa que se ejecuta en el terminal ha recibido también el código 97, comprueba que es el carácter "a" y hace con él lo que considere adecuado. Como aclaración, los programas que escribe un usuario normalmente no leen letra por letra, sino que llaman a funciones del lenguaje de programación que sea, por ejemplo para leer una línea del teclado y dicha función se encarga de leer caracteres hasta que el usuario pulsa la tecla Intro.
Vemos que lo que parece tan sencillo en realidad implica varios pasos. En particular sorprende al principio el hecho de que cada pulsación del usuario casi siempre viaja dos veces (excepto cuando se ha desactivado el eco): de la terminal al ordenador y vice-versa. Como curiosidad, diremos que el sistema operativo debe interrumpir lo que esté haciendo cada vez que recibe un carácter de algún terminal para que el driver de terminales, que a fin de cuentas es una parte del propio sistema operativo, se haga cargo de él. Aún más grave es cuando estamos conectados a UNIX a través de la red, en vez de por un cable serie directo. En este caso cada pulsación y cada respuesta implican un paquete TCP, cuya manipulación requiere más tiempo de CPU. Esto en realidad no es tan grave como parece, debido a la rapidez de la CPU, aunque otros sistemas operativos solucionan estos inconvenientes con terminales más inteligentes que se encargan de todos los detalles de bajo nivel y entregan al sistema operativo líneas o incluso pantallas completas, pero UNIX tiene la ventaja de que este esquema le permite funcionar con casi cualquier tipo de terminal. Podemos comprobar también la importancia del código ASCII. Puesto que por los cables sólo viajan bytes (números, a fin de cuentas), tiene que existir un acuerdo entre todas las máquinas para saber qué letra corresponde a cada número y vice-versa. He ahí el sentido de este código. Como hemos dicho, la conexión por terminales asíncronos conectados directamente al ordenador no es actualmente lo más habitual. Hoy las redes mandan. Sin embargo, lo fundamental de lo explicado más arriba no varía, por tanto seguiremos hablando sobre este modelo y más adelante explicaremos otras formas de conexión.

El teclado

Ya hemos dicho que todo lo que pulsamos en el teclado y todo lo que el ordenador envía a nustra pantalla viajan por el mismo cable serie. Aunque nos conectemos a través de red local y por tanto no exista ese cable serie, ocurre lo mismo, en el sentido de que existe un único canal lógico bidireccional entre nuestro teclado y pantalla y el ordenador. Ya hemos explicado también el proceso que ocurre cuando pulsamos una tecla como la letra "a". Lo mismo sucede al pulsar cualquier otra tecla que corresponda a un carácter incluido en el código ASCII. Surgen ahora dos dificultades:

  1. Las teclas como letras acentuadas, ñ, y otras no incluidas en el código ASCII.
  2. Las teclas especiales como las de movimiento del cursor, teclas de función, etc.

Caracteres especiales

Un inconveniente del código ASCII es que define códigos numéricos de un byte para los caracteres. Esto da un máximo de 265. Teniendo en cuenta que históricamente los canales de comunicación que se usaban usaban sólo 7 bits en vez de 8, y que este código se definió para acomodarse a cualquier canal, sólo usa 7 bits, lo que permite un máximo de 128 caracteres. De ellos, no todos corresponden a letras; también hay códigos especiales como el espacio (32), el salto de línea (10), el retorno de carro (13), el salto de página (12), y muchos otros. Por tanto los únicos caracteres que contempla este código son las letras mayúsculas y minúsculas, los dígitos (0 a 9) y pocos más. Eso es suficiente para el idioma inglés, pero no es suficiente para muchos otros idiomas. En particular los caracteres españoles como letras acentuadas, ñ, interrogación y admiración abiertas, no están en ese código. Para suplir estas deficiencias, se definieron otros códigos para contemplar otros idiomas. Uno de los más extendidos es el ISO 8859/1, también llamado ISO Latin 1. Es éste un código de 8 bits (por tanto soporta hasta 256 códigos) que incluye los mismos que ASCII más los caracteres usados por la mayoría de los lenguajes occidentales. Casi todas las variantes de UNIX usan internamente este código; entonces, ¿ qué problema hay ?. Por un lado, el teclado que estemos usando debe estar correctamente configurado para que cuando yo pulse la secuencia de teclas que componen la "á" ("a" acentuada): ' seguido de a, genere el código ISO Latin 1 y envíe el ordenador ese código. Eso se hace:

Terminales asíncronas

Basta con configurarlas indicándoles que usen el teclado español.

PCs

Sea cual sea la forma en que nos conectemos desde un PC a UNIX (existen varias), el PC se debe configurar para que use teclado español (normalmente con el comando keyb).

Terminal X o estación gráfica

En este caso es responsabilidad de los administradores de sistemas su correcta configuración, que es algo más compleja.

Por otro lado, la línea de comunicación que tengamos con la máquina UNIX debe funcionar a 8 bits, como ya hemos dicho. Si esta línea está compuesta de varios pasos (como puede ser cuando nos conectamos desde casa con un modem), todos ellos deben funcionar a 8 bits. Como curiosidad, la causa más común de que si enviamos un mensaje con caracteres españoles por correo electrónico estos no lleguen correctamente a su destino es que el mensaje puede pasar por varios ordenadores UNIX antes de ser entregado y alguno de ellos puede hacer la transmisión a 7 bits (el estandar de correo electrónico no obliga a usar 8 bits). Hay que decir también que aunque el mensaje llegue bien el destinatario debe tener su terminal bien configurada según lo que estamos explicando en este documento a fin de leerlo correctamente. Puede ocurrir que la línea de comunicación funcione a 8 bits pero el driver de terminales del sistema operativo que controla nuestra terminal sólo funcione a 7 bits. Esto se controla mediante uno de los parámetros del driver que mencionamos anteriormente y que se manipulan mediante el comando stty. Este caso concreto se arregla con el comando:
stty -istrip cs8

Teclas especiales

Las teclas de movimiento del cursor o las teclas de función no tienen ningún código asignado. De hecho al pulsar una de esas teclas se generan varios bytes que viajan por la línea de comunicación al ordenador. Este debe reconocer esa secuencia y saber qué tecla hemos pulsado. El primer byte de la secuencia siempre es el mismo, el 27 (ESC). Cuando el programa que estemos usando recibe un código 27, sabe que probablemente hemos pulsado una tecla especial, y por tanto analiza los códigos siguientes para saber qué tecla es. El problema aquí es que no todos los terminales tienen las mismas teclas y la misma tecla en distintos terminales no siempre genera los mismos códigos en todos ellos. Aquí existe infinidad de terminales de distintas marcas y no es posible establecer un estandar como ASCII o ISO Latin 1. Esto se resuelve en UNIX mediante una pequeña base de datos que contiene las secuencias de las teclas especiales para muchos tipos de terminales. Esta base de datos figura tradicionalmente en el fichero /etc/termcap (de terminal capabilites) y en los UNIX derivados de System V en el sistema llamado terminfo. Los programas que usan las teclas especiales (como los editores y los programas que usan menús) necesitan saber qué tipo de terminal tenemos, para obtener la información que necesita de esa base de datos. Esto lo hace obteniendo el valor de la variable TERM. Si ésta contiene el valor vt220, el programa leerá al principio de su ejecución de la base de datos la información sobre ese terminal para saber qué códigos generan sus teclas especiales y así poder reconocerlas cuando las reciba (en esa base de datos no sólo hay información sobre el teclado sino también sobre los códigos de manejo de la pantalla, como veremos más adelante). He aquí la explicación del sentido y la importancia de la variable TERM. El usuario debe preocuparse de que se corresponda con el tipo de terminal que realmente tiene; si no es así, las aplicaciones a pantalla completa no funcionarán bien. En la Universida de Córdoba está implantado el Sistema LOGIN que configura la entrada de cada usuario a los sistemas UNIX y que tiene en cuenta entre otras cosas este aspecto, por lo que es difícil que surjan problemas de este tipo.

La pantalla

Con la pantalla ocurre algo parecido al teclado. Cuando un programa que funcione a toda pantalla (como editores, programas con menús, etc) necesita borrar la pantalla o poner una línea en video inverso debe enviar una secuencia de códigos a la misma (a través de la línea de comunicación que existe entre nosotros y el ordenador). Como ya habrás adivinado, estos códigos no son iguales para todos los tipos de terminales. El programa debe saber, también a través de la variable TERM la terminal que estamos usando para leer de la base de datos de terminales (sea /etc/termcap o terminfo) los códigos de manejo de nuestra pantalla. Muchas veces ocurre que al ejecutar un editor la pantalla se llena de caracteres raros o desordenados. La causa más común es que la variable TERM no tenga el valor correcto.

Disciplina de línea

Ya hemos dicho en un par de ocasiones que el driver de terminales tiene unos parámetros que se pueden configurar. A la forma en que funciona según esos parámetros se conoce con el nombre de disciplina de línea, y el comando stty sirve para modificar su comportamiento. Ya hemos indicado que en el sistema operativo existe un único driver para cada tipo de dispositivo, por tanto sólo existe uno para controlar todas las terminales. Sin embargo cada terminal tiene su propio conjunto de parámetros, es decir, su propia disciplina de línea, y cada usuario puede ejecutar el comando stty para cambiar la suya. Pongamos un ejemplo: Supongamos que hemos escrito un programa en el que en un momento dado pedimos al usuario que introduzca algún dato por el teclado. Esto es muy común y en el lenguaje C se suele hacer mediante una función como scanf o gets. Cuando el usuario está ejecutando el programa y llega a este punto, escribe lo que se le pide. Supongamos que se equivoca y tiene que usar la tecla de borrado para hacer las correcciones. En cuanto pulsa la tecla INTRO, lo que ha escrito es recibido por el programa. Hay que hacer notar varias cosas:

  1. Hasta que el usuario no pulsa la tecla INTRO, el programa no recibe nada. Es así porque el driver del terminal guarda lo que el usuario va escribiendo en un buffer y hasta que no recibe el carácter de la tecla INTRO no valida el contenido (y entonces entrega dicho contenido al programa que lo esté esperando).
  2. Mientras el usuario escribe, puede pulsar una serie de teclas especiales conocidas por el driver para editar lo que escribe. Las más importantes son (entre paréntesis ponemos los valores más habituales, donde la notación ^u, por ejemplo, significa pulsar la tecla Control y la u):
    erase (^h o ^?)

    Es la tecla que sirve para borrar los últimos caracteres escritos.

    kill (^u)

    Es la tecla que sirve para anular toda la línea que llevamos escrita hasta el momento.

    intr (^c)

    Es la tecla que, cuando es recibida por el driver, corta el programa que se esté ejecutando en el terminal.

    Según esto, si el usuario está escribiendo algo y quiere anularlo, y volver a empezar, debe pulsar ^u. El parámetro que debe cambiar más a menudo el usuario es el de la tecla de borrado (erase), debido a que algunos terminales generan un código al pulsar la tecla de borrado y otros generan otro diferente; unos generan el código 8 (^h) y otros el 127 (^?). Si el driver está configurado para que la tecla de borrado sea ^? y nos conectamos desde una terminal cuya tecla de borrado genera el código 8 ^h, cuando queramos borrar ocurrirá lo siguiente:
    en un lugar da^H
    Vemos que al intentar borrar la letra "a" para poner una "e", en vez de borrarse aparece el código generado por nuestra tecla de borrado, que no es el que espera el driver para borrar. Igualmente sucede a menudo el caso contrario: el driver tiene configurada para borrar la tecla que genera el 8 (^h) y la tecla de borrar de nuestro terminal genera el 127 (^?). Entonces aparecería:
    en un lugar da^?
    Esto ocurre a menudo porque hay gran cantidad de terminales de ambos tipos. La forma de solucionarlo es la siguiente:
    stty erase <-
    Donde hemos puesto <- para indicar que ahí se pulsa nuestra tecla de borrado. Si esto ocurre a menudo o siempre que nos conectamos, podemos poner una de las dos líneas siguientes en nuestro fichero .cshrc:
    stty erase "^h"
    stty erase "^?"
    De esta forma, literalmente, también se puede hacer, pero aquí tenemos que conocer el código que genera nuestra tecla de borrado. De la misma forma podemos cambiar los caracteres para las operaciones kill o intr.
Todo lo explicado hasta ahora lo controla el driver de terminales, que intercepta todo lo que le llega al programa del usuario. Este no puede alterar ese comportamiento si no es modificando los parámetros del driver. Por ejemplo, un programa como un editor debe reconocer cuándo ha pulsado el usuario las teclas de movimiento del cursor inmediatamente, sin esperar hasta que el usuario pulse INTRO. Para ello debe modidificar los parámetros del driver, o sea, la disciplina de línea. La forma de hacer esto desde un programa C no es la misma en todas las versiones de UNIX, pero normalmente está explicado en las páginas de manual de termio o de termios, según la versión. La librería curses incluye muchas funciones para situar el cursor en pantalla, crear ventanas, etc. Ver su página de manual para más información. Un caso que no se explica con esto son los programas como muchos juegos, en los que el programa no está esperando hasta que el usuario pulse algo desde el teclado, sino que la acción sucede continuamente y el usuario pulsa ocasionalmente teclas para diversas acciones. Un programa de este tipo por supuesto debe enular el parámetro que hace que la entrada del usuario sólo se reciba cuando éste pulsa INTRO (como los editores, programas de menú, etc), sino también tener la posibilidad de averiguar regularmente si el usuario ha pulsado algo, y si no seguir con lo que esté haciendo. Este no es un problema ya de terminales sino de la función del sistema operativo que se esté usando para leer la entrada del terminal. Normalmente estas funciones se quedan esperando hasta que el driver de terminales entregue algo (o sea, hasta que el usuario pulse alguna tecla o INTRO, según sus parámetros). El comportamiento que nos interesa en este caso se llama lectura no bloqueante, y existen distintos mecanismos en UNIX para utilizarlo.

Control de flujo

Otra de las cosas de las que se encarga el driver de terminales es el control de flujo, entendido aquí como la posibilidad del usuario de interrumpir momentaneamente la salida a pantalla (por ejemplo, si ésta es muy abundante y no da tiempo a leerla) y hacer luego que continue. Hay dos caracteres para eso, que mencionamos junto con sus nombres para el comando stty (entre paréntesis, sus valores por defecto):

stop (^s)

Al pulsar esa tecla, la salida a pantalla se detiene.

start (^q)

Al pulsarla, se desbloqeua la salida.

Esto produce una de las confusiones más comunes a los usuarios, que pulsan sin darse cuenta ^s y piensan que la pantalla se ha bloqueado. Cuando ocurra este síntoma, lo primero que se debe intentar es pulsar ^q.

Otras opciones del comando stty

stty -a

Nos informa de todos los parámetros actuales de nuestra terminal. Al principio salen las teclas asignadas y algunos otros valores. A continuación, algunos parámetros que pueden estar activados o no. Si no lo están, aparecen con un guión delante. Así, icanon indica que ese parámetro está activado, pero -istrip indica que este otro no lo está.

stty echo, stty -echo

La opción -echo sirve para desactivar el eco al terminal. Esto se explicó al principio de este documento. La opción echo vuelve a activarlo. Esto se puede usar desde una shell-script que tenga que pedir una clave y que ésta no aparezca en pantalla.

stty -istrip cs8

Este caso ya lo comentamos antes. Se usa cuando sospechamos que el driver está funcionando a 7 bits en vez de a 8. El síntoma es que no vemos caracteres acentuados, ñ, etc, ni tampoco podemos escribirlos.

stty rows 24 cols 80

Con eso indicamos el tamaño de la pantalla. Sólo es necesario a veces, sobre todo al conectarnos a otro ordenador si al usar un editor no sale bien (en ese caso, por supuesto lo primero que hay que mirar es que la variable TERM tenga el valor adecuado).

Ya hemos comentado en varias ocasiones el comando stty para adecuar a nuestro gusto la disciplina de línea de nuestro terminal. En la página de manual del comando (así como en las de termio o termios) se mencionan todas las opciones. Vamos a comentar ahora algunas de las más usuales. También hemos dicho que los editores y otros programas a toda pantalla alteran la disciplina de línea para leer inmediatamente las pulsaciones del usuario. Cuando uno de estos programas termina, debe dejar el terminal en el estado en el que estaba inicialmente, que es el adecuado para el uso de una shell interactiva. Sin embargo, puede que el programa aborte repentinamente o lo cortemos con ^c. En ese caso la terminal no queda en el estado apropiado. Síntomas típicos son que no vemos lo que escribimos, que al pulsar INTRO el indicador de la shell aparece al lado y no en la línea siguiente, etc. La forma de arreglarlo es:
^j
stty sane^j
Exactamente esa secuencia, sin pulsar INTRO hasta después de ella. El comando stty sane pone al terminal en un estado "sano". El ^j es porque UNIX usa el código 10 como salto de línea, por tanto es el código que espera para aceptar una orden. La tecla INTRO suele generar el 13, y una de las cosas que hace el driver de terminales es cambiarlo por un 10. Como en este caso el driver se ha quedado "indispuesto", tampoco hace eso. Al pulsar ^j generamos directamente un carácter 10.

Emuladores de terminal y otros tipos de conexión

Como hemos dicho, la conexión por terminales asíncronos conectados directamente al ordenador no es actualmente lo más habitual. Aunque todo lo dicho hasta ahora se aplica a cualquier tipo de conexión con un sistema UNIX, vamos a comentar algunas peculiaridades de otros tipos de conexión. Un emulador de terminal es un programa que nos permite trabajar como si estuviéramos en una terminal pero sin estarlo. Por ejemplo, un PC no es un terminal en el sentido que hemos considerado hasta ahora, es decir, cada tecla no envía un código y la pantalla no se gestiona con códigos, sino directamente por los programas que se ejecutan en el PC. Cuando conectamos el PC a un ordenador UNIX por red local o mediante un cable serie, se necesita un programa que "simule" el comportamiento de una terminal. Además deben simular a un tipo de terminal más o menos estandar, uno que esté registrado en la base de datos de terminales de los sistemas UNIX, como ya mencionamos. Ejemplos de estos programas para PC son Kermit, ProComm, HyperTerminal, etc. Casi todos ellos simulan a terminales VT (vt100, vt200, etc) que son los más ampliamente utilizados. Por tanto, al conectarnos con ellos a UNIX, si la terminal no funciona correctamente habrá que poner el valor de la variable TERM a vt110 o vt200 probablemente. Estos programas suelen tener una configuración en la que podemos elegir el tipo de emulación, así como otros parámetros como si deben funcionar a 7 u 8 bits, qué código genera la tecla de borrado (suelen dar dos opciones: BS, que es el código 8 o ^h y DEL, que es el 127 o ^?). En una terminal X o en una estación gráfica ocurre lo mismo. En este caso se suelen usar los programas xterm o cmdtool (éste último es propio de los ordenadores SUN). Los valores que debe tomar la variable TERM en estos casos es xterm y sun-cmd, respectivamente. Al ejecutar uno de estos programas, la variable TERM se pone automaticamente, por lo que no hay problema. Pero si luego nos conectamos a otro ordenador con rlogin, su valor se perderá y puede que haya que ponerlo.

telnet y rlogin

El programa telnet está disponible en muchos tipos de ordenadores y sistemas operativos diferentes; permite que nos conectemos a través de una red local a otro ordenador, normalmente UNIX (también podemos conectarnos por telnet a otros tipos de sistemas operativos). rlogin es semejante, sin embargo es practicamente exclusivo de UNIX. En primer lugar, hay que decir que rlogin nos conecta a otro ordenador a 7 bits. Esto significa que los caracteres acentuados, ñ, etc no podremos verlos ni escribirlos. Para que se conecte a 8 bits hay que ejecutar:

rlogin ordenador -8
Terminales X y estaciones gráficas

pp Lo único que comentaremos aquí es que puede haber problemas con los caracteres especiales debidos no sólo a los factores comentados en este documento, sino relacionados con tipos de letra y configuración de teclados, que en estos casos es algo complicada. Si surgen este tipo de problemas, deben ponerse en contacto con los administradores de sistemas.