_images/Logo_OCW5.jpg

Parte 2. Probando la entrada/salida de datos

En la reciente reunión del Consorcio para el proyecto del robot explorador se adoptó el uso de Linux como plataforma de desarrollo y de implementación. Los socios responsables de la parte hardware se encargarán de desarrollar los drivers necesarios para los sensores, de forma coordinada con los fabricantes de estos. Los sensores se accederán desde las aplicaciones Linux como dispositivos de caracteres en el directorio /dev de la manera habitual en Linux. Por ejemplo, el sensor de CO2 se verá en el sistema de ficheros de Linux como /dev/CO2. De esta forma, y mientras el hardware no esté disponible, podremos avanzar en el desarrollo del Módulo de Cartografiado de la Probabilidad de Vida (MCPV) con secuencias de entrada de caracteres que representan los datos suministrados por los sensores.

Una primera versión del MCPV va a considerar un único sensor, el de CO2. El MCPV leerá valores de CO2 por la entrada estándar y los convertirá en probabilidades de vida, que producirá como salida. Los datos del sensor serán dígitos en el rango [‘0’..‘5’], que representan concentraciones de CO2, y que se corresponderán linealmente con probabilidades en el rango [0..1]. Por ejemplo, un valor de 3 corresponde a un probabilidad de 3/5 = 0.6. Una entrada:

3015

producirá una salida:

0.6
0.0
0.2
1.0

En la reunión se advirtieron las condiciones extremas de operación con las que se va a encontrar el robot. El suministro de energía será limitado, por lo que se utilizará un procesador de bajo consumo y poco potente. La cantidad de memoria disponible será asimismo reducida, ya que las memorias RAM y flash comerciales no son resistentes a los rayos cósmicos y los discos duros están descartados por su escasa tolerancia a los golpes, lo que obliga a utilizar tecnologías de baja densidad de integración. Todo esto implica que la placa procesadora que controla el robot montará una versión reducida de Linux, y en consecuencia esta carecerá de muchos de los módulos de los Linux de sobremesa, como gran parte de la biblioteca estándar de C.

Finalmente, preocupan las consecuencias de un fallo del sistema de alimentación del robot o del propio sistema operativo durante el almacenamiento de los datos. Como medida complementaria se propuso que el robot enviara en tiempo real a La Tierra la secuencia de datos capturada por los sensores. Esto deberá hacerse de forma cifrada para evitar que sea interceptada por la NASA o la FKA (Agencia Espacial Federal Rusa).

Actividad 2.1. Manejando los datos del sensor con el shell de Linux

Dedicación estimada: 60 minutos

La entrada-salida de UNIX/Linux se apoya en la abstracción fichero. Más allá de lo que entendemos coloquialmente por “fichero”, en UNIX/Linux un fichero es una abstracción en el sentido de que representa cualquier objeto del que podamos leer información y/o sobre el que podemos escribir información. Esto es muy interesante, ya que representa tanto a ficheros ordinarios como a dispositivos de todo tipo, incluidos los sensores y más cosas, ocultando al programador las características específicas de estos elementos y facilitando la construcción de programas, ya que estos funcionarán con independencia de donde tomen la entrada y arrojen la salida. Así, Linux asigna nombres en el sistema de ficheros, por ejemplo, al terminal (/dev/tty) o, una vez que lo instalemos, a nuestro sensor, /dev/CO2.

De acuerdo a esta idea, el shell de Linux proporciona mecanismos sencillos y potentes que nos permitirán manejar las secuencias de datos suministradas por los sensores.

Qué hay que hacer

  1. En primer lugar, debes aprender a manejar los mecanismos de entrada/salida del shell, aunque probablemente ya los conozcas. Esta batería de pruebas con el shell te servirá tanto para evaluar tu nivel de manejo actual del shell como para explorar y aprender las posibilidades de este en cuanto a la entrada/salida. Crea un subdirectorio para esta actividad y sitúate en él. Intenta anticipar el resultado de la ejecución de cada orden de la secuencia, ayudándote del man si es preciso, y luego ejecuta la orden para comprobar su efecto, verificar si lo has predicho correctamente, y, en su caso, revisar por qué no has acertado.

  2. A los programas cuya entrada/salida puede ser redirigida desde/hacia uno u otro fichero (mediante < y >) se les denomina filtros. Como la mayoría de las órdenes que has probado, cat funciona como un filtro (aunque alternativamente admite especificar como parámetros la entrada y la salida). Si te fijas, nuestro MCPV también se puede construir como un filtro, y de eso nos ocuparemos en la siguiente actividad. Cuando construyas entonces tu MCPV, tendrás que alimentarlo con datos del sensor ./MCPV < /dev/CO2. Pero como este sensor no existe todavía en /dev, de momento lo representaremos en nuestro directorio local, de forma que nos referiremos a él como ./dev/CO2. Con lo que has aprendido en el paso anterior no te será difícil preparar un dev/CO2 que contenga una secuencia con unas pocas decenas de valores en el rango especificado, que luego te servirá para probar tu MCPV.

    Nota

    Tienes varias formas de hacerlo. Si proporcionas entrada desde teclado, recuerda que el fin de la entrada se genera con [ctrl] D.

