Buses y Periféricos 2010/2011 Práctica 3. Ensamblador Reloj Descripción El objetivo de esta práctica es utilizar las interrupciones de BIOS y MSDOS para presentar un reloj en pantalla. Se hará uso del sistema de interrupciones del procesador para obtener una referencia temporal basada en el reloj del sistema y además instalaremos el programa como residente para que su funcionamiento no interfiera con la interacción entre el usuario y el sistema de comandos. Requerimientos En un recuadro situado en la esquina superior derecha [según se mira] de la pantalla se mostrará un pequeño recuadro en el interior del cual aparecerán seis dígitos emparejados dos a dos y separados, cada pareja, por dos puntos. La pareja de más a la izquierda mostrará la hora, la pareja central los minutos y la de más a la derecha los segundos. Esta imagen estará actualizándose constantemente incluso durante la ejecución de otros programas que podremos invocar desde el sistema de comandos sin ningún impedimento. El programa, antes de iniciar el reloj deberá solicitar al usuario el estado inicial de los datos horarios. Y además deberá introducirse una opción que desinstale el reloj para que deje de mostrarse. Procedimiento: Para proporcionar una referencia temporal para nuestro reloj necesitaremos instalar una función en la interrupción 1Ch. Esta interrupción la genera el sistema 18’2 veces por segundo. Tendremos que escribir una rutina que actualice un contador cada vez que sea disparada. Cuando este contador alcance la cuenta de 18 habrá transcurrido aproximadamente un segundo y es el momento de actualizar el reloj. Se invocará entonces a otra rutina que se encargará de actualizar los datos del reloj y volver a escribir toda la información en pantalla. 1 Buses y Periféricos 2010/2011 Práctica 3. Ensamblador El programa principal inicializará los datos del reloj – pidiendo por teclado al usuario estos datos – e instalará la función de interrupción en sus vector correspondiente y luego tendrá que dejar el código de estas funciones residente en memoria antes de salir de la ejecución dejando el sistema funcionando. El programa principal también tendrá una opción – que deberá preguntar al usuario [excelencia: o bien leer como parámetro de la línea de comandos] – para desinstalar la función, para lo cual deberá restaurar el contenido del vector de interrupción a su valor original. Documentación: Instalar una función en un vector de interrupción implica guardar en la posición del vector correspondiente la dirección de la rutina. La dirección de una rutina (y de hecho cualquier dirección de memoria) viene especificada con un formato segmentado: segmento:desplazamiento. Cada campo tiene 16 bits. El segmento (x16) indica el comienzo de una zona en memoria y el desplazamiento indica cuantos bytes hay que desplazarse a partir de esa dirección. Dada la dirección simbólica de la rutina (nombre) el segmento se obtiene con la directiva SEG aplicada al nombre de la rutina y el desplazamiento con la directiva OFFSET aplicada al nombre de la rutina. Obtenidos estos datos hay que instalarlos en el vector correspondiente. Para facilitarlo hay una llamada al sistema operativo que lo hace: (buscar en internet int 21h, ejemplo: http://www.esimez.ipn.mx/computacionv/Hojas/INT%2021H%20Y%2010%20H.pdf) La función en concreto es la 25h. En los parámetros se suministra el segmento en el registro DS y el desplazamiento en DX. El vector de interrupción donde cargarlo debe ir en AL. Antes de instalar la nueva función en el vector debemos prever que ese vector se esté utilizando previamente por otra aplicación por lo que convendría recuperar la dirección que hubiera ya instalada, la localizáramos en memoria y saltáramos a ella después de haber realizado nuestras acciones. Para recuperar la dirección que hubiere previamente en el vector de interrupción se utiliza la función 35h. La parte del programa que actualiza los datos del reloj necesitará escribir en pantalla en una localización concreta. Para ello utilizaremos interrupciones de la BIOS (int 10h) (http://www.uv.tietgen.dk/staff/mlha/PC/Prog/ASM/INT/INT10.htm). 2 En concreto, Buses y Periféricos 2010/2011 Práctica 3. Ensamblador tendremos que posicionar el cursor con la función 02h, recordar siempre inicializar el registro BH a la página 0, en DH y DL colocaremos el número de fila y columna donde localizaremos el cursor. Trabajaremos con una pantalla de 80 columnas por 25 filas. La ejecución de la rutina de interrupción no debe interferir con el programa que se esté ejecutando actualmente por lo que antes de cambiar el cursor de su posición actual a la de nuestra aplicación, tendremos que guardar esa posición para restaurarla luego, antes de salir de la interrupción. Para leer la posición del cursor usaremos la función 03h y para escribir en la posición actual de cursor un carácter usaremos la función 0Ah. (Sería bonito que el reloj apareciera en colorines) El programa principal tiene que solicitar datos de teclado y para ello tendrá que imprimir un texto de pantalla, volvemos a usar aquí las funciones de DOS, int 21h, con la función 09h para imprimir un texto y la función 08h. Por último el programa principal debe dejar el código y los datos de la rutina de interrupción residente en memoria, de manera que al salir esa zona quede protegida por el sistema operativo (necesario puesto que cada vez que se dispare la interrupción se irá a buscar allí el código a ejecutar) De ello hablamos en la siguiente sección. Siguiente Sección: programas residentes Cuando ejecutamos un programa el sistema operativo carga su código ejecutable en una zona de memoria e inicia su ejecución. Cuando esta termina debe invocar un retorno al sistema operativo para que este retome el control del sistema. Hay varias maneras de hacerlo, una es invocando a la función 4Ch (int 21h). Para nuestros propósitos nosotros buscaremos una manera más complicada. En la zona de memoria en la que se instala nuestro programa, precediendo a los datos y el código ejecutable hay 100h bytes de información de control. Los primeros dos bytes son en realidad una instrucción que retorna el control al sistema operativo (int 20h). Para poder ejecutar esa instrucción tendríamos que conseguir hacer un saldo hacia ella como última acción de nuestro código. Lo haremos colocando un RETF. Un retf es un retorno de rutina (llamado largo), que lee de la pila el desplazamiento y segmento de la dirección hacia la que tiene que retornar. Como primera acción de nuestro programa debemos instalar en la pila esos datos. El segmento se encuentra en el registro DS y el desplazamiento es simplemente un 3 0 por lo tanto Buses y Periféricos 2010/2011 Práctica 3. Ensamblador las primeras instrucciones que debe y la última ejecutar nuestro programa son retf esto debería funcionar. push ds mov ax,0 push ax Ahora bien, si queremos dejar nuestro programa residente en lugar de salir con int 20h debemos hacerlos con int 27h, CS debe estar apuntando al comienzo de la sección de datos de control (llamada PSP) y DX debe contener el número de bytes que deseamos dejar residentes a partir del comienzo de segmento (incluyendo la sección de 100h datos de control). El procedimiento será cambiar el segundo byte de la sección de control, que contiene un 20h, por un 27h. Colocar un puntero al final de la sección de código que queremos dejar residente y antes de ejecutar el retf cargarlo en el registro dx. Desinstalación y liberación de memoria. Por “desinstalar” el reloj se quiere decir que éste debe dejar de funcionar y desaparecer de pantalla. Para que deje de funcionar basta con quitar la dirección del programa que lo mantiene activo de la interrupción 1Ch. Con esto deja de dispararse el programa y dejará de refrescarse la imagen del reloj en pantalla. Es importante dejar en la interrupción 1Ch la dirección del programa que había antes (en realidad un salto a una localización donde hay una instrucción IRET, si nadie más ha manipulado la interrupción) Aunque ya se ha “desinstalado”, la zona de memoria reservada sigue estando reservada y habría que decirle al sistema operativo que la libere. Para ellos convendría invocar a la función 49h de la Int 21h facilitándole el segmento de la zona PSP de la sección de memoria a liberar. Esta información es la que hemos guardado al principio del programa (que originalmente estaba en DS al inicio de la ejecución) así que tendríamos que almacenarla en alguna parte accesible luego para poder disponer de ella. Una ultima cosa: Nuestro programa va a constar en esta ocasión de un único segmento de código, no crearemos un segmento de datos ni tampoco un segmento de pila. Los datos los localizaremos dentro del segmento de código, preferiblemente antes del código ejecutable, confiaremos al sistema la inicialización del segmento de pila. 4 Buses y Periféricos 2010/2011 Práctica 3. Ensamblador Evaluación Niveles de dificultad 1. El reloj queda instalado y residente (mínimo requerido) 2. Se añade una opción de desinstalar y deja de funcionar cuando se invoca con ella. 3. Se utiliza la línea de comandos para pasarle parámetros al programa 4. Se comprueba antes de desinstalar que lo que hay colgado de la interrupción es nuestra rutina. 5. Tras desinstalar se libera memoria. 6. Es posible instalar varias copias del reloj y que todas se mantengan en funcionamiento (encadenamiento de interrupciones) El alumno debe realizar el programa siguiendo las indicaciones, pero aportando su propia iniciativa. Se exige que el programa cumpla los requisitos, pero se admiten variantes del procedimiento siempre que sirvan para aportar conocimientos y no para evitar la adquisición de estos. Se valorará un programa bien diseñado, ordenado en términos de uso de procedimientos, macros, comentarios. Cada instrucción empleada en el programa debe estar justificada, el alumno debe ser capaz de explicar en la defensa el porqué y el para qué de cada línea de código. La memoria justificativa del trabajo no es un simple comentario de las instrucciones, sino un informe explicando funcionalmente el programa realizado, las dificultades, los defectos, las posibles variantes y mejoras. Bibliografía. Se encuentran en Internet multitud de páginas que describen cada una de las interrupciones BIOS y MSDOS. Así como tutoriales de introducción al ensamblador. Rodríguez Rosello, Miguel Angel 8088-8086/8087 : programación ensamblador en entorno MS DOS / Miguel Angel Rodríguez-Roselló Norton, Peter Guía del programador en ensamblador para IBM PC, XT, AT y compatibles / Peter Norton, John Socha ; [traducción, José Félix Rábago] [1ª ed.] Padrón Morales, Gabino Guía de programación en lenguaje ensamblador Las Palmas de Gran Canaria : Universidad de Las Palmas de Gran Canaria, Escuela Universitaria de Informática, 1994 5