Resultados

La actividad se evalúa como correcta si has sido capaz de preparar el contenido de dev/CO2 con una secuencia de valores utilizando las redirecciones del shell.

_static/up.jpg

Actividad 2.2. Nuestro módulo es un filtro

Dedicación estimada: 2 horas

Vamos a construir un primer prototipo del MCPV que trate los datos del sensor de CO2 para generar las probabilidades de vida de acuerdo a lo especificado. Para aprovechar la potencia del shell vamos a construir el MCPV como un filtro, y como tal podrás probarlo con los valores del sensor que has preparado en la actividad anterior o con una secuencia que introduzcas desde cualquier otro dispositivo (fichero), como el terminal.

Qué hay que hacer

  1. Construye el MCPV según lo especificado utilizando funciones de la biblioteca estándar de C (más adelante prescindiremos de ella), pero evita las funciones de entrada con formato (scanf). Considera que los dígitos de entrada vienen en una secuencia sin ningún separador entre ellos y haz que ignore cualquier carácter que no esté en el rango [‘0’..‘5’].

  2. ¿Es tu MCPV un filtro? Si ejecutas simplemente: ./MCPV, ¿de dónde toma la entrada? ¿Se puede redireccionar? Haz diferentes pruebas, combinando redirección de la entrada y la salida, por ejemplo:

    ./MCPV < dev/CO2 > Probabilidades
    
  3. MCPV no tiene parámetros (¡las redirecciones no lo son!), por lo que debería informar con un mensaje si se le pasa alguno. Si no habías previsto esta comprobación, utiliza como modelo el código de cualquiera de los ejemplos de la actividad 1.7 para introducir el control del número de parámetros y pruébalo con, por ejemplo:

    ./MCPV dev/CO2 > Probabilidades
    
  4. Inspecciona con cuidado cómo ha funcionado esta prueba. ¿Te parece razonable este comportamiento? Es el momento de recapitular sobre el concepto de entrada/salida estándar del modelo UNIX/Linux. Modifica ahora el tratamiento de errores para que el mensaje se escriba por la salida de error estándar.

  5. ¿Se puede redireccionar la salida de error estándar? Sería realmente útil. Por ejemplo, nos permitiría llevar a un fichero la salida del gcc para analizar detenidamente los errores, o crear un fichero de log para nuestro robot donde se recojan las incidencias. Investiga si el shell permite redireccionar la salida de error estándar y vuelve a forzar un error en el número de parámetros como el del punto 3. ¿Eres capaz de llevar los mensaje de error a un fichero Errores mientras redireccionas las salida estándar a Probabilidades?

Resultados

La actividad se evalúa como correcta si MCPV funciona como un filtro de acuerdo a lo especificado y puedes ejecutarlo de forma que los mensajes de error queden en un fichero Errores y la salida del programa en Probabilidades.

_static/up.jpg

Actividad 2.3. Cifrando los datos

Dedicación estimada: 4 horas

De acuerdo a lo propuesto en la reunión del Consorcio, deberemos proporcionar una salida extra con la secuencia de valores de entrada de sensores cifrada para su envío a La Tierra. Para la implementación vamos a aprovechar la modularidad que nos proporciona el uso de filtros en Linux, desarrollando una herramienta de cifrado, que llamaremos encriptador, y que es un filtro que va a funcionar autónomamente.

Además deberemos abordar la posibilidad de que haya que prescindir de la Biblioteca de C.

Qué hay que hacer

  1. Como modelo para programar el encriptador y, más adelante, el descifrador, te suministro dos versiones de una misma herramienta que ya existe en Linux, tee(1):

    • La versión ctee, en un principio desarrollada para Windows pero que, como puedes comprobar, se puede compilar para Linux.
    • La versión stee, programada con llamadas al sistema Linux (y que por lo tanto no es compatible con Windows).
  2. Prueba ambas versiones. Observarás que, como filtros, ambas son funcionalmente equivalentes, en el sentido de que dan idéntica salida para una misma entrada. En otras palabras: implementan la misma especificación funcional (que sería un subconjunto de la de tee(1)). Analiza su código con ayuda del man para responder a las siguientes preguntas: (a) ¿qué llamadas al sistema usa stee y cómo son sus parámetros?, (b) ¿cómo se representan los descriptores de ficheros en ambas versiones?

  3. Prepara un fichero de texto largo, de algunos miles de caracteres. Si se lo suministras a stee y a ctee como entrada, ¿notas alguna diferencia en la ejecución de ambos? Fíjate que los pequeños retardos, con usleep(), del bucle no son casuales... ¿Y en cuanto al rendimiento? Para responder a esto último, elimina los usleep(), introduce un cronómetro con gettimeofday() como hiciste para el reloj, y compara los tiempos de ejecución de ambas versiones.

  4. Es necesario explicar las diferencias de comportamiento que has observado. Ahora comprendemos mejor alguna de las implicaciones de utilizar las bibliotecas de entrada/salida de C o las llamadas al sistema. Seguramente sabrás interpretar los resultados del punto anterior en clave de los buffers de la biblioteca y responder a la siguiente pregunta: ¿qué tamaño de buffer utiliza la biblioteca de entrada/salida de C?

  5. Construye una primera versión del encriptador utilizando la biblioteca estándar de C, a partir del código de ctee. Puedes llamar al fichero fuente por ejemplo cencriptador.c, pero llama al ejecutable encriptador. El documento de especificación no dice qué método de cifrado utilizar, lo que se justifica porque este documento ha de servir luego como manual de usuario, y el método y la clave de cifrado queremos que sean confidenciales. Como función de cifrado aplicaremos la función XOR entre la clave y cada carácter ASCII de la entrada, lo que nos permitirá usar luego la misma clave para descifrar, ya que (a XOR k) XOR k = a, siendo k la clave. La clave será de 4 bits y se define internamente en el código. Se aplicará a los 4 bits menos significativos de los caracteres ASCII de entrada, evitando cifrar caracteres especiales como el salto de línea.

  6. Vamos a comprobar su funcionamiento de forma preliminar. Una verificación más completa podrás llevarla a cabo más adelante, una vez que tengamos un descifrador. Crea un shell script con el siguiente contenido:

    stee para_cifrar < dev/CO2 | ./MCPV
    ./encriptador < para_cifrar salida_cifrada
    rm para_cifrar
    

    y comprueba que, dada una secuencia de valores del sensor, se obtiene (a) las probabilidades de vida por la salida estándar, y (b) el fichero salida_cifrada con la entrada cifrada.

    Nota

    El operador | del shell permite conectar la salida estándar de un programa con la entrada estándar de otro. Más adelante entraremos en los detalles de esto.

  7. A partir de la versión anterior te será sencillo programar una nueva versión del encriptador utilizando llamadas al sistema si usas el código de stee como modelo para la entrada/salida. Puedes llamar al fichero fuente sencriptador.c, pero crea el ejecutable en encriptador para que puedas probarla con el script del punto anterior. Comprueba que es funcionalmente equivalente a la versión con la biblioteca de C. La versión con llamadas al sistema que has construido será la candidata a montar en el robot. Es cierto que utiliza fprintf sobre stderr, pero no nos sería difícil implementarla como una macro si no nos la proporcionaran como función de biblioteca.

Resultados

La actividad se evalúa como correcta si, de acuerdo a la prueba propuesta, ambas versiones del encriptador cumplen la especificación funcional.

_static/up.jpg

Actividad 2.4. Construcción del descifrador terrícola

Dedicación estimada: 4 horas

Ahora necesitamos construir la utilidad de descifrado para utilizar sobre los datos recibidos en La Tierra. De acuerdo a lo especificado para el cifrado la clave va contenida en el propio código de la aplicación, y habrá sido cuidadosamente custodiada aquí en La Tierra. Para descifrar los datos recibidos deberemos introducir la clave al usar el descifrador. Como es habitual, esto hay que hacerlo secretamente, es decir sin que la clave aparezca en pantalla al teclear.

Estamos acostumbrados a una determinada forma de trabajo con el teclado de un ordenador o cualquier otro dispositivo. Pulsar una tecla, por ejemplo la correspondiente al carácter ‘a’, provoca ciertamente una operación de entrada, la lectura del carácter ‘a’, pero lleva también asociada una operación de salida: la escritura del carácter ‘a’ en la pantalla. A esto se le conoce como “eco”. La cosa se complica un poco más cuando pulsamos la techa de borrar: se sustituye el último carácter escrito por un espacio en blanco y el cursor se retrasa una posición. Otros comportamientos singulares como este tienen lugar cuando se pulsa el salto de línea o una tilde. Finalmente, muchas aplicaciones interpretan las teclas a su manera: el comando more o el man interpretan el espacio en blanco y otros caracteres como órdenes, y lo mismo ocurre con algunos editores o con un juego.

Queda claro que el dispositivo /dev/tty, que en Linux se “ve” como un fichero y como tal se aplican las llamadas al sistema habituales (open(), close(), read(), write(), etc), tiene un buen número de características singulares que lo distinguen claramente de los ficheros ordinarios. Esto mismo se aplica al resto de dispositivos representados en el directorio /dev.

En esta actividad vamos a explorar la forma de configurar el terminal para que podamos introducir la clave en nuestro descifrador de forma secreta.

Qué hay que hacer

  1. Explora la orden stty en el shell. Utilízala para desactivar y volver a activar (¡esto último a ciegas!) el eco del teclado.
  2. Evidentemente, el control sobre el modo de funcionamiento del teclado ha de estar soportado por llamadas al sistema y las funciones de biblioteca C correspondientes. Explora esta función de cambio de modo que te proporciono e identifica dichas funciones de biblioteca. Búscalas en el man para que puedas comprender el funcionamiento de la función de cambio de modo eco. Finalmente, busca las llamadas al sistema correspondientes. Puedes observar que el manejo de los modos de operación es farragoso. No es central para el desarrollo del robot, así que basta con que entiendas lo que hace cambia_eco.
  3. Ahora ya estás en condiciones de abordar la implementación del descifrador. Elabora un documento con los casos de prueba para la verificación del descifrador de acuerdo a su especificación. Ten en cuenta que la verificación ha de hacerse conjuntamente con la del encriptador, que había quedado pendiente, lo que simplificará el diseño de lo casos de prueba.
  4. Programa el descifrador utilizando la función de cambio de modo proporcionada. Recuerda que se va a utilizar en La Tierra, así que puedes usar la biblioteca de entrada/salida de C. Una particularidad del descifrador es que la entrada y la salida se especifican como parámetros. De nuevo, ctee.c te servirá como modelo. Verifica su funcionamiento utilizando los casos de prueba y generando la entrada con encriptador.

Resultados

La actividad se evalúa como correcta si el descifrador cumple la especificación y la verificación conjunta de las dos aplicaciones es satisfactoria.

Actividad 2.5. Cómo Linux gestiona la entrada/salida

Dedicación estimada: 1 hora

Es el momento de recapitular sobre cómo Linux soporta todas las formas de entrada/salida.

Qué hay que hacer

  1. Aquí tienes un resumen acerca de cómo se organiza e implementa la entrada/salida en el modelo UNIX/Linux. Léelo y consulta si lo estimas necesario la bibliografía sobre UNIX/Linux.

Resultados

Evalúa lo que has aprendido con este test de autoevaluación. Repasa los conceptos en función de los resultados obtenidos.

_static/up.jpg

Actividad 2.6. ¿Están seguros nuestros datos en el robot?

Dedicación estimada: 2 horas

En las actividades anteriores hemos desarrollado un mecanismo para evitar que la información pueda ser escuchada en su transmisión a La Tierra. Como en Marte no existe vida inteligente, no hay que temer por el robo de la información directamente en el robot. Sin embargo la información está expuesta a otras eventualidades. Por ejemplo, ¿qué ocurrirá con la información ante un problema de alimentación? Se supone que los ingenieros encargados de la gestión de la energía del robot estarán implementando los mecanismos adecuados para minimizar este riesgo. No obstante, nosotros tendremos parte de responsabilidad si finalmente ocurre este problema. ¿Es seguro el sistema de ficheros de Linux al respecto? Hay que tener en cuenta que apagar un ordenador “en caliente” suele tener consecuencias desagradables.

Qué hay que hacer

  1. Considera la siguiente cuestión: ¿por qué no es conveniente apagar un ordenador “en caliente” (desenchufando la alimentación) o extraer un dispositivo externo sin “expulsar”? Para responder a esta cuestión te será de ayuda el siguiente texto, que M.J. Rockhind, en su libro sobre programación avanzada en UNIX, pone en boca del sistema operativo como respuesta a una llamada al sistema write():

    He tomado nota de tu petición (…). He copiado tus datos satisfactoriamente y comprobado que hay espacio suficiente en el disco. Más tarde, cuando pueda y si sigo vivo, intentaré poner tus datos en su lugar correspondiente del disco. Si entonces descubro un error, intentaré escribir algo en el terminal, pero no te lo comunicaré directamente (igual entonces ya has acabado). Si tú, u otro proceso, intentáis leer los datos antes de que los escriba, te los proporcionaré directamente desde el buffer-cache en memoria, de forma que, si todo va bien, no podrás saber si he completado la escritura ni cuándo lo he hecho. No preguntes nada más. Confía en mí y agradece mi rápida respuesta, que me imagino es lo que realmente te importa.

    El mecanismo de escritura retardada descrito en este texto de Rockhind permite al sistema acortar significativamente los tiempos de respuesta en la escritura. Los datos se van acumulando en un buffer que se escribe sobre el dispositivo en el momento adecuado. Fíjate que un mecanismo análogo de lectura retardada carecería de sentido, ya que los datos leídos deben estar disponibles para que un programa pueda continuar. Aun así, el uso general de buffers del sistema operativo para todas las operaciones de entrada/salida permite también acelerar la lectura, al tener disponible para la biblioteca de la aplicación la entrada de forma anticipada (siempre que el buffer del sistema tenga al menos el tamaño del de la biblioteca). Esto explica por qué en la actividad 2.3 no hallaste una mejora significativa en el rendimiento del programa ctee con respecto a stee.

    Nota

    El tema de los buffers no acaba aquí. Los controladores de los dispositivos incluyen buffers-cache donde almacenan los bloques recientemente accedidos para evitar en lo posible los costosos accesos al disco. Por no hablar de que por el otro extremo nos encontramos con una jerarquía de memorias cache entre la RAM y el procesador...

  2. La escritura retardada tiene su contrapartida. Ante un problema de alimentación o de cualquier otro tipo, el buffer con la información en RAM aun no volcada a memoria permanente se perdería. Esto puede afectar tanto a ficheros ordinarios como a directorios y conducir a inconsistencias en el sistema de ficheros, algo que quizás hayas experimentado alguna vez. Para evitar este problema en nuestro robot, debemos preguntarnos si se puede forzar a Linux para que escriba físicamente en el dispositivo cada vez que lea un valor de sensor. Si exploras en Internet y en el man, encontrarás el mecanismo que ofrece Linux, en cuanto a llamadas al sistema, para forzar la escritura inmediata. Introduce este mecanismo en stee.c para que el script de la Actividad 2.3 proporcione una escritura segura en el fichero para_cifrar. Distinguir el funcionamiento del stee con este mecanismo o sin él no es fácil... Linux nos engaña muy bien cuando no lo usamos. Una prueba definitiva consiste en redireccionar la salida a un fichero en un dispositivo USB externo y extraerlo durante la ejecución del programa con ambas versiones, el stee original y el de escritura forzada. Utiliza un fichero de entrada de gran tamaño e introduce un pequeño retardo entre las escrituras. ¿Se conservan los datos?

    Nota

    En adelante, el mecanismo de escritura forzada puedes introducirlo en las sucesivas versiones de los módulos involucrados en el funcionamiento del MCPV.

  3. Una consecuencia de prescindir de los buffers del sistema operativo es que el rendimiento debería empeorar. Introduce un cronómetro, como hemos hecho otras veces, para medir el tiempo de ejecución del programa en ambas versiones del stee. Asegúrate de eliminar los retardos que habías introducido. ¿Notas diferencias importantes? Puedes repetir las pruebas usando diferentes soportes para el fichero de salida, si tienes posibilidad de ello.

Resultados

Evalúa lo que has aprendido con este test de autoevaluación. Repasa los conceptos en función de los resultados obtenidos.

_static/up.jpg _images/Licencia5.jpg