EL UNIVERSO DIGITAL
DEL IBM PC, AT Y PS/2 Edición 4.0
Versión impresa del original electrónico ubicado en:
http://www.gui.uva.es/udigital
(4ª edición)
Limitación de garantía: Pese a que todos los programas e ideas incluidas en el libro han sido probados, el autor y el editor no se responsabilizan de los daños que su funcionamiento pueda ocasionar bajo ninguna circunstancia ni están obligados a corregir el contenido del libro.
Marcas registradas: IBM PCjr, PC, XT, AT, PS/2, OS/2 y Microchannel son marcas registradas de International Business Machines. MS-DOS, WINDOWS, Microsoft C y Microsoft Macro Assembler son marcas registradas de Microsoft Corporation. DR-DOS es marca registrada de Digital Research Inc. QEMM y Desqview son marcas registradas de Qarterdeck Corporation. UNIX es marca registrada de AT&T Bell Laboratories. Intel es marca registrada de Intel Corporation. Motorola es marca registrada de Motorola Inc. Turbo Assembler, Turbo C, Turbo Debugger y Borland C++ son marcas registradas de Borland International Inc.
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2 Ciriaco García de Celis Edición 4.0
Ediciones Grupo Universitario de Informática (Valladolid)
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2 - v4.0 Ciriaco García de Celis. Grupo Universitario de Informática, 1992-1997.
Publica: Asociación Grupo Universitario de informática, 1992-1997. Apartado de correos 6062, Valladolid. Internet: http://www.gui.uva.es Autor: Ciriaco García de Celis (http://www.gui.uva.es/~ciri) Registro de propiedad Intelectual nº 1121; Madrid, 1993. Versión electrónica en Internet: http://www.gui.uva.es/udigital Imprimió, durante la etapa impresa: Servicio de Reprografía de la Universidad de Valladolid. Casa del Estudiante, avda. Real de Burgos s/n. [Actualmente no se edita impreso; absténganse de contactar con ellos]. Tirada, durante la etapa impresa: Más de 1200 ejemplares. Licencia de uso y distribución: Ver página 11.
5
ÍNDICE
ÍNDICE PRÓLOGO DE LA EDICIÓN 4.0 ELECTRÓNICA....................................................................... PRÓLOGO DE LA TERCERA EDICIÓN (1994).......................................................................... 1 - INTRODUCCIÓN .......................................................................................................................... 1.1 - Números binarios, octales y hexadecimales ................................................................. 1.2 - Cambio de base ............................................................................................................. 1.3 - Estructura elemental de la memoria .............................................................................. 1.4 - Operaciones aritméticas sencillas en binario ................................................................ 1.5 - Complemento a dos ....................................................................................................... 1.6 - Agrupaciones de bytes .................................................................................................. 1.7 - Representación de datos en memoria........................................................................... 1.8 - Operaciones lógicas en binario ..................................................................................... 2 - ARQUITECTURA E HISTORIA DE LOS MICROORDENADORES........................................... 2.1 - Arquitectura Von Neuman.............................................................................................. 2.2 - El microprocesador ........................................................................................................ 2.3 - Breve historia del ordenador personal y el DOS ............................................................ 3 - MICROPROCESADORES 8086/88, 286, 386, 486 y Pentium .................................................. 3.1 - Características generales .............................................................................................. 3.2 - Registros del 8086 y del 286 ......................................................................................... 3.3 - Registros del 386 y procesadores superiores ............................................................... 3.4 - Modos de direccionamiento ........................................................................................... 3.5 - La pila ............................................................................................................................. 3.6 - Un programa de ejemplo ............................................................................................... 4 - JUEGO DE INSTRUCCIONES 80x86 ......................................................................................... 4.1 - Descripción completa de las instrucciones.................................................................... 4.1.1 - De carga de registros y direcciones ............................................................... 4.1.2 - De manipulación del registro de estado ......................................................... 4.1.3 - De manejo de la pila ....................................................................................... 4.1.4 - De transferencia de control............................................................................. 4.1.5 - De entrada/salida............................................................................................ 4.1.6 - Aritméticas ...................................................................................................... Suma............................................................................................................. Resta............................................................................................................. Multiplicación................................................................................................. División.......................................................................................................... Conversiones ................................................................................................ 4.1.7 - Manipulación de cadenas............................................................................... 4.1.8 - Operaciones lógicas a nivel de bit.................................................................. 4.1.9 - De control del procesador............................................................................... 4.1.10 - De rotación y desplazamiento ...................................................................... 4.2 - Resumen alfabético de las instrucciones y banderines. Índice..................................... 4.3 - Instrucciones específicas del 286, 386 y 486 en modo real ......................................... 4.3.1 - Diferencias en el comportamiento global respecto al 8086 ........................... 4.3.2 - Instrucciones específicas del 286................................................................... 4.3.3 - Instrucciones propias del 386 y 486............................................................... 4.3.4 - Detección de un sistema AT o superior ......................................................... 4.3.5 - Evaluación exacta del microprocesador instalado ......................................... 4.3.6 - Modo plano (flat) del 386 y superiores ...........................................................
11 17 21 21 22 22 23 23 23 23 24 25 25 26 27 31 31 33 36 36 38 39 41 41 41 43 45 46 49 49 49 51 53 54 55 55 58 59 60 63 64 64 65 66 68 68 70
5
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
5 - EL LENGUAJE ENSAMBLADOR DEL 80x86............................................................................ 71 5.1 - Sintaxis de una línea en ensamblador........................................................................... 71 5.2 - Constantes y operadores............................................................................................... 72 5.2.1 - Constantes...................................................................................................... 72 5.2.2 - Operadores aritméticos .................................................................................. 72 5.2.3 - Operadores lógicos......................................................................................... 73 5.2.4 - Operadores relacionales................................................................................. 73 5.2.5 - Operadores de retorno de valores.................................................................. 73 5.2.6 - Operadores de atributos ................................................................................. 73 5.3 - Principales directivas ..................................................................................................... 75 5.3.1 - De definición de datos .................................................................................... 75 5.3.2 - De definición de símbolos............................................................................... 75 5.3.3 - De control del ensamblador............................................................................ 76 5.3.4 - De definición de segmentos y procedimientos............................................... 76 5.3.5 - De referencias externas.................................................................................. 78 5.3.6 - De definición de bloques ................................................................................ 78 5.3.7 - Condicionales ................................................................................................. 80 5.3.8 - De listado ........................................................................................................ 80 5.4 - Macros............................................................................................................................ 81 5.4.1 - Definición y borrado de las macros ................................................................ 81 5.4.2 - Ejemplo de una macro sencilla....................................................................... 82 5.4.3 - Parámetros formales y parámetros actuales.................................................. 82 5.4.4 - Etiquetas dentro de macros. Variables locales. ............................................. 83 5.4.5 - Operadores de macros ................................................................................... 84 5.4.6 - Directivas útiles para macros.......................................................................... 85 5.4.7 - Macros avanzadas con número variable de parámetros ............................... 87 5.5 - Programación modular y paso de parámetros .............................................................. 88 6 - EL ENSAMBLADOR EN ENTORNO DOS.................................................................................. 91 6.1 - Tipos de programas ejecutables bajo DOS................................................................... 91 6.2 - Ejemplo de programa de tipo COM ............................................................................... 91 6.3 - Ejemplo de programa de tipo EXE ................................................................................ 92 6.4 - Proceso de ensamblaje ................................................................................................. 94 6.5 - La utilidad DEBUG/SYMDEB ........................................................................................ 96 6.6 - Las funciones del DOS y de la BIOS............................................................................. 99 7 - ARQUITECTURA DEL PC, AT y PS/2 BAJO DOS .................................................................... 103 7.1 - Las interrupciones.......................................................................................................... 103 7.2 - La memoria. Los puertos de entrada y salida. .............................................................. 105 7.3 - La pantalla en modo texto.............................................................................................. 105 7.4 - La pantalla en modo gráfico........................................................................................... 106 7.4.1 - Modos gráficos................................................................................................ 106 7.4.2 - Detección de la tarjeta gráfica instalada ........................................................ 108 7.4.3 - Introducción al estándar gráfico VGA............................................................. 108 7.4.4 - Ejemplo de gráficos empleando la BIOS........................................................ 114 7.4.5 - Ejemplo de gráficos a nivel hardware............................................................. 115 7.4.6 - El estándar gráfico VESA ............................................................................... 116 7.5 - El teclado........................................................................................................................ 119 7.5.1 - Bajo nivel......................................................................................................... 119 7.5.2 - Nivel intermedio .............................................................................................. 122 7.5.3 - Alto nivel.......................................................................................................... 125 7.6 - Los discos ...................................................................................................................... 125 7.6.1 - Estructura física .............................................................................................. 125 7.6.2 - Cabeza 0. Pista 0. Sector 1. ........................................................................... 126 7.6.3 - La FAT ............................................................................................................ 127 7.6.4 - El directorio raíz .............................................................................................. 129 7.6.5 - Los subdirectorios........................................................................................... 130
ÍNDICE
5
7.6.6 - El BPB y el DPB.............................................................................................. 131 7.6.7 - La BIOS y los disquetes ................................................................................. 131 7.6.8 - Disquetes floptical 3½ de 20 Mb .................................................................... 132 7.6.9 - Ejemplo de acceso al disco a alto nivel.......................................................... 132 7.6.10 - Ejemplo de acceso al disco a bajo nivel....................................................... 133 7.7 - El PSP ............................................................................................................................ 137 7.8 - El proceso de arranque del PC...................................................................................... 139 7.9 - Formato de las extensiones ROM ................................................................................. 139 7.10 - Formato físico de los ficheros EXE.............................................................................. 140 8 - LA GESTIÓN DE MEMORIA DEL DOS ...................................................................................... 143 8.1 - Tipos de memoria en un PC .......................................................................................... 143 8.2 - Bloques de memoria ...................................................................................................... 145 8.2.1 - El bloque de memoria del programa .............................................................. 145 8.2.2 - El bloque del entorno...................................................................................... 145 8.2.3 - Los bloques de control de memoria (MCB's) ................................................. 146 8.2.4 - La cadena de los bloques de memoria .......................................................... 146 8.2.5 - Relación entre bloque de programa y de entorno.......................................... 147 8.2.6 - Tipos de bloques de memoria ........................................................................ 147 8.2.7 - Liberar el espacio de entorno en programas residentes................................ 148 8.2.8 - Peculiaridades del MS-DOS 4.0 y 5.0............................................................ 148 8.2.9 - Cómo recorrer los bloques de memoria. Ejemplo.......................................... 149 8.3 - Memorias extendida y superior XMS............................................................................. 152 8.4 - Memoria expandida EMS............................................................................................... 153 9 - SUBPROCESOS, RECUBRIMIENTOS Y FILTROS................................................................... 157 9.1 - Llamada a subprocesos y recubrimientos u overlays ................................................... 157 9.2 - Construcción de filtros.................................................................................................... 159 10 - PROGRAMAS RESIDENTES .................................................................................................... 161 10.1 - Principios básicos ........................................................................................................ 161 10.2 - Un ejemplo sencillo ...................................................................................................... 162 10.3 - Localización de un programa residente....................................................................... 163 10.3.1 - Método de los vectores de interrupción ....................................................... 163 10.3.2 - Método de la cadena de bloque de memoria............................................... 163 10.3.3 - Método de la interrupción Multiplex.............................................................. 164 10.4 - Expulsión de un programa residente de la memoria................................................... 164 10.5 - Gestión avanzada de la interrupción Multiplex............................................................ 165 10.5.1 - El convenio BMB Compuscience ................................................................. 165 10.5.2 - El convenio CiriSOFT ................................................................................... 165 10.5.3 - La propuesta AMIS ....................................................................................... 170 10.5.4 - Comparación entre métodos ........................................................................ 172 10.6 - Métodos especiales para economizar memoria.......................................................... 172 10.7 - Programas autoinstalables en memoria superior........................................................ 173 10.8 - Programas residentes en memoria extendida con DR-DOS 6.0 ................................ 174 10.9 - Ejemplo de programa residente que utiliza la BIOS.................................................... 176 10.10 - Uso sin límites de servicios del DOS en programas residentes ............................... 184 10.10.1 - Una primera aproximación ......................................................................... 185 10.10.2 - Pasos a realizar para usar el DOS............................................................. 186 10.10.3 - Resumiendo, ¡no es tan difícil! ................................................................... 187 10.10.4 - Un método alternativo: el SDA ................................................................... 188 10.10.5 - Métodos menos ortodoxos ......................................................................... 189 10.11 - Ejemplo de programa residente que utiliza el DOS .................................................. 189 10.12 - Programas residentes invocables en modos gráficos............................................... 197 10.13 - Programas residentes en entorno WINDOWS 3....................................................... 199 11 - CONTROLADORES DE DISPOSITIVO..................................................................................... 203 11.1 - Introducción.................................................................................................................. 203
5
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
11.2 - Encabezamiento y palabra de atributos ...................................................................... 203 11.3 - Rutinas de estrategia e interrupción ............................................................................ 205 11.4 - Ordenes a soportar por el controlador de dispositivo.................................................. 205 11.5 - La cadena de controladores de dispositivo instalados................................................ 210 11.6 - Ejemplo de controlador de dispositivo de caracteres.................................................. 212 11.7 - Ejemplo de controlador de dispositivo de bloques ...................................................... 214 11.7.1 - Disco virtual TURBODSK: Características................................................... 214 11.7.2 - Ensamblando TURBODSK........................................................................... 216 11.7.3 - Análisis detallado del listado de TURBODSK .............................................. 216 11.8 - Los controladores de dispositivo y el DOS.................................................................. 244 12 - EL HARDWARE DE APOYO AL MICROPROCESADOR........................................................ 245 12.1 - La arquitectura del ordenador compatible ................................................................... 245 12.2 - El interfaz de periféricos 8255 ..................................................................................... 247 12.2.1 - Descripción del integrado ............................................................................. 247 12.2.2 - El 8255 en el PC ........................................................................................... 248 12.2.3 - Un método para averiguar la configuración del PC/XT................................ 248 12.3 - El temporizador 8253 u 8254....................................................................................... 249 12.3.1 - Descripción del integrado ............................................................................. 249 12.3.2 - El 8254 en el ordenador ............................................................................... 255 12.3.3 - Temporización .............................................................................................. 256 12.3.4 - Síntesis de sonido ........................................................................................ 258 12.4 - El controlador de interrupciones 8259......................................................................... 261 12.4.1 - Cómo y por qué de las interrupciones.......................................................... 261 12.4.2 - Descripción del integrado 8259 .................................................................... 261 12.4.3 - El 8259 dentro del ordenador ....................................................................... 267 12.4.4 - Ejemplo: cambio de la base de las interrupciones....................................... 269 12.5 - El chip DMA 8237 ........................................................................................................ 270 12.5.1 - El acceso directo a memoria ........................................................................ 270 12.5.2 - Descripción del integrado 8237 .................................................................... 270 12.5.3 - El 8237 en el ordenador ............................................................................... 279 12.5.4 - Ralentizar un equipo AT con el DMA ........................................................... 281 12.5.5 - Acerca de las páginas de DMA .................................................................... 283 12.6 - El controlador de disquetes NEC 765 ......................................................................... 284 12.6.1 - La tecnología de grabación en disco............................................................ 284 12.6.2 - Descripción del FDC (Floppy Disk Controller) 765 ...................................... 286 12.6.3 - El 765 dentro del ordenador ......................................................................... 294 12.6.4 - Densidades de disco y formatos estándar ................................................... 294 12.6.5 - Acceso a disco con DMA.............................................................................. 297 12.6.6 - Lectura y escritura de sectores de disco sin DMA ....................................... 305 12.6.7 - Programación avanzada del controlador de disquetes: 2M 3.0................... 309 12.6.7.1 - Formato de la primera pista .......................................................... 311 12.6.7.2 - Puntualizaciones sobre el formato de máxima capacidad ........... 315 12.6.7.3 - Descripción de funcionamiento del soporte residente .................. 316 12.6.7.4 - Descripción del programa de formateo (2MF) para 2M................ 330 12.6.7.5 - Un programa para medir el rendimiento de los disquetes............ 338 12.6.7.6 - La versión para PC/XT de 2M: 2MX ............................................. 340 12.6.7.7 - La opción BIOS de 2M: 2M-ABIOS y 2M-XBIOS ......................... 341 12.6.7.8 - La utilidad 2MDOS ........................................................................ 341 12.6.7.9 - Cómo superar los 2.000.000 de bytes en 3½: 2MGUI ................. 342 12.6.7.10 - Uso de 2M 3.0 en OS/2 2.1......................................................... 345 12.7 - El disco duro del AT (IDE, MFM, Bus Local) ............................................................... 346 12.7.1 - El interface .................................................................................................... 346 12.7.2 - Programación de la controladora ................................................................. 346 12.7.3 - Ejemplo práctico de programación............................................................... 349 12.8 - El controlador del teclado: 8042 .................................................................................. 351
ÍNDICE
5
12.8.1 - El 8042 .......................................................................................................... 351 12.8.2 - El teclado del AT........................................................................................... 352 12.8.3 - Comunicación CPU ─¾ teclado ................................................................... 352 12.8.4 - Comunicación teclado ─¾ CPU ................................................................... 355 12.9 - El puerto serie: UART 8250 ......................................................................................... 356 12.9.1 - Descripción del integrado ............................................................................. 356 12.9.2 - El 8250 en el ordenador ............................................................................... 363 12.9.3 - Ejemplo: autodiagnóstico del 8250 .............................................................. 364 12.10 - El puerto de la impresora........................................................................................... 365 12.10.1 - Los registros del puerto paralelo ................................................................ 365 12.10.2 - Envío de caracteres.................................................................................... 365 12.10.3 - Cable NULL-MODEM para conectar dos ordenadores ............................. 366 12.11 - El ratón ....................................................................................................................... 367 12.12 - El reloj de tiempo real del AT: Motorola MC146818.................................................. 368 12.12.1 - Descripción del integrado ........................................................................... 368 12.12.2 - El MC146818 dentro del ordenador ........................................................... 370 12.12.3 - Un método para averiguar la configuración del AT y PS/2 ........................ 371 13 - EL ENSAMBLADOR Y EL LENGUAJE C ................................................................................ 373 13.1 - Uso del Turbo C y Borland C a bajo nivel ................................................................... 373 13.1.1 - Acceso a los puertos de E/S ........................................................................ 373 13.1.2 - Acceso a la memoria .................................................................................... 373 13.1.3 - Control de interrupciones.............................................................................. 374 13.1.4 - Llamada a interrupciones ............................................................................. 374 13.1.5 - Cambio de vectores de interrupción............................................................. 374 13.1.6 - Programas residentes................................................................................... 375 13.1.7 - Variables globales predefinidas interesantes............................................... 375 13.1.8 - Inserción de código en línea......................................................................... 375 13.1.9 - Las palabras clave interrupt y asm............................................................... 375 13.2 - Interfaz C (Borland/Microsoft) - Ensamblador ............................................................. 376 13.2.1 - Modelos de memoria .................................................................................... 376 13.2.2 - Integración de módulos en ensamblador ..................................................... 376 APÉNDICES: I II III IV V VI VII VIII IX X XI
Mapa de memoria ...................................................................................................... 381 Tabla de interrupciones del sistema .......................................................................... 383 Tabla de variables de la BIOS ................................................................................... 385 Puertos de E/S ........................................................................................................... 389 Códigos de rastreo del teclado .................................................................................. 391 Tamaños y tiempos de ejecución de las instrucciones ............................................. 393 Señales del slot de expansión ISA ............................................................................ 399 Funciones del sistema, la BIOS y el DOS aludidas en este libro.............................. 401 Especificaciones XMS y EMS: Todas sus funciones ................................................ 423 Juego de caracteres ASCII extendido ....................................................................... 427 Bibliografía ................................................................................................................. 429
5
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
10
PRÓLOGO DE LA EDICIÓN 4.0 ELECTRÓNICA
PRÓLOGO DE LA EDICIÓN 4.0 ELECTRÓNICA* (*) http://www.gui.uva.es/udigital
Nota:Pudiendo haber discrepancias entre sucesivas ediciones de estas normas, la versión de referencia válida e inapelable será la ubicada en todo momento en la red, en la dirección electrónica arriba indicada o cualquier otra que pudiera sucederla.
Licencia de uso y distribución para particulares. La edición 4.0 (4ª edición) de El Universo Digital del IBM PC, AT y PS/2 es un libro electrónico/impreso de dominio público; de libre uso, difusión, copia y distribución entre particulares, en cualquier soporte. Quienes decidan utilizarlo deberán registrarse por vía electrónica una sola vez, por razones de ética (http://www.gui.uva.es/udigital). También es posible hacerlo enviando una carta o postal ordinaria (mejor en un sobre) al autor, con cualquier texto, a la siguiente dirección: Ciriaco García de Celis Apartado 6105 47080 Valladolid España Indicando claramente que el motivo es registrar el Universo Digital. Los que hayan comprado la versión impresa en persona no necesitan registrarse, aunque lo recibiría con agrado, incluso si ha pasado bastante tiempo (pero si lo compraron por correo no deben registrarse: conservo su pedido). Me gustaría conocer en alguna medida la difusión de la obra, en especial a partir de este momento, lo que hasta ahora me resultaba algo más sencillo. Por supuesto, los datos o direcciones indicadas por los usuarios nunca serán divulgados por mí. Licencia de uso para empresas, asociaciones y organizaciones.
10
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Se aplican exactamente las mismas condiciones que para usuarios particulares, con la excepción de que se recomienda un único registro electrónico o una sola carta o postal en representación de todos los posibles usuarios de la entidad. Licencia de distribución para empresas, asociaciones y organizaciones. Editando revistas (no libros) la distribución está permitida en cualquier formato digital (HTML, PostScript, WordPerfect, texto, o cualesquiera otros) tanto en fragmentos como toda la obra completa. Siendo el formato una revista impresa sólo se permiten fragmentos que no totalicen más del 75% de la obra en los sucesivos números publicados. Es necesario citar la procedencia. La distribución por empresas que cobren una cierta cantidad por el soporte es libre. Mi única sugerencia es que la empresa me envíe una copia del soporte (CD, etc.) en que se publique, por cortesía. Tratándose de empresas editoriales u otras cualesquiera que planeen incluirlo, entero o por fragmentos, en el soporte impreso, electrónico u online de algún libro que vayan a publicar, deberían contactar primero conmigo para negociar una nueva versión (que en todo caso no implicaría la desaparición de ésta en su estatus actual). Modificaciones. La realización de cambios (añadidos, eliminación de contenidos o reemplazamiento de los mismos) es competencia exclusiva del autor, que centraliza la generación de nuevas versiones actualizadas. Quien realizara alguna modificación sin consentimiento habría de destinar la obra resultante para uso personal e intransferible. Orígenes de El Universo Digital. El Universo Digital no nació tras una decisión premeditada. Su objetivo inicial fue dotar de un manual de apoyo al Curso de Lenguaje Ensamblador, que ofrece todos los años la asociación Grupo Universitario de Informática de la Universidad de Valladolid, en el marco de unos Cursos de Introducción a la Informática -para los alumnos y personal en general de la Universidad- que abarcan un espectro mucho más amplio que el de la programación de los ordenadores. La primera versión ocupaba 116 páginas, cuando su denominación era aún la de Curso de Ensamblador. Sin embargo, en una época en la que era difícil encontrar información, y buena bibliografía especializada, el autor siguió recopilando material interesante y añadiéndolo al curso. Una buena parte de dicho material y del añadido después ha sido además de cosecha propia. La primera edición de El Universo Digital, editada no mucho tiempo después del manual del curso, rebasó ligeramente las 300 páginas. Posteriormente se incrementaría aún algo más, hasta las 420 de la 3ª edición que ha mantenido durante la mayor parte del tiempo.
El DOS en la actualidad. Actualmente, y desde hace algún tiempo, la programación en DOS ya no es importante, y mucho menos al nivel que desarrolla este libro, y ello pese a que incluso Windows 95 corre aún en
PRÓLOGO DE LA EDICIÓN 4.0 ELECTRÓNICA
10
alguna parte sobre DOS, comportamiento que irá reduciéndose hasta la eliminación en próximas versiones. El futuro de la programación, sin embargo, no es sólo para los programadores de alto nivel. En alguna manera, los propios usuarios pueden y podrán cada vez en mayor medida hacer sus propios programas incluso sin darse cuenta. Sin embargo, siempre hay alguien que tiene que construir los sistemas operativos, y sobre todo, los controladores para dar soporte a los dispositivos en los diversos sistemas operativos. Por no mencionar las aplicaciones especializadas, desde máquinas industriales al microprocesador de las sondas espaciales (que, evidentemente, no corre bajo Windows). Es para los programadores de sistemas, y para aquellos que necesitan o quieren saber cómo funciona el PC por dentro, como ejemplo práctico de arquitectura interna de un ordenador, para los que va destinado este libro. Que podrán practicar en un entorno cómodo para este tipo de programación, como es el DOS (que deja todo el control de la máquina a cada tarea). Aunque algunos contenidos muy relacionados con el DOS siguen presentes en esta obra, el lector habrá de tener en cuenta si es pertinente profundizar en ellos o no, en la época que vivimos. Mis contactos con editoriales. Mi objetivo inicial no fue publicarlo, aunque hace dos o tres años sí me lo planteé un poco en serio. Las ventajas de una edición oficial sería su no engorrosa distribución (uno de los motivos por los que siempre ha costado poco es porque nuestra Asociación y el propio autor ha puesto su mano de obra gratis), así como su mayor difusión. Puesto en contacto con cuatro prestigiosas editoriales; las que han respondido han valorado muy positivamente la obra, sin embargo la han rechazado aduciendo otros motivos («sobrecarga del programa editorial», solapamiento en contenidos con «obras publicadas o en fase de publicación», o simplemente «falta de interés comercial»). Una de ellas aún no ha respondido. Los inconvenientes de su publicación por una editorial serían el importante aumento de precio, y mi renuncia a los derechos de distribución (en particular, nuestra Asociación tendría que comprar en la librería los ejemplares para nuestros cursos). Sin embargo, la ventaja de la publicación para facilitar la difusión popular es obvia, máxime si lo hace una editorial importante (si no, no aparecería en todas las estanterías, la publicidad la harían los lectores lentamente, como ya se venía haciendo, y la distribución sería incluso más limitada pese al recurso a los baratos servicios de reprografía por parte de los usuarios). El Universo Digital en Internet. Mi decisión final ya la había acariciado con anterioridad. Algo había que hacer, pues la distribución gratuita del libro llevaba mucho tiempo. Uno de los motivos que han terminado empujándome a esta decisión, ha sido la considerable cantidad de pedidos que hemos recibido desde países de hispanoamérica. Se trata de ciudadanos que conocen el índice del libro a través del Web y lo piden, sobre todo desde México. Sin embargo, sólo en la primera ocasión lo he enviado (a Perú); los motivos son, desgraciadamente, la práctica imposibilidad de comerciar a pequeña escala con esos países (no existe el envío contrarreembolso, por ejemplo); las enormes demoras del envío por superficie (el coste del envío aéreo supera el del propio libro) y las
10
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
complicadas gestiones de pago e injustas comisiones bancarias (aunque las pague el usuario final); finalmente habría que añadir incluso mi temor inconsciente a un aumento incontrolado de la demanda, cuando ya había demasiado trabajo que hacer para atender la de origen nacional (en mi memoria estaba lo que ocurrió cuando empezaron a aparecer mensajes y comenzaron a recibirse pedidos por FidoNET). Pido desde aquí disculpas a todos los que lo han solicitado desde fuera de España, mayores además si no he contestado el E-Mail por no haber tomado aún una decisión al respecto. El Universo Digital de dominio público en formato electrónico, podrá ser accedido desde cualquier lugar del mundo, y en cualquier CD de los kioscos. El inconveniente es que no todos tienen igual acceso a estas redes y medios, aunque ese inconveniente disminuirá exponencialmente con el tiempo (con el mismo exponente con que crezca la red). Fin de la distribución impresa. Naturalmente, una vez que he renunciado a mis derechos sobre el libro, donándolo al dominio público, ya no estoy obligado a venderlo impreso (medida tomada únicamente para mantener el copyright). Realmente, no tenemos tiempo ni medios para atender la demanda actual: aunque es una medida dura de imponer, lamento renunciar a realizar más envíos de ejemplares impresos. Renuncio con ello a facilitar su difusión a los lectores menos introducidos en las redes telemáticas, pero beneficio a otros muchos, que además podrán seguir usando la versión manuscrita utilizando una impresora. Por otro lado, haber facturado sólo aproximadamente el coste de impresión y distribución, me permiten tomar esa decisión sin temer el enfado de quienes lo habían comprado. El coste de impresión de los últimos números en la reprografía oficial de la Universidad (rechazamos opciones más baratas de menor calidad), encuadernación y disquete era de 1900 pts. El libro (realmente, apuntes técnicos fotocopiados) se vendía a 2100 pts más gastos de envío. Ese margen de beneficios era más bien de maniobra, ya que por ejemplo, en los ejemplares que no llegaban a su destino, el coste del envío y la devolución lo pagábamos nosotros. Cada envío llevaba una media de 20 minutos de tiempo total de mano de obra, contabilizando la preparación de los libros (transporte físico, disquete, gestión del pedido...), y la mayoría eran de una sola unidad (pese a que se penalizaba su envío con 100 pts adicionales). El precio de los más de 1200 Universos Digitales vendidos ha tenido un crecimiento nominal cero en los cinco años de difusión impresa. Obtención de ejemplares impresos. Aunque en general no se harán más envíos, la única excepción corresponderá a los pedidos realizados desde bibliotecas (universitarias o no universitarias), que tal vez no tengan la impresora adecuada o tiempo para reproducirlo, lo que perjudicaría a un amplio conjunto potencial de usuarios. No se harán envíos a otras organizaciones, ni a librerías o a particulares. Subrayamos que El Universo Digital impreso tiene el carácter legal de apuntes técnicos impresos y no de libro. Los pedidos de ejemplares impresos serán admitidos sólo desde España. Habrán de realizarse exclusivamente por carta impresa, que deberá estar compulsada por el sello y en su caso papel oficial de la biblioteca que hace el pedido, además de debidamente firmada por quien corresponda. Es conveniente que figure el teléfono de la biblioteca o en su defecto de la conserjería del centro. Además del nombre completo, dirección y NIF. Nos reservamos el derecho de rechazar aquellos pedidos que no cumplan alguno de estos requisitos, o los de sospechosa procedencia. La dirección es: Grupo
PRÓLOGO DE LA EDICIÓN 4.0 ELECTRÓNICA
10
Universitario de Informática. Apartado 6062. 47080 Valladolid. El precio por ejemplar será el que figure en la factura que realizará el propio servicio de reprografía (unas 2000 pts/unidad); sumando al final el coste exacto del envío y los disquetes. Agradecimientos. Agradezco desde aquí al servicio de Reprografía de la Universidad, ubicado en la Casa del Estudiante, el esmero puesto durante tanto tiempo en la reproducción y encuadernación de cada número durante la etapa impresa. Cualquier pequeño problema de calidad se ha debido siempre a los fallos inevitables que en ocasiones presenta toda máquina, por buena que sea. Mis agradecimientos también a las diversas instituciones de la Universidad de Valladolid, que han recibido en ocasión la presión de la demanda a través de incorrectas llamadas telefónicas solicitando el libro, no siendo ellos los encargados de su distribución; también al Grupo Universitario de Informática, por su colaboración a todos los niveles. No puedo decir lo mismo de los funcionarios de Correos: aunque algunos son amables, en general, el funcionamiento de esa institución es el que cabía esperar de un monopolio no sometido a la libre competencia en envíos postales ordinarios (y que, por tanto, no tiene la obligación de tratar bien a sus clientes, porque también volverán mañana). El trato que reciben los clientes no se diferencia mucho del de los paquetes, y estos son muy expresivos en ocasiones al llegar al destino. Por otro lado, la cantidad de papeles que hay que rellenar en cada envío, y algunas normas de la empresa (como el plomo adherido a los paquetes postales) no se han simplificado desde finales del siglo XIX. Tampoco es comprensible que sólo Argentaria sea aún la única entidad financiera con el privilegio de gestionar las denominadas Cuentas Corrientes Postales. Además de que el servicio de correos es caro en la realidad (esto es, cuando se incluye lo que pagamos en impuestos para cubrir las pérdidas de la compañía) se mantiene el viejo vicio de indexar las tarifas anuales (aumento del 8% en 1997, cuando hay un 2% de inflación nacional). Sin embargo, he de reconocer que la fiabilidad de Correos (entendida en cuanto a paquetes que llegan a su destino o en su defecto vuelven por motivo de dirección incorrecta) es próxima al 100%: los envíos no suelen perderse, al menos los de los reembolsos. En puntualidad, aunque hay extremos de gran aleatoriedad (desde paquetes que llegan en tres días a un pueblo perdido en la otra punta del país, a los que tardan quince en ir de Valladolid a Madrid) el tiempo promedio podría aproximarse, aunque por debajo, a lo que afirma la empresa. Ciriaco García de Celis Valladolid, Noviembre de 1997
PRÓLOGO DE LA TERCERA EDICIÓN (1994)
Ha pasado un año desde la publicación de la primera edición de esta obra. Desde entonces, ha continuado la expansión de los interfaces gráficos de usuario y los sistemas operativos avanzados para PC. Sin embargo, pese a que la programación continúa alejándose cada vez más del bajo nivel de las máquinas, los programadores de sistemas en el entorno del PC siguen existiendo y son muchos más que los que trabajan para las empresas punteras en el desarrollo de los sistemas operativos. Los ordenadores compatibles poseen numerosas aplicaciones en el campo industrial, para las que es conveniente un conocimiento elevado del funcionamiento interno del ordenador en general y del MS-DOS en particular. Para aquellas personas que necesitan comprender el funcionamiento de un ordenador, las máquinas compatibles constituyen una interesante oportunidad y punto de partida. Este libro pretende cubrir una importante laguna en la bibliografía disponible actualmente sobre la programación a nivel de sistemas de los ordenadores compatibles. Respecto a la primera edición, se han incrementado los contenidos en una proporción equivalente al 20% de lo que ya existía, corrigiéndose además algunos errores. Aunque el libro comience con una introducción a la aritmética binaria que pueda indicar todo lo contrario, se presupone que el lector tiene unos mínimos conocimientos de informática, al menos un dominio básico del sistema operativo MS-DOS, siendo más que recomendable conocer algún lenguaje de programación. Seguidamente se explica el lenguaje ensamblador de la serie 80x86 de Intel separando claramente las instrucciones de los diversos procesadores, aunque dejando de lado algunas instrucciones del 286 y 386 que se salen del entorno MS-DOS. También se describe la sintaxis del lenguaje ensamblador; sin embargo, aunque este último aspecto está extensamente documentado, los lectores que no conozcan el lenguaje ensamblador de ningún microprocesador habrán de trabajar considerablemente leyendo multitud de listados hasta adquirir la soltura necesaria y, sobre todo, creando los suyos propios. Aunque sería conveniente describir el lenguaje C, íntimo aliado del ensamblador en la programación de sistemas, ello se deja por razones de espacio para otras publicaciones. El libro describe con profundidad la arquitectura de los ordenadores compatibles, de manera especial en lo referente a la organización interna de la memoria (actualizada hasta el MS-DOS 6.0 y el DR-DOS 6.0), los discos y el teclado. El apartado de los gráficos se repasa sólo superficialmente, ya que por sí solo necesitaría de un buen libro más grueso que este. Se dan pistas sobre la manera de conmutar los modos de vídeo sin alterar el contenido de la pantalla, aspecto que resulta de especial
PRÓLOGO DE LA TERCERA EDICIÓN (1994)
16
interés para los programas residentes. Las memorias extendida XMS y expandida EMS son descritas con cierto detenimiento, dada su presencia en todos los ordenadores modernos y su importancia. Existen apéndices que describen todas las funciones del DOS, de la BIOS y del sistema usadas en las rutinas y programas desarrollados, así como la totalidad de las funciones XMS y EMS. Sin embargo, no están ni muchísimo menos todas las interrupciones necesarias, por lo que se insta al lector a conseguir el impresionante fichero de dominio público INTERRUPT.LST, complemento ideal de este libro (ver bibliografía). Los programas residentes reciben un tratamiento especialmente profundo: desde los métodos más eficientes para que detecten su propia presencia en memoria, a las técnicas más avanzadas para economizar memoria, pasando por el uso de funciones del DOS de manera concurrente al programa principal, así como técnicas de empleo de memoria extendida y superior para conseguir programas que usen 0 Kb dentro de los primeros 640 Kb de la máquina y todo ello sin olvidar la convivencia con los actuales entornos operativos, como Windows, y la posibilidad de ser activados desde pantallas gráficas. Este libro también trata los controladores de dispositivo o device drivers, desde los dos posibles enfoques de su uso: bien sea la creación de controladores de dispositivo de caracteres, bien la de nuevas unidades de disco añadidas a las del sistema; en ambos casos se incluyen ejemplos reales de controladores completos y comprobados, en particular el ejemplo de disco virtual: un completo ejemplo de controlador redimensionable que soporta memoria convencional, XMS y EMS. Existe un capítulo muy próximo al hardware en el que se describen a fondo y sin omisiones todos los chips del ordenador, para permitir al programador de sistemas un control completo del equipo. Para asimilar este capítulo hace falta cierta formación previa en los sistemas digitales; sin embargo, los ejemplos que siguen a la información técnica aclaran las explicaciones previas y pueden ser aprovechados de manera inmediata incluso sin entender todo lo anterior. Los chips de apoyo al microprocesador son descritos de manera total: primero, no relacionados con el PC sino como tales circuitos; después integrándolos en el ordenador y documentando profusamente su uso, con ejemplos probados. Se consideran el interfaz de periféricos 8255 (útil para averiguar la configuración de los PC/XT), el temporizador 8253/8254 (para temporización y síntesis de sonido), el controlador de interrupciones 8259, el controlador de DMA 8237 (para acceso a disco), el controlador de disquetes 765 (acceso directo a los sectores), la controladora de disco duro de los AT (IDE, MFM ó Bus Local); el controlador del teclado del AT (8042); el UART 8250 (empleado en las comunicaciones serie) y el reloj de tiempo real MC146818 (configuración de AT y programación de alarmas y temporizaciones). Los ejemplos en este capítulo experimentan una importante potenciación respecto a la edición anterior; en particular, en lo relacionado con el controlador de disquetes se puede considerar que la información vertida es prácticamente casi toda la existente, existiendo pautas suficientes para que el lector cree sus propios programas copiones, protecciones de disco, formatos de alta capacidad, etc. Existen también capítulos que describen el funcionamiento y programación de la impresora; sin entrar en aspectos particulares relativos a los modelos de las diversas marcas, sí se suministra información común a todas. También se comenta en un capítulo el funcionamiento al más bajo nivel del ratón, aspecto que habitualmente no suele ser considerado. Dada la importancia del lenguaje C en la programación en general y en la programación de sistemas en particular, tanto en la actualidad como durante los próximos años, se incluye un capítulo que describe la manera de comunicar el ensamblador con el lenguaje C, con objeto de superar las limitaciones de este lenguaje en los puntos críticos de la programación de sistemas. Este capítulo
16
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
requiere un dominio elemental del lenguaje C por parte del lector, aunque probablemente sólo sea útil para aquellos que lo conocen más o menos. Resumiendo, el libro pretende reunir en una sola obra la mayoría de la información necesaria para el programador de sistemas, exponiendo toda la información y no sólo lo imprescindible, sin olvidos ni omisiones; también se pretende explicar las técnicas más avanzadas de creación de programas residentes. Este afán de información completa es el responsable del título del libro. Todos los listados de ejemplo se suponen de dominio público y las rutinas pueden ser incluidas por los lectores libremente en sus propios programas, aunque en el caso de los programas completos debe citarse la procedencia y dejar bien claro en las versiones modificadas quién las ha alterado. En todo caso, pese a que todas las rutinas y programas han sido probados debidamente en un 8088, un 286, un 386 o un 486 -bajo varios sistemas operativos y con diferentes configuraciones del hardware- el autor del libro no se responsabiliza de su correcto funcionamiento en todas las circunstancias.
21
INTRODUCCIÓN
Capítulo I: INTRODUCCIÓN
1.1. - NUMEROS BINARIOS, OCTALES Y HEXADECIMALES. El sistema de numeración utilizado habitualmente es la base 10; es decir, consta de 10 dígitos (0-9) que podemos colocar en grupos, ordenados de izquierda a derecha y de mayor a menor. Cada posición tiene un valor o peso de 10n donde n representa el lugar contado por la derecha: 1357 = 1 x 103 + 3 x 102 + 5 x 101 + 7 x 100 Explícitamente, se indica la base de numeración como 135710. En un ordenador el sistema de numeración es binario -en base 2, utilizando el 0 y el 1- hecho propiciado por ser precisamente dos los estados estables en los dispositivos digitales que componen una computadora. Análogamente a la base 10, cada posición tiene un valor de 2n donde n es la posición contando desde la derecha y empezando por 0: 1012 = 1 x 22 + 0 x 21 + 1 x 20 Además, por su importancia y utilidad, es necesario conocer otros sistemas de numeración como pueden ser el octal (base 8) y el hexadecimal (base 16). En este último tenemos, además de los números del 0 al 9, letras -normalmente en mayúsculas- de la A a la F. Llegar a un número en estos sistemas desde base 2 es realmente sencillo si agrupamos las cifras binarias de 3 en 3 (octal) o de 4 en 4 (hexadecimal): Base 2 a base 8: 101 0112 = 538 Base 2 a base 16: 0010 10112 = 2B16 A la inversa, basta convertir cada dígito octal o hexadecimal en binario: Base 8 a base 2: 248 = 010 1002 Base 16 a base 2: 2416 = 0010 01002 De ahora en adelante, se utilizarán una serie de sufijos para determinar el sistema de numeración empleado: ╔══════════╤══════════╤══════════════╗ ║ Sufijo │ Base │ Ejemplos ║ ╟──────────┼──────────┼──────────────╢ ║ b │ 2 │ 01101010b ║ ║ o,q │ 8 │ 175o ║ ║ d │ 10 │ 789d ║ ║ h │ 16 │ 6A5h ║ ╚══════════╧══════════╧══════════════╝
En caso de que no aparezca el sufijo, el número se considera decimal; es decir, en base 10.
21
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
1.2. - CAMBIO DE BASE. Pese a que las conversiones entre base 2 y base 8 y 16 son prácticamente directas, existe un sistema general para realizar el cambio de una base a otra. El paso de cualquier base a base 10 lo vimos antes: 2
1
0
6A5h = 6 x 16 + 10 x 16 + 5 x 16
Inversamente, si queremos pasar de base 10 a cualquier otra habrá que realizar sucesivas divisiones por la base y tomar los restos: 1234 114 2
│ 16 └───────── 77 │ 16 └───────── 4 13
1234d = 4D2h
donde 4 es el último cociente (menor que la base) y los restantes dígitos son los restos en orden inverso. 1.3. - ESTRUCTURA ELEMENTAL DE LA MEMORIA. 1.3.1. - BIT. Toda la memoria del ordenador se compone de dispositivos electrónicos que pueden adoptar únicamente dos estados, que representamos matemáticamente por 0 y 1. Cualquiera de estas unidades de información se denomina BIT, contracción de «binary digit» en inglés. 1.3.2. - BYTE. Cada grupo de 8 bits se conoce como byte u octeto. Es la unidad de almacenamiento en memoria, la cual está constituida por un elevado número de posiciones que almacenan bytes. La cantidad de memoria de que dispone un sistema se mide en Kilobytes (1 Kb = 1024 bytes), en Megabytes (1 Mb = 1024 Kb), Gigabytes (1 Gb = 1024 Mb), Terabytes (1 Tb = 1024 Gb) o Petabytes (1 Pb = 1024 Tb). Los bits en un byte se numeran de derecha a izquierda y de 0 a 7, correspondiendo con los exponentes de las potencias de 2 que reflejan el valor de cada posición. Un byte nos permite, por tanto, representar 256 estados (de 0 a 255) según la combinación de bits que tomemos. 1.3.3. - NIBBLE. Cada grupo de cuatro bits de un byte constituye un nibble, de forma que los dos nibbles de un byte se llaman nibble superior (el compuesto por los bits 4 a 7) e inferior (el compuesto por los bits 0 a 3). El nibble tiene gran utilidad debido a que cada uno almacena un dígito hexadecimal: ╔═════════╤═════════╤═════════╦═════════╤═════════╤═════════╗ ║ Binario │ Hex. │ Decimal ║ Binario │ Hex. │ Decimal ║ ╟─────────┼─────────┼─────────╫─────────┼─────────┼─────────╢ ║ 0000 │ 0 │ 0 ║ 1000 │ 8 │ 8 ║ ║ 0001 │ 1 │ 1 ║ 1001 │ 9 │ 9 ║ ║ 0010 │ 2 │ 2 ║ 1010 │ A │ 10 ║ ║ 0011 │ 3 │ 3 ║ 1011 │ B │ 11 ║ ║ 0100 │ 4 │ 4 ║ 1100 │ C │ 12 ║ ║ 0101 │ 5 │ 5 ║ 1101 │ D │ 13 ║
21
INTRODUCCIÓN
║ 0110 │ 6 │ 6 ║ 1110 │ E │ 14 ║ ║ 0111 │ 7 │ 7 ║ 1111 │ F │ 15 ║ ╚═════════╧═════════╧═════════╩═════════╧═════════╧═════════╝
1.4. - OPERACIONES ARITMÉTICAS SENCILLAS EN BINARIO. Para sumar números, tanto en base 2 como hexadecimal, se sigue el mismo proceso que en base 10:
1010 1010b + 0011 1100b ────────────── 1110 0110b
Podemos observar que la suma se desarrolla de la forma tradicional; es decir: sumamos normalmente, salvo en el caso de 1 + 1 = 102 , en cuyo caso tenemos un acarreo de 1 (lo que nos llevamos).
1.5. - COMPLEMENTO A DOS. En general, se define como valor negativo de un número el que necesitamos sumarlo para obtener 00h, por ejemplo: FFh + 01h ────── 100h
Como en un byte solo tenemos dos nibbles, es decir, dos dígitos hexadecimales, el resultado es 0 (observar cómo el 1 más significativo subrayado es ignorado). Luego FFh=-1. Normalmente, el bit 7 se considera como de signo y, si está activo (a 1) el número es negativo.
Por esta razón, el número 80h, cuyo complemento a dos es él mismo, se considera negativo (-128) y el número 00h, positivo. En general, para hallar el complemento a dos de un número cualquiera basta con calcular primero su complemento a uno, que consiste en cambiar los unos por ceros y los ceros por unos en su notación binaria; a continuación se le suma una unidad para calcular el complemento a dos. Con una calculadora, la n operación es más sencilla: el complemento a dos de un número A de n bits es 2 -A. Otro factor a considerar es cuando se pasa de operar con un número de cierto tamaño (ej., 8 bits) a otro mayor (pongamos de 16 bits). Si el número es positivo, la parte que se añade por la izquierda son bits a 0. Sin embargo, si era negativo (bit más significativo activo) la parte que se añade por la izquierda son bits a 1. Este fenómeno, en cuya demostración matemática no entraremos, se puede resumir en que el bit más significativo se copia en todos los añadidos: es lo que se denomina la extensión del signo: los dos siguientes números son realmente el mismo número (el -310): 11012 (4 bits) y 111111012 (8 bits). 1.6. - AGRUPACIONES DE BYTES. ╔═══════════════════════╤════════════════════════════════════╗ ║ Tipo │ Definición ║ ╟───────────────────────┼────────────────────────────────────╢ ║ Palabra │ 2 bytes contiguos ║ ║ Doble palabra │ 2 palabras contiguas (4 bytes) ║ ║ Cuádruple palabra │ 4 palabras contiguas (8 bytes) ║ ║ Párrafo │ 16 bytes ║ ║ Página │ 256 bytes, 16 Kb, etc. ║ ║ Segmento │ 64 Kbytes ║ ╚═══════════════════════╧════════════════════════════════════╝
1.7. - REPRESENTACIÓN DE LOS DATOS EN MEMORIA. 1.7.1. - NUMEROS BINARIOS: máximo número representable:
21
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
╔═══════════╤═══════════════════════════════╗ ║ Tipo │ Sin signo ║ ╟───────────┼───────────────────────────────╢ ║ 1 byte │ 255 ║ ║ 2 bytes │ 65.535 ║ ║ 4 bytes │ 4.294.967.295 ║ ║ 8 bytes │ 18.446.744.073.709.551.615 ║ ╚═══════════╧═══════════════════════════════╝ ╔════════════╤═════════════════════════════╤═══════════════════════════════╗ ║ Tipo │ Positivo │ Negativo ║ ╟────────────┼─────────────────────────────┼───────────────────────────────╢ ║ 1 byte │ 127 │ -128 ║ ║ 2 bytes │ 32.767 │ -32.768 ║ ║ 4 bytes │ 2.147.483.647 │ -2.147.483.648 ║ ║ 8 bytes │ 9.223.372.036.854.775.807 │ -9.223.372.036.854.775.808 ║ ╚════════════╧═════════════════════════════╧═══════════════════════════════╝
Los números binarios de más de un byte se almacenan en la memoria en los procesadores de Intel en orden inverso: 01234567h se almacenaría: 67h, 45h, 23h, 01h. 1.7.2. - NUMEROS BINARIOS CODIFICADOS EN DECIMAL (BCD). Consiste en emplear cuatro bits para codificar los dígitos del 0 al 9 (desperdiciando las seis combinaciones que van de la 1010 a la 1111). La ventaja es la simplicidad de conversión a/de base 10, que resulta inmediata. Los números BCD pueden almacenarse desempaquetados, en cuyo caso cada byte contiene un dígito BCD (Binary-Coded Decimal); o empaquetados, almacenando dos dígitos por byte (para construir los números que van del 00 al 99). La notación BCD ocupa cuatro bits -un nibble- por cifra, de forma que en el formato desempaquetado el nibble superior siempre es 0. 1.7.3. - NUMEROS EN PUNTO FLOTANTE. Son grupos de bytes en los que una parte se emplea para guardar las cifras del número (mantisa) y otra para indicar la posición del punto flotante (exponente), de modo equivalente a la notación científica. Esto permite trabajar con números de muy elevado tamaño -según el exponente- y con una mayor o menor precisión en función de los bits empleados para codificar la mantisa. 1.7.4. - CÓDIGO ASCII. El código A.S.C.I.I. (American Standard Code for Information Interchange) es un convenio adoptado para asignar a cada carácter un valor numérico; su origen está en los comienzos de la Informática tomando como muestra algunos códigos de la transmisión de información de radioteletipo. Se trata de un código de 7 bits con capacidad para 128 símbolos que incluyen todos los caracteres alfanuméricos del inglés, con símbolos de puntuación y algunos caracteres de control de la transmisión. Con posterioridad, con la aparición de los microordenadores y la gran expansión entre ellos de los IBM-PC y compatibles, la ampliación del código ASCII realizada por esta marca a 8 bits, con capacidad para 128 símbolos adicionales, experimenta un considerable auge, siendo en la actualidad muy utilizada y recibiendo la denominación oficial de página de códigos 437 (EEUU). Se puede consultar al final de este libro. Es habitualmente la única página soportada por las BIOS de los PC. Para ciertas nacionalidades se han diseñado otras páginas específicas que requieren de un software externo. En las lenguas del estado español y en las de la mayoría de los demás países de la UE, esta tabla cubre todas las necesidades del idioma. 1.8. - OPERACIONES LÓGICAS EN BINARIO.
21
INTRODUCCIÓN
Se realizan a nivel de bit y pueden ser de uno o dos operandos: ╔═════╤═════════╗ ║ x │ NOT (x) ║ ╟─────┼─────────╢ ║ 0 │ 1 ║ ║ 1 │ 0 ║ ╚═════╧═════════╝
╔════════════╤══════════════╤═════════════╤════════════╗ ║ x y │ x AND y │ x OR y │ x XOR y ║ ╟────────────┼──────────────┼─────────────┼────────────╢ ║ 0 0 │ 0 │ 0 │ 0 ║ ║ 0 1 │ 0 │ 1 │ 1 ║ ║ 1 0 │ 0 │ 1 │ 1 ║ ║ 1 1 │ 1 │ 1 │ 0 ║ ╚════════════╧══════════════╧═════════════╧════════════╝
ARQUITECTURA E HISTORIA DE LOS MICROORDENADORES
25
Capítulo II: ARQUITECTURA E HISTORIA DE LOS MICROORDENADORES
El ensamblador es un lenguaje de programación que, por la traducción directa de los mnemónicos a instrucciones maquina, permite realizar aplicaciones rápidas, solucionando situaciones en las que los tiempos de ejecución constituye el factor principal para que el proceso discurra con la suficiente fluidez. Esta situación, que indudablemente sí influye sobre la elección del lenguaje de programación a utilizar en el desarrollo de una determinada rutina, y dada la aparición de nuevos compiladores de lenguajes de alto nivel que optimizan el código generado a niveles muy próximos a los que un buen programador es capaz de realizar en ensamblador, no es la única razón para su utilización. Es sobradamente conocido que los actuales sistemas operativos son programados en su mayor parte en lenguajes de alto nivel, especialmente C, pero siempre hay una parte en la que el ensamblador se hace casi insustituible bajo DOS y es la programación de los drivers para los controladores de dispositivos, relacionados con las tareas de más bajo nivel de una máquina, fundamentalmente las operaciones de entrada/salida en las que es preciso actuar directamente sobre los demás chips que acompañan al microprocesador. Por ello y porque las instrucciones del lenguaje ensamblador están íntimamente ligadas a la máquina, vamos a realizar primero un somero repaso a la arquitectura interna de un microordenador. 2.1. - ARQUITECTURA VON NEWMAN. Centrándonos en los ordenadores sobre los que vamos a trabajar desarrollaré a grandes rasgos la arquitectura Von Newman que, si bien no es la primera en aparecer, sí que lo hizo prácticamente desde el comienzo de los ordenadores y se sigue desarrollando actualmente. Claro es que está siendo desplazada por otra que permiten una mayor velocidad de proceso, la RISC. En los primeros tiempos de los ordenadores, con sistemas de numeración decimal, una electrónica sumamente complicada muy susceptible a fallos y un sistema de programación cableado o mediante fichas, Von Newman propuso dos conceptos básicos que revolucionarían la incipiente informática: a)
La utilización del sistema de numeración binario. Simplificaba enormemente los problemas que la implementación electrónica de las operaciones y funciones lógicas planteaban, a la vez proporcionaba una mayor inmunidad a los fallos (electrónica digital).
b)
Almacenamiento de la secuencia de instrucciones de que consta el programa en una memoria interna, fácilmente accesible, junto con los datos que referencia. De este forma la velocidad de proceso experimenta un considerable incremento; recordemos que anteriormente una instrucción o un dato estaban codificados en una ficha en el mejor de los casos.
Tomando como modelo las máquinas que aparecieron incorporando las anteriores características, el ordenador se puede considerar compuesto por las siguientes partes: - La Unidad Central de Proceso, U.C.P., más conocida por sus siglas en inglés (CPU). - La Memoria Interna, MI. - Unidad de Entrada y Salida, E/S. - Memoria masiva Externa, ME. Realicemos a continuación una descripción de lo que se entiende por cada una de estas partes y cómo
25
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
están relacionadas entre si: - La Unidad Central de Proceso (CPU) viene a ser el cerebro del ordenador y tiene por misión efectuar las operaciones aritmético-lógicas y controlar las transferencias de información a realizar. - La Memoria Interna (MI) contiene el conjunto de instrucciones que ejecuta la CPU en el transcurso de un programa. Es también donde se almacenan temporalmente las variables del mismo, todos los datos que se precisan y todos los resultados que devuelve. - Unidades de entrada y salida (E/S) o Input/Output (I/O): son las encargadas de la comunicación de la máquina con el exterior, proporcionando al operador una forma de introducir al ordenador tanto los programas como los datos y obtener los resultados. Como es de suponer, estas tres partes principales de que consta el ordenador deben estar íntimamente conectadas; aparece en este momento el concepto de bus: el bus es un conjunto de líneas que enlazan los distintos componentes del ordenador, por ellas se realiza la transferencia de datos entre todos sus elementos. Se distinguen tres tipos de bus: - De control: forman parte de él las líneas que seleccionan desde dónde y hacia dónde va dirigida la información, también las que marcan la secuencia de los pasos a seguir para dicha transferencia. - De datos: por él, de forma bidireccional, fluyen los datos entre las distintas partes del ordenador. - De direcciones: como vimos, la memoria está dividida en pequeñas unidades de almacenamiento que contienen las instrucciones del programa y los datos. El bus de direcciones consta de un conjunto de líneas que permite seleccionar de qué posición de la memoria se quiere leer su contenido. También direcciona los puertos de E/S. La forma de operar del ordenador en su conjunto es direccionar una posición de la memoria en busca de una instrucción mediante el bus de direcciones, llevar la instrucción a la unidad central de proceso -CPU- por medio del bus de datos, marcando la secuencia de la transferencia el bus de control. En la CPU la instrucción se decodifica, interpretando qué operandos necesita: si son de memoria, es necesario llevarles a la CPU; una vez que la operación es realizada, si es preciso se devuelve el resultado a la memoria. 2.2. - EL MICROPROCESADOR. Un salto importante en la evolución de los ordenadores lo introdujo el microprocesador: se trata de una unidad central de proceso contenida totalmente en un circuito integrado. Comenzaba así la gran carrera en busca de lo más rápido, más pequeño; rápidamente el mundo del ordenador empezó a ser accesible a pequeñas empresas e incluso a nivel doméstico: es el boom de los microordenadores personales. Aunque cuando entremos en la descripción de los microprocesadores objeto de nuestro estudio lo ampliaremos, haré un pequeño comentario de las partes del microprocesador: - Unidad aritmético-lógica: Es donde se efectúan las operaciones aritméticas (suma, resta, y a veces producto y división) y lógicas (and, or, not, etc.). - Decodificador de instrucciones: Allí se interpretan las instrucciones que van llegando y que componen el programa. - Bloque de registros: Los registros son celdas de memoria en donde queda almacenado un dato temporalmente. Existe un registro especial llamado de indicadores, estado o flags, que refleja el estado operativo del microprocesador. - Bloque de control de buses internos y externos: supervisa todo el proceso de transferencias de información dentro del microprocesador y fuera de él. 2.3. - BREVE HISTORIA DEL ORDENADOR PERSONAL Y EL DOS.
ARQUITECTURA E HISTORIA DE LOS MICROORDENADORES
25
La trepidante evolución del mundo informático podría provocar que algún recién llegado a este libro no sepa exactamente qué diferencia a un ordenador "AT" del viejo "XT" inicial de IBM. Algunos términos manejados en este libro podrían ser desconocidos para los lectores más jóvenes. Por ello, haremos una pequeña introducción sobre la evolución de los ordenadores personales, abarcando toda la historia (ya que no es muy larga).
La premonición. En 1973, el centro de investigación de Xerox en Palo Alto desarrolló un equipo informático con el aspecto externo de un PC personal actual. Además de pantalla y teclado, disponía de un artefacto similar al ratón; en general, este aparato (denominado Alto) introdujo, mucho antes de que otros los reinventaran, algunos de los conceptos universalmente aceptados hoy en día. Sin embargo, la tecnología del momento no permitió alcanzar todas las intenciones. Alguna innovación, como la pantalla vertical, de formato similar a una hoja de papel (que desearían algunos actuales internautas para los navegadores) aún no ha sido adoptada: nuestros PC's siguen pareciendo televisores con teclas, y los procesadores de textos no muestran legiblemente una hoja en vertical completa incluso en monitores de 20 pulgadas.
El microprocesador. El desarrollo del primer microprocesador por Intel en 1971, el 4004 (de 4 bits), supuso el primer paso hacia el logro de un PC personal, al reducir drásticamente la circuitería adicional necesaria. Sucesores de este procesador fueron el 8008 y el 8080, de 8 bits. Ed Roberts construyó en 1975 el Altair 8800 basándose en el 8080; aunque esta máquina no tenía teclado ni pantalla (sólo interruptores y luces), era una arquitectura abierta (conocida por todo el mundo) y cuyas tarjetas se conectaban a la placa principal a través de 100 terminales, que más tarde terminarían convirtiéndose en el bus estándar S-100 de la industria. El Apple-I apareció en 1976, basado en el microprocesador de 8 bits 6502, en aquel entonces un recién aparecido aunque casi 10 veces más barato que el 8080 de Intel. Fue sucedido en 1977 por el Apple-II. No olvidemos los rudimentos de la época: el Apple-II tenía un límite máximo de 48 Kbytes de memoria. En el mismo año, Commodore sacó su PET con 8 Kbytes. Se utilizaban cintas de casete como almacenamiento, aunque comenzaron a aparecer las unidades de disquete de 5¼. Durante finales de los 70 aparecieron muchos otros ordenadores, fruto de la explosión inicial del microprocesador.
Los micros de los 80. En 1980, Sir Clive Sinclair lanzó el ZX-80, seguido muy poco después del ZX-81. Estaban basados en un microprocesador sucesor del 8085 de Intel: el Z80 (desarrollado por la empresa Zilog, creada por un ex-ingeniero de Intel). Commodore irrumpió con sus VIC-20 y, posteriormente, el Commodore 64, basados aún en el 6502 y, este último, con mejores posibilidades gráficas y unos 64 Kb de memoria. Su competidor fue el ZX-Spectrum de Sinclair, también basado en el Z80, con un chip propio para gestión de gráficos y otras tareas, la ULA, que permitió rebajar su coste y multiplicó su difusión por europa, y en particular por España. Sin embargo, todos los ordenadores domésticos de la época, como se dieron en llamar, estaban basados en procesadores de 8 bits y tenían el límite de 64 Kb de memoria. Los intentos de rebasar este límite manteniendo aún esos chips por parte de la plataforma MSX (supuesto estándar mundial con la misma suerte que ha corrido el Esperanto) o los CPC de Amstrad, de poco sirvieron.
El IBM PC. Y es que IBM también fabricó su propio ordenador personal con vocación profesional: el 12 de agosto de 1981 presentó el IBM PC. Estaba basado en el microprocesador 8088, de 16 bits, cuyas instrucciones serán las que usemos en este libro, ya que todos los procesadores posteriores son básicamente (en MS-DOS) versiones mucho más rápidas del mismo. El equipamiento de serie consistía en 16 Kbytes de memoria ampliables a 64 en la placa base (y a 256 añadiendo tarjetas); el almacenamiento externo se hacía en cintas de casete, aunque
25
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
pronto aparecieron las unidades de disco de 5¼ pulgadas y simple cara (160/180 Kb por disco) o doble cara (320/360 Kb). En 1983 apareció el IBM PC-XT, que traía como novedad un disco duro de 10 Mbytes. Un año más tarde aparecería el IBM PC-AT, introduciendo el microprocesador 286, así como ranuras de expansión de 16 bits (el bus ISA de 16 bits) en contraposición con las de 8 bits del PC y el XT (bus ISA de 8 bits), además incorporaba un disco duro de 20 Mbytes y disquetes de 5¼ pero con 1.2 Mbytes. En general, todos los equipos con procesador 286 o superior pueden catalogarse dentro de la categoría AT; el término XT hace referencia al 8088/8086 y similares. Finalmente, por PC (a secas) se entiende cualquiera de ambos; aunque si se hace distinción entre un PC y un AT en la misma frase, por PC se sobreentiende un XT, menos potente. El término PC ya digo, no obstante, es hoy en día mucho más general, referenciando habitualmente a cualquier ordenador personal. Alrededor del PC se estaba construyendo un imperio de software más importante que el propio hardware: estamos hablando del sistema operativo PC-DOS. Cuando aparecieron máquinas compatibles con el PC de IBM, tenían que respetar la compatibilidad con ese sistema, lo que fue sencillo (ya que Microsoft, le gustara o no a IBM, desarrolló el MS-DOS, compatible con el PC-DOS pero que no requería la BIOS del ordenador original, cuyo copyright era de IBM). Incluso, el desarrollo de los microprocesadores posteriores ha estado totalmente condicionado por el MS-DOS. [Por cierto, la jugada del PC-DOS/MS-DOS se repetiría en alguna manera pocos años después con el OS/2-Windows]. A partir de 1986, IBM fue paulatinamente dejando de tener la batuta del mercado del PC. La razón es que la propia IBM tenía que respetar la compatibilidad con lo anterior, y en ese terreno no tenía más facilidades para innovar que la competencia. El primer problema vino con la aparición de los procesadores 386: los demás fabricantes se adelantaron a IBM y lanzaron máquinas con ranuras de expansión aún de 16 bits, que no permitían obtener todo el rendimiento. IBM desarrolló demasiado tarde, en 1987, la arquitectura Microchannel, con bus de 32 bits pero cerrada e incompatible con tarjetas anteriores (aunque se desarrollaron nuevas tarjetas, eran caras) y la incluyó en su gama de ordenadores PS/2 (alguno de cuyos modelos era aún realmente ISA). La insolente respuesta de la competencia fue la arquitectura EISA, también de 32 bits pero compatible con la ISA anterior. Otro ejemplo: si IBM gobernó los estándares gráficos hasta la VGA, a partir de ahí sucedió un fenómeno similar y los demás fabricantes se adelantaron a finales de los 80 con mejores tarjetas y más baratas; sin embargo, se perdió la ventaja de la normalización (no hay dos tarjetas superiores a la VGA que funcionen igual). EISA también era caro, así que los fabricantes orientales, cruzada ya la barrera de los años 90, desarrollaron con la norma VESA las placas con bus local (VESA Local Bus); básicamente es una prolongación de las patillas de la CPU a las ranuras de expansión, lo que permite tarjetas rápidas de 32 bits pero muy conflictivas entre sí. Esta arquitectura de bus se popularizó mucho con los procesadores 486. Sin embargo, al final el estándar que se ha impuesto ha sido el propuesto por el propio fabricante de las CPU: Intel, con su bus PCI, que con el Pentium se ha convertido finalmente en el único estándar de bus de 32 bits. Estas máquinas aún admiten no obstante las viejas tarjetas ISA, suficientes para algunas aplicaciones de baja velocidad (modems,... etc).
La evolución del MS-DOS. Una manera sencilla de comprender la evolución de los PC es observar la evolución de las sucesivas versiones del DOS y los sistemas que le han sucedido. En 1979, Seatle Computer necesitaba apoyar de alguna manera a sus incipientes placas basadas en el 8086. Como Digital Research estaba tardando demasiado en convertir el CP/M-80 a CP/M-86, desarrolló su propio sistema: el QDOS 0.1, que fue presentado en 1980. Antes de finales de año apareció QDOS 0.3. Bill Gates, dueño de Microsoft, de momento sólo poseía una versión de lenguaje BASIC para 8086 no orientada a ningún sistema operativo particular, que le gustó a algún directivo de IBM. Bill Gates ya había
ARQUITECTURA E HISTORIA DE LOS MICROORDENADORES
25
hecho la primera demostración mundial de BASIC corriendo en un 8086 en las placas de Seatle Computer (en julio de 1979) y había firmado un contrato de distribución no exclusiva para el QDOS 0.3 a finales de 1980. En abril de 1981 aparecieron las primeras versiones de CP/M-86 de Digital, a la vez que QDOS se renombraba a 86-DOS 1.0 aunque en principio parecía tener menos futuro que el CP/M. En Julio, sin embargo, Microsoft adquiría todos los derechos del 86-DOS. Digital Research no ocupa actualmente el lugar de Microsoft porque en 1981 era una compañía demasiado importante como para cerrar un acuerdo con IBM sin imponer sus condiciones para cederle los derechos del sistema operativo CP/M. Así que IBM optó por Bill Gates, que acababa de adquirir un sistema operativo, el 86-DOS, que pasó a denominarse PC-DOS 1.0. Las versiones de PC-DOS no dependientes de la ROM BIOS de IBM se denominarían MS-DOS, término que ha terminado siendo más popular. A continuación se expone la evolución hasta la versión 5.0; las versiones siguientes no añaden ninguna característica interna nueva destacable (aunque a nivel de interfaz con el usuario y utilidades incluidas haya más cambios). El MS-DOS 7.0 sobre el que corre Windows 95 sí tiene bastantes retoques internos, pero no es frecuente su uso aislado o independiente de Windows 95. Aunque PC-DOS y MS-DOS siembre han caminado paralelos, hay una única excepción: la versión 7.0 (no confundir MS-DOS 7.0 con PC-DOS 7.0: este último es, realmente, el equivalente al MS-DOS 5.0 ó 6.2). Agosto de 1981.Presentación del MS-DOS 1.0 original. Marzo de 1982.MS-DOS 1.25, añadiendo soporte para disquetes de doble cara. Las funciones del DOS (en INT 21h) sólo llegaban hasta la 1Fh (¡la 30h no estaba implementada!). Marzo de 1983.MS-DOS 2.0 introducido con el XT: reescritura del núcleo en C; mejoras en el sistema de ficheros (FAT, subdirectorios,...); separación de los controladores de dispositivo del sistema. Mayo de 1983.MS-DOS 2.01: soporte de juegos de caracteres internacionales. Octubre de 1983.MS-DOS 2.11: eliminación de errores. Agosto de 1984.MS-DOS 3.0: Añade soporte para disquetes de 1.2M y discos duros de 20 Mb. No sería necesaria una nueva versión del DOS para cada nuevo formato de disco si el controlador integrado para A:, B: y C: lo hubieran hecho flexible algún día. Marzo de 1985. MS-DOS 3.1: Soporte para redes locales. Diciembre de 1985.MS-DOS 3.2: Soporte para disquetes de 720K (3½-DD). Abril de 1987.MS-DOS 3.3: Soporte para disquetes de 1.44M (3½-HD). Permite particiones secundarias en los discos duros. Soporte internacional: páginas de códigos. Julio de 1988.MS-DOS 4.0: Soporte para discos duros de más de 32 Mb (cambio radical interno que forzó la reescritura de muchos programas de utilidad) hasta 2 Gb. Controlador de memoria EMM386. Precipitada salida al mercado. Noviembre de 1988.MS-DOS 4.01: Corrige las erratas de la 4.0. Junio de 1991.MS-DOS 5.0: Soporte para memoria superior. La competencia de Digital Research, que irrumpe en el mundo del DOS una década más tarde (con DR-DOS), obliga a Microsoft a incluir ayuda online y a ocuparse un poco más de los usuarios. Digital Research trabajó arduamente para lograr una compatibilidad total con MS-DOS, y finalmente consiguió lanzar al mercado su sistema DR-DOS. Las versiones 5.0 y 6.0 de este sistema, así como el
25
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Novell DOS 7.0 (cuando cedió los derechos a Novell) se pueden considerar prácticamente 100% compatibles. El efecto del DR-DOS fue positivo, al forzar a Microsoft a mejorar la interacción del sistema operativo con los usuarios (documentación en línea, programas de utilidad, ciertos detalles...); por poner un ejemplo, hasta el MS-DOS 6.2 ha sido necesario intercambiar tres veces el disquete origen y el destino durante la copia de un disquete normal de 1.44M. En cierto modo, la prepotencia de Microsoft con el MS-DOS a principios de los noventa era similar a la de Digital Research a principios de los 80 con el CP/M.
El futuro. El resto de la historia de los sistemas operativos de PC ya la conoce el lector, a menos que no esté informado de la actualidad. Caminamos hacia la integración de los diversos Windows en uno sólo, que esperemos que algún día sea suficientemente abierto para que le surjan competidores. Si en el futuro hubiera un sólo sistema operativo soportado por Microsoft, no vamos por buen camino. En ese caso, sería de agradecer que algún juez les obligara a publicar una especificación completa de las funciones y protocolos del sistema, con objeto de que algún organismo de normalización internacional las recogiera sin ambigüedades para permitir la libre competencia de otros fabricantes. El DOS y el Windows actuales no son ningún invento maravilloso de Microsoft. Por poner un ejemplo, el MS-DOS 1.0 carecía de función para identificar la versión del sistema. Exactamente lo mismo le ha sucedido a las primeras versiones de Windows (hay varios chequeos distintos para detectarlas, según el modo de funcionamiento y la versión): el MS-DOS no lo escribió inicialmente Microsoft, pero Windows sí, y salta a la vista que sus programadores, para cometer semejante despiste, se sentaron delante del teclado antes de hacer un análisis de la aplicación a desarrollar, igual que lo hubiera hecho alguien que hubiera aprendido a programar con unos fascículos comprados en el kiosco. Con tanto analista en el paro... No olvidemos que el DOS y Windows son el fruto de toda la sociedad utilizando el mismo tipo de ordenadores y necesitando la compatibilidad con lo anterior a cualquier precio. La prueba evidente son los procesadores de Intel, construidos desde hace tiempo para dar servicio al sistema operativo del PC. Somos prisioneros, usuarios obligados de Microsoft. Naturalmente, no tengo nada contra Microsoft, pero opino que el poder adquirido durante una década, gracias a la exclusiva de los derechos sobre un sistema operativo sin ayuda en la línea de comandos, o de un Windows cerrado íntimamente ligado al DOS (de quien sólo Microsoft tiene el código fuente) no legitima a ninguna empresa a tener tanto poder. No lo olvidemos: el MS-DOS ha dado un vuelco hacia la amigabilidad con el usuario cuando Digital Research ha aparecido con el DR-DOS. Del mismo modo que Windows seguirá lento o colgándose mientras Unix no tenga más aplicaciones comerciales. Si hay alguien que puede competir con Windows es Unix. Y en Unix no dependemos de ningún fabricante concreto, ni de hardware ni de software. Probablemente, la insuficiente normalización actual la corregiría pronto el propio mercado. ¿Tiene usted Linux instalado en casa y lo utiliza al menos para conectarse a Internet por Infovía, o quizá le gustaría hacerlo algún día?. ¿O por el contrario es de los que piensan que Bill Gates es un genio?. Si se queda con la segunda opción, es que ve mucho la tele, aunque evidentemente tiene razón: y cuantos más como usted, más genio que será... ;-)
MICROPROCESADORES 8086/88, 286, 386 Y 486
31
Capítulo III: Microprocesadores 8086/88, 286, 386, 486 y Pentium.
3.1. - CARACTERÍSTICAS GENERALES. Los microprocesadores Intel 8086 y 8088 se desarrollan a partir de un procesador anterior, el 8080, que, en sus diversas encarnaciones -incluyendo el Zilog Z-80- ha sido la CPU de 8 bits de mayor éxito. Poseen una arquitectura interna de 16 bits y pueden trabajar con operandos de 8 y 16 bits; una capacidad de direccionamiento de 20 bits (hasta 1 Mb) y comparten el mismo juego de instrucciones. La filosofía de diseño de la familia del 8086 se basa en la compatibilidad y la creación de sistemas informáticos integrados, por lo que disponen de diversos coprocesadores como el 8089 de E/S y el 8087, coprocesador matemático de coma flotante. De acuerdo a esta filosofía y para permitir la compatibilidad con los anteriores sistemas de 8 bits, el 8088 se diseñó con un bus de datos de 8 bits, lo cual le hace más lento que su hermano el 8086, pues éste es capaz de cargar una palabra ubicada en una dirección par en un solo ciclo de memoria mientras el 8088 debe realizar dos ciclos leyendo cada vez un byte. Disponen de 92 tipos de instrucciones, que pueden ejecutar con hasta 7 modos de direccionamiento. Tienen una capacidad de direccionamiento en puertos de entrada y salida de hasta 64K (65536 puertos), por lo que las máquinas construidas entorno a estos microprocesadores no suelen emplear la entrada/salida por mapa de memoria, como veremos. Entre esas instrucciones, las más rápidas se ejecutan en 2 ciclos teóricos de reloj y unos 9 reales (se trata del movimiento de datos entre registros internos) y las más lentas en 206 (división entera con signo del acumulador por una palabra extraída de la memoria). Las frecuencias internas de reloj típicas son 4.77 MHz en la versión 8086; 8 MHz en la versión 8086-2 y 10 MHz en la 8086-1. Recuérdese que un MHz son un millón de ciclos de reloj, por lo que un PC estándar a 4,77 MHz puede ejecutar de 20.000 a unos 0,5 millones de instrucciones por segundo, según la complejidad de las mismas (un 486 a 50 MHz, incluso sin memoria caché externa es capaz de ejecutar entre 1,8 y 30 millones de estas instrucciones por segundo). El microprocesador Intel 80286 se caracteriza por poseer dos modos de funcionamiento completamente diferenciados: el modo real en el que se encuentra nada más ser conectado a la corriente y el modo protegido en el que adquiere capacidad de proceso multitarea y almacenamiento en memoria virtual. El proceso multitarea consiste en realizar varios procesos de manera aparentemente simultánea, con la ayuda del sistema operativo para conmutar automáticamente de uno a otro optimizando el uso de la CPU, ya que mientras un proceso está esperando a que un periférico complete una operación, se puede atender otro proceso diferente. La memoria virtual permite al ordenador usar más memoria de la que realmente tiene, almacenando parte de ella en disco: de esta manera, los programas creen tener a su disposición más memoria de la que realmente existe; cuando acceden a una parte de la memoria lógica que no existe físicamente, se produce una interrupción y el sistema operativo se encarga de acceder al disco y traerla. Cuando la CPU está en modo protegido, los programas de usuario tienen un acceso limitado al juego de instrucciones; sólo el proceso supervisor -normalmente el sistema operativo- está capacitado para realizar ciertas tareas. Esto es así para evitar que los programas de usuario puedan campar a sus anchas y entrar en conflictos unos con otros, en materia de recursos como memoria o periféricos. Además, de esta manera, aunque un error software provoque el cuelgue de un proceso, los demás pueden seguir funcionando normalmente, y el sistema operativo podría abortar el proceso colgado. Por desgracia, con el DOS el 286 no está en modo
31
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
protegido y el cuelgue de un solo proceso -bien el programa principal o una rutina operada por interrupcionessignifica la caída inmediata de todo el sistema. El 8086 no posee ningún mecanismo para apoyar la multitarea ni la memoria virtual desde el procesador, por lo que es difícil diseñar un sistema multitarea para el mismo y casi imposible conseguir que sea realmente operativo. Obviamente, el 286 en modo protegido pierde absolutamente toda la compatibilidad con los procesadores anteriores. Por ello, en este libro sólo trataremos el modo real, único disponible bajo DOS, aunque veremos alguna instrucción extra que también se puede emplear en modo real. Las características generales del 286 son: tiene un bus de datos de 16 bits, un bus de direcciones de 24 bits (16 Mb); posee 25 instrucciones más que el 8086 y admite 8 modos de direccionamiento. En modo virtual permite direccionar hasta 1 Gigabyte. Las frecuencias de trabajo típicas son de 12 y 16 MHz, aunque existen versiones a 20 y 25 MHz. Aquí, la instrucción más lenta es la misma que en el caso del 8086, solo que emplea 29 ciclos de reloj en lugar de 206. Un 286 de categoría media (16 MHz) podría ejecutar más de medio millón de instrucciones de estas en un segundo, casi 15 veces más que un 8086 medio a 8 MHz. Sin embargo, transfiriendo datos entre registros la diferencia de un procesador a otro se reduce notablemente, aunque el 286 es más rápido y no sólo gracias a los MHz adicionales. Versiones mejoradas de los Intel 8086 y 8088 se encuentran también en los procesadores NEC-V30 y NEC-V20 respectivamente. Ambos son compatibles Hardware y Software, con la ventaja de que el procesado de instrucciones está optimizado, llegando a superar casi en tres veces la velocidad de los originales en algunas instrucciones aritméticas. También poseen una cola de prebúsqueda mayor (cuando el microprocesador está ejecutando una instrucción, si no hace uso de los buses externos, carga en una cola FIFO de unos pocos bytes las posiciones posteriores a la que está procesando, de esta forma una vez que concluye la instrucción en curso ya tiene internamente la que le sigue). Además, los NEC V20 y V30 disponen de las mismas instrucciones adicionales del 286 en modo real, al igual que el 80186 y el 80188. Por su parte, el 386 dispone de una arquitectura de registros de 32 bits, con un bus de direcciones también de 32 bits (direcciona hasta 4 Gigabytes = 4096 Mb) y más modos posibles de funcionamiento: el modo real (compatible 8086), el modo protegido (relativamente compatible con el del 286), un modo protegido propio que permite -¡por fin!- romper la barrera de los tradicionales segmentos y el modo «virtual 86», en el que puede emular el funcionamiento simultáneo de varios 8086. Una vez más, todos los modos son incompatibles entre sí y requieren de un sistema operativo específico: si se puede perdonar al fabricante la pérdida de compatibilidad del modo avanzados del 286 frente al 8086, debido a la lógica evolución tecnológica, no se puede decir lo mismo del 386 respecto al 286: no hubiera sido necesario añadir un nuevo modo protegido si hubiera sido mejor construido el del 286 apenas un par de años atrás. Normalmente, los 386 suelen operar en modo real (debido al DOS) por lo que no se aprovechan las posibilidades multitarea ni de gestión de memoria. Por otra parte, aunque se pueden emplear los registros de 32 bits en modo real, ello no suele hacerse -para mantener la compatibilidad con procesadores anteriores- con lo que de entrada se está tirando a la basura un 50% de la capacidad de proceso del chip, aunque por fortuna estos procesadores suelen trabajar a frecuencias de 16/20 MHz (obsoletas) y normalmente de 33 y hasta 40 MHz. El 386sx es una variante del 386 a nivel de hardware, aunque es compatible en software. Básicamente, es un 386 con un bus de datos de sólo 16 bits -más lento, al tener que dar dos pasadas para un dato de 32 bits-. De hecho, podría haber sido diseñado perfectamente para mantener una compatibilidad hardware con el 286, aunque el fabricante lo evitó probablemente por razones comerciales. El 486 se diferencia del 386 en la integración en un solo chip del coprocesador 387. También se ha mejorado la velocidad de operación: la versión de 25 MHz dobla en términos reales a un 386 a 25 MHz equipado con el mismo tamaño de memoria caché. La versión 486sx no se diferencia en el tamaño del bus, también de 32 bits, sino en la ausencia del 387 (que puede ser añadido externamente). También existen versiones de 486 con buses de 16 bits, el primer fabricante de estos chips, denominados 486SLC, ha sido Cyrix. Una tendencia iniciada por el 486 fue la de duplicar la velocidad del reloj interno (pongamos por caso de 33 a 66 MHz) aunque en las comunicaciones con los buses exteriores se respeten los 33 MHz. Ello agiliza la ejecución de las instrucciones más largas: bajo DOS, el rendimiento general del sistema se puede considerar
31
MICROPROCESADORES 8086/88, 286, 386 Y 486
prácticamente el doble. Son los chips DX2 (también hay una variante a 50 MHz: 25 x 2). La culminación de esta tecnología viene de la mano de los DX4 a 75/100 MHz (25/33 x 3). El Pentium, último procesador de Intel en el momento de escribirse estas líneas, se diferencia respecto al 486 en el bus de datos (ahora de 64 bits, lo que agiliza los accesos a memoria) y en un elevadísimo nivel de optimización y segmentación que le permite, empleando compiladores optimizados, simultanear en muchos casos la ejecución de dos instrucciones consecutivas. Posee dos cachés internas, tiene capacidad para predecir el destino de los saltos y la unidad de coma flotante experimenta elevadas mejoras. Sin embargo, bajo DOS, un Pentium básico sólo es unas 2 veces más rápido que un 486 a la misma frecuencia de reloj. Comenzó en 60/90 MHz hasta los 166/200/233 MHz de las últimas versiones (Pentium Pro y MMX), que junto a diversos clones de otros fabricantes, mejoran aún más el rendimiento. Todos los equipos Pentium emplean las técnicas DX, ya que las placas base típicas corren a 60 MHz. Para hacerse una idea, por unas 200000 pts de 1997 un equipo Pentium MMX a 233 MHz es cerca de 2000 veces más rápido en aritmética entera que el IBM PC original de inicios de la década de los 80; en coma flotante la diferencia aumenta incluso algunos órdenes más de magnitud. Y a una fracción del coste (un millón de pts de aquel entonces que equivale a unos 2,5 millones de hoy en día). Aunque no hay que olvidar la revolución del resto de los componentes: 100 veces más memoria (central y de vídeo), 200 veces más grande el disco duro... y que un disco duro moderno transfiere datos 10 veces más deprisa que la memoria de aquel IBM PC original. Por desgracia, el software no ha mejorado el rendimiento, ni remotamente, en esa proporción: es la factura pasada por las técnicas de programación cada vez a un nivel más alto (aunque nadie discute sus ventajas). Una característica de los microprocesadores a partir del 386 es la disponibilidad de memorias caché de alta velocidad de acceso -muy pocos nanosegundos- que almacenan una pequeña porción de la memoria principal. Cuando la CPU accede a una posición de memoria, cierta circuitería de control se encarga de ir depositando el contenido de esa posición y el de las posiciones inmediatamente consecutivas en la memoria caché. Cuando sea necesario acceder a la instrucción siguiente del programa, ésta ya se encuentra en la caché y el acceso es muy rápido. Lo ideal sería que toda la memoria del equipo fuera caché, pero esto no es todavía posible actualmente. Una caché de tamaño razonable puede doblar la velocidad efectiva de proceso de la CPU. El 8088 carecía de memoria caché, pero sí estaba equipado con una unidad de lectura adelantada de instrucciones con una cola de prebúsqueda de 4 bytes: de esta manera, se agilizaba ya un tanto la velocidad de proceso al poder ejecutar una instrucción al mismo tiempo que iba leyendo la siguiente. 3.2. - REGISTROS DEL 8086 Y DEL 286. Estos procesadores disponen de 14 registros de 16 bits (el 286 alguno más, pero no se suele emplear bajo DOS). La misión de estos registros es almacenar las posiciones de memoria que van a experimentar repetidas manipulaciones, ya que los accesos a memoria son mucho más lentos que los accesos a los registros. Además, hay ciertas operaciones que sólo se pueden realizar sobre los registros. No todos los registros sirven para almacenar datos, algunos están especializados en apuntar a las direcciones de memoria. La mecánica básica de funcionamiento de un programa consiste en cargar los registros con datos de la memoria o de un puerto de E/S, procesar los datos y devolver el resultado a la memoria o a otro puerto de E/S. Obviamente, si un dato sólo va a experimentar un cambio, es preferible realizar la operación directamente sobre la memoria, si ello es posible. A continuación se describen los registros del 8086. ┌────────────┐ │ AX │ ├────────────┤ │ BX │ ├────────────┤ │ CX │ ├────────────┤ │ DX │ └────────────┘
┌────────────┐ │ SP │ ├────────────┤ │ BP │ ├────────────┤ │ SI │ ├────────────┤ │ DI │ └────────────┘
Registros de
┌────────────┐ │ CS │ ├────────────┤ │ DS │ ├────────────┤ │ SS │ ├────────────┤ │ ES │ └────────────┘
Registros punteros de
┌────────────┐ │ IP │ ├────────────┤ │ flags │ └────────────┘ Registro puntero de instrucciones y flags
Registros de
31
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
datos
pila e índices
segmento
- Registros de datos: AX, BX, CX, DX: pueden utilizarse bien como registros de 16 bits o como dos registros separados de 8 bits (byte superior e inferior) cambiando la X por H o L según queramos referirnos a la parte alta o baja respectivamente. Por ejemplo, AX se descompone en AH (parte alta) y AL (parte baja). Evidentemente, ¡cualquier cambio sobre AH o AL altera AX!: valga como ejemplo que al incrementar AH se le están añadiendo 256 unidades a AX. AX = Acumulador. Es el registro principal, es utilizado en las instrucciones de multiplicación y división y en algunas instrucciones aritméticas especializadas, así como en ciertas operaciones de carácter específico como entrada, salida y traducción. Obsérvese que el 8086 es suficientemente potente para realizar las operaciones lógicas, la suma y la resta sobre cualquier registro de datos, no necesariamente el acumulador. BX = Base. Se usa como registro base para referenciar direcciones de memoria con direccionamiento indirecto, manteniendo la dirección de la base o comienzo de tablas o matrices. De esta manera, no es preciso indicar una posición de memoria fija, sino la número BX (así, haciendo avanzar de unidad en unidad a BX, por ejemplo, se puede ir accediendo a un gran bloque de memoria en un bucle). CX = Contador. Se utiliza comúnmente como contador en bucles y operaciones repetitivas de manejo de cadenas. En las instrucciones de desplazamiento y rotación se utiliza como contador de 8 bits. DX = Datos. Usado en conjunción con AX en las operaciones de multiplicación y división que involucran o generan datos de 32 bits. En las de entrada y salida se emplea para especificar la dirección del puerto E/S. - Registros de segmento: Definen áreas de 64 Kb dentro del espacio de direcciones de 1 Mb del 8086. Estas áreas pueden solaparse total o parcialmente. No es posible acceder a una posición de memoria no definida por algún segmento: si es preciso, habrá de moverse alguno. CS = Registro de segmento de código (code segment). Contiene la dirección del segmento con las instrucciones del programa. Los programas de más de 64 Kb requieren cambiar CS periódicamente. DS = Registro de segmento de datos (data segment). Segmento del área de datos del programa. SS = Registro de segmento de pila (stack segment). Segmento de pila. ES = Registro de segmento extra (extra segment). Segmento de ampliación para zona de datos. Es extraordinariamente útil actuando en conjunción con DS: con ambos se puede definir dos zonas de 64 Kb, tan alejadas como se desee en el espacio de direcciones, entre las que se pueden intercambiar datos.
MICROPROCESADORES 8086/88, 286, 386 Y 486
31
- Registros punteros de pila: SP = Puntero de pila (stack pointer). Apunta a la cabeza de la pila. Utilizado en las instrucciones de manejo de la pila. BP = Puntero base (base pointer). Es un puntero de base, que apunta a una zona dentro de la pila dedicada al almacenamiento de datos (variables locales y parámetros de las funciones en los programas compilados). - Registros índices: SI = Índice fuente (source index). Utilizado como registro de índice en ciertos modos de direccionamiento indirecto, también se emplea para guardar un valor de desplazamiento en operaciones de cadenas. DI = Índice destino (destination index). Se usa en determinados modos de direccionamiento indirecto y para almacenar un desplazamiento en operaciones con cadenas. - Puntero de instrucciones o contador de programa: IP = Puntero de instrucción (instruction pointer). Marca el desplazamiento de la instrucción en curso dentro del segmento de código. Es automáticamente modificado con la lectura de una instrucción. - Registro de estado o de indicadores (flags). Es un registro de 16 bits de los cuales 9 son utilizados para indicar diversas situaciones durante la ejecución de un programa. Los bits 0, 2, 4, 6, 7 y 11 son indicadores de condición, que reflejan los resultados de operaciones del programa; los bits del 8 al 10 son indicadores de control y el resto no se utilizan. Estos indicadores pueden ser comprobados por las instrucciones de salto condicional, lo que permite variar el flujo secuencial del programa según el resultado de las operaciones. ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │15 │14 │13 │12 │11 │10 │ 9 │ 8 │ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ │ │ │ │OF │DF │IF │TF │SF │ZF │ │AF │ │PF │ │CF │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ CF (Carry Flag) Indicador de acarreo. Su valor más habitual es lo que nos llevamos en una suma o resta. OF (Overflow Flag)Indicador de desbordamiento. Indica que el resultado de una operación no cabe en el tamaño del operando destino. ZF (Zero Flag) Indicador de resultado 0 o comparación igual. SF (Sign Flag) Indicador de resultado o comparación negativa. PF (Parity Flag) Indicador de paridad. Se activa tras algunas operaciones aritmético-lógicas para indicar que el número de bits a uno resultante es par. AF (Auxiliary Flag)Para ajuste en operaciones BCD. DF (Direction Flag)Indicador de dirección. Manipulando bloques de memoria, indica el sentido de avance (ascendente/descendente). IF (Interrupt Flag)Indicador de interrupciones: puesto a 1 están permitidas. TF (Trap Flag) Indicador de atrape (ejecución paso a paso). 3.3. - REGISTROS DEL 386 Y PROCESADORES SUPERIORES.
31
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Los 386 y superiores disponen de muchos más registros de los que vamos a ver ahora. Sin embargo, bajo el sistema operativo DOS sólo se suelen emplear los que veremos, que constituyen básicamente una extensión a 32 bits de los registros originales del 8086. ┌──────┬──────┐
┌──────┐
┌──────┐
┌──────┐
│
│
│
│
│
AX
│
└──────┴──────┘
SP
│
└──────┘
CS
│
└──────┘
IP
│
└──────┘
EAX ┌──────┬──────┐
┌──────┬──────┐
┌──────┐
┌──────┐
│
│
│
│
│ flags│
└──────┘
└──────┘
│
BX
│
│
BP
│
└──────┴──────┘
└──────┴──────┘
EBX
EBP
DS
┌──────┬──────┐
┌──────┬──────┐
┌──────┐
┌──────┐
│
│
│
│
│
CX
│
│
SI
│
└──────┴──────┘
└──────┴──────┘
ECX
ESI
ES
│
└──────┘
FS
│
└──────┘
┌──────┬──────┐
┌──────┬──────┐
┌──────┐
┌──────┐
│
│
│
│
│
DX
│
│
DI
│
└──────┴──────┘
└──────┴──────┘
EDX
EDI
SS
│
└──────┘
Se amplía el tamaño de los registros de datos (que pueden ser accedidos en fragmentos de 8, 16 ó 32 bits) y se añaden dos nuevos registros de segmento multipropósito (FS y GS). Algunos de los registros aquí mostrados son realmente de 32 bits (como EIP en vez de IP), pero bajo sistema operativo DOS no pueden ser empleados de manera directa, por lo que no les consideraremos.
GS
│
└──────┘
3.4. - MODOS DE DIRECCIONAMIENTO. Son los distintos modos de acceder a los datos en memoria por parte del procesador. Antes de ver los modos de direccionamiento, echaremos un vistazo a la sintaxis general de las instrucciones, ya que pondremos alguna en los ejemplos: INSTRUCCIÓN DESTINO, FUENTE
Donde destino indica dónde se deja el resultado de la operación en la que pueden participar (según casos) FUENTE e incluso el propio DESTINO. Hay instrucciones, sin embargo, que sólo tienen un operando, como la siguiente, e incluso ninguno: INSTRUCCIÓN DESTINO
Como ejemplos, aunque no hemos visto aún las instrucciones utilizaremos un par de ellas: la de copia o movimiento de datos (MOV) y la de suma (ADD). 3.4.1. - ORGANIZACIÓN DE DIRECCIONES: SEGMENTACIÓN. Como ya sabemos, los microprocesadores 8086 y compatibles poseen registros de un tamaño máximo de 16 bits que direccionarían hasta 64K; en cambio, la dirección se compone de 20 bits con capacidad para 1Mb, hay por tanto que recurrir a algún artificio para direccionar toda la memoria. Dicho artificio consiste en la segmentación: se trata de dividir la memoria en grupos de 64K. Cada grupo se asocia con un registro de segmento; el desplazamiento (offset) dentro de ese segmento lo proporciona otro registro de 16 bits. La dirección absoluta se calcula multiplicando por 16 el valor del registro de segmento y sumando el offset, obteniéndose una dirección efectiva de 20 bits. Esto equivale a concebir el mecanismo de generación de la dirección absoluta, como si se tratase de que los registros de segmento tuvieran 4 bits a 0 (imaginarios) a la derecha antes de sumarles el desplazamiento: dirección = segmento * 16 + offset
31
MICROPROCESADORES 8086/88, 286, 386 Y 486
En la práctica, una dirección se indica con la notación SEGMENTO:OFFSET; además, una misma dirección puede expresarse de más de una manera: por ejemplo, 3D00h:0300h es equivalente a 3D30:0000h. Es importante resaltar que no se puede acceder a más de 64 Kb en un segmento de datos. Por ello, en los procesadores 386 y superiores no se deben emplear registros de 32 bit para generar direcciones (bajo DOS), aunque para los cálculos pueden ser interesantes (no obstante, sí sería posible configurar estos procesadores para poder direccionar más memoria bajo DOS con los registros de 32 bits, aunque no resulta por lo general práctico). 3.4.2. - MODOS DE DIRECCIONAMIENTO. - Direccionamiento inmediato: El operando es una constante situada detrás del código de la instrucción. Sin embargo, como registro destino no se puede indicar uno de segmento (habrá que utilizar uno de datos como paso intermedio). ADD AX,0fffh El número hexadecimal 0fffh es la constante numérica que en el direccionamiento inmediato se le sumará al registro AX. Al trabajar con ensambladores, se pueden definir símbolos constantes (ojo, no variables) y es más intuitivo: dato
EQU 0fffh MOV AX,dato
; símbolo constante
Si se referencia a la dirección de memoria de una variable de la siguiente forma, también se trata de un caso de direccionamiento inmediato: dato
DW 0fffh MOV AX,OFFSET dato
; ahora es una variable ; AX = «dirección de memoria» de
dato
Porque hay que tener en cuenta que cuando traduzcamos a números el símbolo podría quedar: 17F3:0A11
DW FFF MOV AX,0A11
- Direccionamiento de registro: Los operandos, necesariamente de igual tamaño, están contenidos en los registros indicados en la instrucción: MOV DX,AX MOV AH,AL - Direccionamiento directo o absoluto: El operando está situado en la dirección indicada en la instrucción, relativa al segmento que se trate: MOV AX,[57D1h] MOV AX,ES:[429Ch] Esta sintaxis (quitando la 'h' de hexadecimal) sería la que admite el programa DEBUG (realmente habría que poner, en el segundo caso, ES: en una línea y el MOV en otra). Al trabajar con ensambladores, las variables en memoria se pueden referenciar con etiquetas simbólicas: MOV AX,dato MOV AX,ES:dato dato
DW
1234h
; variable del programa
31
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
En el primer ejemplo se transfiere a AX el valor contenido en la dirección apuntada por la etiqueta dato sobre el segmento de datos (DS) que se asume por defecto; en el segundo ejemplo se indica de forma explícita el segmento tratándose del segmento ES. La dirección efectiva se calcula de la forma ya vista con anterioridad: Registro de segmento * 16 + desplazamiento_de_dato (este desplazamiento depende de la posición al ensamblar el programa). - Direccionamiento indirecto: El operando se encuentra en una dirección señalada por un registro de segmento*16 más un registro base (BX/BP) o índice (SI/DI). (Nota: BP actúa por defecto con SS). MOV AX,[BP] MOV ES:[DI],AX
; AX = [SS*16+BP] ; [ES*16+DI] = AX
- Indirecto con índice o indexado: El operando se encuentra en una dirección determinada por la suma de un registro de segmento*16, un registro de índice, SI o DI y un desplazamiento de 8 ó 16 bits. Ejemplos: MOV AX,[DI+DESP] ó ADD [SI+DESP],BX ó
MOV AX,desp[DI] ADD desp[SI],BX
- Indirecto con base e índice o indexado a base: El operando se encuentra en una dirección especificada por la suma de un registro de segmento*16, uno de base, uno de índice y opcionalmente un desplazamiento de 8 ó 16 bits: MOV AX,ES:[BX+DI+DESP] ó MOV CS:[BX+SI+DESP],CX ó
MOV AX,ES:desp[BX][DI] MOV CS:desp[BX][SI],CX
Combinaciones de registros de segmento y desplazamiento. Como se ve en los modos de direccionamiento, hay casos en los que se indica explícitamente el registro de segmento a usar para acceder a los datos. Existen unos segmentos asociados por defecto a los registros de desplazamiento (IP, SP, BP, BX, DI, SI); sólo es necesario declarar el segmento cuando no coincide con el asignado por defecto. En ese caso, el ensamblador genera un byte adicional (a modo de prefijo) para indicar cuál es el segmento referenciado. La siguiente tabla relaciona las posibles combinaciones de los registros de segmento y los de desplazamiento:
IP SP BP BX SI DI
CS SS DS ES ╔═══════════════╦═══════════════╦═══════════════╦═══════════════╗ ║ Sí ║ No ║ No ║ No ║ ╠═══════════════╬═══════════════╬═══════════════╬═══════════════╣ ║ No ║ Sí ║ No ║ No ║ ╠═══════════════╬═══════════════╬═══════════════╬═══════════════╣ ║ con prefijo ║ por defecto ║ con prefijo ║ con prefijo ║ ╠═══════════════╬═══════════════╬═══════════════╬═══════════════╣ ║ con prefijo ║ con prefijo ║ por defecto ║ con prefijo ║ ╠═══════════════╬═══════════════╬═══════════════╬═══════════════╣ ║ con prefijo ║ con prefijo ║ por defecto ║ con prefijo ║ ╠═══════════════╬═══════════════╬═══════════════╬═══════════════╣ ║ con prefijo ║ con prefijo ║ por defecto ║ con prefijo(1)║ ╚═══════════════╩═══════════════╩═══════════════╩═══════════════╝ (1) También por defecto en el manejo de cadenas.
Los 386 y superiores admiten otros modos de direccionamiento más sofisticados, que se verán en el próximo capítulo, después de conocer todas las instrucciones del 8086. Por ahora, con todos estos modos se
31
MICROPROCESADORES 8086/88, 286, 386 Y 486
puede considerar que hay más que suficiente. De hecho, algunos se utilizan en muy contadas ocasiones. 3.5. - LA PILA. La pila es un bloque de memoria de estructura LIFO (Last Input First Output: último en entrar, primero en salir) que se direcciona mediante desplazamientos desde el registro SS (segmento de pila). Las posiciones individuales dentro de la pila se calculan sumando al contenido del segmento de pila SS un desplazamiento contenido en el registro puntero de pila SP. Todos los datos que se almacenan en la pila son de longitud palabra, y cada vez que se introduce algo en ella por medio de las instrucciones de manejo de pila (PUSH y POP), el puntero se decrementa en dos; es decir, la pila avanza hacia direcciones decrecientes. El registro BP suele utilizarse normalmente para apuntar a una cierta posición de la pila y acceder indexadamente a sus elementos generalmente en el caso de variables- sin necesidad de desapilarlos para consultarlos. La pila es utilizada frecuentemente al principio de una subrutina para preservar los registros que no se desean modificar; al final de la subrutina basta con recuperarlos en orden inverso al que fueron depositados. En estas operaciones conviene tener cuidado, ya que la pila en los 8086 es común al procesador y al usuario, por lo que se almacenan en ella también las direcciones de retorno de las subrutinas. Esta última es, de hecho, la más importante de sus funciones. La estructura de pila permite que unas subrutinas llamen a otras que a su vez pueden llamar a otras y así sucesivamente: en la pila se almacenan las direcciones de retorno, que serán las de la siguiente instrucción que provocó la llamada a la subrutina. Así, al retornar de la subrutina se extrae de la pila la dirección a donde volver. Los compiladores de los lenguajes de alto nivel la emplean también para pasar los parámetros de los procedimientos y para generar en ella las variables automáticas -variables locales que existen durante la ejecución del subprograma y se destruyen inmediatamente después-. Por ello, una norma básica es que se debe desapilar siempre todo lo apilado para evitar una pérdida de control inmediata del ordenador. Ejemplo de operación sobre la pila (todos los datos son arbitrarios): Memoria ├───────────┤ │
66h
SS:SP
│
│
├───────────┤ │
91h
│
Memoria ├───────────┤
<-- 14C0:D022
66h
SS:SP
│
Memoria ├───────────┤ │
66h
│
├───────────┤
├───────────┤
│
│
91h
│
91h
│
├───────────┤
├───────────┤
├───────────┤
│
│
│
F3h
│
12h
│
├───────────┤
├───────────┤
│
│
21h
│
├───────────┤
34h
│
12h
SS:SP
<-- 14C0:D022
│
├───────────┤ <-- 14C0:D020
├───────────┤
│
34h
│
├───────────┤
Situación inicial
después de PUSH AX
después de POP BX
AX = 1234h
AX = 1234h
AX = 1234h
BX = 9D33h
BX = 9D33h
BX = 1234h
3.6. - UN PROGRAMA DE EJEMPLO. Aunque las instrucciones del procesador no serán vistas hasta el próximo capítulo, con objeto de ayudar a la imaginación del lector elaboraremos un primer programa de ejemplo en lenguaje ensamblador. La utilidad de este programa es dejar patente que lo único que entiende el 8086 son números, aunque nosotros nos referiremos a ellos con unos símbolos que faciliten entenderlos. También es interesante este ejemplo para afianzar el concepto de registro de segmento. En este programa sólo vamos a emplear las instrucciones MOV, ya conocida, y alguna otra más como la instrucción INC (incrementar), DEC (disminuir una unidad) y JNZ (saltar si el resultado no es cero). Suponemos que el programa está ubicado a partir de la dirección de memoria 14D3:7A10 (arbitrariamente
31
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
elegida) y que lo que pretendemos hacer con él es limpiar la pantalla. Como el ordenador es un PC con monitor en color, la pantalla de texto comienza en B800:0000 (no es más que una zona de memoria). Por cada carácter que hay en dicha pantalla, comenzando arriba a la izquierda, a partir de la dirección B800:0000 tenemos dos bytes: el primero, con el código ASCII del carácter y el segundo con el color. Lo que vamos a hacer es rellenar los 2000 caracteres (80 columnas x 25 líneas) con espacios en blanco (código ASCII 32, ó 20h en hexadecimal), sin modificar el color que hubiera antes. Esto es, se trata de poner el valor 32 en la dirección B800:0000, la B800:0002, la B800:0004... y así sucesivamente. El programa quedaría en memoria de esta manera: La primera columna indica la dirección de memoria donde está el programa que se ejecuta (CS=14D3h e IP=7A10h al principio). La segunda columna constituye el código máquina que interpreta el 8086. Algunas instrucciones ocupan un byte de memoria, otras dos ó tres (las hay de más). La tercera columna contiene el nombre de las instrucciones, algo mucho más legible para los humanos que los números: 14D3:7A10
B9 D0 07
MOV
CX,7D0H
; CX = 7D0h (2000 decimal = 7D0 hexadecimal)
14D3:7A13
B8 00 B8
MOV
AX,0B800h
; segmento de la memoria de pantalla
14D3:7A16
8E D8
MOV
DS,AX
; apuntar segmento de datos a la misma
14D3:7A18
BB 00 00
MOV
BX,0
; apuntar al primer carácter ASCII de la pantalla
14D3:7A1B 14D3:7A1E
C6 07 20 43
MOV INC
BYTE PTR [BX],32 BX
; se pone BYTE PTR para indicar que 32 es de 8 bits ; BX=BX+1 -¾ apuntar al byte de color
14D3:7A1F
43
INC
BX
; BX=BX+1 -¾ apuntar al siguiente carácter ASCII
14D3:7A20
49
DEC
CX
; CX=CX-1 -¾ queda un carácter menos
14D3:7A21
75 F8
JNZ
-8
; si CX no es 0, saltar 8 bytes atrás (a 14D3:7A1B)
Como se puede ver, la segunda instrucción (bytes de código máquina 0B8h, 0 y 0B8h colocados en posiciones consecutivas) está colocada a partir del desplazamiento 7A13h, ya que la anterior que ocupaba 3 bytes comenzaba en 7A10h. En el ejemplo cargamos el valor 0B800h en DS apoyándonos en AX como intermediario. El motivo es que los registros de segmento no admiten el direccionamiento inmediato. A medida que se van haciendo programas, el ensamblador da mensajes de error cuando se encuentra con estos fallos y permite ir aprendiendo con facilidad las normas, que tampoco son demasiadas. La instrucción MOV BYTE PTR [BX],32 equivale a decir: «poner en la dirección de memoria apuntada por BX (DS:[BX] para ser más exactos) el byte de valor 32». El valor 0F8h del código máquina de la última instrucción es el complemento a dos (número negativo) del valor 8. Normalmente, casi nunca habrá que ensamblar a mano consultando unas tablas, como hemos hecho en este ejemplo. Sin embargo, la mejor manera de aprender ensamblador es no olvidando la estrecha relación de cada línea de programa con la CPU y la memoria.
41
JUEGO DE INSTRUCCIONES 80x86
Capítulo IV: JUEGO DE INSTRUCCIONES 80x86
4.1. - DESCRIPCIÓN COMPLETA DE LAS INSTRUCCIONES. Nota: en el efecto de las instrucciones sobre el registro de estado se utilizará la siguiente notación: - bit no modificado ? desconocido o indefinido x modificado según el resultado de la operación 1 puesto siempre a 1 0 puesto siempre a 0 4.1.1. - INSTRUCCIONES DE CARGA DE REGISTROS Y DIRECCIONES. MOV (transferencia) Sintaxis: MOV dest, origen. Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Transfiere datos de longitud byte o palabra del operando origen al operando destino. Pueden ser operando origen y operando destino cualquier registro o posición de memoria direccionada de las formas ya vistas, con la única condición de que origen y destino tengan la misma dimensión. Existen ciertas limitaciones, como que los registros de segmento no admiten el direccionamiento inmediato: es incorrecto MOV DS,4000h; pero no lo es por ejemplo MOV DS,AX o MOV DS,VARIABLE. No es posible, así mismo, utilizar CS como destino (es incorrecto hacer MOV CS,AX aunque pueda admitirlo algún ensamblador). Al hacer MOV hacia un registro de segmento, las interrupciones quedan inhibidas hasta después de ejecutarse la siguiente instrucción (8086/88 de 1983 y procesadores posteriores). Ejemplos:
mov mov mov
ds,ax bx,es:[si] si,offset dato
En el último ejemplo, no se coloca en SI el valor de la variable «dato» sino su dirección de memoria o desplazamiento respecto al segmento de datos. En otras palabras, SI es un puntero a «dato» pero no es «dato». En el próximo capítulo se verá cómo se declaran las variables. XCHG (intercambiar) Sintaxis: XCHG destino, origen Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Intercambia el contenido de los operandos origen y destino. No pueden utilizarse registros de
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
segmentos como operandos. Ejemplo:
xchg xchg
bl,ch mem_pal,bx
XLAT (traducción) Sintaxis: XLAT tabla Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Se utiliza para traducir un byte del registro AL a un byte tomado de la tabla de traducción. Los datos se toman desde una dirección de la tabla correspondiente a BX + AL, donde bx es un puntero a el comienzo de la tabla y AL es un índice. Indicar «tabla» al lado de xlat es sólo una redundancia opcional. Ejemplo:
mov mov xlat
bx,offset tabla al,4
LEA (carga dirección efectiva) Sintaxis: LEA destino, origen Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Transfiere el desplazamiento del operando fuente al operando destino. Otras instrucciones pueden a continuación utilizar el registro como desplazamiento para acceder a los datos que constituyen el objetivo. El operando destino no puede ser un registro de segmento. En general, esta instrucción es equivalente a «MOV destino,OFFSET fuente» y de hecho los buenos ensambladores (TASM) la codifican como MOV para economizar un byte de memoria. Sin embargo, LEA es en algunos casos más potente que MOV al permitir indicar registros de índice y desplazamiento para calcular el offset: lea
dx,datos[si]
En el ejemplo de arriba, el valor depositado en DX es el offset de la etiqueta «datos» más el registro SI. Esa sola instrucción es equivalente a estas dos: mov add
dx,offset datos dx,si
LDS (carga un puntero utilizando DS) Sintaxis: LDS destino, origen Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Traslada un puntero de 32 bits (dirección completa de memoria compuesta por segmento y
41
JUEGO DE INSTRUCCIONES 80x86
desplazamiento), al destino indicado y a DS. A partir de la dirección indicada por el operando origen, el procesador toma 4 bytes de la memoria: con los dos primeros forma una palabra que deposita en «destino» y, con los otros dos, otra en DS. Ejemplo:
punt
dd lds
12345678h si,punt
Como resultado de esta instrucción, en DS:SI se hace referencia a la posición de memoria 1234h:5678h; 'dd' sirve para definir una variable larga de 4 bytes (denominada «punt» en el ejemplo) y será explicado en el capítulo siguiente. LES (carga un puntero utilizando ES) Sintaxis: LES destino, origen Esta instrucción es análoga a LDS, pero utilizando ES en lugar de DS. LAHF (carga AH con los indicadores) Sintaxis: LAHF Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Carga los bits 7, 6, 4, 2 y 0 del registro AH con el contenido de los indicadores SF, ZF, AF, PF Y CF respectivamente. El contenido de los demás bits queda sin definir. SAHF (copia AH en los indicadores) Sintaxis: SAHF Indicadores:
OF -
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF x
Transfiere el contenido de los bits 7, 6, 4, 2 y 0 a los indicadores SF, ZF, AF, PF y CF respectivamente. 4.1.2. - INSTRUCCIONES DE MANIPULACIÓN DEL REGISTRO DE ESTADO. CLC (baja el indicador de acarreo) Sintaxis: CLC Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
Borra el indicador de acarreo (CF) sin afectar a ninguno otro. CLD (baja el indicador de dirección) Sintaxis: CLD
PF -
CF 0
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Indicadores:
OF -
DF 0
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Pone a 0 el indicador de dirección DF, por lo que los registros SI y/o DI se autoincrementan en las operaciones de cadenas, sin afectar al resto de los indicadores. Es NECESARIO colocarlo antes de las instrucciones de manejo de cadenas si no se conoce con seguridad el valor de DF. Véase STD. CLI (baja indicador de interrupción) Sintaxis: CLI Indicadores:
OF -
DF -
IF 0
TF -
SF -
ZF -
AF -
PF -
CF -
Borra el indicador de activación de interrupciones IF, lo que desactiva las interrupciones enmascarables. Es muy conveniente hacer esto antes de modificar la pareja SS:SP en los 8086/88 anteriores a 1983 (véase comentario en la instrucción MOV), o antes de cambiar un vector de interrupción sin el apoyo del DOS. Generalmente las interrupciones sólo se inhiben por breves instantes en momentos críticos. Véase también STI. CMC (complementa el indicador de acarreo) Sintaxis: CMC Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF x
PF -
CF 1
Complementa el indicador de acarreo CF invirtiendo su estado. STC (pone a uno el indicador de acarreo) Sintaxis: STC Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
Pone a 1 el indicador de acarreo CF sin afectar a ningún otro indicador. STD (pone a uno el indicador de dirección) Sintaxis: STD Indicadores:
OF -
DF 1
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Pone a 1 el indicador de dirección DF, por lo que los registros SI y/o DI se autodecrementan en las operaciones de cadenas, sin afectar al resto de los indicadores. Es NECESARIO colocarlo antes de las instrucciones de manejo de cadenas si no se conoce con seguridad el estado de DF. Véase también CLD.
41
JUEGO DE INSTRUCCIONES 80x86
STI (pone a uno el indicador de interrupción) Sintaxis: STI Indicadores:
OF -
DF -
IF 1
TF -
SF -
ZF -
AF -
PF -
CF -
Pone a 1 la bandera de desactivación de interrupciones IF y activa las interrupciones enmascarables. Una interrupción pendiente no es reconocida, sin embargo, hasta después de ejecutar la instrucción que sigue a STI. Véase también CLI. 4.1.3. - INSTRUCCIONES DE MANEJO DE LA PILA. POP (extraer de la pila) Sintaxis: POP destino Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Transfiere el elemento palabra que se encuentra en lo alto de la pila (apuntado por SP) al operando destino que a de ser tipo palabra, e incrementa en dos el registro SP. La instrucción POP CS, poco útil, no funciona correctamente en los 286 y superiores. Ejemplos:
pop pop
ax pepe
PUSH (introduce en la pila) Sintaxis: PUSH origen Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Decrementa el puntero de pila (SP) en 2 y luego transfiere la palabra especificada en el operando origen a la cima de la pila. El registro CS aquí sí se puede especificar como origen, al contrario de lo que afirman algunas publicaciones. Ejemplo:
push
cs
POPF (extrae los indicadores de la pila) Sintaxis: POPF Indicadores:
OF x
DF x
IF x
TF x
SF x
ZF x
AF x
PF x
CF x
Traslada al registro de los indicadores la palabra almacenada en la cima de la pila; a continuación el puntero de pila SP se incrementa en dos.
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
PUSHF (introduce los indicadores en la pila) Sintaxis: PUSHF Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Decrementa en dos el puntero de pila y traslada a la cima de la pila el contenido de los indicadores. 4.1.4. - INSTRUCCIONES DE TRANSFERENCIA DE CONTROL. Incondicional CALL (llamada a subrutina) Sintaxis: CALL destino Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Transfiere el control del programa a un procedimiento, salvando previamente en la pila la dirección de la instrucción siguiente, para poder volver a ella una vez ejecutado el procedimiento. El procedimiento puede estar en el mismo segmento (tipo NEAR) o en otro segmento (tipo FAR). A su vez la llamada puede ser directa a una etiqueta (especificando el tipo de llamada NEAR -por defecto- o FAR) o indirecta, indicando la dirección donde se encuentra el puntero. Según la llamada sea cercana o lejana, se almacena en la pila una dirección de retorno de 16 bits o dos palabras de 16 bits indicando en este último caso tanto el offset (IP) como el segmento (CS) a donde volver. Ejemplos: dir
call
proc1
dd call
0f000e987h dword ptr dir
En el segundo ejemplo, la variable dir almacena la dirección a donde saltar. De esta última manera -conociendo su dirección- puede llamarse también a un vector de interrupción, guardando previamente los flags en la pila (PUSHF), porque la rutina de interrupción retornará (con IRET en vez de con RETF) sacándolos. JMP (salto) Sintaxis: JMP dirección o JMP SHORT dirección Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Transfiere el control incondicionalmente a la dirección indicada en el operando. La bifurcación puede ser también directa o indirecta como anteriormente vimos, pero además puede ser corta (tipo SHORT) con un desplazamiento comprendido entre -128 y 127; o larga, con un desplazamiento de dos bytes con signo. Si se hace un JMP SHORT y no llega el salto (porque está demasiado alejada esa etiqueta) el ensamblador dará error. Los buenos ensambladores (como TASM) cuando dan dos pasadas colocan allí donde es posible un salto corto, para
41
JUEGO DE INSTRUCCIONES 80x86
economizar memoria, sin que el programador tenga que ocuparse de poner «short». Si el salto de dos bytes, que permite desplazamientos de 64 Kb en la memoria sigue siendo insuficiente, se puede indicar con «far» que es largo (salto a otro segmento). Ejemplos:
jmp jmp
etiqueta far ptr etiqueta
RET / RETF (retorno de subrutina) Sintaxis: RET [valor] o RETF [valor] Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Retorna de un procedimiento extrayendo de la pila la dirección de la siguiente dirección. Se extraerá el registro de segmento y el desplazamiento en un procedimiento de tipo FAR (dos palabras) y solo el desplazamiento en un procedimiento NEAR (una palabra). si esta instrucción es colocada dentro de un bloque PROC-ENDP (como se verá en el siguiente capítulo) el ensamblador sabe el tipo de retorno que debe hacer, según el procedimiento sea NEAR o FAR. En cualquier caso, se puede forzar que el retorno sea de tipo FAR con la instrucción RETF. «Valor», si es indicado permite sumar una cantidad «valor» en bytes a SP antes de retornar, lo que es frecuente en el código generado por los compiladores para retornar de una función con parámetros. También se puede retornar de una interrupción con RETF 2, para que devuelva el registro de estado sin restaurarlo de la pila. Condicional Las siguientes instrucciones son de transferencia condicional de control a la instrucción que se encuentra en la posición IP+desplazamiento (desplazamiento comprendido entre -128 y +127) si se cumple la condición. Algunas condiciones se pueden denotar de varias maneras. Todos los saltos son cortos y si no alcanza hay que apañárselas como sea. En negrita se realzan las condiciones más empleadas. Donde interviene SF se consideran con signo los operandos implicados en la última comparación u operación aritmetico-lógica, y se indican en la tabla como '±' (-128 a +127 ó -32768 a +32767); en los demás casos, indicados como '+', se consideran sin signo (0 a 255 ó 0 a 65535): JA/JNBE Salto si mayor (above), si no menor o igual (not below or equal), si CF=0 y ZF=0. JAE/JNB Salto si mayor o igual (above or equal), si no menor (not below), si CF=0. JB/JNAE/JC Salto si menor (below), si no superior ni igual (not above or equal), si acarreo, si CF=1. JBE/JNA Salto si menor o igual (not below or equal), si no mayor (not above), si CF=1 ó ZF=1. JCXZ Salto si CX=0. JE/JZ Salto si igual (equal), si cero (zero), si ZF=1. JG/JNLE Salto si mayor (greater), si no menor ni igual (not less or equal), si ZF=0 y SF=0. JGE/JNL Salto si mayor o igual (greater or equal), si no menor (not less), si SF=0. JL/JNGE Salto si menor (less), si no mayor ni igual (not greater or equal), si SF<>OF. JLE/JNG Salto si menor o igual (less or equal), si no mayor (not greater), si ZF=0 y SF<>OF. JNC Salto si no acarreo, si CF=0. JNE/JNZSalto si no igual, si no cero, si ZF=0. JNO Salto si no desbordamiento, si OF=0. JNP/JPO Salto si no paridad, si paridad impar, si PF=0. JNS Salto si no signo, si positivo, si SF=0. JO Salto si desbordamiento, si OF=1. JP/JPE Salto si paridad, si paridad par, si PF=1. JS Salto si signo, si SF=1.
Gestión de bucle LOOP (bucle)
+ + + +
± ± ± ±
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Sintaxis: LOOP desplazamiento Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Decrementa el registro contador CX; si CX es cero, ejecuta la siguiente instrucción, en caso contrario transfiere el control a la dirección resultante de sumar a IP + desplazamiento. El desplazamiento debe estar comprendido entre -128 y +127. Ejemplo: mov bucle: ....... ....... loop
cx,10 bucle
Con las mismas características que la instrucción anterior: LOOPE/LOOPZ Bucle si igual, si cero. Z=1 y CX<>0 LOOPNE/LOOPNZ Bucle si no igual, si no cero. Z=0 y CX<>0 Interrupciones INT (interrupción) Sintaxis: INT n (0 <= n <= 255) Indicadores:
OF -
DF -
IF 0
TF 0
SF -
ZF -
AF -
PF -
CF -
Inicializa un procedimiento de interrupción de un tipo indicado en la instrucción. En la pila se introduce al llamar a una interrupción la dirección de retorno formada por los registros CS e IP y el estado de los indicadores. INT 3 es un caso especial de INT, al ensamblarla el ensamblador genera un sólo byte en vez de los dos habituales; esta interrupción se utiliza para poner puntos de ruptura en los programas. Véase también IRET y el apartado 1 del capítulo VII. Ejemplo:
int
21h
INTO (interrupción por desbordamiento) Sintaxis: INTO Indicadores:
OF -
DF -
IF 0
TF 0
SF -
ZF -
AF -
PF -
CF -
Genera una interrupción de tipo 4 (INT 4) si existe desbordamiento (OF=1). De lo contrario se continúa con la instrucción siguiente. IRET (retorno de interrupción) Sintaxis: IRET Indicadores:
OF
DF
IF
TF
SF
ZF
AF
PF
CF
41
JUEGO DE INSTRUCCIONES 80x86
x
x
x
x
x
x
x
x
x
Devuelve el control a la dirección de retorno salvada en la pila por una interrupción previa y restaura los indicadores que también se introdujeron en la pila. En total, se sacan las 3 palabras que fueron colocadas en la pila cuando se produjo la interrupción. Véase también INT. 4.1.5. - INSTRUCCIONES DE ENTRADA SALIDA (E/S). IN (entrada) Sintaxis: IN acumulador, puerto. Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Transfiere datos desde el puerto indicado hasta el registro AL o AX, dependiendo de la longitud byte o palabra respectivamente. El puerto puede especificarse mediante una constante (0 a 255) o a través del valor contenido en DX (0 a 65535). Ejemplo:
in in
ax,0fh al,dx
OUT (salida) Sintaxis: OUT puerto, acumulador Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Transfiere un byte o palabra del registro AL o AX a un puerto de salida. El puerto puede especificarse con un valor fijo entre 0 y 255 ó a través del valor contenido en el registro DX (de 0 a 65535). Ejemplo:
out out
12h,ax dx,al
4.1.6. - INSTRUCCIONES ARITMÉTICAS. *** SUMA *** AAA (ajuste ASCII para la suma) Sintaxis: AAA Indicadores:
OF ?
DF -
IF -
TF -
SF ?
ZF ?
AF x
PF ?
CF x
Convierte el contenido del registro AL en un número BCD no empaquetado. Si los cuatro bits menos significativos de AL son mayores que 9 ó si el indicador AF está a 1, se suma 6 a AL, 1 a AH, AF se pone a 1, CF se iguala a AF y AL pone sus cuatro bits más significativos a 0. Ejemplo:
add
al,bl
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
aaa En el ejemplo, tras la suma de dos números BCD no empaquetados colocados en AL y BL, el resultado (por medio de AAA) sigue siendo un número BCD no empaquetado. ADC (suma con acarreo) Sintaxis: ADC destino, origen Indicadores:
OF x
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF x
Suma los operandos origen, destino y el valor del indicador de acarreo (0 ó 1) y el resultado lo almacena en el operando destino. Se utiliza normalmente para sumar números grandes, de más de 16 bits, en varios pasos, considerando lo que nos llevamos (el acarreo) de la suma anterior. Ejemplo:
adc
ax,bx
ADD (suma) Sintaxis: ADD destino, origen Indicadores:
OF x
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF x
Suma los operandos origen y destino almacenando el resultado en el operando destino. Se activa el acarreo si se desborda el registro destino durante la suma. Ejemplos:
add add
ax,bx cl,dh
DAA (ajuste decimal para la suma) Sintaxis: DAA Indicadores:
OF ?
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF x
Convierte el contenido del registro AL en un par de valores BCD: si los cuatro bits menos significativos de AL son un número mayor que 9, el indicador AF se pone a 1 y se suma 6 a AL. De igual forma, si los cuatro bits más significativos de AL tras la operación anterior son un número mayor que 9, el indicador CF se pone a 1 y se suma 60h a AL. Ejemplo:
add daa
al,cl
En el ejemplo anterior, si AL y CL contenían dos números BCD empaquetados, DAA hace que el resultado de la suma (en AL) siga siendo también un BCD empaquetado. INC (incrementar)
41
JUEGO DE INSTRUCCIONES 80x86
Sintaxis: INC destino Indicadores:
OF x
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF -
Incrementa el operando destino. El operando destino puede ser byte o palabra. Obsérvese que esta instrucción no modifica el bit de acarreo (CF) y no es posible detectar un desbordamiento por este procedimiento (utilícese ZF). Ejemplos:
inc inc inc inc
al es:[di] ss:[bp+4] word ptr cs:[bx+di+7]
*** RESTA *** AAS (ajuste ASCII para la resta) Sintaxis: AAS Indicadores:
OF ?
DF -
IF -
TF -
SF ?
ZF ?
AF x
PF ?
CF x
Convierte el resultado de la sustracción de dos operandos BCD no empaquetados para que siga siendo un número BCD no empaquetado. Si el nibble inferior de AL tiene un valor mayor que 9, de AL se resta 6, se decrementa AH, AF se pone a 1 y CF se iguala a AF. El resultado se guarda en AL con los bits de 4 a 7 puestos a 0. Ejemplo:
sub aas
al,bl
En el ejemplo, tras la resta de dos números BCD no empaquetados colocados en AL y BL, el resultado (por medio de AAS) sigue siendo un número BCD no empaquetado. CMP (comparación) Sintaxis: CMP destino, origen Indicadores:
OF x
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF x
Resta origen de destino sin retornar ningún resultado. Los operandos quedan inalterados, paro los indicadores pueden ser consultados mediante instrucciones de bifurcación condicional. Los operandos pueden ser de tipo byte o palabra pero ambos de la misma dimensión. Ejemplo:
cmp cmp
bx, mem_pal ch,cl
DAS (ajuste decimal para la resta) Sintaxis: DAS Indicadores:
OF -
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF x
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Corrige el resultado en AL de la resta de dos números BCD empaquetados, convirtiéndolo también en un valor BCD empaquetado. Si el nibble inferior tiene un valor mayor que 9 o AF es 1, a AL se le resta 6, AF se pone a 1. Si el nibble mas significativo es mayor que 9 ó CF está a 1, entonces se resta 60h a AL y se activa después CF. Ejemplo:
sub das
al,bl
En el ejemplo anterior, si AL y BL contenían dos números BCD empaquetados, DAS hace que el resultado de la resta (en AL) siga siendo también un BCD empaquetado. DEC (decrementar) Sintaxis: DEC destino Indicadores:
OF x
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF -
Resta una unidad del operando destino. El operando puede ser byte o palabra. Obsérvese que esta instrucción no modifica el bit de acarreo (CF) y no es posible detectar un desbordamiento por este procedimiento (utilícese ZF). Ejemplo:
dec dec
ax mem_byte
NEG (negación) Sintaxis: NEG destino Indicadores:
OF x
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF x
Calcula el valor negativo en complemento a dos del operando y devuelve el resultado en el mismo operando. Ejemplo:
neg
al
SBB (resta con acarreo) Sintaxis: SBB destino, origen Indicadores:
OF x
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF x
Resta el operando origen del operando destino y el resultado lo almacena en el operando destino. Si está a 1 el indicador de acarreo además resta una unidad más. Los operandos pueden ser de tipo byte o palabra. Se utiliza normalmente para restar números grandes, de más de 16 bits, en varios pasos, considerando lo que nos llevamos (el acarreo) de la resta anterior. Ejemplo:
sbb sbb
ax,ax ch,dh
41
JUEGO DE INSTRUCCIONES 80x86
SUB (resta) Sintaxis: SUB destino, origen Indicadores:
OF x
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF x
Resta el operando destino al operando origen, colocando el resultado en el operando destino. Los operandos pueden tener o no signo, siendo necesario que sean del mismo tipo, byte o palabra. Ejemplos:
sub sub
al,bl dx,dx
*** MULTIPLICACION *** AAM (ajuste ASCII para la multiplicación) Sintaxis: AAM Indicadores:
OF ?
DF -
IF -
TF -
SF x
ZF x
AF ?
PF x
CF ?
Corrige el resultado en AX del producto de dos números BCD no empaquetados, convirtiéndolo en un valor BCD también no empaquetado. En AH sitúa el cociente de AL/10 quedando en AL el resto de dicha operación. Ejemplo:
mul aam
bl
En el ejemplo, tras el producto de dos números BCD no empaquetados colocados en AL y BL, el resultado (por medio de AAA) sigue siendo, en AX, un número BCD no empaquetado. IMUL (multiplicación entera con signo) Sintaxis: IMUL origen (origen no puede ser operando inmediato en 8086, sí en 286) Indicadores:
OF x
DF -
IF -
TF -
SF ?
ZF ?
AF ?
PF ?
CF x
Multiplica un operando origen con signo de longitud byte o palabra por AL o AX respectivamente. Si «origen» es un byte el resultado se guarda en AH (byte más significativo) y en AL (menos significativo), si «origen» es una palabra el resultado es devuelto en DX (parte alta) y AX (parte baja). Si las mitades más significativas son distintas de cero, independientemente del signo, CF y OF son activados. Ejemplo:
MUL (multiplicación sin signo)
imul imul
bx ch
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Sintaxis: MUL origen (origen no puede ser operando inmediato) Indicadores:
OF x
DF -
IF -
TF -
SF ?
ZF ?
AF ?
PF ?
CF x
Multiplica el contenido sin signo del acumulador por el operando origen. Si el operando destino es un byte el acumulador es AL guardando el resultado en AH y AL, si el contenido de AH es distinto de 0 activa los indicadores CF y OF. Cuando el operando origen es de longitud palabra el acumulador es AX quedando el resultado sobre DX y AX, si el valor de DX es distinto de cero los indicadores CF y OF se activan. Ejemplo:
mul mul mul
byte ptr ds:[di] dx cl
*** DIVISION *** AAD (ajuste ASCII para la división) Sintaxis: AAD Indicadores:
OF ?
DF -
IF -
TF -
SF x
ZF x
AF ?
PF x
CF ?
Convierte dos números BCD no empaquetados contenidos en AH y AL en un dividendo de un byte que queda almacenado en AL. Tras la operación AH queda a cero. Esta instrucción es necesaria ANTES de la operación de dividir, al contrario que AAM. Ejemplo:
aad div
bl
En el ejemplo, tras convertir los dos números BCD no empaquetados (en AX) en un dividendo válido, la instrucción de dividir genera un resultado correcto. DIV (división sin signo) Sintaxis: DIV origen (origen no puede ser operando inmediato) Indicadores:
OF ?
DF -
IF -
TF -
SF ?
ZF ?
AF ?
PF ?
CF ?
Divide, sin considerar el signo, un número contenido en el acumulador y su extensión (AH, AL si el operando es de tipo byte o DX, AX si el operando es palabra) entre el operando fuente. El cociente se guarda en AL o AX y el resto en AH o DX según el operando sea byte o palabra respectivamente. DX o AH deben ser cero antes de la operación. Cuando el cociente es mayor que el resultado máximo que puede almacenar, cociente y resto quedan indefinidos produciéndose una interrupción 0. En caso de que las partes más significativas del cociente tengan un valor distinto de cero se activan los indicadores CF y OF. Ejemplo:
IDIV (división entera)
div div
bl mem_pal
41
JUEGO DE INSTRUCCIONES 80x86
Sintaxis: IDIV origen (origen no puede ser operando inmediato) Indicadores:
OF ?
DF -
IF -
TF -
SF ?
ZF ?
AF ?
PF ?
CF ?
Divide, considerando el signo, un número contenido en el acumulador y su extensión entre el operando fuente. El cociente se almacena en AL o AX según el operando sea byte o palabra y de igual manera el resto en AH o DX. DX o AH deben ser cero antes de la operación. Cuando el cociente es positivo y superior al valor máximo que puede almacenarse (7fh ó 7fffh), o cuando el cociente es negativo e inferior al valor mínimo que puede almacenarse (81h u 8001h) entonces cociente y resto quedan indefinidos, generándose una interrupción 0, lo que también sucede si el divisor es 0. Ejemplo:
idiv idiv
bl bx
*** CONVERSIONES*** CBW (conversión de byte en palabra) Sintaxis: CBW Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Copia el bit 7 del registro AL en todos los bits del registro AH, es decir, expande el signo de AL a AX como paso previo a una operación de 16 bits. CWD (conversión de palabra a doble palabra) Sintaxis: CWD Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Expande el signo del registro AX sobre el registro DX, copiando el bit más significativo de AH en todo DX. 4.1.7. - INSTRUCCIONES DE MANIPULACIÓN DE CADENAS. CMPS/CMPSB/CMPSW (compara cadenas) Sintaxis:
Indicadores:
CMPS cadena_destino, cadena_origen CMPSB (bytes) CMPSW (palabras) OF x
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF x
Compara dos cadenas restando al origen el destino. Ninguno de los operandos se alteran, pero los indicadores resultan afectados. La cadena origen se direcciona con registro SI sobre el
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
segmento de datos DS y la cadena destino se direcciona con el registro DI sobre el segmento extra ES. Los registros DI y SI se autoincrementan o autodecrementan según el valor del indicador DF (véanse CLD y STD) en una o dos unidades, dependiendo de si se trabaja con bytes o con palabras. «Cadena origen» y «cadena destino» son dos operandos redundantes que sólo indican el tipo del dato (byte o palabra) a comparar, es más cómodo colocar CMPSB o CMPSW para indicar bytes/palabras. Si se indica un registro de segmento, éste sustituirá en la cadena origen al DS ordinario. Ejemplo: lea si,origen lea di,destino cmpsb LODS/LODSB/LODSW (cargar cadena) Sintaxis:
LODS cadena_origen LODSB (bytes) LODSW (palabras)
Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Copia en AL o AX una cadena de longitud byte o palabra direccionada sobre el segmento de datos (DS) con el registro SI. Tras la transferencia, SI se incrementa o decrementa según el indicador DF (véanse CLD y STD) en una o dos unidades, según se estén manejando bytes o palabras. «Cadena_origen» es un operando redundante que sólo indica el tipo del dato (byte o palabra) a cargar, es más cómodo colocar LODSB o LODSW para indicar bytes/palabras. Ejemplo:
cld lea lodsb
si,origen
MOVS/MOVSB/MOVSW (mover cadena) Sintaxis:
MOVS cadena_destino, cadena_origen MOVSB (bytes) MOVSW (palabras)
Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Transfiere un byte o una palabra de la cadena origen direccionada por DS:SI a la cadena destino direccionada por ES:DI, incrementando o decrementando a continuación los registros SI y DI según el valor de DF (véanse CLD y STD) en una o dos unidades, dependiendo de si se trabaja con bytes o con palabras. «Cadena origen» y «cadena destino» son dos operandos redundantes que sólo indican el tipo del dato (byte o palabra) a comparar, es más cómodo colocar MOVSB o MOVSW para indicar bytes/palabras. Si se indica un registro de segmento, éste sustituirá en la cadena origen al DS ordinario. Ejemplo:
lea si,origen lea di,destino movsw
41
JUEGO DE INSTRUCCIONES 80x86
SCAS/SCASB/SCASW (explorar cadena) Sintaxis:
SCAS cadena_destino SCASB (bytes) SCASW (palabras)
Indicadores:
OF x
DF -
IF -
TF -
SF x
ZF x
AF x
PF x
CF x
Resta de AX o AL una cadena destino direccionada por el registro DI sobre el segmento extra. Ninguno de los valores es alterado pero los indicadores se ven afectados. DI se incrementa o decrementa según el valor de DF (véanse CLD y STD) en una o dos unidades -según se esté trabajando con bytes o palabras- para apuntar al siguiente elemento de la cadena. «Cadena_destino» es un operando redundante que sólo indica el tipo del dato (byte o palabra), es más cómodo colocar SCASB o SCASW para indicar bytes/palabras. Ejemplo:
lea mov scasb
di,destino al,50
STOS/STOSB/STOSW (almacena cadena) Sintaxis:
STOS cadena_destino STOSB (bytes) STOSW (palabras)
Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Transfiere el operando origen almacenado en AX o AL, al destino direccionado por el registro DI sobre el segmento extra. Tras la operación, DI se incrementa o decrementa según el indicador DF (véanse CLD y STD) para apuntar al siguiente elemento de la cadena. «Cadena_destino» es un operando redundante que sólo indica el tipo del dato (byte o palabra) a cargar, es más cómodo colocar STOSB o STOSW para indicar bytes/palabras. Ejemplo:
lea mov stosw
di,destino ax,1991
REP/REPE/REPZ/REPNE/REPNZ (repetir) REP repetir operación de cadena REPE/REPZ repetir operación de cadena si igual/si cero REPNE/REPNZ repetir operación de cadena si no igual (si no 0) Estas instrucciones se pueden colocar como prefijo de otra instrucción de manejo de cadenas, con objeto de que la misma se repita un número determinado de veces incondicionalmente o hasta que se verifique alguna condición. El número de veces se indica en CX. Por sentido común sólo deben utilizarse las siguientes combinaciones: Prefijo ----------REP REPE/REPZ
Función ------------------------------Repetir CX veces Repetir CX veces mientras ZF=1
Instrucciones ---------------MOVS, STOS CMPS, SCAS
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
REPNE/REPNZ
Repetir CX veces mientras ZF=0
CMPS, SCAS
Ejemplos: 1) Buscar el byte 69 entre las 200 primeras posiciones de «tabla» (se supone «tabla» en el segmento ES): LEA
DI,tabla
MOV
CX,200
MOV
AL,69
CLD REPNE
SCASB
JE
encontrado
2) Rellenar de ceros 5000 bytes de una tabla colocada en «datos» (se supone «datos» en el segmento ES): LEA
DI,datos
MOV
AX,0
MOV
CX,2500
CLD REP
STOSW
3) Copiar la memoria de pantalla de texto (adaptador de color) de un PC en un buffer (se supone «buffer» en el segmento ES): MOV
CX,0B800h
; segmento de pantalla
MOV
DS,CX
; en DS
LEA
DI,buffer
; destino en ES:DI
MOV
SI,0
; copiar desde DS:0
MOV
CX,2000 ; 2000 palabras
CLD REP
; hacia adelante MOVSW
; copiar CX palabras
4.1.8. - INSTRUCCIONES DE OPERACIONES LÓGICAS A NIVEL DE BIT. AND (y lógico) Sintaxis: AND destino, origen Indicadores:
OF 0
DF -
IF -
TF -
SF x
ZF x
AF ?
PF x
CF 0
Realiza una operación de Y lógico entre el operando origen y destino quedando el resultado en el destino. Son válidos operandos byte o palabra, pero ambos del mismo tipo. Ejemplos:
and and
ax,bx bl,byte ptr es:[si+10h]
NOT (no lógico) Sintaxis: NOT destino Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
41
JUEGO DE INSTRUCCIONES 80x86
Realiza el complemento a uno del operando destino, invirtiendo cada uno de sus bits. Los indicadores no resultan afectados. Ejemplo:
not
ax
OR (O lógico) Sintaxis: OR destino, origen Indicadores:
OF 0
DF -
IF -
TF -
SF x
ZF x
AF ?
PF x
CF 0
Realiza una operación O lógico a nivel de bits entre los dos operandos, almacenándose después el resultado en el operando destino. Ejemplo:
or
ax,bx
TEST (comparación lógica) Sintaxis: TEST destino, origen Indicadores:
OF 0
DF -
IF -
TF -
SF x
ZF x
AF ?
PF x
CF 0
Realiza una operación Y lógica entre los dos operandos pero sin almacenar el resultado. Los indicadores son afectados con la operación. Ejemplo:
test
al,bh
XOR (O exclusivo) Sintaxis: XOR destino, origen Indicadores:
OF 0
DF -
IF -
TF -
SF x
ZF x
AF ?
PF x
CF 0
Operación OR exclusivo a nivel de bits entre los operandos origen y destino almacenándose el resultado en este último. Ejemplo:
xor
di,ax
4.1.9. - INSTRUCCIONES DE CONTROL DEL PROCESADOR. NOP (operación nula) Sintaxis: NOP Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Realiza una operación nula, es decir, el microprocesador decodifica la instrucción y pasa a la siguiente. Realmente se trata de la instrucción XCHG AX,AX.
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
ESC (salida a un coprocesador) Sintaxis: ESC código_operación, origen Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Se utiliza en combinación con procesadores externos, tales como los coprocesadores de coma flotante o de E/S, y abre al dispositivo externo el acceso a las direcciones y operandos requeridos. Al mnemónico ESC le siguen los códigos de operación apropiados para el coprocesador así como la instrucción y la dirección del operando necesario. Ejemplo:
esc
21,ax
HLT (parada hasta interrupción o reset) Sintaxis: HLT Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
El procesador se detiene hasta que se restaura el sistema o se recibe una interrupción. Como en los PC se producen normalmente 18,2 interrupciones de tipo 8 por segundo (del temporizador) algunos programadores utilizan HLT para hacer pausas y bucles de retardo. Sin embargo, el método no es preciso y puede fallar con ciertos controladores de memoria. LOCK (bloquea los buses) Sintaxis: LOCK Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Es una instrucción que se utiliza en aplicaciones de recursos compartidos para asegurar que no accede simultáneamente a la memoria más de un procesador. Cuando una instrucción va precedida por LOCK, el procesador bloquea inmediatamente el bus, introduciendo una señal por la patilla LOCK. WAIT (espera) Sintaxis: WAIT Indicadores:
OF -
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF -
Provoca la espera del procesador hasta que se detecta una señal en la patilla TEST. Ocurre, por ejemplo, cuando el copro ha terminado una operación e indica su finalización. Suele preceder a ESC para sincronizar las acciones del procesador y coprocesador. 4.1.10. - INSTRUCCIONES DE ROTACIÓN Y DESPLAZAMIENTO.
41
JUEGO DE INSTRUCCIONES 80x86
RCL (rotación a la izquierda con acarreo) Sintaxis: RCL destino, contador Indicadores:
OF x
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF x
Rotar a la izquierda los bits del operando destino junto con el indicador de acarreo CF el número de bits especificado en el segundo operando. Si el número de bits a desplazar es 1, se puede especificar directamente, en caso contrario el valor debe cargarse en CL y especificar CL como segundo operando. No es conveniente que CL sea mayor de 7, en bytes; ó 15, en palabras. ┌─────────────────────────────────┐ ┌─┴──┐ ┌───────────────────────À─┐ │ CF │½─────┤ alto ½── bajo │ RCL └────┘ └─────────────────────────┘
Ejemplos:
rcl rcl rcl
ax,1 al,cl di,1
RCR (rotación a la derecha con acarreo) Sintaxis: RCR destino, contador Indicadores:
OF x
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF x
Rotar a la derecha los bits del operando destino junto con el indicador de acarreo CF el número de bits especificado en el segundo operando. Si el número de bits es 1 se puede especificar directamente; en caso contrario su valor debe cargarse en CL y especificar CL como segundo operando: ┌─────────────────────────────────┐ ┌─À───────────────────────┐ ┌──┴─┐ │ alto ──¾ bajo ├─────¾│ CF │ RCR └─────────────────────────┘ └────┘
Ejemplos:
rcr rcr
bx,cl bx,1
ROL (rotación a la izquierda) Sintaxis: ROL destino, contador Indicadores:
OF x
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF x
Rota a la izquierda los bits del operando destino el número de bits especificado en el segundo operando, que puede ser 1 ó CL previamente cargado con el valor del número de veces. ┌─────────────────────┐ ┌────┐ ┌─┴─────────────────────À─┐ │ CF │½─────┤ alto ½── bajo │ ROL └────┘ └─────────────────────────┘
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Ejemplos:
rol rol
dx,cl ah,1
ROR (rotación a la derecha) Sintaxis: ROR destino, contador Indicadores:
OF x
DF -
IF -
TF -
SF -
ZF -
AF -
PF -
CF x
Rota a la derecha los bits del operando destino el número de bits especificado en el segundo operando. Si el número de bits es 1 se puede poner directamente, en caso contrario debe ponerse a través de CL. ┌─────────────────────┐ ┌─À─────────────────────┴─┐ ┌────┐ │ alto ──¾ bajo ├─────¾│ CF │ ROR └─────────────────────────┘ └────┘
Ejemplos:
ror ror
cl,1 ax,cl
SAL/SHL (desplazamiento aritmético a la izquierda) Sintaxis: SAL/SHL destino, contador Indicadores:
OF x
DF -
IF -
TF -
SF x
ZF x
AF ?
PF x
CF x
Desplaza a la izquierda los bits del operando el número de bits especificado en el segundo operando que debe ser CL si es mayor que 1 los bits desplazados. ┌────┐ ┌─────────────────────────┐ │ CF │½─────┤ alto ½── bajo │ ½── 0 └────┘ └─────────────────────────┘ Ejemplos:
shl sal
SAL/SHL
dx,1 bx,cl
SAR (desplazamiento aritmético a la derecha) Sintaxis: SAR destino, contador Indicadores:
OF x
DF -
IF -
TF -
SF x
ZF x
AF ?
PF x
CF x
Desplaza a la derecha los bits del operando destino el número de bits especificado en el segundo operando. Los bits de la izquierda se rellenan con el bit de signo del primer operando. Si el número de bits a desplazar es 1 se puede especificar directamente, si es mayor se especifica a través de CL. ┌────┐ │ ┌─À───────────────────────┐
┌────┐
41
JUEGO DE INSTRUCCIONES 80x86
└──┤ alto ──¾ bajo ├─────¾│ CF │ SAR └─────────────────────────┘ └────┘
Ejemplos:
sar sar
ax,cl bp,1
SHR (desplazamiento lógico a la derecha) Sintaxis: SHR destino, contador Indicadores:
OF x
DF -
IF -
TF -
SF x
ZF x
AF ?
PF x
CF x
Desplaza a la derecha los bits del operando destino el número de los bits especificados en el segundo operando. Los bits de la izquierda se llena con cero. Si el número de bits a desplazar es 1 se puede especificar directamente en el caso en que no ocurra se pone el valor en CL: ┌─────────────────────────┐ ┌────┐ 0 ──¾ │ alto ──¾ bajo ├─────¾│ CF │ SHR └─────────────────────────┘ └────┘
Ejemplos:
shr shr
ax,cl cl,1
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
4.2. - RESUMEN ALFABÉTICO DE LAS INSTRUCCIONES Y BANDERINES. ÍNDICE. Nota: en el efecto de las instrucciones sobre el registro de estado se utilizará la siguiente notación: - bit no modificado ? desconocido o indefinido x modificado según el resultado de la operación 1 puesto siempre a 1 0 puesto siempre a 0 Instrucción ────────────────
Sintaxis ─────────────────────
AAA AAD AAM AAS ADC dst,fnt ADD dst,fnt AND dst,fnt CALL dsp CBW CLC CLD CLI CMC CMP dst,fnt CMPS/CMPSB CMPSW cdst,cfnt CWD DAA DAS DEC dst DIV fnt ESC opcode,fnt HLT IDIV fnt IMUL fnt IN acum,port INC dst INT interrup INTO IRET Jcc (JA, JBE...) JMP JCXZ dsp LAHF LDS dst,fnt LEA dst,fnt LES dst,fnt LOCK LODS/LODSB/ LODSW cfnt LOOP LOOPcc (LOOPE...) MOV dst,fnt MOVS/MOVSB/ MOVSW cdst,cfnt MUL fnt NEG dst
AAA AAD AAM AAS ADC ADD AND CALL CBW CLC CLD CLI CMC CMP
dst,fnt dst,fnt dst,fnt dsp
dst,fnt
CMPS CWD DAA DAS DEC DIV ESC HLT IDIV IMUL IN INC INT INTO IRET Jcc JMP JCXZ LAHF LDS LEA LES LOCK
cdst,cfnt
LODS LOOP LOOPcc MOV MOVS MUL NEG
Efecto sobre los flags ────────────────────────── OF DF IF TF SF ZF AF PF CF ? - - - ? ? x ? x ? - - - x x ? x ? ? - - - x x ? x ? ? - - - ? ? x ? x x - - - x x x x x x - - - x x x x x 0 - - - x x ? x 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - - - - - - - - 0 - - - - - - - - - - - - - x x - - - x x x x x
pág. ──── 49 54 53 51 50 50 58 46 55 43 43 44 44 51
x ? x ? ? x x x -
x -
0 0 x -
0 0 x -
x x x x ? ? ? x x -
x x x x ? ? ? x x -
x x x x ? ? ? x x -
x x x x ? ? ? x x -
x x x ? ? x x -
55 55 50 51 52 54 59 59 54 53 49 50 48 48 48 47 46 47 43 42 42 43 60
mem dsp dsp dst,fnt
-
-
-
-
-
-
-
-
-
56 47 48 41
cdst,cfnt fnt fnt
x x
-
-
-
? x
? x
? x
? x
x x
56 53 52
dst dst opcode,fnt fnt fnt acum,port dst interrup
dsp dsp dsp dst,fnt dst,fnt dst,fnt
41
JUEGO DE INSTRUCCIONES 80x86
NOP NOT dst OR dst,fnt OUT port,acum POP dst POPF PUSH dst PUSHF
NOP NOT OR OUT POP POPF PUSH PUSHF
Instrucción ────────────────
Sintaxis ─────────────────────
RCL dst,cnt RCR dst,cnt REP/REPE/REPZ/ REPNE/REPNZ RET [val] RETF [val] ROL dst,cnt ROR dst,cnt SAHF SAL/SHL dst,cnt SAR dst,cnt SBB dst,fnt SCAS/SCASB/ SCASW cdst SHR dst,cnt STC STD STI STOS/STOSB/ STOSW cdst SUB dst,fnt TEST dst,fnt WAIT XCHG dst,fnt XLAT tfnt XOR dst,fnt
RCL RCR REP RET RETF ROL ROR SAHF SAL SAR SBB
dst dst,fnt port,acum dst dst
dst,cnt dst,cnt
[val] [val] dst,cnt dst,cnt dst,cnt dst,cnt dst,fnt
0 x -
x -
x -
x -
x x -
x x -
? x -
x x -
0 x -
Efecto sobre los flags ────────────────────────── OF DF IF TF SF ZF AF PF CF x - - - - - - - x x - - - - - - - x
59 58 58 49 45 45 45 45 pág. ──── 60 61
x x x x x
-
-
-
x x x x
x x x x
x ? ? x
x x x x
x x x x x x
57 47 47 61 61 43 62 62 52
SCAS SHR STC STD STI
cdst dst,cnt
x x -
1 -
1
-
x x -
x x -
x ? -
x x -
x x 1 -
56 62 44 44 45
STOS SUB TEST WAIT XCHG XLAT XOR
cdst dst,fnt dst,fnt
x 0 0
-
-
-
x x x
x x x
x ? ?
x x x
x 0 0
57 52 59 60 41 42 59
dst,fnt tfnt dst,fnt
4.3. - INSTRUCCIONES ESPECIFICAS DEL 286, 386 y 486 EN MODO REAL. 4.3.1. - DIFERENCIAS EN EL COMPORTAMIENTO GLOBAL RESPECTO AL 8086. - Excepciones de división: Las excepciones INT 0, debidas a una división por cero o a un cociente excesivamente grande, provocan que en la pila se almacene el valor de CS:IP para la siguiente instrucción en el 8086. En el 286 y superiores se almacena el CS:IP de la propia instrucción que causa la excepción. - Códigos de operación indefinidos. En el 286 y superiores se produce una excepción 6 (INT 6) o, si es una instrucción con sentido para estos procesadores, se ejecuta. El 8086 se estrella. - Valor de PUSH SP. El valor que introduce en la pila en el 286 y superiores es el de SP antes del PUSH; en el 8086 es el de SP después del PUSH (dos unidades menos). - Desplazamientos y rotaciones.
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
El valor de desplazamiento en las operaciones de manipulación de bits del 8086 es una constante de 8 bits (indicada en CL); en el 286 y superiores se toma módulo 32 (sólo se consideran los 5 bits menos significativos). - Prefijos redundantes. Las instrucciones tienen una longitud ilimitada en el 8086; en el 286 y superiores no pueden exceder de 15 bytes. Por tanto, los prefijos redundantes pueden producir excepciones de código de operación no válido. - Accesos al límite del segmento. Un acceso de 16 bits en el offset 0FFFFh en el 8086 provoca un acceso a los bytes ubicados en las posiciones 0FFFFh y 0 (se da la vuelta alrededor del segmento). En el 286 y superiores, se produce una excepción de violación de límites. En el 386 y superiores se produce también en accesos de 32 bits en las posiciones 0FFFDh a la 0FFFFh. Esto se cumple tanto para accesos a datos en memoria como a instrucciones del programa en esos puntos críticos. - LOCK. Esta instrucción no está limitada de ninguna manera en el 8086 y en el 286. En el 386 y superiores su uso está restringido a determinadas instrucciones. - Ejecución paso a paso. La prioridad de la excepción paso a paso en el 286 y superiores es más alta que la de una interrupción externa; por tanto, las interrupciones externas no pueden ser traceadas. - Registro de FLAGS. Difiere algo en los bits 12 al 15 en todos los procesadores; el 386 dispone además de un registro de flags de 32 bits. - Interrupción NMI. Desde el 286 y superiores, una NMI no puede interrumpir una rutina de tratamiento NMI. - Error del coprocesador. En el 286 y superiores se utiliza el vector 16; en el 8086 cualquier vector. - Prefijos de las instrucciones del coprocesador. Al producirse una excepción de error de coprocesador, en el 8086 se almacena un CS:IP que no incluye prefijos -si los había-, al contrario que en el 286 y superiores. - Límite del primer megabyte. En el 8086 la memoria es circular; al final del primer megabyte se vuelve a comenzar por las posiciones más bajas de la memoria. En el 286 y superiores, se accede a la memoria extendida (un artificio hardware en los PC lo impide al forzar A20 a estado bajo, pero puede ser solventado). - Instrucciones de cadena repetitivas. El CS:IP grabado en el 8086 no incluye el prefijo, si existe; en el 286 y superiores sí. 4.3.2. - INSTRUCCIONES ESPECIFICAS DEL 286. A continuación se describen las instrucciones adicionales que incorporan los 286 en modo real, que también pueden ser consideradas cuando trabajamos con los microprocesadores compatibles V20 y V30, así como con los procesadores superiores al 286. Las instrucciones del modo protegido se dirigen especialmente a la multiprogramación y el tiempo compartido, siendo específicas de la conmutación de procesos y tratamiento de la memoria virtual y no pueden emplearse directamente bajo DOS. BOUND r16, mem16: Comprueba si el registro de 16 bits indicado como primer operando está dentro de los
41
JUEGO DE INSTRUCCIONES 80x86
límites de una matriz. Los límites de la matriz los definen dos palabras consecutivas en la memoria apuntadas por mem16. Si está fuera de los límites, se produce una interrupción 5 en la que el IP apilado queda apuntando a la instrucción BOUND (¡no se incrementa!). ENTER crea una estructura de pila para un procedimiento de alto nivel. Las instrucciones PUSH permiten meter valores inmediatos a la pila: es válido hacer PUSH 40h. IMUL puede multiplicar cualquier registro de 16 bits por una constante inmediata, devolviendo un resultado palabra (CF=1 si no cabe en 16 bits); por ejemplo, es válido IMUL CX,25. También se admiten tres operandos: IMUL r1, r2, imm. En este caso, se multiplica r2 por el valor inmediato (8/16 bits) y el resultado se almacena en r1. Tanto r1 como r2 han de ser de 16 bits. LEAVE abandona los procedimientos de alto nivel (equivale a MOV SP,BP / POP BP). PUSHA/POPA: Introduce en la pila y en este orden los registros AX, CX, DX, BX, SP, BP, SI y DI -o los saca en orden inverso-. Ideal en el manejo de interrupciones y muy usada en las BIOS de 286 y 386. OUTS (salida de cadenas) e INS (entrada de cadenas) repetitivas (equivalente a MOVS y LODS). RCR/RCL, ROR/ROL, SAL/SAR y SHL/SHR admiten una constante de rotación distinta de 1. 4.3.3. - INSTRUCCIONES PROPIAS DEL 386 Y 486. Además de todas las posibilidades adicionales del 286, el 386 y el 486 permiten utilizar cualquier registro de 32 bits de propósito general en todos los modos de funcionamiento, incluido el modo real, tales como EAX, EBX, ECX, EDX, ESI, EDI, EBP. Sin embargo no deben intentarse direccionamientos por encima de los 64K. En otras palabras, se pueden utilizar para acelerar las operaciones pero no para acceder a más memoria. Por ejemplo, si EBX > 0FFFFh, la instrucción MOV AX,[EBX] tendría un resultado impredecible. Además, estos procesadores cuentan con dos segmentos más: además de DS, ES, CS y SS se pueden emplear también FS y GS. Aviso: parece ser que en algunos 386 fallan ocasionalmente las instrucciones de multiplicar de 32 bits. Nota:No es del todo cierto que el 386 y el 486 no permitan acceder a más de 64 Kb en modo real: en la sección 4.3.6 hay un ejemplo de ello. Los modos de direccionamiento aumentan notablemente su flexibilidad en el 386 y superiores. Con los registros de 16 bits sólo están disponibles los modos tradicionales. En cambio, con los de 32 se puede utilizar en el direccionamiento indirecto cualquier registro: es válida, por ejemplo, una instrucción del tipo MOV AX,[ECX] o MOV EDX,[EAX]. Los desplazamientos en el direccionamiento indexado con registros de 32 bits pueden ser de 8 y también de 32 bits. Cuando dos registros deben sumarse para calcular la dirección efectiva, el segundo puede estar multiplicado por 2, 4 u 8; por ejemplo, es válida la instrucción MOV AL,[EDX+EAX*8]. Por supuesto, bajo DOS hay que asegurarse siempre que el resultado de todas las operaciones que determinan la dirección efectiva no excede de 0FFFFh (0FFFEh si se accede a palabras y 0FFFCh en accesos a dobles palabras en memoria). BOUND r32, mem32: Se admiten ahora operandos de 32 bits. BSF/BSR: Exploración de bits hacia adelante y atrás, respectivamente. La sintaxis es: BSF reg, reg BSR reg, reg
ó ó
BSF reg, [memoria] BSR reg, [memoria]
Donde reg puede ser de 16 ó 32 bits. Se comienza a explorar por el bit 0 (BSF) o por el más significativo (BSR) del segundo operando: si no aparece ningún bit activo (a 1) el indicador ZF se
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
activa; en caso contrario se almacena en el primer operando la posición relativa de ese bit: MOV AX,8 BSF BX,AX JZ ax_es_0 ; no se saltará, además BX = 3 BT/BTC/BTR/BTS: Operaciones sobre bits: comprobación, comprobación y complementación, comprobación y puesta a 0, comprobación y puesta a 1. Sintaxis (ejemplo sobre BT): BT reg, reg
ó BT reg, imm8
Donde reg puede ser de 16 ó 32 bits, el operando inmediato es necesariamente de 8. Estas instrucciones copian el número de bit del primer operando que indique el segundo operando (entre 0 y 31) en el acarreo. A continuación no le hacen nada a ese bit (BT), lo complementan (BTC), lo borran (BTR) o lo activan (BTS). Ejemplo: MOV AX,16 BTC AX,4
; resultado: CF = 1 y AX = 0
CDQ: Similar a CWD, extiende el signo de EAX a EDX:EAX. CMPSD: Similar a CMPSW pero empleando ESI, EDI, ECX y comparando datos de 32 bits. Se puede emplear bajo DOS siempre que ESI y EDI (utilizando REP también ECX) no excedan de 0FFFFh. CWDE: Extiende el signo de AX a EAX. IMUL: Ahora se admite un direccionamiento a memoria en el 2º operando: IMUL CX,[dato] INSD: Similar a INSW pero empleando ESI, EDI, ECX y leyendo datos de 32 bits. Se puede emplear bajo DOS siempre que ESI y EDI (utilizando REP también ECX) no excedan de 0FFFFh. Jcc: Los saltos condicionales ahora pueden ser de ¡32 bits!. Mucho cuidado con la directiva .386 en los programas en que se desee mantener la compatibilidad con procesadores anteriores. JECXZ se utiliza en vez de JCXZ (mismo código de operación). LODSD: Similar a LODSW pero empleando ESI, EDI y ECX y cargando datos de 32 bits en EAX. Se puede emplear bajo DOS siempre que ESI y EDI (utilizando REP también ECX) no excedan de 0FFFFh. LSS, LFS, LGS: similar a LDS o LES pero con esos registros de segmento. MOV CRx,reg / MOV DRx,reg y los recíprocos: acceso a registros de control y depuración. MOVSD: Similar a MOVSW pero empleando ESI, EDI, ECX y moviendo datos de 32 bits. Se puede emplear bajo DOS para acelerar las transferencias siempre que ESI y EDI (utilizando REP también ECX) no excedan de 0FFFFh. Operando sobre la memoria de vídeo sólo se obtiene ventaja si la tarjeta es realmente de 32 bits. MOVSX / MOVZX: carga con extensión de signo o cero. Toma el segundo operando, le extiende adecuadamente el signo (o le pone a cero la parte alta) hasta que sea tan grande como el primer operando y luego lo carga en el primer operando. Si el primer operando es de 16 bits, el segundo sólo puede ser de 8; si el primero es de 32 bits el segundo puede ser de 8 ó 16. El primer operando debe ser un registro, el segundo puede ser un registro u operando en memoria (nunca inmediato): MOV EAX,0FFFFFFFFh MOV AX,7FFFh ; resultado: EAX = 0FFFF7FFFh
41
JUEGO DE INSTRUCCIONES 80x86
MOVSX EAX,AX
; resultado: EAX = 000007FFFh
OUTSD: Similar a OUTSW pero empleando ESI, EDI, ECX y enviando datos de 32 bits. Se puede emplear bajo DOS siempre que ESI y EDI (usando REP también ECX) no rebasen 0FFFFh. Prefijos FS: y GS: en los accesos a memoria, referenciando a esos segmentos. PUSHAD / POPAD: Similares a PUSHA y POPA pero con los registro de 32 bits. La instrucción POPAD falla en la mayoría de los 386, incluidos los de AMD. Para solventar el fallo (que consiste en que EAX no se restaura correctamente) basta colocar un NOP inmediatamente detrás de POPAD. PUSHFD/POPFD introducen y sacan de la pila los flags de 32 bits. SCASD: Similar a SCASW pero empleando ESI, EDI, ECX y buscando datos de 32 bits. Se puede emplear bajo DOS siempre que ESI y EDI (usando REP también ECX) no rebasen 0FFFFh. SETcc reg8 ó mem8: Si se cumple la condición cc, se pone a 1 el byte de memoria o registro de 8 bits indicado (si no, a 0). Por ejemplo, con el acarreo activo, SETC AL pone a 1 el registro AL. SHLD / SHRD: Desplazamiento de doble precisión a la izquierda/derecha. La sintaxis es (ejemplo sobre SHLD): SHLD regmem16, reg16, imm8 ó SHLD regmem16, reg16, CL SHLD regmem32, reg32, imm8 ó SHLD regmem32, reg32, CL Donde regmem es un registro u operando en memoria, indistintamente, del tamaño indicado. En el caso de SHLD, se desplaza el primer operando a la izquierda tanto como indique el tercer operando (contador). Una vez desplazado, los bits menos significativos se rellenan con los más significativos del segundo operando, que no resulta alterado. SHRD es análogo pero al revés. MOV AX,1234h MOV BX,5678h SHLD AX,BX,4
; resultado: AX=2345h, BX=5678h
STOSD: Similar a STOSW pero empleando ESI, EDI, ECX y almacenando EAX. Se puede emplear bajo DOS siempre que ESI y EDI (utilizando REP también ECX) no excedan de 0FFFFh. 4.3.4. - DETECCIÓN DE UN SISTEMA AT O SUPERIOR. Hay casos en los que es necesario determinar si una máquina es AT o superior: no ya de cara a emplear instrucciones propias del 286 en modo real (también disponibles en los V20/V30 y 80188/80186) sino debido a la necesidad de acceder a ciertos chips (por ejemplo, el segundo controlador de interrupciones) que de antemano se sabe que sólo equipan máquinas AT o superiores. Es importante por tanto determinar la presencia de un AT, de cara a evitar ciertas instrucciones que podrían bloquear un PC o XT. No se debe en estos casos comprobar los bytes de la ROM que identifican el equipo: a veces no son correctos y, además, la evolución futura que tengan es impredecible. Lo ideal es verificar directamente si está instalado un 286 o superior. PUSHF POP AND PUSH POPF PUSHF POP AND CMP JE JMP
AX ; AX = flags AH,0Fh ; borrar nibble más significativo AX ; intentar poner a 0 los 4 bits más significativos de los flags AX AH,0F0h ; seguirán valiendo 1 excepto en un 80286 o superior AH,0F0h no_es_AT si_es_AT ; es 286 o superior
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
4.3.5. - EVALUACIÓN EXACTA DEL MICROPROCESADOR INSTALADO. Sobra decir que las instrucciones avanzadas deben ser utilizadas con la previa comprobación del tipo de procesador, aunque sólo sea para decir al usuario que se compre una máquina más potente antes de abortar la ejecución del programa. Para averiguar el procesador de un ordenador puede emplearse el siguiente programa de utilidad, basado en el procedimiento procesador? que devuelve en AX un código numérico entro 0 y 8 distinguiendo entre los 9 procesadores más difíciles de identificar de los ordenadores compatibles. Nota: el 486 no tiene que tener coprocesador necesariamente (el 486sx carece de él). Algunas versiones de procesador 486 y todos los procesadores posteriores soportan la instrucción CPUID que permite identificar la CPU. Basta comprobar un bit del registro de estado para saber si está soportada y, en ese caso, poder emplear dicha instrucción. De este modo, resulta trivial detectar el Pentium o cualquier procesador posterior que aparezca. Esta instrucción está documentada, por ejemplo en alguno de los ficheros que acompañan al Interrupt List. Para los propósitos de este libro no es preciso en general detectar más allá del 386. Es normal que el lector recién iniciado en el ensamblador no entienda absolutamente nada de este programa, ya que hasta los siguientes capítulos no será explicada la sintaxis del lenguaje. En ese caso, puede saltarse este ejemplo y continuar en el capítulo siguiente, máxime si no tiene previsto trabajar con otras instrucciones que no sean las del 8086. Por último, recordar que las instrucciones específicas del 286 en modo real también están disponibles en los V20/V30 de NEC y la serie 80188/80186 de Intel.
; ********************************************************************
CALL
print
; *
*
CMP
CX,AX
(c) Septiembre 1992 CiriSOFT
*
JNE
no_es_este
(c) Grupo Universitario de Informática - Valladolid
*
LEA
DX,apuntador_txt
*
CALL
print
; *
CPU v2.2
; * ; * ; * ; *
LEA
DX,separador_txt
*
CALL
print
*
CMP
CX,7
Este programa determina el tipo de microprocesador del equipo * y devuelve un código ERRORLEVEL indicándolo:
; *
no_es_este:
; ¿procesador del equipo?
; sí lo es: indicarlo
; número de CPUs tratadas-1
; *
0-8088, 1-8086, 2-NEC V20, 3-NEC V30,
*
JBE
otro_proc
; *
4-80188, 5-80186, 6-286, 7-386, 8-486
*
LEA
DX,texto_fin
*
CALL
print
*
MOV
AH,4Ch
; retornar código errorlevel AL
*
INT
21h
; fin de programa
; * ; *
Aviso: Utilizar TASM 2.0 o compatible exclusivamente.
; *
; últimos caracteres
; ********************************************************************
procesador? cpu
; devolver el tipo de microprocesador en AX
PUSHF
ASSUME CS:cpu, DS:cpu
PUSH
DS
PUSH
ES
PUSH
CX
PUSH
DX
PUSH
DI
PUSH
SI
MOV
AX,CS
MOV
.386
ORG
100h
inicio:
otro_proc:
PROC
SEGMENT
LEA
DX,texto_ini
; texto de saludo
MOV
AH,9
DS,AX
; durante la rutina se guardará
INT
21h
; imprimirlo
MOV
ES,AX
; el tipo de procesador en DL:
CALL
procesador?
; tipo de procesador en AX
MOV
DL,6
; supuesto un 286 (DL=6) ...
PUSH
AX
; guardarlo para el final
PUSHF
LEA
BX,cpus_indice-2
; tabla de nombres-2
POP
AX
; AX = flags
MOV
CX,0FFFFh
; número de iteración-1
AND
AX,0FFFh
; borrar nibble más significativo
INC
CX
PUSH
AX
ADD
BX,2
MOV
DX,[BX]
; nombre del primer procesador
POPF
; intentar poner a 0 los 4 bits más
PUSHF
; significativos de los flags
41
JUEGO DE INSTRUCCIONES 80x86
NOP
POP
AX
AND
AX,0F000h
; seguirán valiendo 1 excepto en
CMP
AX,0F000h
; un 80286 o superior
JE
ni286ni_super
PUSHF
INC
AX
OR
AX,7000h
PUSH
AX
; cola de lectura adelantada.
tipo_bus_dest: STI
; intentar activar bit 12, 13 ó 14
cpu_hallada:
JCXZ
cpu_hallada
; el bus ya era supuesto de 8 bits
INC
DL
; resulta que es de 16
MOV
AL,DL
XOR
AH,AH
POPF
POP
SI
PUSHF
POP
DI
POP
DX
POP
AX
AND
AX,7000h
; 286 pone bits 12, 13 y 14 a cero
POP
CX
JZ
cpu_hallada
; es un 286 (DL=6)
POP
ES
INC
DL
; es un 386 (DL=7) ... de momento
POP
DS
PUSH
DX
CLI
POPF RET
; AX = CPU: 0/1-8088/86, 2/3-NEC V20/V30
procesador?
ENDP
;
print
PROC
; momento crítico
MOV
EDX,ESP
; preservar ESP en EDX
AND
ESP,0FFFFh
; borrar parte alta de ESP
AND
ESP,0FFFCh
; forzar ESP a múltiplo de 4
PUSHFD
; guardar flags en pila (32 bits)
POP
EAX
; recuperar flags en EAX
MOV
ECX,EAX
XOR
EAX,40000h
PUSH
EAX
POPFD
; 8088/80188/V20) porque está en la
tipo_bus_byte: STI
; es 286 o superior
POP
; en un 8086/80186/V30 (y no en un CX
; conmutar bit 18
; intentar cambiar este bit
PUSHFD
PUSH
AX
PUSH
BX
PUSH
CX
MOV
AH,9
INT
21h
POP
CX
4/5-80188/186, 6-286, 7-386, 8-486
POP
EAX
; ECX conserva el bit inicial
POP
BX
XOR
EAX,ECX
; bit 18 de EAX a 1 si cambió
POP
AX
SHR
EAX,12h
; mover bit 18 a bit 0
RET
AND
EAX,1
; dejar sólo ese bit
print
ENDP
PUSH
ECX ; restaurar bit 18 de los flags
cpus_indice
DW
i88,i86,v20,v30,i188,i186,i286,i386,i486
; restaurar ESP
i88
DB
"Intel 8088 $"
; permitir interrupciones de nuevo
i86
DB
"Intel 8086 $"
; recuperar tipo de CPU en DL
v20
DB
" NEC
V20
$"
v30
DB
" NEC
V30
$"
POPFD MOV
ESP,EDX
STI POP
DX
CMP
AX,0
JE
cpu_hallada
; es 386: DL=7 (bit 18 no cambió)
i188
DB
"Intel 80188$"
INC
DL
; es 486: DL=8 (bit 18 cambió)
i186
DB
"Intel 80186$"
JMP
cpu_hallada
i286
DB
"Intel 80286$"
i386
DB
"Intel 80386$"
i486
DB
"Intel 80486$"
apuntador_txt
DB
" <───$"
texto_ini
LABEL BYTE
ni286ni_super: MOV
DL,4
; supuesto un 80188 ...
MOV
AX,0FFFFh
MOV
CL,33
SHL
AX,CL
JNZ
tipo_bus_proc ; ... lo es, calcular bus (188/186)
MOV
DL,2
MOV
CX,0FFFFh
; (80188/80186 toman
CL mod 32)
; no lo es, supuesto un V20 ...
STI DB
0F3h,26h,0ACh ; opcode de REPZ
JCXZ
tipo_bus_proc ; ... lo es, calcular bus (V20/V30)
XOR
DL,DL
tipo_bus_proc: STD
LODSB ES:
"(c) Septiembre 1992 Ciriaco García de Celis."
DB
13,10,"
DB
"equipo es compatible:",10
separador_txt
DB
13,10,9,9,9,"$"
texto_fin
DB
13,10,"$"
cpu
ENDS
MOV
AL,BYTE PTR DS:tipo_bus_byte ; opcode de STI
MOV
CX,3
END
CLI ; transferir tres bytes
CLD NOP
; el INC CX (1 byte) será machacado
NOP
; con STOSB pero aún se ejecutará
inicio
"
El microprocesador de este "
; transferencias hacia arriba DI,tipo_bus_dest
STOSB
13,10,"CPU Test v2.2
DB
; ya sólo puede ser un 8088/8086
LEA
REP
DB
41
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
4.3.6. - MODO PLANO (FLAT) DEL 386 Y SUPERIORES. Como ya se comentó, no es estrictamente cierto que no se pueda rebasar el límite de 64 Kb en los segmentos en modo real. El problema es que al encender el ordenador, el 386 tiene definidos por defecto dichos límites de 64 Kb. Sin embargo, se puede pasar un momento a modo protegido, ampliar el límite y volver a modo real. Entonces se consigue el llamado modo flat o plano. No solo es factible de este modo saltar la restricción de 64 Kb, sino que además se puede acceder directamente, desde el modo real, a toda la memoria por encima del primer megabyte. El problema es que pasar a modo protegido no es sencillo cuando la máquina ya está en modo protegido emulando al modo real (el conocido como modo virtual 86). Por tanto, el siguiente programa de ejemplo no funciona si está cargado un controlador de memoria expandida (EMM386, QEMM) o dentro de Windows 3.x. Arrancando sin controlador de memoria (excepto HIMEM) no habrá problema alguno. El programa de ejemplo se limita a llenar la pantalla de texto (empleando ahora la dirección absoluta 0B8000h a través de EBX) de letras 'A'. Otra restricción de este programa de ejemplo es que no activa la línea A20 de direcciones; dicho de otro modo, el bit 21º (de los 32 bits de la dirección de memoria) suele estar forzado a 0 por defecto al arrancar. Para acceder a la memoria de vídeo esto no es problema, pero por encima del primer megabyte podría haber problemas según a qué dirección se pretenda acceder. De todos modos, sería relativamente sencillo habilitar la línea A20 directamente o a través de una función del controlador XMS. Naturalmente, se sale de los objetivos de este libro describir el modo protegido o explicar los pasos que realiza esta rutina de demostración. Consúltese al efecto la bibliografía recomendada del apéndice. ; ┌──────────────────────────────────────────────────────────────────┐ ; │ Rutina para activar el modo flat del 386 y superiores (acceso
│
; │ a 4 Gb en modo real).
│
PUSH
DS
; │
│
PUSH
ES
│
PUSH
EAX
│
PUSH
BX
; └──────────────────────────────────────────────────────────────────┘
PUSH
CX
MOV
CX,SS
XOR
EAX,EAX
MOV
AX,CS
SEGMENT USE16
SHL
EAX,4
; dirección lineal de segmento CS
ASSUME CS:segmento, DS:segmento
ADD
EAX,OFFSET gdt
; desplazamiento de GDT
MOV
CS:[gd2],EAX
; guardar dirección lineal de GDT
LGDT
CS:[gdtr]
; cargar tabla global de descriptores
MOV
EAX,CR0
; │
TASM
; │
TLINK flat386 /t /32
flat386 /m5
.386p
segmento
; sólo para 386 o superior
PROC
CLI
ORG
100h
CALL
flat386
XOR
AX,AX
OR
AL,1
; bit de modo protegido
MOV
DS,AX
MOV
CR0,EAX
; pasar a modo protegido
MOV
EBX,0B8000h
JMP
SHORT $+2
; borrar cola de prebúsqueda
MOV
CX,2000
MOV
BX,gcodl
; índice de descriptor en BX
MOV
BYTE PTR [EBX],'A'
MOV
DS,BX
; cargar registro de segmento DS
INC
EBX
MOV
ES,BX
; ES
MOV
BYTE PTR [EBX],15
MOV
SS,BX
; SS
INC
EBX
MOV
FS,BX
; FS
LOOP
llena_pant
MOV
GS,BX
; GS
INT
20h
AND
AL,11111110b
prueba:
llena_pant:
flat386
; activar modo flat
; dirección de vídeo absoluta
; fin de programa
MOV
CR0,EAX
; volver a modo real
; ------------ Esta rutina pasa momentáneamente a modo protegido de
JMP
SHORT $+2
; borrar cola de prebúsqueda
;
manera directa (necesita la CPU en modo real). No se
MOV
SS,CX
;
activa la línea A20
STI
;
o a través de algún servicio XMS
;
las áreas de memoria extendida afectadas).
(necesario hacerlo directamente antes de acceder a
POP
CX
POP
BX
41
JUEGO DE INSTRUCCIONES 80x86
POP
EAX
POP
ES
POP
DS
RET
gdtr
LABEL QWORD
gd1
DW
gdtl-1
gd2
DD
?
gdt
DB
0,0,0,0,0,0,0,0
gcod
DB
0ffh,0ffh,0,0,0,9fh,0cfh,0
gcodl
EQU
$-OFFSET gdt
gdat
DB
0ffh,0ffh,0,0,0,93h,0cfh,0
gdtl
EQU
$-OFFSET gdt
flat386
ENDP
segmento
ENDS END
prueba
; datos para cargar en GDTR
; GDT
EL LENGUAJE ENSAMBLADOR DEL 80x86
71
Capítulo V: EL LENGUAJE ENSAMBLADOR DEL 80x86
Hasta ahora hemos visto los mnemónicos de las instrucciones que pasadas a su correspondiente código binario ya puede entender el microprocesador. Si bien se realiza un gran avance al introducir los mnemónicos respecto a programar directamente en lenguaje maquina -es decir, con números en binario o hexadecimal- aún resultaría tedioso tener que realizar los cálculos de los desplazamientos en los saltos a otras partes del programa en las transferencias de control, reservar espacio de memoria dentro de un programa para almacenar datos, etc... Para facilitar estas operaciones se utilizan las directivas que indican al ensamblador qué debe hacer con las instrucciones y los datos. Los programas de ejemplo de este libro y la sintaxis de ensamblador tratada son las del MASM de Microsoft y el ensamblador de IBM. No obstante, todos los programas han sido desarrollados con el Turbo Assembler 2.0 de Borland (TASM), compatible con el clásico MASM 5.0 de Microsoft pero más potente y al mismo tiempo mucho más rápido y flexible. TASM genera además un código más reducido y optimizado. Por otra parte, MASM 5.0 no permite cambiar (aunque sí la 6.0) dentro de un segmento el modo del procesador: esto conlleva el riesgo de ejecutar indeseadamente instrucciones de 32 bits al no poder acotar exactamente las líneas donde se desea emplearlas, algo vital para mantener la compatibilidad con procesadores anteriores. También es propenso a generar errores de fase y otros similares al tratar con listados un poco grandes. Respecto a MASM 6.0, el autor de este libro encontró que en ocasiones calcula incorrectamente el valor de algunos símbolos y etiquetas, aunque es probable que la versión 6.1 (aparecida sospechosa e inusualmente muy poco tiempo después) haya corregido dichos fallos, intolerables en un ensamblador. Por otro lado, las posibilidades adicionales de TASM no han sido empleadas por lo general. Muchos programas han sido ensamblados una vez con MASM, para asegurar que éste puede ensamblarlos. Conviene decir aquí que este capítulo es especialmente arduo para aquellos que no conocen el lenguaje ensamblador de ninguna máquina. La razón es que la información está organizada a modo de referencia, por lo que con frecuencia se utilizan unos elementos -para explicar otros- que aún no han sido definidos. Ello por otra parte resulta inevitable también en algunos libros más básicos, debido a la complejidad de la sintaxis del lenguaje ensamblador ideada por el fabricante (que no la del microprocesador). Por ello, es un buen consejo actuar a dos pasadas, al igual que el propio ensamblador en ocasiones: leer todo una vez primero -aunque no se entienda del todo- y volverlo a leer después más despacio. 5.1. - SINTAXIS DE UNA LÍNEA EN ENSAMBLADOR. Un programa fuente en ensamblador contiene dos tipos de sentencias: las instrucciones y las directivas. Las instrucciones se aplican en tiempo de ejecución, pero las directivas sólo son utilizadas durante el ensamblaje. El formato de una sentencia de instrucción es el siguiente: [etiqueta] nombre_instrucción [operandos] [comentario] Los corchetes, como es normal al explicar instrucciones en informática, indican que lo especificado entre ellos es opcional, dependiendo de la situación que se trate. Campo de etiqueta. Es el nombre simbólico de la primera posición de una instrucción, puntero o dato. Consta de hasta 31 caracteres que pueden ser las letras de la A a la Z, los números del 0 al 9 y algunos caracteres especiales como «@», «_», «.» y «$». Reglas:
71
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
- Si se utiliza el punto «.» éste debe colocarse como primer carácter de la etiqueta. - El primer carácter no puede ser un dígito. - No se pueden utilizar los nombres de instrucciones o registros como nombres de etiquetas. las etiquetas son de tipo NEAR cuando el campo de etiqueta finaliza con dos puntos (:); esto es, se considera cercana: quiere esto decir que cuando realizamos una llamada sobre dicha etiqueta el ensamblador considera que está dentro del mismo segmento de código (llamadas intrasegmento) y el procesador sólo carga el puntero de instrucciones IP. Téngase en cuenta que hablamos de instrucciones; las etiquetas empleadas antes de las directivas, como las directivas de definición de datos por ejemplo, no llevan los dos puntos y sin embargo son cercanas. Las etiquetas son de tipo FAR si el campo de etiqueta no termina con los dos puntos: en estas etiquetas la instrucción a la que apunta no se encuentra en el mismo segmento de código sino en otro. Cuando es referenciada en una transferencia de control se carga el puntero de instrucciones IP y el segmento de código CS (llamadas intersegmento). Campo de nombre. Contiene el mnemónico de las instrucciones vistas en el capítulo anterior, o bien una directiva de las que veremos más adelante. Campo de operandos. Indica cuales son los datos implicados en la operación. Puede haber 0, 1 ó 2; en el caso de que sean dos al 1º se le llama destino y al 2º -separado por una coma- fuente. mov ax, es:[di] ──¾
ax es:[di]
destino origen
Campo de comentarios. Cuando en una línea hay un punto y coma (;) todo lo que sigue en la línea es un comentario que realiza aclaraciones sobre lo que se está haciendo en ese programa, resulta de gran utilidad de cara a realizar futuras modificaciones al mismo. 5.2. - CONSTANTES Y OPERADORES. Las sentencias fuente -tanto instrucciones como directivas- pueden contener constantes y operadores. 5.2.1. - CONSTANTES. Pueden ser binarias (ej. 10010b), decimales (ej. 34d), hexadecimales (ej. 0E0h) u octales (ej. 21o ó 21q); también las hay de cadena (ej. 'pepe', "juan") e incluso con comillas dentro de comillas de distinto tipo (como 'hola,"amigo"'). En las hexadecimales, si el primer dígito no es numérico hay que poner un 0. Sólo se puede poner el signo (-) en las decimales (en las demás, calcúlese el complemento a dos). Por defecto, las numéricas están en base 10 si no se indica lo contrario con una directiva (poco recomendable como se verá). 5.2.2. - OPERADORES ARITMÉTICOS. Pueden emplearse libremente (+), (-), (*) y (/) -en este último caso la división es siempre entera-. Es válida, por ejemplo, la siguiente línea en ensamblador (que se apoya en la directiva DW, que se verá más adelante, para reservar memoria para una palabra de 16 bits): dato
DW
12*(numero+65)/7
También se admiten los operadores MOD (resto de la división) y SHL/SHR (desplazar a la izquierda/derecha cierto número de bits). Obviamente, el ensamblador no codifica las instrucciones de desplazamiento (al aplicarse sobre datos constantes el resultado se calcula en tiempo de ensamblaje): dato
DW
(12 SHR 2) + 5
71
EL LENGUAJE ENSAMBLADOR DEL 80x86
5.2.3. - OPERADORES LÓGICOS. Pueden ser el AND, OR, XOR y NOT. Realizan las operaciones lógicas en las expresiones. Ej.: MOV
BL,(255 AND 128) XOR 128
; BL = 0
5.2.4. - OPERADORES RELACIONALES. Devuelven condiciones de cierto (0FFFFh ó 0FFh) o falso (0) evaluando una expresión. Pueden ser: EQ (igual), NE (no igual), LT (menor que), GT (mayor que), LE (menor o igual que), GE (mayor o igual que). Ejemplo: dato
EQU MOV MOV
100 AL,dato GE 10 AH,dato EQ 99
; «dato» vale 100 ; AL = 0FFh (cierto) ; AH = 0 (falso)
5.2.5. - OPERADORES DE RETORNO DE VALORES. Operador SEG: devuelve el valor del segmento de la variable o etiqueta, sólo se puede emplear en programas de tipo EXE: MOV
AX,SEG tabla_datos
Operador OFFSET: devuelve el desplazamiento de la variable o etiqueta en su segmento: MOV
AX,OFFSET variable
Si se desea obtener el offset de una variable respecto al grupo (directiva GROUP) de segmentos en que está definida y no respecto al segmento concreto en que está definida: MOV
AX,OFFSET nombre_grupo:variable
MOV
AX,OFFSET DS:variable
también es válido: Operador .TYPE: devuelve el modo de la expresión indicada en un byte. El bit 0 indica modo «relativo al código» y el 1 modo «relativo a datos», si ambos bits están inactivos significa modo absoluto. El bit 5 indica si la expresión es local (0 si está definida externamente o indefinida); el bit 7 indica si la expresión contiene una referencia externa. El TASM utiliza también el bit 3 para indicar algo que desconozco. Este operador es útil sobre todo en las macros para determinar el tipo de los parámetros: info
.TYPE
variable
Operador TYPE: devuelve el tamaño (bytes) de la variable indicada. No válido en variables DUP: kilos
DW MOV
76 AX,TYPE kilos
; AX = 2
Tratándose de etiquetas -en lugar de variables- indica si es lejana o FAR (0FFFEh) o cercana o NEAR (0FFFFh). Operadores SIZE y LENGTH: devuelven el tamaño (en bytes) o el nº de elementos, respectivamente, de la variable indicada (definida obligatoriamente con DUP): matriz
DW MOV MOV
100 DUP (12345) AX,SIZE matriz BX,LENGTH matriz
; AX = 200 ; BX = 100
Operadores MASK y WIDTH: informan de los campos de un registro de bits (véase RECORD).
71
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
5.2.6. - OPERADORES DE ATRIBUTOS. Operador PTR: redefine el atributo de tipo (BYTE, WORD, DWORD, QWORD, TBYTE) o el de distancia (NEAR o FAR) de un operando de memoria. Por ejemplo, si se tiene una tabla definida de la siguiente manera: tabla
DW
10 DUP (0)
; 10 palabras a 0
Para colocar en AL el primer byte de la misma, la instrucción MOV AL,tabla es incorrecta, ya que tabla (una cadena 10 palabras) no cabe en el registro AL. Lo que desea el programador debe indicárselo en este caso explícitamente al ensamblador de la siguiente manera: MOV
AL,BYTE PTR tabla
Trabajando con varios segmentos, PTR puede redefinir una etiqueta NEAR de uno de ellos para convertirla en FAR desde el otro, con objeto de poder llamarla. Operadores CS:, DS:, ES: y SS: el ensamblador genera un prefijo de un byte que indica al microprocesador el segmento que debe emplear para acceder a los datos en memoria. Por defecto, se supone DS para los registros BX, DI o SI (o sin registros de base o índice) y SS para SP y BP. Si al acceder a un dato éste no se encuentra en el segmento por defecto, el ensamblador añadirá el byte adicional de manera automática. Sin embargo, el programador puede forzar también esta circunstancia: MOV
AL,ES:variable
En el ejemplo, variable se supone ubicada en el segmento extra. Cuando se referencia una dirección fija hay que indicar el segmento, ya que el ensamblador no conoce en qué segmento está la variable, es uno de los pocos casos en que debe indicarse. Por ejemplo, la siguiente línea dará un error al ensamblar: MOV
AL,[0]
Para solucionarlo hay que indicar en qué segmento está el dato (incluso aunque éste sea DS): MOV
AL,DS:[0]
En este último ejemplo el ensamblador no generará el byte adicional ya que las instrucciones MOV operan por defecto sobre DS (como casi todas), pero ha sido necesario indicar DS para que el ensamblador nos entienda. Sin embargo, en el siguiente ejemplo no es necesario, ya que midato está declarado en el segmento de datos y el ensamblador lo sabe: MOV
AL,midato
Por lo general no es muy frecuente la necesidad de indicar explícitamente el segmento: al acceder a una variable el ensamblador mira en qué segmento está declarada (véase la directiva SEGMENT) y según como estén asignados los ASSUME, pondrá o no el prefijo adecuado según sea conveniente. Es responsabilidad exclusiva del programador inicializar los registros de segmento al principio de los procedimientos para que el ASSUME no se quede en tinta mojada... sí se emplean con bastante frecuencia, sin embargo, los prefijos CS en las rutinas que gestionan interrupciones (ya que CS es el único registro de segmento que apunta en principio a las mismas, hasta que se cargue DS u otro). Operador SHORT: indica que la etiqueta referenciada, de tipo NEAR, puede alcanzarse con un salto corto (128 a +127 posiciones) desde la actual situación del contador de programa. El ensamblador TASM, si se solicitan dos pasadas, coloca automáticamente instrucciones SHORT allí donde es posible, para economizar memoria (el MASM no). Operador '$': indica la posición del contador de posiciones («Location Counter») utilizado por el ensamblador dentro del segmento para llevar la cuenta de por dónde se llega ensamblando. Muy útil:
71
EL LENGUAJE ENSAMBLADOR DEL 80x86
frase longitud
DB EQU
"simpático" $-OFFSET frase
En el ejemplo, longitud tomará el valor 9. Operadores HIGH y LOW: devuelven la parte alta o baja, respectivamente (8 bits) de la expresión: dato
EQU MOV MOV
1025 AL,LOW dato AH,HIGH dato
; AL = 1 ; AH = 4
5.3. - PRINCIPALES DIRECTIVAS. La sintaxis de una sentencia directiva es muy similar a la de una sentencia de instrucción: [nombre] nombre_directiva [operandos] [comentario] Sólo es obligatorio el campo «nombre_directiva»; los campos han de estar separados por al menos un espacio en blanco. La sintaxis de «nombre» es análoga a la de la «etiqueta» de las líneas de instrucciones, aunque nunca se pone el sufijo «:». El campo de comentario cumple también las mismas normas. A continuación se explican las directivas empleadas en los programas ejemplo de este libro y alguna más, aunque falta alguna que otra y las explicadas no lo están en todos los casos con profundidad. 5.3.1. - DIRECTIVAS DE DEFINICIÓN DE DATOS. DB (definir byte), DW (definir palabra), DD (definir doble palabra), DQ (definir cuádruple palabra), DT (definir 10 bytes): sirven para declarar las variables, asignándolas un valor inicial: anno mes numerazo texto
DW DB DD DB
1991 12 12345678h "Hola",13,10
Se pueden definir números reales de simple precisión (4 bytes) con DD, de doble precisión (8 bytes) con DQ y «reales temporales» (10 bytes) con DT; todos ellos con el formato empleado por el coprocesador. Para que el ensamblador interprete el número como real ha de llevar el punto decimal: temperatura DD espanoles91 DQ
29.72 38.9E6
Con el operando DUP pueden definirse estructuras repetitivas. Por ejemplo, para asignar 100 bytes a cero y 25 palabras de contenido indefinido (no importa lo que el ensamblador asigne): ceros basura
DB DW
100 DUP (0) 25 DUP (?)
Se admiten también los anidamientos. El siguiente ejemplo crea una tabla de bytes donde se repite 50 veces la secuencia 1,2,3,7,7: tabla
DB
50 DUP (1, 2, 3, 2 DUP (7))
5.3.2. - DIRECTIVAS DE DEFINICIÓN DE SÍMBOLOS. EQU (EQUivalence): Asigna el valor de una expresión a un nombre simbólico fijo: olimpiadas
EQU
1992
71
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Donde olimpiadas ya no podrá cambiar de valor en todo el programa. Se trata de un operador muy flexible. Es válido hacer: edad
EQU MOV
[BX+DI+8] AX,edad
= (signo '='): asigna el valor de la expresión a un nombre simbólico variable: Análogo al anterior pero con posibilidad de cambiar en el futuro. Muy usada en macros (sobre todo con REPT). num = 19 num = pepe + 1 dato = [BX+3] dato = ES:[BP+1]
5.3.3. - DIRECTIVAS DE CONTROL DEL ENSAMBLADOR. ORG (ORiGin): pone el contador de posiciones del ensamblador, que indica el offset donde se deposita la instrucción o dato, donde se indique. En los programas COM (que se cargan en memoria con un OFFSET 100h) es necesario colocar al principio un ORG 100h, y un ORG 0 en los controladores de dispositivo (aunque si se omite se asume de hecho un ORG 0). END [expresión]: indica el final del fichero fuente. Si se incluye, expresión indica el punto donde arranca el programa. Puede omitirse en los programas EXE si éstos constan de un sólo módulo. En los COM es preciso indicarla y, además, la expresión -realmente una etiqueta- debe estar inmediatamente después del ORG 100h. .286, .386 Y .8087 obligan al ensamblador a reconocer instrucciones específicas del 286, el 386 y del 8087. También debe ponerse el «.» inicial. Con .8086 se fuerza a que de nuevo sólo se reconozcan instrucciones del 8086 (modo por defecto). La directiva .386 puede ser colocada dentro de un segmento (entre las directivas SEGMENT/ENDS) con el ensamblador TASM, lo que permite emplear instrucciones de 386 con segmentos de 16 bits; alternativamente se puede ubicar fuera de los segmentos (obligatorio en MASM) y definir éstos explícitamente como de 16 bits con USE16. EVEN: fuerza el contador de posiciones a una posición par, intercalando un byte con la instrucción NOP si es preciso. En buses de 16 ó más bits (8086 y superiores, no en 8088) es dos veces más rápido el acceso a palabras en posición par: EVEN dato_rapido DW
0
.RADIX n: cambia la base de numeración por defecto. Bastante desaconsejable dada la notación elegida para indicar las bases por parte de IBM/Microsoft (si se cambia la base por defecto a 16, ¡los números no pueden acabar en 'd' ya que se confundirían con el sufijo de decimal!: lo ideal sería emplear un prefijo y no un sufijo, que a menudo obliga además a iniciar los números por 0 para distinguirlos de las etiquetas). 5.3.4. - DIRECTIVAS DE DEFINICIÓN DE SEGMENTOS Y PROCEDIMIENTOS. SEGMENT-ENDS: SEGMENT indica el comienzo de un segmento (código, datos, pila, etc.) y ENDS su final. El programa más simple, de tipo COM, necesita la declaración de un segmento (común para datos, código y pila). Junto a SEGMENT puede aparecer, opcionalmente, el tipo de alineamiento, la combinación, el uso y la clase: nombre SEGMENT [alineamiento] [combinación] [uso] ['clase'] . . . . nombre ENDS
71
EL LENGUAJE ENSAMBLADOR DEL 80x86
Se pueden definir unos segmentos dentro de otros (el ensamblador los ubicará unos tras otros). El alineamiento puede ser BYTE (ninguno), WORD (el segmento comienza en posición par), DWORD (comienza en posición múltiplo de 4), PARA (comienza en una dirección múltiplo de 16, opción por defecto) y PAGE (comienza en dirección múltiplo de 256). La combinación puede ser: - (No indicada): los segmentos se colocan unos tras otros físicamente, pero son lógicamente independientes: cada uno tiene su propia base y sus propios offsets relativos. - PUBLIC: usado especialmente cuando se trabaja con segmentos definidos en varios ficheros que se ensamblan por separado o se compilan con otros lenguajes, por ello debe declararse un nombre entre comillas simples -'clase'- para ayudar al linkador. Todos los segmentos PUBLIC de igual nombre y clase tienen una base común y son colocados adyacentemente unos tras otros, siendo el offset relativo al primer segmento cargado. - COMMON: similar, aunque ahora los segmentos de igual nombre y clase se solapan. Por ello, las variables declaradas han de serlo en el mismo orden y tamaño. - AT: asocia un segmento a una posición de memoria fija, no para ensamblar sino para declarar variables (inicializadas siempre con '?') de cara a acceder con comodidad a zonas de ROM, vectores de interrupción, etc. Ejemplo: vars_bios p_serie0 vars_bios
SEGMENT AT 40h DW ? ENDS
De esta manera, la dirección del primer puerto serie puede obtenerse de esta manera (por ejemplo): MOV MOV MOV
AX,variables_bios ES,AX AX,ES:p_serie0
; segmento ; inicializar ES
- STACK: segmento de pila, debe existir uno en los programas de tipo EXE; además el Linkador de Borland (TLINK 4.0) exige obligatoriamente que la clase de éste sea también 'STACK', con el LINK de Microsoft no siempre es necesario indicar la clase del segmento de pila. Similar, por lo demás, a PUBLIC. - MEMORY: segmento que el linkador ubicará al final de todos los demás, lo que permitiría saber dónde acaba el programa. Si se definen varios segmentos de este tipo el ensamblador acepta el primero y trata a los demás como COMMON. Téngase en cuenta que el linkador no soporta esta característica, por lo que emplear MEMORY es equivalente a todos los efectos a utilizar COMMON. Olvídate de MEMORY. El uso indica si el segmento es de 16 bits o de 32; al emplear la directiva .386 se asumen por defecto segmentos de 32 bits por lo que es necesario declarar USE16 para conseguir que los segmentos sean interpretados como de 16 bits por el linkador, lo que permite emplear algunas instrucciones del 386 en el modo real del microprocesador y bajo el sistema operativo DOS. Por último, 'clase' es un nombre opcional que empleará el linkador para encadenar los módulos, siendo conveniente nombrar la clase del segmento de pila con 'STACK'. ASSUME (Suponer): Indica al ensamblador el registro de segmento que se va a utilizar para direccionar cada segmento dentro del módulo. Esta instrucción va normalmente inmediatamente después del SEGMENT. El programa más sencillo necesita que se «suponga» CS como mínimo para el segmento de código, de lo contrario el ensamblador empezará a protestar un montón al no saber que registro de segmento asociar al código generado. También conviene hacer un assume del registro de segmento DS hacia el segmento de datos, incluso en el caso de que éste sea el mismo que el de código: si no, el ensamblador colocará un byte de prefijo adicional en todos los accesos a memoria para forzar que éstos sean sobre CS. Se puede indicar ASSUME NOTHING para cancelar un ASSUME anterior. También se puede indicar el nombre de un grupo o emplear «SEG variable» o «SEG etiqueta» en vez de
71
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
nombre_segmento: ASSUME reg_segmento:nombre_segmento[,...] PROC-ENDP permite dar nombre a una subrutina, marcando con claridad su inicio y su fin. Aunque es redundante, es muy recomendable para estructurar los programas. cls cls
PROC ... ENDP
El atributo FAR que aparece en ocasiones junto a PROC indica que es un procedimiento lejano y las instrucciones RET en su interior se ensamblan como RETF (los CALL hacia él serán, además, de 32 bits). Observar que la etiqueta nunca termina con dos puntos. 5.3.5. - DIRECTIVAS DE REFERENCIAS EXTERNAS. PUBLIC: permite hacer visibles al exterior (otros ficheros objeto resultantes de otros listados en ensamblador u otro lenguaje) los símbolos -variables y procedimientos- indicados. Necesario para programación modular e interfaces con lenguajes de alto nivel. Por ejemplo:
proc1 proc1 var_x
PUBLIC proc1, var_x PROC FAR ⋅⋅⋅ ENDP DW 0
Declara la variable var_x y el procedimiento proc1 como accesibles desde el exterior por medio de la directiva EXTRN. EXTRN: Permite acceder a símbolos definidos en otro fichero objeto (resultante de otro ensamblaje o de una compilación de un lenguaje de alto nivel); es necesario también indicar el tipo del dato o procedimiento (BYTE, WORD o DWORD; NEAR o FAR; se emplea además ABS para las constantes numéricas): EXTRN proc1:FAR, var_x:WORD
En el ejemplo se accede a los símbolos externos proc1 y var_x (ver ejemplos de PUBLIC) y a continuación sería posible hacer un CALL proc1 o un MOV CX,var_x. Si la directiva EXTRN se coloca dentro de un segmento, se supone el símbolo dentro del mismo. Si el símbolo está en otro segmento, debe colocarse EXTRN fuera de todos los segmentos indicando explícitamente el prefijo del registro de segmento (o bien hacer el ASSUME apropiado) al referenciarlo. Evidentemente, al final, al linkar habrá que enlazar este módulo con el que define los elementos externos. INCLUDE nombre_fichero: Añade al fichero fuente en proceso de ensamblaje el fichero indicado, en el punto en que aparece el INCLUDE. Es exactamente lo mismo que mezclar ambos ficheros con un editor de texto. Ahorra trabajo en fragmentos de código que se repiten en varios programas (como quizá una librería de macros). No se recomiendan INCLUDE's anidados. 5.3.6. - DIRECTIVAS DE DEFINICIÓN DE BLOQUES. NAME nombre_modulo_objeto: indica el nombre del módulo objeto. Si no se incluye NAME, se tomará de la directiva TITLE o, en su defecto, del nombre del propio fichero fuente. GROUP segmento1, segmento2,... permite agrupar dos o más segmentos lógicos en uno sólo de no más de 64 Kb totales (ojo: el ensamblador no comprueba este extremo, aunque sí el enlazador). Ejemplo: superseg
GROUP datos, codigo, pila
71
EL LENGUAJE ENSAMBLADOR DEL 80x86
codigo codigo
SEGMENT ⋅⋅⋅ ENDS
datos dato datos
SEGMENT DW 1234 ENDS
pila
SEGMENT STACK 'STACK' DB 128 DUP (?) ENDS
pila
Cuando se accede a un dato definido en algún segmento de un grupo y se emplea el operador OFFSET es preciso indicar el nombre del grupo como prefijo, de lo contrario el ensamblador no generará el desplazamiento correcto ¡ni emitirá errores!: MOV MOV
AX,dato AX,supersegmento:dato
; ¡incorrecto! ; correcto
La ventaja de agrupar segmentos es poder crear programas COM y SYS que contengan varios segmentos. En todo caso, téngase en cuenta aún en ese caso que no pueden emplearse todas las características de la programación con segmentos (por ejemplo, no se puede utilizar la directiva SEG ni debe existir segmento de pila). LABEL: Permite referenciar un símbolo con otro nombre, siendo factible redefinir el tipo. La sintaxis es: nombre LABEL tipo (tipo = BYTE, WORD, DWORD, NEAR o FAR). Ejemplo: palabra byte_bajo byte_alto
LABEL DB DB
WORD 0 0
En el ejemplo, con MOV AX,palabra se accederá a ambos bytes a la vez (el empleo de MOV AX,byte_bajo daría error: no se puede cargar un sólo byte en un registro de 16 bits y el ensamblador no supone que realmente pretendíamos tomar dos bytes consecutivos de la memoria). STRUC - ENDS: permite definir registros al estilo de los lenguajes de alto nivel, para acceder de una manera más elegante a los campos de una información con cierta estructura. Estos campos pueden componerse de cualquiera de los tipos de datos simples (DB, DW, DD, DQ, DT) y pueden ser modificables o no en función de si son simples o múltiples, respectivamente: alumno mote edadaltura peso otros telefono alumno
STRUC DB '0123456789' DB 20,175 DB 0 DB 10 DUP(0) DD ? ENDS
; ; ; ; ;
modificable no modificable modificable no modificable modificable
La anterior definición de estructura no lleva implícita la reserva de memoria necesaria, la cual ha de hacerse expresamente utilizando los ángulos '<' y '>': felipe
alumno <'Gordinflas',,101,,251244>
En el ejemplo se definen los campos modificables (los únicos definibles) dejando sin definir (comas consecutivas) los no modificables, creándose la estructura 'felipe' que ocupa 27 bytes. Las cadenas de caracteres son rellenadas con espacios en blanco al final si no alcanzan el tamaño máximo de la
71
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
declaración. El TASM es más flexible y permite definir también el primer elemento de los campos múltiples sin dar error. Tras crear la estructura, es posible acceder a sus elementos utilizando un (.) para separar el nombre del campo: MOV LEA MOV
AX,OFFSET felipe.telefono BX,felipe CL,[BX].peso ; equivale a [BX+12]
RECORD: similar a STRUC pero operando con campos de bits. Permite definir una estructura determinada de byte o palabra para operar con comodidad. Sintaxis: nombre
RECORD nombre_de_campo:tamaño[=valor],...
Donde nombre permitirá referenciar la estructura en el futuro, nombre_de_campo identifica los distintos campos, a los que se asigna un tamaño (en bits) y opcionalmente un valor por defecto. registro
RECORD a:2=3, b:4=5, c:1
La estructura registro totaliza 7 bits, por lo que ocupa un byte. Está dividida en tres campos que ocupan los 7 bits menos significativos del byte: el campo A ocupa los bits 6 y 5, el B los bits 1 al 4 y el C el bit 0: 6 5 4 3 2 1 0 ┌────┬────────┬──┐ │ 1 1│ 0 1 0 1│ ?│ └────┴────────┴──┘
La reserva de memoria se realiza, por ejemplo, de la siguiente manera: reg1
registro <2,,1>
Quedando reg1 con el valor binario 1001011 (el campo B permanece inalterado y el A y C toman los valores indicados). Ejemplos de operaciones soportadas: MOV
AL, A
MOV MOV
AL, MASK A AL, WIDTH A
; AL = 5 (desplazamiento del bit ; menos significativo de A) ; AL = 01100000b (máscara de A) ; AL = 2 (anchura de A)
5.3.7. - DIRECTIVAS CONDICIONALES. Se emplean para que el ensamblador evalúe unas condiciones y, según ellas, ensamble o no ciertas zonas de código. Es frecuente, por ejemplo, de cara a generar código para varios ordenadores: pueden existir ciertos símbolos definidos que indiquen en un momento dado si hay que ensamblar ciertas zonas del listado o no de manera condicional, según la máquina. En los fragmentos en ensamblador del código que generan los compiladores también aparecen con frecuencia (para actuar de manera diferente, por ejemplo, según el modelo de memoria). Es interesante también la posibilidad de definir un símbolo que indique que el programa está en fase de pruebas y ensamblar código adicional en ese caso con objeto de depurarlo. Sintaxis: IFxxx ... ELSE ... ENDIF IF
[símbolo/exp./arg.] ; xxx es la condición
expresion
; el ELSE es opcional
(expresión distinta de cero)
71
EL LENGUAJE ENSAMBLADOR DEL 80x86
IFE expresión IF1 IF2 IFDEF símbolo IFNDEF símbolo IFB
(expresión igual a cero) (pasada 1 del ensamblador) (pasada 2 del ensamblador) (símbolo definido o declarado como externo) (símbolo ni definido ni declarado como externo) (argumento en blanco en macros -incluir '<' y '>'-
IFNB
(lo contrario, también es obligado poner '<' y
IFIDN IFDIF
, ,
(arg1 idéntico a arg2, requiere '<' y '>') (arg1 distinto de arg2, requiere '<' y '>')
) '>')
5.3.8. - DIRECTIVAS DE LISTADO. PAGE num_lineas, num_columnas: Formatea el listado de salida; por defecto son 66 líneas por página (modificable entre 10 y 255) y 80 columnas (seleccionable de 60 a 132). PAGE salta de página e incrementa su número. «PAGE +» indica capítulo nuevo (y se incrementa el número). TITLE título: indica el título que aparece en la 1ª línea de cada página (máximo 60 caracteres). SUBTTL subtítulo: Ídem con el subtítulo (máx. 60 caracteres). .LALL: Listar las macros y sus expansiones. .SALL: No listar las macros ni sus expansiones. .XALL: Listar sólo las macros que generan código objeto. .XCREF: Suprimir listado de referencias cruzadas (listado alfabético de símbolos junto al nº de línea en que son definidos y referenciados, de cara a facilitar la depuración). .CREF: Restaurar listado de referencias cruzadas. .XLIST: Suprimir el listado ensamblador desde ese punto. .LIST: Restaurar de nuevo la salida de listado ensamblador. COMMENT delimitador comentario delimitador: Define un comentario que puede incluso ocupar varias líneas, el delimitador (primer carácter no blanco ni tabulador que sigue al COMMENT) indica el inicio e indicará más tarde el final del comentario. ¡No olvidar cerrar el comentario!. %OUT mensaje: escribe en la consola el mensaje indicado durante la fase de ensamblaje y al llegar a ese punto del listado, excepto cuando el listado es por pantalla y no en fichero. .LFCOND: Listar los bloques de código asociados a una condición falsa (IF). .SFCOND: suprimir dicho listado. .TFCOND: Invertir el modo vigente de listado de los bloques asociados a una condición falsa. 5.4. - MACROS. Cuando un conjunto de instrucciones en ensamblador aparecen frecuentemente repetidas a lo largo de un listado, es conveniente agruparlas bajo un nombre simbólico que las sustituirá en aquellos puntos donde
71
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
aparezcan. Esta es la misión de las macros; por el hecho de soportarlas el ensamblador eleva su categoría a la de macroensamblador, al ser las macros una herramienta muy cotizada por los programadores. No conviene confundir las macros con subrutinas: es estas últimas, el conjunto de instrucciones aparece una sola vez en todo el programa y luego se invoca con CALL. Sin embargo, cada vez que se referencia a una macro, el código que ésta representa se expande en el programa definitivo, duplicándose tantas veces como se use la macro. Por ello, aquellas tareas que puedan ser realizadas con subrutinas siempre será más conveniente realizarlas con las mismas, con objeto de economizar memoria. Es cierto que las macros son algo más rápidas que las subrutinas (se ahorra un CALL y un RET) pero la diferencia es tan mínima que en la práctica es despreciable en el 99,99% de los casos. Por ello, es absurdo e irracional realizar ciertas tareas con macros que pueden ser desarrolladas mucho más eficientemente con subrutinas: es una pena que en muchos manuales de ensamblador aún se hable de macros para realizar operaciones sobre cadenas de caracteres, que generarían programas gigantescos con menos de un 1% de velocidad adicional. 5.4.1. - DEFINICIÓN Y BORRADO DE LAS MACROS. La macro se define por medio de la directiva MACRO. Es necesario definir la macro antes de utilizarla. Una macro puede llamar a otra. Con frecuencia, las macros se colocan juntas en un fichero independiente y luego se mezclan en el programa principal con la directiva INCLUDE: IF1 INCLUDE fichero.ext ENDIF
La sentencia IF1 asegura que el ensamblador lea el fichero fuente de las macros sólo en la primera pasada, para acelerar el ensamblaje y evitar que aparezcan en el listado (generado en la segunda fase). Conviene hacer hincapié en que la definición de la macro no consume memoria, por lo que en la práctica es indiferente declarar cientos que ninguna macro: nombre_simbólico MACRO [parámetros] ... ... ; instrucciones de la macro ENDM
El nombre simbólico es el que permitirá en adelante hacer referencia a la macro, y se construye casi con las mismas reglas que los nombres de las variables y demás símbolos. La macro puede contener parámetros de manera opcional. A continuación vienen las instrucciones que engloba y, finalmente, la directiva ENDM señala el final de la macro. No se debe repetir el nombre simbólico junto a la directiva ENDM, ello provocaría un error un tanto curioso y extraño por parte del ensamblador (algo así como «Fin del fichero fuente inesperado, falta directiva END»), al menos con MASM 5.0 y TASM 2.0. En realidad, y a diferencia de lo que sucede con los demás símbolos, el nombre de una macro puede coincidir con el de una instrucción máquina o una directiva del ensamblador: a partir de ese momento, la instrucción o directiva machacada pierde su significado original. El ensamblador dará además un aviso de advertencia si se emplea una instrucción o directiva como nombre de macro, aunque tolerará la operación. Normalmente se las asignará nombres normales, como a las variables. Sin embargo, si alguna vez se redefiniera una instrucción máquina o directiva, para restaurar el significado original del símbolo, la macro puede ser borrada -o simplemente porque ya no va a ser usada a partir de cierto punto del listado, y así ya no consumirá espacio en las tablas de macros que mantiene en memoria el ensamblador al ensamblar-. No es necesario borrar las macros antes de redefinirlas. Para borrarlas, la sintaxis es la siguiente: PURGE nombre_simbólico[,nombre_simbólico,...] 5.4.2. - EJEMPLO DE UNA MACRO SENCILLA. Desde el 286 existe una instrucción muy cómoda que introduce en la pila 8 registros, y otra que los saca
71
EL LENGUAJE ENSAMBLADOR DEL 80x86
(PUSHA y POPA). Quien esté acostumbrado a emplearlas, puede crear unas macros que simulen estas instrucciones en los 8086: SUPERPUSH
MACRO PUSH PUSH PUSH PUSH PUSH PUSH PUSH PUSH ENDM
AX CX DX BX SP BP SI DI
La creación de SUPERPOP es análoga, sacando los registros en orden inverso. El orden elegido no es por capricho y se corresponde con el de la instrucción PUSHA original, para compatibilizar. A partir de la definición de esta macro, tenemos a nuestra disposición una nueva instrucción máquina (SUPERPUSH) que puede ser usada con libertad dentro de los programas. 5.4.3. - PARÁMETROS FORMALES Y PARÁMETROS ACTUALES. Para quien no haya tenido relación previa con algún lenguaje estructurado de alto nivel, haré un breve comentario acerca de lo que son los parámetros formales y actuales en una macro, similar aquí a los procedimientos de los lenguajes de alto nivel. Cuando se llama a una macro se le pueden pasar opcionalmente un cierto número de parámetros de cierto tipo. Estos parámetros se denominan parámetros actuales. En la definición de la macro, dichos parámetros aparecen asociados a ciertos nombres arbitrarios, cuya única misión es permitir distinguir unos parámetros de otros e indicar en qué orden son entregados: son los parámetros formales. Cuando el ensamblador expanda la macro al ensamblar, los parámetros formales serán sustituidos por sus correspondientes parámetros actuales. Considerar el siguiente ejemplo: SUMAR
MACRO a,b,total PUSH AX MOV AX,a ADD AX,b MOV total,AX POP AX ENDM .... SUMAR positivos, negativos, total
En el ejemplo, «a», «b» y «total» son los parámetros formales y «positivos», «negativos» y «total» son los parámetros actuales. Tanto «a» como «b» pueden ser variables, etiquetas, etc. en otro punto del programa; sin embargo, dentro de la macro, se comportan de manera independiente. El parámetro formal «total» ha coincidido en el ejemplo y por casualidad con su correspondiente actual. El código que genera el ensamblador al expandir la macro será el siguiente: PUSH MOV ADD MOV POP
AX AX,positivos AX,negativos total,AX AX
Las instrucciones PUSH y POP sirven para no alterar el valor de AX y conseguir que la macro se comporte como una caja negra; no es necesario que esto sea así pero es una buena costumbre de programación para evitar que los programas hagan cosas raras. En general, las macros de este tipo no deberían alterar los
71
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
registros y, si los cambian, hay que tener muy claro cuáles. Si se indican más parámetros de los que una macro necesita, se ignorarán los restantes. En cambio, si faltan, el MASM asumirá que son nulos (0) y dará un mensaje de advertencia, el TASM es algo más rígido y podría dar un error. En general, se trata de situaciones atípicas que deben ser evitadas. También puede darse el caso de que no sea posible expandir la macro. En el ejemplo, no hubiera sido posible ejecutar SUMAR AX,BX,DL porque DL es de 8 bits y la instrucción MOV DL,AX sería ilegal. 5.4.4. - ETIQUETAS DENTRO DE MACROS. VARIABLES LOCALES. Son necesarias normalmente para los saltos condicionales que contengan las macros más complejas. Si se pone una etiqueta a donde saltar, la macro sólo podría ser empleada una vez en todo el programa para evitar que dicha etiqueta aparezca duplicada. La solución está en emplear la directiva LOCAL que ha de ir colocada justo después de la directiva MACRO: MINIMO
ya_esta:
MACRO LOCAL MOV CMP JB MOV MOV ENDM
dato1, dato2, ya_esta AX,dato1 AX,dato2 ya_esta AX,dato2 resultado,AX
resultado
; ¿es dato1 el menor? ; sí ; no, es dato2
En el ejemplo, al invocar la macro dos veces el ensamblador no generará la etiqueta «ya_esta» sino las etiquetas ??0000, ??0001, ... y así sucesivamente. La directiva LOCAL no sólo es útil para los saltos condicionales en las macros, también permite declarar variables internas a los mismos. Se puede indicar un número casi indefinido de etiquetas con la directiva LOCAL, separándolas por comas. 5.4.5. - OPERADORES DE MACROS. Operador ;; Indica que lo que viene a continuación es un comentario que no debe aparecer al expansionar la macro. Cuando al ensamblar se genera un listado del programa, las macros suelen aparecer expandidas en los puntos en que se invocan; sin embargo sólo aparecerán los comentarios normales que comiencen por (;). Los comentarios relacionados con el funcionamiento interno de la macro deberían ir con (;;), los relativos al uso y sintaxis de la misma con (;). Esto es además conveniente porque durante el ensamblaje son mantenidos en memoria los comentarios de macros (no los del resto del programa) que comienzan por (;), y no conviene desperdiciar memoria... Operador & Utilizado para concatenar texto o símbolos. Es necesario para lograr que el ensamblador sustituya un parámetro dentro de una cadena de caracteres o como parte de un símbolo: SALUDO
MACRO MOV etiqueta&c: CALL ENDM
c AL,"&c" imprimir
Al ejecutar SALUDO A se producirá la siguiente expansión:
etiquetaA:
MOV CALL
AL,"A" imprimir
Si no se hubiera colocado el & se hubiera expandido como MOV AL,"c"
71
EL LENGUAJE ENSAMBLADOR DEL 80x86
Cuando se utilizan estructuras repetitivas REPT, IRP o IRPC (que se verán más adelante) existe un problema adicional al intentar crear etiquetas, ya que el ensamblador se come un & al hacer la primera sustitución, generando la misma etiqueta a menos que se duplique el operador &: MEMORIA x&i
MACRO IRP DB ENDM ENDM
x i, <1, 2> i
Si se invoca MEMORIA ET se produce el error de "etiqueta ETi repetida", que se puede salvar añadiendo tantos '&' como niveles de anidamiento halla en las estructuras repetitivas empleadas, como se ejemplifica a continuación: MEMORIA x&&i
MACRO IRP DB ENDM ENDM
x i, <1, 2> i
Lo que con MEMORIA ET generará correctamente las líneas: ET1 ET2
DB 1 DB 2
Operador ! o <> Empleado para indicar que el carácter que viene a continuación debe ser interpretado literalmente y no como un símbolo. Por ello, !; es equivalente a <;>. Operador % Convierte la expresión que le sigue -generalmente un símbolo- a un número; la expresión debe ser una constante (no relocalizable). Sólo se emplea en los argumentos de macros. Dada la macro siguiente: PSUM
MACRO %OUT ENDM
mensaje, suma * mensaje, suma *
(Evidentemente, el % que precede a OUT forma parte de la directiva y no se trata del % operador que estamos tratando) Supuesta la existencia de estos símbolos: SIM1 SIM2
EQU EQU
120 500
Invocando la macro con las siguientes condiciones: PSUM
< SIM1 + SIM2 = >, (SIM1+SIM2)
Se produce la siguiente expansión: %OUT * SIM1 + SIM2 = (SIM1+SIM2) *
Sin embargo, invocando la macro de la siguiente manera (con %): PSUM < SIM1 + SIM2 = >, %(SIM1+SIM2)
71
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Se produce la expansión deseada: %OUT * SIM1 + SIM2 = 620 *
5.4.6. - DIRECTIVAS ÚTILES PARA MACROS. Estas directivas pueden ser empleadas también sin las macros, aumentando la comodidad de la programación, aunque abundan especialmente dentro de las macros. REPT veces ... ENDM (Repeat) Permite repetir cierto número de veces una secuencia de instrucciones. El bloque de instrucciones se delimita con ENDM (no confundirlo con el final de una macro). Por ejemplo: REPT OUT ENDM
2 DX,AL
Esta secuencia se transformará, al ensamblar, en lo siguiente: OUT OUT
DX,AL DX,AL
Empleando símbolos definidos con (=) y apoyándose además en las macros se puede llegar a crear pseudo-instrucciones muy potentes: SUCESION
MACRO n num = 0 REPT n DB num num = num + 1 ENDM ENDM
; fin de REPT ; fin de macro
La sentencia SUCESION 3 provocará la siguiente expansión: DB DB DB
0 1 2
IRP simbolo_control, ... ENDM (Indefinite repeat) Es relativamente similar a la instrucción FOR de los lenguajes de alto nivel. Los ángulos (<) y (>) son obligatorios. El símbolo de control va tomando sucesivamente los valores (no necesariamente numéricos) arg1, arg2, ... y recorre en cada pasada todo el bloque de instrucciones hasta alcanzar el ENDM (no confundirlo con fin de macro) sustituyendo simbolo_control por esos valores en todos los lugares en que aparece: IRP DB ENDM
i, <1,2,3> 0, i, i*i
Al expansionarse, este conjunto de instrucciones se convierte en lo siguiente: DB DB
0, 1, 1 0, 2, 4
71
EL LENGUAJE ENSAMBLADOR DEL 80x86
DB
0, 3, 9
Nota:Todo lo encerrado entre los ángulos se considera un único parámetro. Un (;) dentro de los ángulos no se interpreta como el inicio de un comentario sino como un elemento más. Por otra parte, al emplear macros anidadas, deben indicarse tantos símbolos angulares '<' y '>' consecutivos como niveles de anidamiento existan. Lógicamente, dentro de una macro también resulta bastante útil la estructura IRP: TETRAOUT
MACRO PUSH PUSH MOV IRP MOV OUT ENDM POP POP ENDM
p1, p2, p3, p4, valor AX DX AL,valor cn, DX, cn DX, AL ; fin de IRP DX AX ; fin de macro
Al ejecutar TETRAOUT 318h, 1C9h, 2D1h, 1A4h, 17 se obtendrá: PUSH PUSH MOV MOV OUT MOV OUT MOV OUT MOV OUT POP POP
AX DX AL, 17 DX, 318h DX, AL DX, 1C9h DX, AL DX, 2D1h DX, AL DX, 1A4h DX,AL DX AX
Cuando se pasan listas como parámetros hay que encerrarlas entre '<' y '>' al llamar, para no confundirlas con elementos independientes. Por ejemplo, supuesta la macro INCD: INCD
MACRO IRP INC ENDM DEC ENDM
lista, p i, i ; fin de IRP p ; fin de macro
Se comprende la necesidad de utilizar los ángulos: INCD AX, BX, CX, DX se expandirá: INC DEC
AX BX ; CX y DX se ignoran (4 parámetros)
INCD , DX se expandirá: INC INC
AX BX
71
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
INC DEC
CX DX
; (2 parámetros)
IRPC simbolo_control, ... ENDM (Indefinite repeat character) Esta directiva es similar a la anterior, con una salvedad: los elementos situados entre los ángulos (<) y (>) -ahora opcionales, por cierto- son caracteres ASCII y no van separados por comas: IRPC DB ENDM
i, <813> i
El bloque anterior generará al expandirse: DB DB DB
8 1 3
Ejemplo de utilización dentro de una macro (en combinación con el operador &): INICIALIZA
MACRO IRPC DB ENDM ENDM
a, b, c, d iter, <&a&b&c&d> iter ; fin de IRPC ; fin de macro
Al ejecutar INICIALIZA 7, 1, 4, 0 se produce la siguiente expansión: DB DB DB DB
7 1 4 0
EXITM Sirve para abortar la ejecución de un bloque MACRO, REPT, IRP ó IRPC. Normalmente se utiliza apoyándose en una directiva condicional (IF...ELSE...ENDIF). Al salir del bloque, se pasa al nivel inmediatamente superior (que puede ser otro bloque de estos). Como ejemplo, la siguiente macro reserva n bytes de memoria a cero hasta un máximo de 100, colocando un byte 255 al final del bloque reservado: MALLOC
MACRO n maximo=100 REPT n IF maximo EQ 0 ; ¿ya van 100? EXITM ; abandonar REPT ENDIF maximo = maximo - 1 DB 0 ; reservar byte ENDM DB 255 ; byte de fin de bloque ENDM
5.4.7. - MACROS AVANZADAS CON NUMERO VARIABLE DE PARÁMETROS. Como se vio al estudiar la directiva IF, existe la posibilidad de chequear condicionalmente la presencia de un parámetro por medio de IFNB, o su ausencia con IFB. Uniendo esto a la potencia de IRP es posible crear macros extraordinariamente versátiles. Como ejemplo, valga la siguiente macro, destinada a introducir en la pila un número variable de parámetros (hasta 10): es especialmente útil en los programas que gestionan
71
EL LENGUAJE ENSAMBLADOR DEL 80x86
interrupciones: XPUSH
MACRO R1,R2,R3,R4,R5,R6,R7,R8,R9,R10 IRP reg, IFNB PUSH reg ENDIF ENDM ; fin de IRP ENDM ; fin de XPUSH
Por ejemplo, la instrucción: XPUSH
AX,BX,DS,ES,VAR1
PUSH PUSH PUSH PUSH PUSH
AX AX DS ES VAR1
Se expandirá en:
El ejemplo anterior es ilustrativo del mecanismo de comprobación de presencia de parámetros. Sin embargo, este ejemplo puede ser optimizado notablemente empleando una lista como único parámetro: XPUSH
MACRO lista IRP i, PUSH i ENDM ENDM
XPOP
MACRO lista IRP i, POP i ENDM ENDM
La ventaja es el número indefinido de parámetros soportados (no sólo 10). Un ejemplo de uso puede ser el siguiente: XPUSH XPOP
PUSH PUSH PUSH POP POP POP
AX BX CX CX BX AX
Que al expandirse queda:
5.5. - PROGRAMACIÓN MODULAR Y PASO DE PARÁMETROS. Aunque lo que viene a continuación no es indispensable para programar en ensamblador, sí es conveniente leerlo en 2 ó 3 minutos para observar ciertas reglas muy sencillas que ayudarán a hacer programas seguros y eficientes. Sin embargo, personalmente considero que cada uno es muy libre de hacer lo que desee; por otra parte, en muchos casos no se pueden cumplir los principios de la programación elegante especialmente en ensamblador- por lo que detesto aquellos profesionales de la informática que se entrometen con la manera de programar de sus colegas o alumnos, obligándolos a hacer las cosas a su gusto. La programación modular consiste en dividir los problemas más complejos en módulos separados con unas ciertas interdependencias, lo que reduce el tiempo de programación y aumenta la fiabilidad del código. Se
71
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
pueden implementar en ensamblador con las directivas PROC y ENDP que, aunque no generan código son bastante útiles para dejar bien claro dónde empieza y acaba un módulo. Reglas para la buena programación: - Dividir los problemas en módulos pequeños relacionados sólo por un conjunto de parámetros de entrada y salida. - Una sola entrada y salida en cada módulo: un módulo sólo debe llamar al inicio de otro (con CALL) y éste debe retornar al final con un único RET, no debiendo existir más puntos de salida y no siendo recomendable alterar la dirección de retorno. - Excepto en los puntos en que la velocidad o la memoria son críticas (la experiencia demuestra que son menos del 1%) debe codificarse el programa con claridad, si es preciso perdiendo eficiencia. Ese 1% documentarlo profusamente como se haría para que lo lea otra persona. - Los módulos han de ser «cajas negras» y no deben modificar el entorno exterior. Esto significa que no deben actuar sobre variables globales ni modificar los registros (excepto aquellos registros y variables en que devuelven los resultados, lo que debe documentarse claramente al principio del módulo). Tampoco deben depender de ejecuciones anteriores, salvo excepciones en que la propia claridad del programa obligue a lo contrario (por ejemplo, los generadores de números aleatorios pueden depender de la llamada anterior). Para el paso de parámetros entre módulos existen varios métodos que se exponen a continuación. Los parámetros pueden pasarse además de dos maneras: directamente por valor, o bien indirectamente por referencia o dirección. En el primer caso se envía el valor del parámetro y en el segundo la dirección inicial de memoria a partir de la que está almacenado. El tipo de los parámetros habrá de estar debidamente documentado al principio de los módulos. - Paso de parámetros en los registros: Los módulos utilizan ciertos registros muy concretos para comunicarse. Todos los demás registros han de permanecer inalterados, por lo cual, si son empleados internamente, han de ser preservados al principio del módulo y restaurados al final. Este es el método empleado por el DOS y la BIOS en la mayoría de las ocasiones para comunicarse con quien los llama. Los registros serán preservados preferiblemente en la pila (con PUSH) y recuperados de la misma (con POP en orden inverso); de esta manera, los módulos son reentrantes y pueden ser llamados de manera múltiple soportando, entre otras características, la recursividad (sin embargo, se requerirá también que las variables locales se generen sobre la pila). - Paso de parámetros a través de un área común: se utiliza una zona de memoria para la comunicación. Este tipo de módulos no son reentrantes y hasta que no acaben de procesar una llamada no se les debe llamar de nuevo en medio de la faena. - Paso de parámetros por la pila. En este método, los parámetros son apilados antes de llamar al módulo que los va a recoger. Este debe conocer el número y tamaño de los mismos, para equilibrar el puntero de pila al final antes de retornar (método de los compiladores de lenguaje Pascal) o en caso contrario el programa que llama deberá encargarse de esta operación (lenguaje C). La ventaja del paso de parámetros por la pila es el prácticamente ilimitado número de parámetros admitido, de cómodo acceso, y que los módulos siguen siendo reentrantes. Un ejemplo puede ser el siguiente: dato
LABEL
DWORD
datoL
DW
?
datoH
DW
?
⋅⋅⋅ PUSH
datoL
PUSH
datoH
CALL
moduloA
; llamada
ADD
SP,4
; equilibrar pila
⋅⋅⋅
; apilar parámetros
71
EL LENGUAJE ENSAMBLADOR DEL 80x86
moduloA
PROC
NEAR
PUSH
BP
MOV
BP,SP
MOV
DX,[BP+4]
; parte alta del dato
MOV
AX,[BP+6]
; parte baja del dato
⋅⋅⋅ POP
BP
RET moduloA
ENDP
En el ejemplo, tenemos la variable dato de 32 bits dividida en dos partes de 16. Dicha variable es colocada en la pila empezando por la parte menos significativa. A continuación se llama a MODULOA, el cual comienza por preservar BP (lo usará posteriormente) para respetar la norma de caja negra. Se carga BP con SP debido a que el 8086 no permite el direccionamiento indexado sobre SP. Como la instrucción CALL se dirige a una dirección cercana (NEAR), en la pila se almacena sólo el registro IP. Por tanto, en [BP+0] está el BP del programa que llama, en [BP+2] el registro IP del programa que llama y en [BP+4] y [BP+6] la variable enviada, que es el caso más complejo (variables de 32 bits). Dicha variable es cargada en DX:AX antes de proceder a usarla (también deberían apilarse AX y DX para conservar la estructura de caja negra). Al final, se retorna con RET y el programa principal equilibra la pila aumentando SP en 4 unidades para compensar el apilamiento previo de dos palabras antes de llamar. Si MODULOA fuera un procedimiento lejano (FAR) la variable estaría en [BP+6] y [BP+8], debido a que al llamar al módulo se habría guardado también en la pila el CS del programa que llama. El lenguaje Pascal hubiera retornado con RET 4, haciendo innecesario que el programa que llama equilibre la pila. Sin embargo, el método del lenguaje C expuesto es más eficiente porque no requiere que el módulo llamado conozca el número de parámetros que se le envían: éste puede ser variable (de hecho, el C apila los parámetros antes de llamar en orden inverso, empezando por el último: de esta manera se accede correctamente a los primeros N parámetros que se necesiten).
EL ENSAMBLADOR EN ENTORNO DOS
91
Capítulo VI: EL ENSAMBLADOR EN ENTORNO DOS
6.1. - TIPOS DE PROGRAMAS EJECUTABLES BAJO DOS. Antes de que el COMMAND.COM pase el control al programa que se pretende ejecutar, se crea un bloque de 256 bytes llamado PSP (Program Segment Prefix), cuya descripción detallada se verá en el próximo capítulo. En él aparecen datos tales como la dirección de retorno al dos cuando finalice el programa, la dirección de retorno en caso de Ctrl-Break y en caso de errores críticos. Además de la cantidad de memoria disponible y los posibles parámetros suministrados del programa. Cuando el programa toma el control, DS y ES apuntan al PSP. Tipos de programas: En los de tipo COM: - CS apunta al PSP e IP=100h (el programa empieza tras el PSP). - SS apunta al PSP y SP toma la dirección más alta dentro del segmento del PSP. En los de tipo EXE: - CS e IP toman los valores del punto de arranque del programa (directiva END etiqueta). - SS apunta al segmento de pila y SP = tamaño de la pila definida. Si el programa es COM podemos terminarlo con la interrupción 20h (INT 20h), o simplemente con un RET si la pila no está desequilibrada (apunta a un INT 20h que hay en la posición 0 del PSP); otra manera de acabar es por medio de la función 4Ch del sistema (disponible desde el DOS 2.0) que acaba cualquier programa sin problemas y sin ningún tipo de requerimientos adicionales, tanto COM como EXE. Los programas de tipo COM se cargan en memoria tal y como están en disco, entregándoseles el control. Los de tipo EXE, que pueden llegar a manejar múltiples segmentos de código de hasta 64 Kb, se almacenan en disco «semiensamblados». En realidad, al ser cargados en memoria, el DOS tiene que realizar la última fase de montaje, calculando las direcciones de memoria absolutas. Por ello, estos programas tienen un formato especial en disco, generado por los ensambladores y compiladores, y su imagen en memoria no se corresponde realmente con lo que está grabado en el disco, aunque esto al usuario no le importe. Por ello, no se extrañe el lector de haber visto alguna vez ficheros EXE de más de 640 Kb: evidentemente, no se cargan enteros en memoria aunque lo parezca. Los programas COM no hacen referencias a datos o direcciones separados más de 64 Kb, por lo que todos los saltos y desplazamientos son relativos a los registros de segmento (no se cambia CS ni DS) con lo que no es necesaria la fase de «montaje». No obstante, un programa COM puede hacer lo que le de la gana con los registros de segmento y acceder a más de 64 Kb de memoria, por cuenta y riesgo del programador. En general, la programación en ensamblador está hoy en día relegada a pequeños programas residentes, controladores de dispositivos o rutinas de apoyo a programas hechos en otros lenguajes, por lo que no es estrictamente necesario trabajar con programas EXE realizados en ensamblador. Salvo excepciones, la mayoría de los programas desarrollados en este libro serán de tipo COM ya que los EXE ocuparían algo más, aunque el ensamblador da algo más de comodidad al programador en los mismos. 6.2. - EJEMPLO DE PROGRAMA DE TIPO COM. El siguiente ejemplo escribe una cadena en pantalla llamando a uno de los servicios estándar de impresión del DOS (función 9 de INT 21h): ┌───────────────────────────────────────────────────────────────────────┐
91
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
│ │ │ cr EQU 13 ; constante de retorno de carro ├─┐ │ lf EQU 10 ; constante de salto de línea │ │ │ │ │ │ programa SEGMENT ; segmento común a CS, DS, ES, SS. │ │ │ │ │ │ ASSUME CS:programa, DS:programa │ │ │ │ │ │ ORG 100h ; programa de tipo COM │ │ │ │ │ │ inicio: LEA DX,texto ; dirección de texto a imprimir │ │ │ MOV AH,9 ; función de impresión │ │ │ INT 21h ; llamar al DOS │ │ │ INT 20h ; volver al sistema operativo │ │ │ │ │ │ texto DB cr,lf,"Grupo Universitario de Informática.",cr,lf,"$" │ │ │ │ │ │ programa ENDS ; fin del segmento │ │ │ │ │ │ END inicio ; fin del programa y punto de inicio │ │ │ │ │ └─┬─────────────────────────────────────────────────────────────────────┘ │ │ Programa tipo COM │ └───────────────────────────────────────────────────────────────────────┘
Olvidándonos de los comentarios que comienzan por «;», en las primeras lineas las directivas EQU definen dos constantes para el preprocesador del compilador: cr=13 y lf=10. El programa, de tipo COM, consta de un único segmento. La directiva ASSUME indica que, por defecto, las instrucciones máquina se ensamblarán para el registro CS en este segmento (lo más lógico, por otra parte); también conviene asumir el registro DS, de lo contrario, si hubiera que acceder a una variable, el ensamblador añadiría el prefijo del segmento CS a la instrucción al no estar seguro de que DS apunta a los datos, consumiendo más memoria. Se pueden añadir los demás registros de segmento en el ASSUME, aunque es redundante. El ORG 100h es obligatorio en programas COM, ya que estos programas serán cargados en memoria en la posición CS:100h. Al final, la dirección del texto a imprimir se coloca en DS:DX (CS=DS=ES=SS en un programa COM recién ejecutado) y se llama al DOS. El carácter '$' delimita la cadena a imprimir, lo cual es una herencia del CP/M (sería más interesante que fuera el 0 el delimitador) por razones históricas. Se acaba el programa con INT 20h. El punto de arranque es indicado con la directiva END, aunque en realidad en los programas COM el punto indicado (en el ejemplo, «inicio») debe estar forzosamente al principio del programa. Obsérvese que no se genera código hasta llegar a la línea «inicio:», todo lo anterior son directivas. 6.3. - EJEMPLO DE PROGRAMA DE TIPO EXE. Los programas EXE (listado en la página siguiente) requieren algo más de elaboración. En primer lugar, es necesario definir una pila y reservar espacio para la misma. Al contrario que los programas COM (cuya pila se sitúa al final del segmento compartido también con el código y los datos) esta característica obliga a definir un tamaño prudente en función de las necesidades del programa. Téngase en cuenta que en la pila se almacenan las direcciones de retorno de las subrutinas y al llamar a una función de la BIOS la pila es usada con intensidad. En general, con medio kilobyte basta para programas tan sencillos como el del ejemplo, e incluso para otros mucho más complejos. El límite máximo está en 64 Kb. El segmento de pila se nombra siempre STACK y con el TLINK de Borland es necesario indicar también la clase 'STACK'. Como se ve, son definidos por separado el segmento de código, pila y datos, lo que también ayuda a estructurar más el programa. El segmento de código se define como procedimiento FAR, entre otras razones para que el ensamblador ensamble el RET del final (con el que se vuelve al DOS) como un RETF. La directiva ASSUME asocia cada registro de segmento con su correspondiente segmento. Como puede observarse al principio del programa, es necesario preparar «a mano» la dirección de retorno al sistema. El PUSH DS del
EL ENSAMBLADOR EN ENTORNO DOS
91
principio coloca el segmento del PSP en la pila; el XOR AX,AX coloca un cero en AX (esta instrucción gasta un byte menos que MOV AX,0) y el PUSH AX mete ese 0 en la pila. Con ello, al volver al DOS con RET (RETF en realidad) el control pasará a DS:0, esto es, a la primera instrucción del PSP (INT 20h). Aunque pueda parecer un tanto lioso, es un juego de niños y estas tres instrucciones consecutivas (PUSH DS / XOR AX,AX / PUSH AX) son la manera de empezar de cientos de programas EXE, que después acaban con RET. En general, a partir del DOS 2.0 es más aconsejable terminar el programa con la función 4Ch del DOS, que no requiere que CS apunte al PSP ni precisa de preparación alguna en la pila y además permite retornar un código de ERRORLEVEL en AL: en los programas futuros esto se hará con bastante frecuencia. También debe observarse cómo se inicializa DS, ya que en los programas EXE por defecto no apunta a los datos. Ahora puede preguntarse el lector, por curiosidad, ¿qué valdrá «datos»?: datos tiene un valor relativo asignado por el ensamblador; cuando el programa sea cargado en memoria, en el proceso de montaje y en función de cuál sea la primera posición de memoria libre, se le asignará un valor determinado por el montador del sistema operativo. ┌───────────────────────────────────────────────────────────────────────┐ │ │ │ cr EQU 13 ├─┐ │ lf EQU 10 │ │ │ │ │ │ ; Segmento de datos │ │ │ │ │ │ datos SEGMENT │ │ │ texto DB cr,lf,"Texto a imprimir",cr,lf,"$" │ │ │ datos ENDS │ │ │ │ │ │ ; Segmento de pila │ │ │ │ │ │ pila SEGMENT STACK 'STACK' ; poner STACK es obligatorio │ │ │ DB 128 dup ('pila') ; reservados 512 bytes │ │ │ pila ENDS │ │ │ │ │ │ ; Segmento de código │ │ │ │ │ │ codigo SEGMENT │ │ │ ejemplo PROC FAR │ │ │ ASSUME CS:codigo, DS:datos, SS:pila │ │ │ │ │ │ ; poner dirección de retorno al DOS en la pila: │ │ │ │ │ │ PUSH DS ; segmento del PSP │ │ │ XOR AX,AX ; AX = 0 │ │ │ PUSH AX ; desplazamiento 0 al PSP │ │ │ │ │ │ ; direccionar segmento de datos con DS │ │ │ │ │ │ MOV AX,datos ; AX = dirección del segmento de datos │ │ │ MOV DS,AX ; inicializar DS │ │ │ │ │ │ ; escribir texto │ │ │ │ │ │ LEA DX,texto ; DS:DX = dirección del texto │ │ │ MOV AH,9 │ │ │ INT 21h │ │ │ │ │ │ ; volver al DOS │ │ │ │ │ │ RET ; en realidad, RETF (PROC FAR) │ │
91
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
│ │ │ │ ejemplo ENDP │ │ │ │ │ │ codigo ENDS ; fin del código │ │ │ END ejemplo ; punto de arranque del programa │ │ │ │ │ └─┬─────────────────────────────────────────────────────────────────────┘ │ │ Programa EXE │ └───────────────────────────────────────────────────────────────────────┘
6.4. - PROCESO DE ENSAMBLAJE. 6.4.1. - TASM/MASM. Es el programa que convierte nuestro listado fuente en código objeto, es decir, lenguaje máquina en el que sólo faltan las referencias a rutinas externas. Permite la obtención de listados de código y de referencias cruzadas (símbolos, etiquetas, variables). En general, bastará con hacer TASM nombre_programa (se supone la extensión .ASM por defecto). El fichero final tiene extensión OBJ. En general, la sintaxis del TASM y MASM es más o menos equivalente: en el primero se obtiene ayuda con /H y en el segundo con /HELP. Con TASM, cuando se va a obtener la versión definitiva del programa, o si éste es corto -o el ordenador rápido- merece la pena utilizar el parámetro /m3, con objeto de que de dos/tres pasadas y optimize más el código. Por su lado, MASM presenta estadísticas adicionales si se indica /v y se puede cambiar con /Btamaño el nº de Kb de memoria que destina al fichero fuente, entre 1 y 63. La sintaxis es (tanto para TASM como MASM): TASM fichero_fuente, fichero_listado, fichero_referencias_cruzadas Se puede omitir el fichero de listado y el de referencias cruzadas. Cuando se emplea MASM 6.X, para ensamblar los listados de este libro hay que indicar la opción /Zm para mantener la compatibilidad con las versiones anteriores del ensamblador, siendo además obligatorio indicar la extensión; como se genera directamente el fichero EXE hay que indicar /c si se desea evitar esto (si no se quiere que linke). La sintaxis quedaría: ML /Zm fihero_fuente.asm A continuación se listan los parámetros comunes a TASM 2.0 (y posterior) y MASM 4.0/5.0 (NO la 6.X): /a y /s Seleccionan un orden alfabético o secuencial de los segmentos. /cGenera un listado de referencias cruzadas en un fichero de extensión CRF listo para ser procesado por CREF (MASM) añadiendo además números de línea al listado, o bien incluye el listado de referencias cruzadas directamente dentro del listado del programa (caso de TASM). Las referencias cruzadas son un listado de todos los símbolos del programa, indicando los números de línea del mismo en que son definidos y referenciados. /DDe la manera /Dsímbolo[=valor] permite crear el símbolo indicado, cuya presencia puede comprobarse en el programa con una directiva IF (es útil para definir externamente un símbolo que indique que el programa está en fase de depuración, de cara a ensamblar cierto código adicional). Aunque /d (en minúsculas) es un obsoleto parámetro de MASM para obtener un listado de la primera pasada del ensamblador, MASM 4.0 es capaz de darse cuenta de que se pretende definir un símbolo con /d a menos que se indique solo /d. /e Emula las instrucciones de punto flotante del 80x87, apoyándose en una librería al efecto. /IrutaPermite indicar el directorio donde el ensamblador debe de buscar los ficheros indicados en el programa fuente con INCLUDE. /l[a] Con /l se genera un listado de ensamblaje y con /la un listado expandido. /mCon /m se indica el nivel de preservación del sentido de mayúsculas y minúsculas en los símbolos: /ml hace que se consideres diferentes mayúsculas de minúsculas en todos los símbolos, /mx sólo con los símbolos globales y /mu hace que se mayusculicen todos los símbolos globales. Al ensamblar módulos para usar desde lenguaje C hay que indicar por lo menos /mx. En MASM 6.X se emplea /Cx en lugar de /mx, /Cp en lugar de /ml y /Cu en vez de /mu. /n Suprime las tablas de símbolos en el listado. /pVerifica que el código generado para el modo protegido es correcto (al emplear la directiva para generar instrucciones de modo protegido). /t Suprime los mensajes si el ensamblaje es correcto. /w Indica el nivel de advertencias: /w0 ninguna, /w1 sólo las serias y /w2 sólo consejos.
EL ENSAMBLADOR EN ENTORNO DOS
/X /z /Zi /Zd
91
Lista las condiciones falsas (ensamblaje condicional). Visualiza la línea del error y no sólo el número de la misma. Genera información simbólica para los depuradores de código. Incluye sólo la información del número de línea.
6.4.2. - TLINK/LINK. El montador o linkador permite combinar varios módulos objeto, realizando las conexiones entre ellos y, finalmente, los convierte en módulo ejecutable de tipo EXE (empleando el ML de MASM 6.X se obtiene directamente el fichero EXE ya que invoca automáticamente al linkador). El linkador permite el uso de librerías de funciones y rutinas. TLINK, a diferencia de LINK, permite generar un fichero de tipo COM directamente de un OBJ si se indica el parámetro /t, lo que agiliza aún más el proceso. Puede obtenerse ayuda ejecutándolo sin parámetros. Los parámetros de TLINK son sensibles a mayúsculas y minúsculas, por lo que /T no es lo mismo que /t. Con LINK se obtiene ayuda indicando /HELP. Aunque los parámetros de uno y otro son bastante distintos, la sintaxis genérica de ambos es: TLINK fich_obj(s), fich_exe, fich_map, fich_libreria, fich_def Los ficheros no necesarios se pueden omitir (o indicar NUL): para linkar el fichero prog1.obj y el prog2.obj con la librería math.lib generando PROG1.EXE basta con ejecutar TLINK prog1+prog2,,,math. Alternativamente se puede indicar TLINK @fichero para que tome los parámetros del fichero de texto FICHERO, en el caso de que estos sean demasiados y sea incómodo teclearlos cada vez que se linka. Los ficheros de texto de extensión MAP contienen información útil para el programador sobre la distribución de memoria de los segmentos. 6.4.3. - EXE2BIN. Los ficheros EXE generados por TLINK o LINK no son copia exacta de lo que aparece en la memoria, sino que el DOS -tras cargarlos- debe realizar una última operación de «montaje». Un programa COM en memoria es una copia del fichero del disco, es algo más corto y más sencillo de desensamblar. Al contrario de lo que algunos opinaron en su día, el tiempo ha demostrado que nunca llegarían a ser directamente compatibles con los actuales entornos multitarea. EXE2BIN permite transformar un fichero EXE en COM siempre que el módulo ocupe menos de 64K y que esté ensamblado con ORG 100h. Si no se indicó el parámetro /t en TLINK, será necesario este programa (al igual que cuando se utiliza LINK). Cuando se crean programas SYS (que se diferencian de los COM básicamente en que no tienen ORG 100h) no se puede ejecutar TLINK /t, por lo que es necesaria la ayuda de EXE2BIN para convertir el programa EXE en SYS. Sintaxis: EXE2BIN fich.exe (a veces hay que indicar EXE2BIN fich.exe fich.com) Si el programa no contiene ORG 100h, EXE2BIN genera un fichero binario puro de extensión BIN. Si además existen referencias absolutas a segmentos, EXE2BIN preguntará el segmento en que va a correr (algunas versiones permiten indicarlo de la manera /Ssegmento): esto permite generar código para ser ejecutado en un segmento determinado de la memoria (como pueda ser una memoria EPROM o ROM). 6.4.4. - TLIB/LIB. El gestor de librerías permite reunir módulos objeto en un único fichero para poder tomar de él las rutinas que se necesiten en cada caso. En este libro no se desarrollan programas tan complejos que justifiquen su utilización. En cualquier caso, la sintaxis es la siguiente: TLIB fichero_libreria comandos, fichero_listado Si no se indican comandos se obtiene simplemente información del contenido de la librería en el fichero
91
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
de listado (que puede ser CON para listado por pantalla). Los comandos son de la forma nombre_de_módulo y pueden ser los siguientes:
+ * -+ -*
añade el módulo objeto indicado a la librería borra el módulo indicado de la librería saca el módulo de la librería sin borrarlo (extrae fichero OBJ) alternativamente +-, reemplaza el módulo existente en la librería alternativamente *-, extrae el módulo de la librería y lo borra de
ella
Por ejemplo, para añadir el módulo QUICK.OBJ, borrar el SLOW.OBJ y reemplazar el SORT.OBJ por una nueva versión en LIBRERIA.LIB se ejecutaría: TLIB libreria +quick-slow-+sort Si la lista es muy larga se puede incluir en un fichero y ejecutar TLIB @fichero para que la lea del mismo (si no cabe en una línea del fichero, puede escribirse & al final antes de pasar a la siguiente). 6.4.5. TCREF/CREF. Esta utilidad genera listados en orden alfabético de los símbolos, como ayuda a la depuración. Con el MASM la opción /c crea un fichero de referencias cruzadas de extensión CRF (respondiendo afirmativamente cuando pregunta por el mismo o indicándolo explícitamente en la línea de comandos); la opción /c de TASM lo incluye en el listado, aunque si se indica el nombre del fichero de referencias cruzadas genera un fichero de extensión XRF. CREF y TCREF interpretan respectivamente los ficheros CRF y XRF generando un fichero de texto con extensión REF que contiene el listado de referencias cruzadas. Ej.: TASM fichero,,,fichero TCREF fichero
Las referencias cruzadas son un listado de todos los símbolos del programa, indicando los números de línea del mismo en que son referenciados (la línea en que son definidos se marca con #); estos números de línea son relativos al listado de ensamblaje del programa (y no al fichero fuente). Es útil para depurar programas grandes y complejos. 6.4.6. - MAKE. Esta utilidad se apoya en unos ficheros especiales, al estilo de los BAT del DOS, de cara a automatizar el proceso de ensamblaje. Sólo es recomendable para programas grandes, divididos en módulos, en los que MAKE chequea la fecha y hora para ensamblar sólo las partes que hayan sido modificadas. 6.5. - LA UTILIDAD DEBUG/SYMDEB. La utilidad DEBUG incluída en los sistemas MS-DOS, es una herramienta para depuración de programas muy interesante que permite desensamblar los módulos y, además, ejecutar programas paso a paso, viendo las modificaciones que sufren los registros y banderas. Se trata de un programa menos complejo, cómodo y potente que depuradores de código como Turbo Debugger (de Borland) o Codeview (Microsoft), pero en algunos casos es más útil. Veremos ahora los principales comandos del DEBUG, los cuales también son admitidos en su mayoría por Codeview, por lo que el tiempo invertido en aprenderlos será útil no sólo para conocer el clásico y mítico DEBUG. Antes de empezar con ellos, conviene hacer referencia al programa SYMDEB que acompaña al MASM de Microsoft: se trata de un DEBUG mejorado, con ayuda, más rápido e inteligente (indica el tipo de función del sistema cuando al tracear un programa éste llama al DOS) y, en la práctica, es 99% compatible. También
EL ENSAMBLADOR EN ENTORNO DOS
91
admite las instrucciones adicionales del 286 y los NEC V20/V30. Su diferencia principal es que al abandonarlo para volver al DOS restaura los vectores de interrupción, lo que puede no ser deseable en algunos casos muy concretos. Además, desde la versión 4.0 se admite el parámetro /S (con SYMDEB /S nomfich.ext) lo que permite conmutar entre la pantalla de depuración y la de ejecución pulsando la tecla '\'. Sintaxis general:DEBUG [programa.ext [parámetros] ] Los programas pueden ser de tipo EXE o COM; en el caso de los primeros se les cargará ya montados y con los registros inicializados, listos para su ejecución. Evidentemente, los programas COM también se cargan con los registros inicializados y el correspondiente PSP preparado, así como con IP=100h. Los parámetros opcionales no son los de el DEBUG o SYMDEB sino los que normalmente se suministrarían al programa a depurar. También se pueden cargar otros ficheros de cualquier extensión o simplemente entrar en el programa sin cargar ningún fichero. Al entrar, aparecerá el prompt particular del DEBUG: un guión (-). Entonces se pueden teclear órdenes que constarán generalmente de una sola letra. La mayoría de las mismas admiten parámetros, que normalmente irán separados por comas. Estos parámetos pueden ser números hexadecimales de hasta dos o cuatro dígitos, registros y, además: - Cadenas de caracteres: Encerradas entre comillas simples o dobles. El texto puede a su vez encerrar fragmentos entrecomillados, empleando comillas distintas a las más exteriores. Ejemplo: "Cadena de caracteres", "Otra 'cadena' más", 'Curso de "8086"' Con SYMDEB debe tenerse cuidado de no colocar el nombre de un registro de segmento en mayúsculas y seguido de dos puntos, ya que no se interpretará correctamente: "ESTO ES: ESTA CADENA SERA MAL TRADUCIDA." La cadena 'ES:' no será bien traducida a sus correspondientes valores ASCII. Con DEBUG este problema no existe. - Direcciones: Pueden expresarse con sus correspondientes valores numéricos o bien apoyándose en algún registro de segmento, aunque el offset siempre será numérico: 1E93:AD21, CS:100, ES:19AC El depurador SYMDEB es mucho más flexible y permite también emplear registros de propósito general en el offset. Sería válida la dirección DS:BX+AX+104. - Rangos: Son dos direcciones separadas por una coma; o bien una dirección, la letra 'L' y un valor numérico que indica el número de bytes a partir de la dirección. - Listas: Son secuencias de bytes y/o cadenas separadas por comas: AC, "Texto de ejemplo", 0D, 0A, '$' El DEBUG del MS-DOS 5.0 y el SYMDEB poseen una ayuda invocable con el comando ?, en la que se resumen las principales órdenes. A continuación se listan las más interesantes: Q (Quit): permite abandonar el programa y volver al DOS. D [ [numbytes]] (dump): visualiza el contenido de la memoria. SYMDEB permite además visualizarla en palabras (DW), dobles palabras (DD), coma flotante ... A [] (assemble): permite ensamblar a partir de CS:IP si no se indica una dirección concreta. Se admiten las directivas DB y DW del ensamblador. Las instrucciones que requieran indicar un registro de segmento, con DEBUG hay que ponerlas en una sola línea. Por ejemplo:
91
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
XLAT MOV CS:
CS: ; mal ensamblado con DEBUG (no así con SYMDEB) WORD PTR ES:[100],1234 ; error en DEBUG (sí vale con SYMDEB) ; bien emsamblado con ambos
ES: MOV
WORD PTR [100],1234
XLAT ; y esto también
Los saltos inter-segmento deben especificarse como FAR (ej., CALL FAR [100]) a no ser que sea evidente que lo son (ej. CALL 1234:5678). E [] (enter): permite consultar y modificar la memoria, byte a byte. Por ejemplo, con E 230 1,2,3 se introducirían los bytes 1, 2 y 3 a partir de DS:230. Si no se indica , se visualizará la memoria byte a byte, pudiéndose modificar los bytes deseados, avanzar al siguiente (barra espaciadora) o retroceder al anterior (signo -). Para acabar se pulsa RETURN. U [ []] (unassemble): desensambla la memoria. Como ejemplos válidos: U ES:100, U E000:1940 ... si se indica rango, DEBUG desensamblará ese número de bytes y SYMDEB ese número de líneas. Por defecto se emplea CS: como registro de segmento. R [] (register): permite visualizar y modificar el valor de los registros. Por ejemplo, si se ejecuta la orden 'rip', se solicitará un nuevo valor para IP; con RF se muestran los flags y se permite modificar alguno: ┌──────────────────┬──────────┬────────────┐ │
Flag
│
Activo
│
Borrado
│
├──────────────────┼──────────┼────────────┤ │ Desbordamiento
│ OV
│ NV
│ Dirección
│ DN (↓)
│ UP (↑)
│ │
│ Interrupción
│ EI
│ DI
│
│ Signo
│ NG (<0)
│ PL (>0)
│
│ Cero
│ ZR (=0)
│ NZ (!=0)
│
│ NA
│
│ Acarreo auxiliar │ AC │ Paridad
│ PE (par) │ PO (impar) │
│ Acarreo
│ CY
│ NC
│
└──────────────────┴──────────┴────────────┘
G [= [,,...]] (go): ejecuta código desde CS:IP (a menos que se indique una dirección concreta). Si se trabaja sobre memoria ROM no debe indicarse la segunda dirección. Para que el flujo del programa se detenga en la 2ª dirección o posteriores debe pasar necesariamente por ella(s). Se puede indicar hasta 10 direcciones donde debe detenerse. T [] (trace): ejecuta una instrucción del programa (a partir de CS:IP) mostrando a continuación el estado de los registros y la siguiente instrucción. Ejecutar T10 equivaldría a ejecutar 16 veces el comando T. Si la instrucción es CALL o INT, se ejecutará como tal introduciéndose en la subrutina o servidor de interrupciones correspondiente (SYMDEB no entra en los INT 21h). P [] (proceed): similar al comando T, pero al encontrarse un CALL o INT lo ejecuta de golpe sin entrar en su interior (ojo, ¡esto último falla al tracear sobre memoria ROM!). N (name): se asigna un nombre al programa que está siendo creado o modificado. Se puede indicar la trayectoria de directorios. L [] (load): carga el fichero de nombre indicado con el comando N. Si es ejecutable lo prepara adecuadamente para su inmediata ejecución. En BX:CX queda depositado el tamaño del fichero (BX=0 para ficheros de menos de 64 Kb). Por defecto, la dirección es CS:100h. L (load): carga sectores de la unidad 0, 1, ... (A, B,
EL ENSAMBLADOR EN ENTORNO DOS
91
...) a memoria. Se trata de sectores lógicos del DOS y no los sectores físicos de la BIOS. Las versiones antiguas de SYMDEB dan errores en particiones de más de 32 Mb. W [] (write): graba el contenido de una zona de memoria a disco. Si no se indica la dirección, se graba desde CS:100h hasta CS:100h+número_bytes; el número de bytes se indica en BX:CX (no es una dirección segmentada sino un valor de 32 bits). Si se trata de un EXE no se permitirá grabarlo (para modificarlos, hay que renombrarles para cambiarles la extensión, aunque de esta manera no serán montados al cargarlos). W (write): graba sectores de la memoria a disco en la unidad 0, 1, ... (A, B, ...). Se trata de sectores lógicos del DOS y no los sectores físicos de la BIOS. Las versiones antiguas de SYMDEB dan errores en particiones de disco duro de más de 32 Mb. S (search): busca una cadena de bytes por la memoria. Para buscar la cadena "PEPE" terminada por cero en un área de 512 bytes desde DS:100 se haría: S 100 L 200 "PEPE",0 (por defecto se busca en DS:). No se encontraría sin embargo "pepe" (en minúsculas). F (fill): llena la zona de memoria especificada con repeticiones de la lista de bytes indicada. Por ejemplo, para rellenar códigos 0AAh 100h bytes a partir de 9800h:0 se ejecutaría F 9800:0 L 100 AA; en vez de AA se podría haber indicado una lista de bytes o cadenas de caracteres. C (compare): compara dos zonas de memoria mostrando las diferencias. Por ejemplo, para comparar 5 bytes de DS:100 y DS:200 se hace: C 100 L 5 200. M (move): Más que mover, copia una zona de memoria en otra de manera inteligente (controlando los posibles solapamientos de los bloques). I (input): visualiza la lectura del puerto de E/S indicado. O (output): envia un valor a un puerto de E/S. H (hexaritmetic): muestra la suma y resta de valor1 y valor2, ambos operandos de un máximo de 16 bits (si hay desbordamiento se trunca el resultado, que tampoco excede los 16 bits). También existen comandos en DEBUG para acceder a la memoria expandida: XS (obtener el estado de la memoria expandida), XA npag (localizar npag páginas), XD handle (desalojar el handle indicado) y XM pagina_logica pagina_fisica handle (mapear páginas). Con SYMDEB pueden además colocarse, con suma facilidad, puntos de ruptura (breakpoints); con DEBUG se pueden implementar con la orden G (indicando más de una dirección hasta un máximo de 10, donde debe detenerse el programa si pasa por ellas) aunque es más incómodo. En SYMDEB se pueden definir con BP dirección, borrarse con BC num_breakpoint, habilitarse con BP num_breakpoint (necesario antes de emplearlos), deshabilitarse con BD num_breakpoint y listar los definidos con BL. Además, SYMDEB puede visualizar datos en coma flotante de 32, 64 y 80 bits con el comando D (DS, DL y DT). SYMDEB es realmente un depurador simbólico (SYMbolic DEBugger) que permite mostrar información adicional y depurar con mayor comodidad los programas que han sido ensamblados con información de depuración. Una posibilidad interesante de DEBUG y SYMDEB es que admiten el redireccionamiento del sistema operativo. Ello permite, por ejemplo, crear ficheros ASCII con órdenes y después suministrárselas al programa, como en el siguiente ejemplo: DEBUG < ORDENES.TXT. La última orden de este fichero deberá ser Q (quit), de lo contrario no se devolvería el control al DOS ni se podría parar el programa (la entrada por defecto -el teclado- no actúa). También es versátil la posibilidad de redireccionar la salida. Por ejemplo, tras DEBUG > SALIDA.TXT, se puede teclear un comando para desensamblar (U) y otro para salir (Q): en el disco aparecerá
91
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
el fichero con los datos del desensamblaje (se teclea a ciegas, lógicamente, porque la salida por pantalla ha sido redireccionada al fichero). Por supuesto, también es posible redireccionar entrada y salida a un tiempo: DEBUG < ORDENES.TXT > SALIDA. 6.6 - LAS FUNCIONES DEL DOS Y DE LA BIOS. El código de la BIOS, almacenado en las memorias ROM del ordenador, constituye la primera capa de software de los ordenadores compatibles. La BIOS accede directamente al hardware, liberando a los programas de usario de las tareas más complejas. Parte del código de la BIOS es actualizado durante el arranque del ordenador, con los ficheros que incluye el sistema operativo. El sistema operativo o DOS propiamente dicho se instala después: el DOS no realiza ningún acceso directo al hardware, en su lugar se apoya en la BIOS, constituyendo una segunda capa de software. El DOS pone a disposición de los programas de usuario unas funciones muy evolucionadas para acceder a los discos y a los recursos del ordenador. Por encima del DOS se suele colocar habitualmente al COMMAND.COM, aunque realmente el COMMAND no constituye capa alguna de software: es un simple programa de utilidad, como cualquier otro, ejecutado sobre el DOS y que además no pone ninguna función a disposición del sistema (al menos, documentada), su única misión es cargar otros programas. FUNCIONES DE LA BIOS Las funciones de la BIOS se invocan, desde los programas de usuario, ejecutando una interrupción software con un cierto valor inicial en los registros. La BIOS emplea un cierto rango de interrupciones, cada una encargada de una tarea específica: INT 10h:Servicios de Vídeo (texto y gráficos). INT 11h:Informe sobre la configuración del equipo. INT 12h:Informe sobre el tamaño de la memoria convencional. INT 13h:Servicios de disco (muy elementales: pistas, sectores, etc.). INT 14h:Comunicaciones en serie. INT 15h:Funciones casette (PC) y servicios especiales del sistema (AT). INT 16h:Servicios de teclado. INT 17h:Servicios de impresora. INT 18h:Llamar a la ROM del BASIC (sólo máquinas IBM). INT 19h:Reinicialización del sistema. INT 1Ah:Servicios horarios. INT 1Fh:Apunta a la tabla de los caracteres ASCII 128-255 (8x8 puntos). La mayoría de las interrupciones se invocan solicitando una función determinada (que se indica en el registro AH al llamar) y se limitan a devolver un resultado en ciertos registros, realizando la tarea solicitada. En general, sólo resultan modificados los registros que devuelven algo, aunque BP es corrompido en los servicios de vídeo de las máquinas más obsoletas. FUNCIONES DEL DOS El DOS emplea varias interrupciones, al igual que la BIOS; sin embargo, cuando se habla de funciones del DOS, todo el mundo sobreentiende que se trata de llamar a la INT 21h, la interrupción más importante con diferencia. INT 20h:Terminar programa (tal vez en desuso). INT 21h:Servicios del DOS. INT 22h:Control de finalización de programas. INT 23h:Tratamiento de Ctrl-C. INT 24h:Tratamiento de errores críticos. INT 25h:Lectura absoluta de disco (sectores lógicos).
91
EL ENSAMBLADOR EN ENTORNO DOS
INT 26h:Escritura absoluta en disco (sectores lógicos). INT 27h:Terminar dejando residente el programa (en desuso). INT 28h:Idle (ejecutada cuando el ordenador está inactivo). INT 29h:Impresión rápida en pantalla (no tanto). INT 2Ah:Red local MS NET. INT 2Bh-2Dh:Uso interno del DOS. INT 2Eh:Procesos Batch. INT 2Fh:Interrupción Multiplex. INT 30h-31h:Compatibilidad CP/M-80. INT 32h:Reservada.
Las funciones del DOS se invocan llamando a la INT 21h e indicando en el registro AH el número de función a ejecutar. Sólo modifican los registros en que devuelven los resultados, devolviendo normalmente el acarreo activo cuando se produce un error (con un código de error en el acumulador). Muchas funciones de los lenguajes de programación frecuentemente se limitan a llamar al DOS. Todos los valores mostrados a continuación son hexadecimales; el de la izquierda es el número de función (lo que hay que cargar en AH antes de llamar); algunas funciones del DOS se dividen a su vez en subfunciones, seleccionables mediante AL (segundo valor numérico, en los casos en que aparece). Las funciones marcadas con U> fueron históricamente indocumentadas, aunque Microsoft desclasificó casi todas ellas a partir del MS-DOS 5.0 (en muchas secciones de este libro, escritas con anterioridad, se las referencia aún como indocumentadas). Se indica también la versión del DOS a partir de la que están disponibles. En general, se debe intentar emplear siempre las funciones que requieran la menor versión posible del DOS; sin embargo, no es necesario buscar la compatibilidad con el DOS 1.0: esta versión no soporta subdirectorios, y el sistema de ficheros se basa en el horroroso método FCB. Los FCB ya no están soportados siquiera en la ventana de compatibilidad DOS de OS/2, siendo recomendable ignorar su existencia y trabajar con los handles, al estilo del UNIX, que consisten en unos números que identifican a los ficheros cuando son abiertos. Existen 5 handles predefinidos permanentemente abiertos: 0 (entrada estándar -teclado-), 1 (salida estándar -pantalla-), 2 (salida de error estándar -también pantalla-), 3 (entrada/salida por puerto serie) y 4 (salida por impresora): la pantalla, el teclado, etc. pueden ser manejados como simples ficheros. Las funciones precedidas de un asterisco son empleadas o mencionadas en este libro, y pueden consultarse en el apéndice al efecto al final del mismo. ENTRADA/SALIDA DE CARACTERES AH AL Versión
Nombre original
Traducción
══ ══ ═══════
═══════════════
══════════
01 -- DOS 1+ - READ CHARACTER FROM STANDARD INPUT, WITH ECHO ............. LEER CARACTER DE LA ENTRADA ESTANDAR, CON IMPRESION *02 -- DOS 1+ - WRITE CHARACTER TO STANDARD OUTPUT .................................... ESCRIBIR CARACTER EN LA SALIDA ESTANDAR 03 -- DOS 1+ - READ CHARACTER FROM STDAUX ..................................................... LEER CARACTER DEL PUERTO SERIE 04 -- DOS 1+ - WRITE CHARACTER TO STDAUX ................................................ ESCRIBIR CARACTER EN EL PUERTO SERIE 05 -- DOS 1+ - WRITE CHARACTER TO PRINTER .................................................. ESCRIBIR CARACTER EN LA IMPRESORA 06 -- DOS 1+ - DIRECT CONSOLE OUTPUT ................................................................ SALIDA DIRECTA A CONSOLA 06 -- DOS 1+ - DIRECT CONSOLE INPUT .............................................................. ENTRADA DIRECTA POR CONSOLA 07 -- DOS 1+ - DIRECT CHARACTER INPUT, WITHOUT ECHO ............................... LECTURA DIRECTA DE CARACTER, SIN IMPRESION 08 -- DOS 1+ - CHARACTER INPUT WITHOUT ECHO ............................................. LECTURA DE CARACTERES, SIN IMPRESION *09 -- DOS 1+ - WRITE STRING TO STANDARD OUTPUT ......................................... ESCRIBIR CADENA EN LA SALIDA ESTANDAR *0A -- DOS 1+ - BUFFERED INPUT ............................................................... ENTRADA DESDE TECLADO POR BUFFER 0B -- DOS 1+ - GET STDIN STATUS ........................................................ OBTENER ESTADO DE LA ENTRADA ESTANDAR 0C -- DOS 1+ - FLUSH BUFFER AND READ STANDARD INPUT ............................. LIMPIAR BUFFER Y LEER DE LA ENTRADA ESTANDAR
GESTION DE FICHEROS
0F -- DOS 1+ - OPEN FILE USING FCB ......................................................... APERTURA DE FICHERO EMPLEANDO FCB 10 -- DOS 1+ - CLOSE FILE USING FCB ............................................................. CERRAR FICHERO EMPLEANDO FCB
91
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
11 -- DOS 1+ - FIND FIRST MATCHING FILE USING FCB ........................................ BUSCAR PRIMER FICHERO EMPLEANDO FCB 12 -- DOS 1+ - FIND NEXT MATCHING FILE USING FCB ........................................ BUSCAR PROXIMO FICHERO EMPLEANDO FCB 13 -- DOS 1+ - DELETE FILE USING FCB ............................................................ BORRAR FICHERO EMPLEANDO FCB 16 -- DOS 1+ - CREATE OR TRUNCATE FILE USING FCB ......................................... CREAR/TRUNCAR FICHERO EMPLEANDO FCB 17 -- DOS 1+ - RENAME FILE USING FCB ......................................................... RENOMBRAR FICHERO EMPLEANDO FCB 23 -- DOS 1+ - GET FILE SIZE FOR FCB ................................................. OBTENER TAMAÑO DE FICHERO EMPLEANDO FCB 29 -- DOS 1+ - PARSE FILENAME INTO FCB .......................................... EXPANDIR EL NOMBRE DEL FICHERO EMPLEANDO FCB *3C -- DOS 2+ - "CREAT" - CREATE OR TRUNCATE FILE ...................................... CREAR/TRUNCAR FICHERO EMPLEANDO HANDLE *3D -- DOS 2+ - "OPEN" - OPEN EXISTING FILE .......................................... ABRIR FICHERO EXISTENTE EMPLEANDO HANDLE *3E -- DOS 2+ - "CLOSE" - CLOSE FILE ................................................ CERRAR FICHERO EXISTENTE EMPLEANDO HANDLE 41 -- DOS 2+ - "UNLINK" - DELETE FILE ........................................................ BORRAR FICHERO EMPLEANDO HANDLE 43 00 DOS 2+ - GET FILE ATTRIBUTES ............................................ OBTENER ATRIBUTOS DEL FICHERO EMPLEANDO HANDLE 43 01 DOS 2+ - "CHMOD" - SET FILE ATTRIBUTES ................................ MODIFICAR ATRIBUTOS DEL FICHERO EMPLEANDO HANDLE 45 -- DOS 2+ - "DUP" - DUPLICATE FILE HANDLE .............................................................. DUPLICAR EL HANDLE 46 -- DOS 2+ - "DUP2", "FORCEDUP" - FORCE DUPLICATE FILE HANDLE ...................................... REDIRECCIONAR EL HANDLE 4E -- DOS 2+ - "FINDFIRST" - FIND FIRST MATCHING FILE ................................. BUSCAR PRIMER FICHERO EMPLEANDO HANDLE 4F -- DOS 2+ - "FINDNEXT" - FIND NEXT MATCHING FILE .................................. BUSCAR PROXIMO FICHERO EMPLEANDO HANDLE 56 -- DOS 2+ - "RENAME" - RENAME FILE ..................................................... RENOMBRAR FICHERO EMPLEANDO HANDLE 57 00 DOS 2+ - GET FILE'S DATE AND TIME .................................... OBTENER FECHA Y HORA DEL FICHERO EMPLEANDO HANDLE 57 01 DOS 2+ - SET FILE'S DATE AND TIME ................................. ESTABLECER FECHA Y HORA DEL FICHERO EMPLEANDO HANDLE 5A -- DOS 3+ - CREATE TEMPORARY FILE ................................................. CREAR FICHERO TEMPORAL EMPLEANDO HANDLE 5B -- DOS 3+ - CREATE NEW FILE ................................ CREAR NUEVO FICHERO SIN MACHACARLO SI EXISTIA EMPLEANDO HANDLE 67 -- DOS 3.3+ - SET HANDLE COUNT ................................. ESTABLECER MAXIMO NUMERO DE HANDLES PARA LA TAREA EN CURSO 68 -- DOS 3.3+ - "FFLUSH" - COMMIT FILE ...................................................... VOLCAR BUFFERS INTERNOS A DISCO
OPERACIONES SOBRE FICHEROS
14 -- DOS 1+ - SEQUENTIAL READ FROM FCB FILE ..................................... LECTURA SECUENCIAL DE FICHERO EMPLEANDO FCB 15 -- DOS 1+ - SEQUENTIAL WRITE TO FCB FILE .................................... ESCRITURA SECUENCIAL EN FICHERO EMPLEANDO FCB *1A -- DOS 1+ - SET DISK TRANSFER AREA ADDRESS .................................... ESTABLECER EL AREA DE TRANSFERENCIA A DISCO 21 -- DOS 1+ - READ RANDOM RECORD FROM FCB FILE .................................. LECTURA ALEATORIA DE REGISTRO EMPLEANDO FCB 22 -- DOS 1+ - WRITE RANDOM RECORD TO FCB FILE ................................. ESCRITURA ALEATORIA DE REGISTRO EMPLEANDO FCB 24 -- DOS 1+ - SET RANDOM RECORD NUMBER FOR FCB ............................ PASAR DE E/S SECUENCIAL A ALEATORIA EMPLEANDO FCB 27 -- DOS 1+ - RANDOM BLOCK READ FROM FCB FILE ..................................... LECTURA ALEATORIA DE BLOQUE EMPLEANDO FCB 28 -- DOS 1+ - RANDOM BLOCK WRITE TO FCB FILE .................................... ESCRITURA ALEATORIA DE BLOQUE EMPLEANDO FCB *2F -- DOS 2+ - GET DISK TRANSFER AREA ADDRESS ......................... OBTENER LA DIRECCION DEL AREA DE TRANSFERENCIA A DISCO *3F -- DOS 2+ - "READ" - READ FROM FILE OR DEVICE ......................................... LEER DE UN FICHERO EMPLEANDO HANDLE *40 -- DOS 2+ - "WRITE" - WRITE TO FILE OR DEVICE ..................................... ESCRIBIR EN UN FICHERO EMPLEANDO HANDLE 42 -- DOS 2+ - "LSEEK" - SET CURRENT FILE POSITION .................. MOVER EL PUNTERO RELATIVO EN EL FICHERO EMPLEANDO HANDLE 5C -- DOS 3+ - "FLOCK" - RECORD LOCKING ............................ BLOQUEAR/DESBLOQUER UNA ZONA DEL FICHERO EMPLEANDO HANDLE
OPERACIONES CON DIRECTORIOS
39 -- DOS 2+ - "MKDIR" - CREATE SUBDIRECTORY ............................................................. CREAR SUBDIRECTORIO 3A -- DOS 2+ - "RMDIR" - REMOVE SUBDIRECTORY ............................................................ BORRAR SUBDIRECTORIO 3B -- DOS 2+ - "CHDIR" - SET CURRENT DIRECTORY .................................................. CAMBIAR EL DIRECTORIO ACTIVO 47 -- DOS 2+ - "CWD" - GET CURRENT DIRECTORY .................................................... OBTENER EL DIRECTORIO ACTUAL
MANEJO DE DISCO
0D -- DOS 1+ - DISK RESET ............................................................................. REINICIALIZAR EL DISCO 0E -- DOS 1+ - SELECT DEFAULT DRIVE ............................................................ ESTABLECER UNIDAD POR DEFECTO 19 -- DOS 1+ - GET CURRENT DEFAULT DRIVE ................................................ OBTENER LA UNIDAD ACTUAL POR DEFECTO 1B -- DOS 1+ - GET ALLOCATION INFORMATION FOR DEFAULT DRIVE ........... OBTENER INFORMACION DE ESPACIO EN EL DISCO POR DEFECTO 1C -- DOS 1+ - GET ALLOCATION INFORMATION FOR SPECIFIC DRIVE ............. OBTENER INFORMACION DE ESPACIO EN EL DISCO INDICADO 2E -- DOS 1+ - SET VERIFY FLAG ........................................................ ESTABLECER EL BANDERIN DE VERIFICACION
91
EL ENSAMBLADOR EN ENTORNO DOS
*36 -- DOS 2+ - GET FREE DISK SPACE ......................................................... OBTENER EL ESPACIO LIBRE EN DISCO 54 -- DOS 2+ - GET VERIFY FLAG ........................................................... OBTENER EL BANDERIN DE VERIFICACION
CONTROL DE PROCESOS
00 -- DOS 1+ - TERMINATE PROGRAM ........................................................................... TERMINAR PROGRAMA 26 -- DOS 1+ - CREATE NEW PROGRAM SEGMENT PREFIX ................................................................... CREAR PSP *31 -- DOS 2+ - TERMINATE AND STAY RESIDENT ................................................... TERMINAR Y PERMANECER RESIDENTE *4B -- DOS 2+ - "EXEC" - LOAD AND/OR EXECUTE PROGRAM ............................................. CARGAR Y/O EJECUTAR PROGRAMA *4C -- DOS 2+ - "EXIT" - TERMINATE WITH RETURN CODE ................................... TERMINAR PROGRAMA CON CODIGO DE RETORNO 4D -- DOS 2+ - GET RETURN CODE ..................................................................... OBTENER CODIGO DE RETORNO *50 -- DOS 2+ internal - SET CURRENT PROCESS ID (SET PSP ADDRESS) ......................... ESTABLECER DIRECCION DEL PSP ACTUAL *51 -- DOS 2+ internal - GET CURRENT PROCESS ID (GET PSP ADDRESS) ............................ OBTENER DIRECCION DEL PSP ACTUAL *62 -- DOS 3+ - GET CURRENT PSP ADDRESS ...................................................... OBTENER DIRECCION DEL PSP ACTUAL
GESTION DE MEMORIA
*48 -- DOS 2+ - ALLOCATE MEMORY ............................................................................... ASIGNAR MEMORIA *49 -- DOS 2+ - FREE MEMORY ................................................................................... LIBERAR MEMORIA *4A -- DOS 2+ - RESIZE MEMORY BLOCK ...................................... MODIFICAR EL TAMAÑO DE UN BLOQUE DE MEMORIA ASIGNADA *58 -- DOS 3+ - GET OR SET MEMORY ALLOCATION STRATEGY ............... OBTENER/ESTABLECER LA ESTRATEGIA DE ASIGNACION DE MEMORIA *58 -- DOS 5.0 - GET OR SET UMB LINK STATE .................... OBTENER/ESTABLECER EL ESTADO DE CONEXION DE LA MEMORIA SUPERIOR
CONTROL DE FECHA Y HORA
*2A -- DOS 1+ - GET SYSTEM DATE .................................................................. OBTENER LA FECHA DEL SISTEMA 2B -- DOS 1+ - SET SYSTEM DATE ............................................................... ESTABLECER LA FECHA DEL SISTEMA *2C -- DOS 1+ - GET SYSTEM TIME ................................................................... OBTENER LA HORA DEL SISTEMA 2D -- DOS 1+ - SET SYSTEM TIME ................................................................ ESTABLECER LA HORA DEL SISTEMA
FUNCIONES MISCELANEAS
18 -- DOS 1+ - NULL FUNCTION FOR CP/M COMPATIBILITY .................................... FUNCION NULA PARA COMPATIBILIDAD CP/M 1D -- DOS 1+ - NULL FUNCTION FOR CP/M COMPATIBILITY .................................... FUNCION NULA PARA COMPATIBILIDAD CP/M 1E -- DOS 1+ - NULL FUNCTION FOR CP/M COMPATIBILITY .................................... FUNCION NULA PARA COMPATIBILIDAD CP/M 1F -- DOS 1+ - GET DRIVE PARAMETER BLOCK FOR DEFAULT DRIVE ........................... OBTENER EL DPB DE LA UNIDAD POR DEFECTO 20 -- DOS 1+ - NULL FUNCTION FOR CP/M COMPATIBILITY .................................... FUNCION NULA PARA COMPATIBILIDAD CP/M *25 -- DOS 1+ - SET INTERRUPT VECTOR ........................................................ ESTABLECER VECTOR DE INTERRUPCION *30 -- DOS 2+ - GET DOS VERSION ....................................................................... OBTENER VERSION DEL DOS 32 -- DOS 2+ - GET DOS DRIVE PARAMETER BLOCK FOR SPECIFIC DRIVE ......................... OBTENER EL DPB DE LA UNIDAD INDICADA 33 -- DOS 2+ - EXTENDED BREAK CHECKING ......................................... CONTROLAR EL NIVEL DE DETECCION DE CTRL-BREAK 33 02 DOS 3.x+ internal - GET AND SET EXTENDED CONTROL-BREAK CHECKING STATE ....... INDICAR/OBTENER NIVEL DETECCION CTRL-BREAK 33 05 DOS 4+ - GET BOOT DRIVE .................................................................. DETERMINAR UNIDAD DE ARRANQUE 33 06 DOS 5.0 - GET TRUE VERSION NUMBER ......................................................... OBTENER VERSION REAL DEL DOS *34 -- DOS 2+ - GET ADDRESS OF INDOS FLAG ....................................................... OBTENER LA DIRECCION DE INDOS *35 -- DOS 2+ - GET INTERRUPT VECTOR ........................................ OBTENER LA DIRECCION DE UN VECTOR DE INTERRUPCION 37 00 DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER ................................. OBTENER EL CARACTER INDICADOR DE PARAMETROS 37 01 DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER .............................. ESTABLECER EL CARACTER INDICADOR DE PARAMETROS 37 -- DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE ....................... CONTROLAR EL USO DEL PREFIJO \DEV\ *38 -- DOS 2+ - GET COUNTRY-SPECIFIC INFORMATION ......................................... OBTENER INFORMACION RELATIVA AL PAIS 38 -- DOS 3+ - SET COUNTRY CODE ................................................................ ESTABLECER EL CODIGO DEL PAIS 44 00 DOS 2+ - IOCTL - GET DEVICE INFORMATION ............................... CONTROL E/S: OBTENER INFORMACION DEL DISPOSITIVO 44 01 DOS 2+ - IOCTL - SET DEVICE INFORMATION ............................ CONTROL E/S: ESTABLECER INFORMACION DEL DISPOSITIVO 44 02 DOS 2+ - IOCTL - READ FROM CHARACTER DEVICE CONTROL CHANNEL ............ CONTROL E/S: LEER DE CANAL CONTROL DISP. CARAC. 44 03 DOS 2+ - IOCTL - WRITE TO CHARACTER DEVICE CONTROL CHANNEL ......... CONTROL E/S: ESCRIBIR EN CANAL CONTROL DISP. CARAC.
91
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
44 04 DOS 2+ - IOCTL - READ FROM BLOCK DEVICE CONTROL CHANNEL ................ CONTROL E/S: LEER DE CANAL CONTROL DISP. BLOQUE 44 05 DOS 2+ - IOCTL - WRITE TO BLOCK DEVICE CONTROL CHANNEL ............. CONTROL E/S: ESCRIBIR EN CANAL CONTROL DISP. BLOQUE 44 06 DOS 2+ - IOCTL - GET INPUT STATUS ............................................ CONTROL E/S: OBTENER ESTADO DE LA ENTRADA 44 07 DOS 2+ - IOCTL - GET OUTPUT STATUS ............................................ CONTROL E/S: OBTENER ESTADO DE LA SALIDA 44 08 DOS 3.0+ - IOCTL - CHECK IF BLOCK DEVICE REMOVABLE ........... CONTROL E/S: COMPROBAR SI EL DISP. DE BLOQUE ES REMOVIBLE 44 09 DOS 3.1+ - IOCTL - CHECK IF BLOCK DEVICE REMOTE ................. CONTROL E/S: COMPROBAR SI EL DISP. DE BLOQUE ES REMOTO 44 0A DOS 3.1+ - IOCTL - CHECK IF HANDLE IS REMOTE ............................. CONTROL E/S: COMPROBAR SI UN HANDLE ES REMOTO 44 0B DOS 3.1+ - IOCTL - SET SHARING RETRY COUNT ........... CONTROL E/S: DEFINIR NUMERO DE REINTENTOS EN MODO DE COMPARTICION 44 0C DOS 3.2+ - IOCTL - GENERIC CHARACTER DEVICE REQUEST ................ CONTROL E/S GENERAL PARA DISPOSITIVOS DE CARACTERES 44 0D DOS 3.2+ - IOCTL - GENERIC BLOCK DEVICE REQUEST ........................ CONTROL E/S GENERAL PARA DISPOSITIVOS DE BLOQUE 44 0E DOS 3.2+ - IOCTL - GET LOGICAL DRIVE MAP ........................................ OBTENER ASIGNACION DE UNIDADES LOGICAS 44 0F DOS 3.2+ - IOCTL - SET LOGICAL DRIVE MAP ........................................ DEFINIR ASIGNACION DE UNIDADES LOGICAS *52 -- U> DOS 2+ internal - "SYSVARS" - GET LIST OF LISTS ........................ OBTENER EL LISTADO DE LAS LISTAS DEL SISTEMA 53 -- DOS 2+ internal - TRANSLATE BIOS PARAMETER BLOCK TO DRIVE PARAM BLOCK ............................... TRADUCIR BPB A DPB 55 -- DOS 2+ internal - CREATE CHILD PSP ...................................................................... CREAR PSP HIJO *59 -- DOS 3+ - GET EXTENDED ERROR INFORMATION ....................................... OBTENER INFORMACION EXTENDIDA DE ERRORES *5D 06 U> DOS 3.0+ internal - GET ADDRESS OF DOS SWAPPABLE DATA AREA ........ OBTENER DIRECCION DEL AREA INTERCAMBIABLE DEL DOS *5D 0A DOS 3.1+ - SET EXTENDED ERROR INFORMATION .................................. ESTABLECER INFORMACION EXTENDIDA DE ERRORES *5D 0B U> DOS 4.x only internal - GET DOS SWAPPABLE DATA AREAS .......................... OBTENER AREAS INTERCAMBIABLES DEL DOS 60 -- DOS 3.0+ - CANONICALIZE FILENAME OR PATH ........... EXPANDIR NOMBRE DE FICHERO A ESPECIFICACION COMPLETA DE DIRECTORIOS 61 -- DOS 3+ - UNUSED ........................................................................................... NO USADA AUN 64 -- DOS 3.2+ internal - SET DEVICE DRIVER LOOKAHEAD FLAG .......... ESTABLECER BANDERIN DE LECTURA ADELANTADA DE DISPOSITIVO 65 -- DOS 3.3+ - GET EXTENDED COUNTRY INFORMATION ..................................... OBTENER INFORMACION EXTENDIDA DEL PAIS 65 23 U> DOS 4+ internal - DETERMINE IF CHARACTER REPRESENTS YES/NO RESPONS ........... DETERMINAR SI UNA LETRA INDICA SI O NO 65 -- U> DOS 4+ internal - COUNTRY-DEPENDENT FILENAME CAPITALIZATION .......... MAYUSCULIZACION DE NOMBRE DEPENDIENTE DEL PAIS 66 01 DOS 3.3+ - GET GLOBAL CODE PAGE TABLE .............................................. OBTENER LA PAGINA DE CODIGOS GLOBAL 66 02 DOS 3.3+ - SET GLOBAL CODE PAGE TABLE ........................................... ESTABLECER LA PAGINA DE CODIGOS GLOBAL 69 -- U> DOS 4+ internal - GET/SET DISK SERIAL NUMBER ...................... OBTENER/ESTABLECER EL NUMERO DE SERIE DE UN DISCO 6B -- U> DOS 5.0 - NULL FUNCTION ................................................................................ FUNCION NULA 6C 00 DOS 4+ - EXTENDED OPEN/CREATE ................................. APERTURA/CREACION DE FICHEROS EXTENDIDA EMPLEANDO HANDLE
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
103
Capítulo VII: ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
7.1. - LAS INTERRUPCIONES Son señales enviadas a la CPU para que termine la ejecución de la instrucción en curso y atienda una petición determinada, continuando más tarde con lo que estaba haciendo. Cada interrupción lleva asociado un número que identifica el tipo de servicio a realizar. A partir de dicho número se calcula la dirección de la rutina que lo atiende y cuando se retorna se continúa con la instrucción siguiente a la que se estaba ejecutando cuando se produjo la interrupción. La forma de calcular la dirección de la rutina es multiplicar por cuatro el valor de la interrupción para obtener un desplazamiento y, sobre el segmento 0, con dicho desplazamiento, se leen dos palabras: la primera es el desplazamiento y la segunda el segmento de la rutina deseada. Por tanto, en el primer kilobyte de memoria física del sistema, existe espacio suficiente para los 256 vectores de interrupción disponibles. Hay tres tipos básicos de interrupciones: - Interrupciones internas o excepciones: Las genera la propia CPU cuando se produce una situación anormal o cuando llega el caso. Por desgracia, IBM se saltó olímpicamente la especificación de Intel que reserva las interrupciones 0-31 para el procesador. INT 0: error de división, generada automáticamente cuando el cociente no cabe en el registro o el divisor es cero. Sólo puede ser generada mediante DIV o IDIV. Hay una sutil diferencia de comportamiento ante esta interrupción según el tipo de procesador: el 8088/8086 y los NEC V20 y V30 almacenan en la pila, como cabría esperar, la dirección de la instrucción que sigue a la que causó la excepción. Sin embargo, el 286 y superiores almacenan la dirección del DIV o IDIV que causa la excepción. INT 1: paso a paso, se produce tras cada instrucción cuando el procesador está en modo traza (utilizada en depuración de programas). INT 2: interrupción no enmascarable, tiene prioridad absoluta y se produce incluso aunque estén inhibidas las interrupciones (con CLI) para indicar un hecho muy urgente (fallo en la alimentación o error de paridad en la memoria). INT 3: utilizada para poner puntos de ruptura en la depuración de programas, debido a que es una instrucción de un solo byte muy cómoda de utilizar. INT 4: desbordamiento, se dispara cuando se ejecuta un INTO y había desbordamiento. INT 5: rango excedido en la instrucción BOUND (sólo 286 y superiores). Ha sido incorrectamente empleada por IBM para volcar la pantalla por impresora. INT 6: código de operación inválido (sólo a partir del 286). Se produce al ejecutar una instrucción indefinida, en la pila se almacena el CS:IP de la instrucción ilegal. INT 7: dispositivo no disponible (sólo a partir del 286).
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
- Interrupciones hardware: Son las generadas por la circuitería del ordenador en respuesta a algún evento. Las más importantes son: INT 8: Se produce con una frecuencia periódica determinada por el canal 0 del chip temporizador 8253/8254 (en la práctica, unas 18,2 veces por segundo). Como desde esta interrupción se invoca a su vez a INT 1Ch -porque así lo dispuso IBM-, es posible ligar un proceso a INT 1Ch para que se ejecute periódicamente. INT 9: generada al pulsar o soltar una tecla. INT 0Ah, 0Bh, 0Ch, 0Dh, 0Eh, 0Fh: Puertos serie, impresora y controladores de disquete. INT 70h, 71h, 72h, 73h, 74h, 75h, 76h, 77h: Generadas en los AT y máquinas superiores por el segundo chip controlador de interrupciones. - Interrupciones software: Producidas por el propio programa (instrucción INT) para invocar ciertas subrutinas. La BIOS y el DOS utilizan algunas interrupciones a las que se puede llamar con determinados valores en los registros para que realicen ciertos servicios. También existe alguna que otra interrupción que se limita simplemente a apuntar a modo de puntero a una tabla de datos. Los vectores de interrupción pueden ser desviados hacia un programa propio que, además, podría quedar residente en memoria. Si se reprograma por completo una interrupción y ésta es de tipo hardware, hay que realizar una serie de tareas adicionales, como enviar una señal fin de interrupción hardware al chip controlador de interrupciones. Si se trata además de la interrupción del teclado del PC o XT, hay que enviar una señal de reconocimiento al mismo ... en resumen: conviene documentarse debidamente antes de intentar hacer nada. Todos estos problemas se evitan si la nueva rutina que controla la interrupción llama al principio (o al final) al anterior gestor de la misma, que es lo más normal, como se verá más adelante. Para cambiar un vector de interrupción existen cuatro métodos: 1) «El elegante»: es además el más cómodo y compatible. De hecho, algunos programas de DOS funcionan también bajo OS/2 si han sido diseñados con esta técnica. Basta con llamar al servicio 25h del DOS (INT 21h) y decirle qué interrupción hay que desviar y a dónde: MOV
AH,25h
; servicio para cambiar vector
MOV
AL,vector
; entre 0 y 255
LEA
DX,rutina
; DS:DX nueva rutina de gestión
INT
21h
; llamar al DOS
2) El «psé»: es menos seguro y compatible (ningún programa que emplea esta técnica corre en OS/2) y consiste en hacer casi lo que hace el DOS pero sin llamarle. Es además mucho más incómodo y largo, pero muy usado por programadores despistados: MOV
BL,vector*4
; vector a cambiar en BL
MOV
BH,0
; ahora en BX
MOV
AX,0
PUSH
DS
; preservar DS
MOV
DS,AX
; apuntar al segmento 0000
LEA
DX,rutina
CLI MOV
[BX],DX
MOV
[BX+2],CS
STI POP
; CS:DX nueva rutina de gestión ; evitar posible interrupción ; cambiar vector (offset) ; cambiar vector (segmento) ; permitir interrupciones
DS
; restaurar DS
3) El «método correcto» es similar al «psé», consiste en cambiar el vector «de un tirón» (cambiar a la vez
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
103
segmento y offset con un REP MOVS) con objeto de evitar una posible interrupción no enmascarable que se pueda producir en ese momento crítico en que ya se ha cambiado el offset pero todavía no el segmento (CLI no inhibe la interrupción no enmascarable). Este sistema es todavía algo más engorroso, pero es el mejor y es el que utiliza el DOS en el método (1). 4) El «método incorrecto» es muy usado por los malos programadores. Es similar al «psé» sólo que sin inhibir las interrupciones mientras se cambia el vector, con el riesgo de que se produzca una interrupción cuando se ha cambiado sólo medio vector. Los peores programadores lo emplean sobre todo para cambiar INT 8 ó INT 1Ch, que se producen con una cadencia de 18,2 veces por segundo. 7.2. - LA MEMORIA. LOS PUERTOS DE ENTRADA Y SALIDA. Dentro del megabyte que puede direccionar un 8086, los primeros 1024 bytes están ocupados por la tabla de vectores de interrupción. A continuación existen 256 bytes de datos de la BIOS y otros tantos para el BASIC y el DOS. De 600h a 9FFFFh está la memoria del usuario (casi 640 Kb). En A0000h comienza el área de expansión de memoria de pantalla (EGA y VGA). En B0000h comienzan otros 64 Kb de los adaptadores de texto MDA y gráficos (CGA). De C0000h a EFFFFh aparecen las extensiones de la ROM (añadidas por las tarjetas gráficas, discos duros, etc.) y en F0000h suele estar colocada la BIOS del sistema (a veces tan sólo 8 Kb a partir de FE000h). Los modernos sistemas operativos (DR-DOS y MS-DOS 5.0 y posteriores) permiten colocar RAM en huecos «vacíos» por encima de los 640 Kb en las máquinas 386 (y algún 286 con cierto juego especial de chips). Esta zona de memoria sirve para cargar programas residentes. De hecho, el propio sistema operativo se sitúa (en 286 y superiores) en los primeros 64 Kb de la memoria extendida (HMA) que pueden ser direccionados desde el DOS, dejando más memoria libre al usuario dentro de los primeros 640 Kb. Para más información, puede consultarse el apéndice I y el capítulo 8. Los puertos de entrada y salida (E/S) permiten a la CPU comunicarse con los periféricos. Los 80x86 utilizan los buses de direcciones y datos ordinarios para acceder a los periféricos, pero habilitando una línea que distinga el acceso a los mismos de un acceso convencional a la memoria (si no existieran los puertos de entrada y salida, los periféricos deberían interceptar el acceso a la memoria y estar colocados en algún área de la misma). Para acceder a los puertos E/S se emplean las instrucciones IN y OUT. Véase el apéndice IV. 7.3.- LA PANTALLA EN MODO TEXTO. Cuando la pantalla está en modo de texto, si está activo un adaptador de vídeo monocromo, ocupa 4 Kb a partir del segmento 0B000h. Con un adaptador de color, son 16 Kb a partir del segmento 0B800h. Un método para averiguar el tipo de adaptador de vídeo es consultar a la BIOS el modo de vídeo activo: será 7 para un adaptador monocromo (tanto MDA como la EGA y VGA si el usuario las configura así) y un valor entre 0 y 4 para un adaptador de color. Los modos 0 y 1 son de 40 columnas y el 2 y 3 de 80. Los modos 0 y 2 son de «color suprimido», aunque en muchos monitores salen también en color (y no en tonos de gris). Cada carácter en la pantalla (empezando por arriba a la izquierda) ocupa dos bytes consecutivos: en el primero se almacena el código ASCII del carácter a visualizar y en el segundo los atributos de color. Obviamente, en un modo de 80x25 se utilizan 4000 bytes (los 96 restantes hasta los 4096 de los 4 Kb se desprecian). En los adaptadores de color, como hay 16 Kb de memoria para texto, se pueden definir entre 4 páginas de texto (80 columnas) y 8 (40 columnas). La página activa puede consultarse también llamando a la BIOS, con objeto de conocer el segmento real donde empieza la pantalla (B800 más un cierto offset). En el 97,5% de los casos sólo se emplea la página 0, lo que no quiere decir que los buenos programas deban asumirla como la única posible. La BIOS utiliza la interrupción 10h para comunicarse con el sistema operativo y los programas de usuario. El byte de atributos permite definir el color de fondo de los caracteres (0-7) con los bits 4-6, el de la tinta (0-15) con los bits 0-3 y el parpadeo con el bit 7. La función de este último bit puede ser redefinida para indicar el brillo de los caracteres de fondo (existiendo entonces también 16 colores de fondo), aunque en CGA es preciso para ello un acceso directo al hardware. En el adaptador monocromo, y para la tinta, el color 0 es el negro; el 1 es «subrayado normal», del 1 al 7 son colores «normales»; el 8 es negro, el 9 es «subrayado
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
brillante» y del 10 al 15 son «brillantes». Para el papel todos los colores son negros menos el 7 (blanco), no obstante para escribir en vídeo inverso es necesario no sólo papel 7 sino además tinta 0 (al menos, en los auténticos adaptadores monocromos). El bit 7 siempre provoca parpadeo en este adaptador. En el adaptador de color no se pueden subrayar caracteres con los códigos de color (aunque sí en la EGA y VGA empleando otros métodos). Tabla de colores: 0 - Negro 4 - Rojo 8 - Gris 12 - Rojo claro 1 - Azul 5 - Magenta 9 - Azul claro 13 - Magenta claro 2 - Verde 6 - Marrón 10 - Verde claro 14 - Amarillo 3 - Cian 7 - Blanco 11 - Cian claro 15 - Blanco brillante
Conviene tener cuidado con la tinta azul (1 y 9) ya que, en estos colores, los adaptadores monocromos subrayan -lo que puede ser un efecto indeseable-. Cuando se llama al DOS para imprimir, éste invoca a su vez a la BIOS, por lo que la escritura puede ser acelerada llamando directamente a este último, que además permite escribir en color. De todas maneras, lo mejor en programas de calidad es escribir directamente sobre la memoria de pantalla para obtener una velocidad máxima, aunque con ciertas precauciones -para convivir mejor con entornos pseudo-multitarea y CGA's con nieve-. Las pantallas de 132 columnas no son estándar y varían de unas tarjetas gráficas a otras, por lo que no las trataremos. Lo que sí se puede hacer -con cualquier EGA y VGA- es llamar a la BIOS para que cargue el juego de caracteres 8x8, lo que provoca un aumento del número de líneas a 43 (EGA) o 50 (VGA), así como un lógico aumento de la memoria de vídeo requerida (que como siempre, empieza en 0B800h). En las variables de la BIOS (apéndice III) los bytes 49h-66h están destinados a controlar la pantalla; su consulta puede ser interesante, como demostrará este ejemplo: el siguiente programa comprueba el tipo de pantalla, para determinar su segmento, llamando a la BIOS (véase el apéndice de las funciones del DOS y de la BIOS). Si no es una pantalla de texto estándar no realiza nada; en caso contrario la recorre y convierte todos sus caracteres a mayúsculas, sin alterar el color: mays
SEGMENT
SHR
AX,1
; desplazamiento / 2
ASSUME CS:mays, DS:mays
SHR
AX,1
; desplazamiento / 4
SHR
AX,1
; desplazamiento / 8
SHR
AX,1
; desplazamiento / 16 (párrafos)
ADD
BX,AX
; segmento de vídeo efectivo
MOV
DS,BX
; DS = segmento de pantalla
XOR
BX,BX
; BX = 0 (primer carácter)
CMP
BYTE PTR [BX],'a'; ¿código ASCII menor que 'a'?
ORG
100h
; programa .COM ordinario
inicio: MOV
AH,15
; función para obtener modo de vídeo
INT
10h
; llamar a la BIOS
MOV
BX,0B000h
; segmento de pantalla monocroma
MOV
CX,2000
; tamaño (caracteres) de la pantalla
CMP
AL,7
; ¿es realmente modo monocromo?
JE
datos_ok
; en efecto
JB
no_minuscula
MOV
BX,0B800h
; segmento de pantalla de color
CMP
BYTE PTR [BX],'z'; ¿código ASCII mayor de 'z'?
CMP
AL,3
; ¿es modo de texto de 80 columnas?
JA
no_minuscula
JE
pant_color
; en efecto
AND
BYTE PTR [BX],0DFh ; poner en mayúsculas
CMP
AL,2
; ¿es modo de texto de 80 columnas?
ADD
BX,2
; apuntar siguiente carácter
JE
pant_color
; en efecto
LOOP
otra_letra
; repetir con los CX caracteres
MOV
CX,1000
; tamaño (caract.) pantalla 40 col.
CMP
AL,1
; ¿es modo texto de 40 columnas?
MOV
AL,0
; fin programa (errorlevel=0)
JBE
pant_color
; así es
MOV
AH,4Ch
MOV
AL,1
; pantalla gráfica o desconocida:
INT
21h
JMP
final
; fin de programa (errorlevel=1)
MOV
AX,40h
; considerar página activa<>0
MOV
DS,AX
; DS = 40h (variables de la BIOS)
MOV
AX,DS:[4Eh] ; desplazamiento de la página activa
datos_ok:
otra_letra:
no_minuscula:
final:
mays pant_color:
7.4 - LA PANTALLA EN MODO GRÁFICO.
ENDS END
inicio
; luego no puede ser minúscula
; luego no puede ser minúscula
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
7.4.1. - MODOS GRÁFICOS. Dada la inmensidad de estándares gráficos existentes para los ordenadores compatibles, que sucedieron al primer adaptador que sólo soportaba texto (MDA), y que de hecho llenan varias estanterías en las librerías, sólo se tratará de una manera general el tema. Se considerarán los estándares más comunes, con algunos ejemplos de programación de la pantalla gráfica CGA con la BIOS y programando la VGA directamente para obtener la velocidad y potencia del ensamblador. Las tarjetas gráficas tradicionales administran normalmente entre 16 Kb y 1 Mb de memoria de vídeo, en el segmento 0B800h las CGA/Hércules y en 0A000h las VGA. En los modos de vídeo que precisan más de 64 Kb se recurre a técnicas especiales, tales como planos de bits para los diferentes colores, o bien dividir la pantalla en pequeños fragmentos que se seleccionan en un puerto E/S. Las tarjetas EGA y posteriores vienen acompañadas de una extensión ROM que parchea la BIOS normal del sistema para añadir soporte al nuevo sistema de vídeo. A continuación se listan los principales modos gráficos disponibles en MDA, CGA, EGA y VGA, así como en las SuperVGA Paradise, Trident y Genoa. No se consideran las peculiaridades del PCJr. Modo ──── 04h 05h 05h 06h 0Dh 0Eh 0Fh 10h 10h 11h 12h 13h 27h 29h 2Dh 2Eh 2Fh 30h 37h 58h 59h 5Bh 5Bh 5Ch 5Ch 5Dh 5Eh 5Eh 5Eh 5Fh 5Fh 5Fh 61h 62h 6Ah 7Ch 7Dh
Texto ───── 40x25 40x25 40x25 80x25 40x25 80x25 80x25 80x25 80x25 80x30 80x30 40x25
100x75 100x75 100x75 80x25 80x30 80x25
80x30
96x64
Resolución ────────── 320x200 320x200 320x200 640x200 320x200 640x200 640x350 640x350 640x350 640x480 640x480 320x200
Colores ─────── 4 4 grises 4 2 16 16 2 4 16 2 16/256k 256/256k
720x512 800x600 640x350 640x480 720x512 800x600 1024x768 800x600 800x600 800x600 640x350 640x400 640x480 640x480 640x400 800x600 800x600 640x480 1024x768 1024x768 768x1024 1024x768 800x600 512x512 512x512
16 16 256/256k 256/256k 256 256/256k 16 16/256k 2 16/256k 256 256 256 256 256 256 256 256 16/256k 16 16/256k 256 16 16 256
Segmento ──────── B800 B800 B800 B800 A000 A000 A000 A000 A000 A000 A000 A000
A000 A000 A000 A000 A000 A000 A000 A000 A000 A000
A000 A000
Tarjeta ───────────────────── CGA, EGA, MCGA, VGA CGA, EGA CGA, VGA CGA, EGA, MCGA, VGA EGA, VGA EGA, VGA EGA, VGA EGA con 64K EGA con 256K, VGA VGA, MCGA VGA VGA, MCGA Genoa Genoa Genoa Genoa Genoa Genoa Genoa Paradise VGA Paradise VGA Trident TVGA Genoa 6400 Trident TVGA Genoa 6400 Trident TVGA Paradise VGA Trident 8900 Genoa 6400 Paradise VGA Trident TVGA Genoa 6400 Trident TVGA Trident TVGA Genoa 6400 Genoa Genoa
8800, 8900 8800 8800 (512K)
(512K) 8800 (512K) 8800 (512K) 8900
Las tarjetas gráficas son muy distintas entre sí a nivel de hardware, por la manera en que gestionan la
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
memoria de vídeo. Las tarjetas SuperVGA complican aún más el panorama. En general, un programa que desee aprovechar al máximo el ordenador deberá apoyarse en drivers o subprogramas específicos, uno para cada tarjeta de vídeo del mercado. Esto es así porque aunque la BIOS del sistema (o el de la tarjeta) soporta una serie de funciones estándar para trabajar con gráficos, existen bastantes problemas. En primer lugar, su ineficiente diseño lo hace extremadamente lento para casi cualquier aplicación seria. Bastaría con que las funciones que implementa la BIOS (pintar y leer puntos de la pantalla) fueran rápidas, ¡sólo eso!, para lo que tan sólo hace falta una rutina específica para cada modo de pantalla, que la BIOS debería habilitar nada más cambiar de modo; casi todas las demás operaciones realizadas sobre la pantalla se apoyan en esas dos y ello no requeriría software adicional para mantener la compatibilidad entre tarjetas. Sin embargo, los programas comerciales no tienen más remedio que incluir sus propias rutinas rápidas para trazar puntos y líneas en drivers apropiados (y de paso añaden alguna función más compleja). Además, y por desgracia, no existe NI UNA SOLA función oficial en la BIOS que informe a los programas que se ejecutan de cosas tan elementales como los modos gráficos disponibles (con sus colores, resolución, etc.); esto no sólo es problemático en las tarjetas gráficas: la anarquía y ausencia de funciones de información también se repite con los discos, el teclado, ... aunque los programadores ya estamos acostumbrados a realizar la labor del detective para averiguar la información que los programas necesitan. Sin embargo, con los gráficos no podemos y nos vemos obligados a preguntar al usuario qué tarjeta tiene, de cuántos colores y resolución, en qué modo... y lo que es peor: la inexistencia de funciones de información se agrava con el hecho de que las VGA de los demás fabricantes hayan asignado de cualquier manera los números de modo. De esta manera, por ejemplo, una tarjeta Paradise en el modo 5Fh tiene de 640x400 puntos con 256 colores, mientras que una Trident tiene, en ese mismo modo, 1024x768 con 16 colores. En lo único que coinciden todas las tarjetas es en los primeros modos de pantalla, definidos inicialmente por IBM. Muchas SuperVGA tienen funciones que informan de sus modos, colores y resoluciones, lo que sucede es que en esto no se han podido poner de acuerdo los fabricantes y la función de la BIOS de la VGA a la que hay que invocar para obtener información, ¡difiere de unas tarjetas a otras!. Afortunadamente, existe un estándar industrial en tarjetas SuperVGA, el estándar VESA, que aunque ha llegado demasiado tarde, múltiples VGA lo soportan y a las que no, se les puede añadir soporte con un pequeño driver residente. Hablaremos de él más tarde. No conviene seguir adelante sin mencionar antes la tarjeta gráfica Hércules. Se trata de una tarjeta que apareció en el mercado muy poco después que la CGA de IBM, con el doble de resolución y manteniendo la calidad MDA en modo texto. Esta tarjeta no está soportada por la BIOS (manufacturada por IBM) y los fabricantes de SuperVGA tampoco se han molestado en soportarla por software, aunque sí por hardware. Está muy extendida en las máquinas antiguas, pero hoy en día no se utiliza y su programación obliga a acceder a los puertos de entrada y salida de manera directa al más bajo nivel. 7.4.2.- DETECCIÓN DE LA TARJETA GRÁFICA INSTALADA. El siguiente procedimiento es uno de tantos para evaluar la tarjeta gráfica instalada en el ordenador. Devuelve un valor en BL que es el mismo que retorna la INT 10h al llamarla con AX=1A00h (ver funciones de la BIOS en los apéndices): 0 ó 1 para indicar que no hay gráficos; 2 si hay CGA; 3, 4 ó 5 si existe una EGA; 6 si detecta una PGA; 7 u 8 si hay VGA o superior y 10, 11 ó 12 si existe MCGA. Retorna 255 si la tarjeta es desconocida (muy raro). La rutina funciona en todos los ordenadores, con o sin tarjetas gráficas instaladas y del tipo que sean. tipo_tarjeta
PROC PUSH MOV INT CMP JE MOV MOV MOV MOV INT CMP
DS AX,1A00h 10h AL,1Ah tarjeta_ok AX,40h DS,AX BL,10h AH,12h 10h BL,10h
; solicitar información VGA a la BIOS ; BL = tipo de tarjeta ; función soportada (hay VGA)
; solicitar información EGA a la BIOS
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
no_ega:
tarjeta_ok: tipo_tarjeta
JE MOV TEST JNZ MOV OR JZ INC JMP MOV CMP JE DEC POP RET ENDP
no_ega ; de momento, no es EGA BL,1 ; supuesto MDA BYTE PTR DS:[87h],8 ; estado del control de vídeo tarjeta_ok ; es MDA BL,4 ; supuesto EGA color BH,BH tarjeta_ok ; así es BL ; es EGA mono tarjeta_ok BL,2 ; supuesto CGA WORD PTR DS:[63h],3D4h ; base del CRT tarjeta_ok ; así es BL ; es MDA DS
7.4.3. - INTRODUCCIÓN AL ESTÁNDAR GRÁFICO VGA. La tarjeta VGA es el estándar actual en ordenadores personales, siendo el sistema de vídeo mínimo que incluye la máquina más asequible. En este apartado estudiaremos la forma básica de programar sus modos gráficos, haciendo un especial hincapié en el tema menos claramente explicado por lo general: el color. Se ignorarán por completo las tarjetas CGA y Hércules, aunque sí se indicará qué parte de lo expuesto se puede aplicar también a la EGA. Tampoco se considerará la MCGA, un híbrido entre EGA y VGA que solo equipa a los PS/2-30 de IBM, bastante incompatible además con la EGA y la VGA. La VGA soporta todos los modos gráficos estándar de las tarjetas anteriores, resumidos en la figura 7.4.3.1, si bien los correspondientes a la CGA (320x200 en 4 colores y 640x200 monocromo) son inservibles para prácticamente cualquier aplicación gráfica actual. ┌────────────┬────────────────┬──────────┬──────────┬───────────────┬───────────┐ │ Modo (hex) │
Resolución
│ Colores
│ Segmento │
Organización │ Adaptador │
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ │
4 y 5
│
320 x 200
│
4
│
B800
│
entrelazado
│
CGA
│
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ │
6
│
640 x 200
│
2
│
B800
│
entrelazado
│
CGA
│
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ │
0Dh
│
320 x 200
│
16
│
A000
│ planos de bit │
EGA
│
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ │
0Eh
│
640 x 200
│
16
│
A000
│ planos de bit │
EGA
│
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ │
0Fh
│
640 x 350
│
2
│
A000
│ planos de bit │
EGA
│
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ │
10h
│
640 x 350
│
4
│
A000
│ planos de bit │
EGA
│
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ │
10h
│
640 x 350
│
16
│
A000
La organización de la memoria (entrelazado, planos de bit o lineal) es la manera en que se direcciona la memoria de vídeo por parte de la CPU. Por ejemplo, en el modo 6, cada pixel de la pantalla está asociado a un bit (8 pixels por byte) a partir de la dirección B800:0000; sin embargo, cuando se recorren 80 bytes en la memoria (640 bits o pixels, primera línea
│ planos de bit │ EGA (128K)│
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ │
11h
│
640 x 480
│
2
│
A000
│
lineal
│
VGA/MCGA │
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ │
12h
│
640 x 480
│
16
│
A000
│ planos de bit │
VGA
│
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ │
13h
│
320 x 200
│
256
│
A000
│
lineal
│
VGA/MCGA │
└────────────┴────────────────┴──────────┴──────────┴───────────────┴───────────┘ FIGURA 7.4.3.1:
MODOS GRÁFICOS DE VIDEO
completa) no se pasa a la segunda línea de la pantalla sino unas cuantas más abajo, en una arquitectura relativamente compleja debida a las limitaciones del hardware de la CGA. Esto ha sido superado en las siguientes tarjetas, en las que las líneas están consecutivas de manera lógica en una organización lineal, si bien
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
el límite de 64 Kb de memoria que puede direccionar en un segmento el 8086 ha obligado al truco de los planos de bit. Para establecer el modo de vídeo se puede emplear una función del lenguaje de programación que se trate o bien llamar directamente a la BIOS, si no se desea emplear la librería gráfica del compilador: la función 0 (AH=0) de servicios de vídeo de la BIOS (INT 10h) establece el modo de vídeo solicitado en AL. En Turbo C sería, por ejemplo: #include main() { struct REGPACK r; r.r_ax=0x0012;
/* AH = 00, AL=12h */
intr (0x10, &r);
/* ejecutar INT 10h */
}
7.4.3.1 - EL HARDWARE DE LA VGA. El chip VGA consta de varios módulos internos, que definen conjuntos de registros direccionables en el espacio E/S del 80x86. En la EGA eran de sólo escritura, aunque en la VGA pueden ser tanto escritos como leídos. Por un lado está el secuenciador, encargado de la temporización necesaria para el acceso a la memoria de vídeo. Por otro lado tenemos el controlador de gráficos, encargado del tráfico de información entre la CPU, la memoria de vídeo y el controlador de atributos; consta de 9 registros cuya programación es necesaria para trazar puntos a gran velocidad en los modos de 16 colores. El controlador de atributos gestiona la paleta de 16 colores y el color del borde. Por último, el DAC o Digital to Analog Converter se encarga en la VGA (no dispone de él la EGA) de gestionar los 262.144 colores que se pueden visualizar en pantalla. La parte del león son los ¡768 registros! de 6 bits que almacenan la intensidad en las componentes roja, verde y azul de cada color, de los 256 que como mucho puede haber simultáneamente en la pantalla (256*3=768). 7.4.3.2 - EL COLOR. La CGA puede generar 16 colores diferentes, utilizando un solo bit por componente de color más un cuarto que indica la intensidad. Sin embargo, la EGA emplea dos bits por cada una de las tres componentes de color, con lo que obtiene 26=64 colores diferentes. Para asociar estos 64 colores a los no más de 16 que puede haber en un momento determinado en la pantalla, se emplean los 16 registros de paleta del controlador de atributos: En cada uno de estos registros, de 6 bits significativos, se definen los 16 colores posibles. La BIOS de la EGA y la VGA carga los registros de paleta adecuadamente para emular los mismos colores de la CGA. Así, por ejemplo, en los modos de texto el color 0 es el negro y el 15 el blanco brillante, si bien se puede alterar esta asignación. Un cambio en un registro de paleta afecta instantáneamente a todo el área de pantalla pintado de ese color. El valor binario almacenado en los registros de paleta tiene el formato xxrgbRGB, siendo rgb los bits asociados a las componentes roja, verde y azul de baja intensidad, y RGB sus homólogos en alta intensidad. Así, el valor 010010b se corresponde con el verde más brillante. Modos de 16 colores en VGA. En la VGA el tema del color en los modos de pantalla de 16 colores (tanto gráficos como de texto) se complica algo más, debido a la presencia del DAC: una matriz de 256 elementos que constan cada uno de 3 registros de 6 bits. Cada uno de los registros de paleta apunta a un elemento del DAC, que es quien realmente contiene el color; lo que sucede es que los registros del DAC son programados por la BIOS para emular los 64 colores de la EGA. Existen dos maneras diferentes de indexar en el DAC los registros de paleta, de manera que se puede dividir el DAC en 16 bloques de 16 elementos o bien en 4 bloques de 64 elementos: en un momento dado, sólo uno de los bloques (denominado página de color del DAC) está activo. Esto significa que se pueden crear 16 ó 4 subpaletas, pudiéndose activar una u otra libremente con una función de la BIOS de la VGA. Por defecto, la BIOS establece 4 páginas de 64 elementos en el DAC, de manera que valores en el rango 0-63 en los 16 registros de paleta referencien a posiciones distintas en el DAC (al área 0-63, al 64-127, al 128-191 ó al 192255): por defecto, la BIOS emplea los elementos 0..63 del DAC que programa para emular los 64 colores de la EGA. Sin embargo, puede resultar más interesante disponer de 16 subpaletas de 16 elementos para conseguir
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
determinados efectos gráficos: en este caso no tiene sentido que los registros de paleta almacenen valores fuera del rango 0-15 (de hecho, solo se consideran los 4 bits menos significativos de los mismos). La figura 7.4.3.2 expresa gráficamente la manera en que se genera el color. Se pueden definir, por ejemplo, las 16 subpaletas en tonos ascendentes de azul y, cambiando la página o subpaleta activa a cierta velocidad se puede hacer que la imagen se encienda y apague rítmica y suavemente. Por supuesto, también se pueden obtener efectos similares alterando directamente los registros del DAC, aunque es mucho más lento que conmutar entre varias paletas ya definidas. Conviene resaltar que el color del borde de la pantalla se define en la EGA y en la VGA en una especie de registro que sigue a los 16 registros de paleta: en la VGA no interviene el DAC en la generación del color del borde, del que solo existen por consiguiente 64 tonos (si bien el borde suele estar en color negro y su tamaño reducido y variable lo hace inservible para nada). Los pixels en los modos gráficos de 16 colores pueden parpadear, si bien es una técnica poco empleada: para ello, basta con cambiar un bit de un registro del controlador de atributos, aunque existe una función de la BIOS que realiza dicha tarea (llamar a la INT 10h con AX=1003h y BX=1 para activar el parpadeo -situación por defecto en los modos de texto- ó BX=0 para desactivarlo). ┌────────────────────┐ │
│
├────────────────────┤ │
│
├────────────────────┤ │
│
├────────────────────┤ │
│
┐
│
│
├─ 64..127
│
┌── valor 0..63 ─────¾ elemento del DAC ───┤
│¾ página (0..3)
│
│
│
├─ 128..191
│
├────────────────────┤
│
│
│ (0 por defecto)
└─ 192..255
┘
┌─ 0..15
┐
│ ─────────────┤
│
├────────────────────┤
│
│
│
│
en pantalla (0..15) ────────┘
┌─ 0..63
├────────────────────┤
┌───¾│
color
CASO 4 X 64
│
├────────────────────┤
seleccionable
└── valor 0..15 ─────¾ elemento del DAC ───┼─ 16..31
│
├─ 32..47
│
:
│¾ página (0..15)
:
│
├────────────────────┤
├─ 224..239
│
│
└─ 240..255
┘
│
│
├────────────────────┤ │
│
│
CASO 16 x 16
seleccionable
├────────────────────┤ │
│
Elementos del DAC
├────────────────────┤ │
│
├────────────────────┤ │
│
├────────────────────┤ │
│
├────────────────────┤ │
│
├────────────────────┤ │
FIGURA 7.4.3.2: OBTENCIÓN DEL COLOR EN LOS MODOS DE 16 COLORES (VGA)
│
└────────────────────┘ 16
Registros de paleta
El truco del mono. Los monitores monocromos VGA solo admiten 64 tonos y se limitan siempre a presentar la componente verde del DAC. Lo que sucede es que la BIOS ajusta la intensidad de la señal verde para emular la presencia de las otras dos. En concreto, suma el 30% del valor rojo, el 59% del verde y el 11% del azul y el resultado lo fuerza al rango 0-63, lo cual simula aproximadamente la intensidad que percibiría el ojo humano con los colores reales. Si se accediera directamente al hardware sin ayuda de la BIOS, lo cual no es nuestro
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
caso, este sería un aspecto a considerar. Por último, decir que en el modo de 4 colores y 350 líneas, solo se emplean los registros de paleta 0, 1, 4 y 5, si bien lo normal aquí es esperar que existan 16 colores (caso de la VGA, o incluso de la EGA con 128K). Modo de 256 colores. En el modo 13h de 320x200 con 256 colores, la generación del color se aparta de lo estudiado hasta ahora para los demás modos gráficos y los de texto, ya que solo interviene el DAC: el byte de memoria de vídeo asociado a cada punto de la pantalla apunta directamente a un elemento del DAC. Por tanto, los registros de paleta del controlador de atributos no se emplean en este modo, siendo más sencillo el proceso de generación del color. Cómo definir la paleta y los registros del DAC. A la hora de cambiar la paleta es conveniente emplear funciones de la BIOS o del lenguaje de programación, ya que un acceso directo al hardware sin más precauciones puede provocar interferencias con algunas tarjetas VGA. Conviene también emplear las funciones que cambian de una sola vez un conjunto de registros del DAC, ya que hacerlo uno por uno es demasiado lento. Otra ventaja de emplear la BIOS es que ésta hace automáticamente las conversiones necesarias para lograr la mejor visualización posible en pantallas monocromas. En algunos casos, las paletas que define por defecto la BIOS al establecer el modo de pantalla son apropiadas. Sin embargo, puede ser útil cambiarlas para lograr un degradado atractivo en los modos de 16 colores y casi obligatorio en el modo de 256 colores, dada la absurda paleta propuesta por la BIOS. Para definir un color en el DAC, basta con un poco de imaginación: si las tres componentes están a cero, saldrá el negro; si están a 63 (valor máximo) saldrá un blanco brillante; si se ponen la roja y la azul en 32 y la verde en 0, saldrá un morado de oscuridad mediana. Se puede realizar un bucle y llenar los primeros 64 elementos del DAC con valores crecientes en una componente de color, poniendo a 0 las demás: de esa manera, se genera una paleta óptima para hacer degradados (escalas de intensidad) de un color puro. FIGURA 7.4.3.3: /* DEFINIR NUEVA PALETA */
/********************************************************************* * EJEMPLO DE CAMBIO DE LA PALETA DE 16 COLORES (EGA/VGA) LLAMANDO AL * * BIOS PARA ELEGIR LOS COLORES DESEADOS, ENTRE LOS 64 POSIBLES DE LA *
paleta[0]=0;
/* __rgbRGB = 0
--> negro
* EGA (POR DEFECTO EMULADOS POR EL DAC DE LA VGA).
paleta[1]=4;
/* __000100 = 4
--> componente roja normal */
paleta[2]=4*8;
/* __100000 = 32 --> componente roja oscura */
paleta[3]=4*8+4;
/* __100100 = 36 --> ambas: rojo brillante
*
*********************************************************************/
*/
void main()
r.r_es=FP_SEG(paleta); r.r_dx=FP_OFF(paleta);
{
r.r_ax=0x1002; intr (0x10, &r); struct REGPACK r; getch();
int gdrv, gmodo, coderr, i, x, color, pixel; char paleta[17];
/* ESTABLECER MODO EGA/VGA 640x350 - 16 COLORES */
detectgraph (&gdrv, &gmodo); coderr=graphresult(); if (((gdrv!=EGA) && (gdrv!=VGA)) || (coderr!=grOk)) { printf("\nNecesaria tarjeta EGA o VGA.\n"); exit(1); } gmodo=EGAHI; initgraph(&gdrv, &gmodo, ""); coderr=graphresult(); if (coderr!=grOk) { printf("Error gráfico: %s.\n", grapherrormsg(coderr)); exit(1);}
/* DIBUJAR BANDAS VERTICALES DE EJEMPLO */
for (x=color=0; color<16; color++) for (pixel=0; pixel
*/
for (i=4; i<17; i++) paleta[i]=0; /* resto colores y borde negros
#include #include
*/
}
closegraph();
/* establecer paleta y borde */
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
Para establecer la paleta se puede llamar a la BIOS (INT 10h) con AX=1002h y ES:DX apuntando a un buffer de 17 bytes: uno para cada registro de paleta más otro final para el color del borde de la pantalla. El Turbo C permite cambiar la paleta con instrucciones de alto nivel; sin embargo, quienes no deseen aprender las particularidades de cada compilador, siempre pueden recurrir a la BIOS, que cambiando la paleta es bastante solvente. Echemos un vistazo al ejemplo de la figura 7.4.3.3 (para ejecutar este programa hay que tener en cuenta que el fichero EGAVGA.BGI del compilador ha de estar en el directorio de trabajo). Al principio se trazan unas bandas verticales con la función line() que serán coloreadas con los 16 colores por defecto, aunque cambiarán instantáneamente al modificar la paleta. Al definir la paleta, los 4 primeros registros son asignados con los 4 posibles tonos de rojo, más bien 3 (el primero es el negro absoluto): rojo, rojo oscuro y rojo brillante. Todos los demás registros y el borde de la pantalla son puestos a 0 (negro) por lo que en la pantalla quedan visibles sólo las tres bandas verticales citadas. El cambio de la paleta es instantáneo, lo que permite hacer efectos especiales. En la VGA, recuérdese que los valores de la paleta son simples punteros al DAC y no los colores reales. Lo que sucede es que los registros del DAC son inicializados al cambiar el modo de pantalla de tal manera que emulan los colores que se obtendría en una EGA... a menos que se cambien los valores de dichos registros. Para ello, nada mejor que llamar de nuevo a la INT 10h con AX=1012h, indicando en BX el primer elemento del DAC a cambiar (típicamente 0) y en CX el número de elementos a modificar (a menudo los 256 posibles). También se pasa en ES:DX la dirección de la tabla de 768 bytes que contiene la información: 3 bytes consecutivos para cada elemento del DAC (rojo, verde y azul) aunque solo son significativos los 6 bits de menor orden de cada byte. Existe también otra función bastante interesante, invocable con AX=1013h y que consta de dos subservicios: el primero se selecciona poniendo un 0 en BL, e indicando en BH si se desean 4 páginas de 64 elementos en el DAC (BH=0) ó 16 páginas de 16 elementos (BH=1). El segundo servicio se indica llamando con BL=1, y permite seleccionar la página del DAC activa en BH (0-3 ó 0-15, según cómo esté estructurado). Obviamente, esta función no está disponible en el modo 13h de 256 colores, en el que no interviene la paleta (sólo el DAC y entero, no a trocitos). La figura 7.4.3.4 contiene un nuevo gmodo=VGAHI; initgraph(&gdrv, &gmodo, ""); coderr=graphresult(); if (coderr!=grOk)
FIGURA 7.4.3.4: /********************************************************************* *
{
printf("Error
gráfico:
%s.\n",
grapherrormsg(coderr));
exit(1);}
EJEMPLO DE CAMBIO DE LA PALETA DE 16 COLORES Y REPROGRAMACION DEL *
* DAC DE LA VGA POR EL BIOS PARA ELEGIR LOS 16 COLORES ENTRE 262.144 * *********************************************************************/
/* DIBUJAR BANDAS VERTICALES DE EJEMPLO */
#include
for (x=color=0; color<16; color++)
#include
for (pixel=0; pixel
void main()
}
{ struct REGPACK r;
/* SELECCIONAR 16 BLOQUES DE 16 ELEMENTOS EN EL DAC */
int gdrv, gmodo, coderr, pagina, i, x, color, pixel; char paleta[17], dac[256][3];
r.r_ax=0x1013; r.r_bx=0x0100; intr (0x10, &r);
/* ESTABLECER MODO VGA 640x480 - 16 COLORES */
/* PAGINA 2: LA PALETA SE APOYARA EN ELEMENTOS 32..47 DEL DAC */
detectgraph (&gdrv, &gmodo); coderr=graphresult();
pagina=2; r.r_ax=0x1013; r.r_bx=(pagina<<8) | 1; intr (0x10, &r);
if ((gdrv!=VGA) || (coderr!=grOk)) { printf("\nNecesaria tarjeta VGA.\n"); exit(1); }
/* APUNTAR REGISTROS DE PALETA A ELEMENTOS CONSECUTIVOS DEL DAC */
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
programa completo de demostración, desarrollado a partir del anterior, que requiere ya un auténtico adaptador VGA. Lo primero que se hace es seleccionar el modo de 16 páginas en el DAC, estableciendo la página 2 como activa (exclusivamente por antojo mio). Ello significa que se emplearán los elementos 32..47 del DAC (la página 0 apuntaría a los elementos 0..15, la 1 hubieran sido los elementos 16..31 y así sucesivamente). Los registros de paleta, simples índices en el DAC, toman los valores 0,1,...,15 (excepto el 17º byte, color del borde, puesto a 0 para seleccionar el negro). A continuación, basta programar los registros 32..47 del DAC con los colores deseados, entre los 262.144 posibles. Como cada componente puede variar entre 0 y 63, elegimos 16 valores espaciados proporcionalmente (0, 4, 8,..., 60) y los asignamos a las componentes roja y verde (rojo+verde=amarillo), apareciendo en la pantalla una escala de 16 amarillos (el primero, negro absoluto) de intensidad creciente. Si bien 16 colores son pocos, son suficientes para representar con relativa precisión algunas imágenes, especialmente en las que predomina un color determinado (los ficheros gráficos se ven normalmente tan mal en los modos de 16 colores debido a que respetan la paleta de la EGA, en la VGA sería otra historia).
for (i=0; i<16; i++) paleta[i]=i; paleta[16]=0;
/* color del borde */
r.r_es=FP_SEG(paleta); r.r_dx=FP_OFF(paleta); r.r_ax=0x1002; intr (0x10, &r);
/* establecer paleta y borde */
/* LLENAR ELEMENTOS 32..47 DEL DAC DE AMARILLOS CRECIENTES */
for (i=32; i<48; i++) { dac[i][0]=i*4;
/* valores crecientes 0..60 de rojo */
dac[i][1]=i*4;
/* valores crecientes 0..60 de verde */
dac[i][2]=0;
/* sin componente azul */
}
r.r_bx=32; r.r_cx=16;
/* primer elemento del DAC */ /* número de elementos a definir */
r.r_es=FP_SEG(dac[32]); r.r_dx=FP_OFF(dac[32]); r.r_ax=0x1012; intr (0x10, &r);
/* programar elementos del DAC */
getch(); closegraph(); }
Por supuesto, existen más funciones que éstas, entre ellas las que permiten cambiar sólo un registro de paleta o un elemento del DAC (y no un bloque); sin embargo, son más lentas cuando se va a cambiar un conjunto de registros. En cualquier caso, el lector puede consultarlas en el fichero INTERRUP.LST si lo desea. También existen en la VGA las funciones inversas (obtener paletas y registros del DAC). El acceso por medio de la BIOS para cambiar la paleta es a menudo más cómodo que emplear funciones del lenguaje de programación y garantiza en ocasiones un mayor nivel de independencia respecto a la evolución futura del hardware (aunque si la librería gráfica llama a la BIOS...). Sin embargo, para otras aplicaciones, es mejor no usar la BIOS. Por ejemplo, el programa de la figura 7.4.3.5 accede directamente a los registros de la VGA para modificar la paleta en dos bucles, en el primero disminuyendo la luminosidad de la pantalla (hasta dejarla negra) y en el segundo restaurándola de nuevo. Este efecto cinematográfico hubiera sido imposible a través de la BIOS por razones de velocidad: el acceso directo al hardware, con precauciones (en este caso, esperar el retrazado vertical para evitar interferencias) es a veces inevitable. El programa de ejemplo funciona también en monitores monocromos, aunque en la práctica sólo actúe en ellos sobre la componente verde. El lector deberá consultar bibliografía especializada para realizar este tipo de programación. 7.4.3.3 - DIRECCIONAMIENTO DE PIXELS. Para pintar pixels en la pantalla y para consultar su color, existen funciones de la BIOS de uso no recomendado. La razón estriba en el mal diseño de la BIOS inicial de IBM, no mejorado tampoco por las VGA clónicas. El problema es que las BIOS emplean 4, 5 y hasta 10 veces más tiempo del necesario para FIGURA 7.4.3.5:
#include
/********************************************************************* *
EFECTO «CINEMATOGRAFICO» DE DESVANECIMIENTO Y POSTERIOR
*
void main()
*
REAPARICION DE LA PANTALLA CON ACCESO DIRECTO AL HARDWARE VGA.
*
{
*********************************************************************/
unsigned char dac[256][3];
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
register i, j;
for (i=0; i<256; i++) {
/* anotar la paleta activa */
disable(); outportb (0x3C7, i); dac [i][0] = inportb (0x3C9);
/* R */
dac [i][1] = inportb (0x3C9);
/* G */
dac [i][2] = inportb (0x3C9);
/* B */
enable(); } /* claridad descendente desde el 64/64-avo al 0/64-avo de intensidad */ for (i=64; i>=0; i--) { while (!((inportb(0x3DA) & 8)==8)); /* esperar retrazo vertical */ while (!((inportb(0x3DA) & 8)==0)); /* esperar su fin */ for (j=0; j<256; j++) { disable(); outportb (0x3C8, j); outportb (0x3C9, dac[j][0]*i >> 6); outportb (0x3C9, dac[j][1]*i >> 6); outportb (0x3C9, dac[j][2]*i >> 6); enable(); } }
103
trazar los puntos. La causa de este problema no reside en que empleen rutinas multipropósito para todos los modos, ya que existen básicamente sólo tres tipos de arquitectura de pantalla (modos CGA, 16 colores y 256 colores). El fallo reside, simplemente, en que han sido desarrollados sin pensar en la velocidad. Por ejemplo, la BIOS emplea el algoritmo más lento posible que existe para trazar puntos en los modos de 16 colores. Lo más conveniente es utilizar los recursos del lenguaje de programación o, mejor aún, acceder directamente a la memoria de pantalla con subrutinas en ensamblador. Este es el procedimiento seguido por la mayoría de las aplicaciones comerciales. Sin embargo, la BIOS tiene la ventaja de que permite normalizar el acceso a la pantalla. Así, un programa puede fácilmente trazar un punto en el modo 1024x768x256 de una SuperVGA (y nunca mejor dicho, porque como sean muchos más de uno...). Para trazar un punto se coloca en CX la coordenada X, en DX la coordenada Y, en AL el color, en BH la página y en AH el valor 0Ch. A continuación se llama,
/* claridad ascendente desde el 0/64-avo al 64/64-avo de intensidad */ for (i=0; i<=64; i++) { while (!((inportb(0x3DA) & 8)==8)); /* esperar retrazo vertical */ while (!((inportb(0x3DA) & 8)==0)); /* esperar su fin */ for (j=0; j<256; j++) { disable(); outportb (0x3C8, j); outportb (0x3C9, dac[j][0]*i >> 6); outportb (0x3C9, dac[j][1]*i >> 6); outportb (0x3C9, dac[j][2]*i >> 6); enable(); } } }
como es costumbre, a la INT 10h. Para consultar el color de un punto en la pantalla, se cargan CX y DX con sus coordenadas y BH con la página, haciendo AH=0Dh antes de llamar a la INT 10h, la cual devuelve el color del pixel en AL. La página será normalmente la 0, aunque en los modos de vídeo que soportan varias páginas ésta se puede seleccionar con la función 5 de la INT 10h. La existencia de varias páginas de vídeo se produce cuando en el segmento de 64 Kb de la arriba a abajo, a partir del segmento A000. Cada punto memoria de vídeo se puede almacenar más de una está asociado a un byte, cuyo valor (0-255) referencia imagen completa (caso por ejemplo del modo directamente a un elemento del DAC. En la figura 640x350x16): existen entonces varias páginas (2, 4, 7.4.3.6 hay un nuevo listado de ejemplo, en este caso etc.) que se reparten el segmento a partes iguales. Se sin emplear la librería gráfica del Turbo C. El programa puede en estas circunstancias visualizar una página se limita a activar este modo cualquiera mientras se trabaja en las otras, que mientras tanto permanecen ocultas a los ojos del usuario. Modo 13h de 256 colores. Este modo, de organización lineal, no presenta complicación alguna: los pixels se suceden en la memoria de vídeo de izquierda a derecha y de
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
FIGURA 7.4.3.6: /******************************************************************** * *
EJEMPLO DE USO DEL MODO DE 320x200 CON 256 COLORES
* *
SIN
EMPLEAR
LA
LIBRERIA
GRAFICA
DEL
COMPILADOR.
* ********************************************************************* /
#include
void main() { struct REGPACK r; char dac[256][3], far *vram; register x, y; int i,ii;
/* ESTABLECER MODO DE PANTALLA */
r.r_ax=0x13; intr (0x10, &r); vram=MK_FP(0xA000, 0);
/* LLENAR LA PANTALLA CON LINEAS HORIZONTALES DE COLOR 0..199 */
for (y=0; y<200; y++) for (x=0; x<320; x++) *vram++=y;
/* DEFINIR PALETA EN EL DAC */
for (i=0; i<100; i++) { dac[i][0]=0; dac[i][1]=0;
/* definir azules */
dac[i][2]=i >> 1; }
for (i=100; i<200; i++) { ii=200-i; dac[i][0]=ii >> 1; dac[i][1]=ii >> 2;
/* definir naranjas */
dac[i][2]=0; }
r.r_ax=0x1012; r.r_bx=0; r.r_cx=200; r.r_es=FP_SEG(dac); r.r_dx=FP_OFF(dac); intr (0x10, &r);
getch(); r.r_ax=3; intr (0x10, &r); }
de pantalla pintando las 200 líneas con los valores 0..199. A continuación define los elementos 0..199 del DAC de la siguiente manera: los primeros 100 en tonos ascendentes de azul, y los siguientes 100 elementos en tonos descendentes de naranja, lo que divide automáticamente la pantalla en dos zonas con la estructura citada. Conseguir el naranja no es complicado: basta sumar rojo con amarillo; como el amarillo es a su vez rojo más verde, el naranja se obtiene sumando dos cantidades de rojo por cada una de verde. Los elementos 200..255 del DAC, no empleados en este ejemplo, podrían ser definidos con otros colores para dibujar alguna otra cosa. Modos de 16 colores. Para direccionar puntos en los modos de 16 colores, en los que actúan interrelacionados los registros de paleta y el DAC de la manera descrita con anterioridad, es necesario un acceso directo al hardware por
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
cuestiones de velocidad. Los lectores que no vayan a emplear las funciones del lenguaje de programación deberán consultar bibliografía especializada en gráficos. Y nada más. La única diferencia de la VGA respecto a la EGA, de hecho, se debe a su peculiar manera de gestionar el color, así como a la inclusión del modo de 320x200 con 256 colores (el modo de 640x480 es idéntico en funcionamiento al de 640x350 de la EGA, solo cambia la altura de la pantalla). Existe también la posibilidad de colocar la VGA en dos modos de 256 colores alternativos al 13h y basados en el mismo; en uno se alcanzan 320x240 puntos y en el otro 320x400. La bibliografía especializada en gráficos explica los pasos a realizar para conseguir esto, factible en la totalidad de las tarjetas VGA del mercado. Sin embargo, estos modos requieren un cambio en el modo de direccionamiento de los pixels, que pasa a ser más complejo -aunque más potente para algunas aplicaciones-. 7.4.4. - EJEMPLO DE GRÁFICOS EMPLEANDO LA BIOS. Este programa ejemplo accede a la pantalla empleando las funciones de la BIOS para trazar puntos (ver apéndice sobre funciones de la BIOS). Utiliza el modo CGA de 640x200 puntos, aunque se puede configurar para cualquier otro modo. El programa dibuja una conocida red en las cuatro esquinas de la pantalla, trazando líneas. El algoritmo empleado es el de Bresseham con cálculo incremental de puntos (aunque al estar separada la rutina que traza el punto esta característica no se aprovecha, pero es fácil de implementar si en vez de llamar a la BIOS para pintar se emplea una rutina propia mezclada con la que traza la recta). La velocidad del algoritmo es muy elevada, sobre todo con las líneas largas, máxime teniendo en cuenta que se trata posiblemente de una de sus implementaciones más optimizada (sólo usa una variable y mantiene todos los demás valores en los 7 registros de datos de la CPU, sin emplear demasiado la pila y duplicando código cuando es preciso en los puntos críticos). No entraré en explicaciones matemáticas del método, del que hay pautas en su listado. Existen versiones de este método que consideran de manera especial las líneas verticales y horizontales para pintarlas de manera más rápida, aunque yo personalmente prefiero rutinas independientes para esas tareas con objeto de no ralentizar el trazado de rectas normales. ; ********************************************************************
SUB
SI,BP
; *
*
CALL
recta
*
MOV
CX,BP
*
MOV
DX,0
; ********************************************************************
MOV
SI,0
MOV
DI,max_y-1
SUB
DI,BX
; *
RED.ASM
-
Demostración de gráfica en CGA utilizando BIOS
; *
modo
EQU
6
max_x
EQU
640
CALL
recta
max_y
EQU
200
MOV
CX,max_x-1
max_color
EQU
2
SUB
CX,BP
MOV
SI,max_x-1
SEGMENT
CALL
recta
ASSUME CS:red, DS:red
ADD
BX,6
ADD
BP,14
CMP
BX,max_y
JB
otras_cuatro
MOV
AH,0
red
ORG
; modo de vídeo
100h
inicio:
otras_cuatro:
; segunda
; tercera
; cuarta
MOV
AX,modo
INT
10h
; modo de pantalla
INT
16h
MOV
AL,max_color-1
; color visible
MOV
AX,3
MOV
BX,0
; contador para eje Y
INT
10h
; volver a modo texto
MOV
BP,0
; contador para eje X
INT
20h
; fin de programa
MOV
CX,0
MOV
DX,BX
MOV
SI,BP
PUSH
AX
; de (CX,DX) a (SI,DI) color AL
MOV
DI,max_y-1
PUSH
BX
CALL
recta
PUSH
CX
MOV
CX,max_x-1
PUSH
DX
MOV
SI,max_x-1
PUSH
SI
recta
; primera recta
; esperar pulsación de tecla
PROC
103
absx2x1:
absy2y1:
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
PUSH
DI
DEC
AX
PUSH
BP
JNZ
penmay1
MOV
color,AL
POP
BP
MOV
AX,SI
POP
DI
SUB
AX,CX
POP
SI
JNC
absx2x1
POP
DX
NEG
AX
POP
CX
XCHG
CX,SI
POP
BX
XCHG
DX,DI
POP
AX
MOV
BX,DI
SUB
BX,DX
MOV
BP,1
JNC
absy2y1
NEG
BP
NEG
BX
CMP
AX,BX
; BX = ABS(Y2-Y1) = «dy»
JA
noswap
; ABS(pendiente) menor de 1
XCHG
AX,BX
SHL
BX,1
MOV
SI,BX
SUB
SI,AX
MOV SUB SUB
DI,AX
fin:
; AX = X2-X1
RET
; AX = ABS(X2-X1) = «dx»
; BP = 1
= «yincr» si
; BP = -1
Y2>Y1
= «yincr» si
Y2<=Y1
color
DB
recta
ENDP
punto
PROC BX
PUSH
CX
PUSH
DX
PUSH
BP
PUSH
SI
PUSH
DI
MOV
AH,0Ch
XOR
BX,BX
DI,BX
INT
10h
DI,AX
POP
DI
POP
SI
POP
BP
POP
DX
; BX = «dy» * 2
; SI = «dy» * 2 - «dx» = «d»
; DI = «dy»*2-«dx»*2 = «incr2»
POPF
penmen1:
noincy:
penmay1:
noincx:
0
PUSH
PUSHF
noswap:
JBE
penmay1
; pendiente mayor de 1
PUSH
AX
POP
CX
MOV
AL,color
POP
BX
CALL
punto
POP
AX
INC
CX
; «x»++
AND
SI,SI
; (SI>0) ?
JS
noincy
ADD
SI,DI
; «d» > 0 : «d» = «d» + «incr2»
ADD
DX,BP
; «y» = «y» + «yincr»
DEC
AX
; «dx»--
JNZ
penmen1
JMP
fin
ADD
SI,BX
DEC
AX
JNZ
penmen1
JMP
fin
PUSH
AX
MOV
AL,color
CALL
punto
POP
AX
ADD
DX,BP
; «y» = «y» + «yincr»
AND
SI,SI
; (SI>0) ?
JS
noincx
ADD
SI,DI
; «d» > 0 : «d» = «d» + «incr2»
INC
CX
; «x»++
DEC
AX
; «dx»--
JNZ
penmay1
JMP
fin
ADD
SI,BX
«d» > 0 ?
; preservar registros (salvo AX)
; trazar punto usando BIOS
RET
; en (CX, DX) = («x», «y»)
->
; «dx»--
punto
ENDP
red
ENDS END
inicio
; «d» < 0 : «d» = «d» + «incr1»
; en (CX, DX) = («x», «y»)
->
«d» > 0 ?
; «d» = «d» + «incr1»
Quizá el lector opine que RED.ASM no es tan rápido. Y tiene razón: la culpa es de la BIOS, que
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
consume un alto porcentaje del tiempo de proceso. Sustituyendo la rutina «punto» por una rutina de trazado de puntos propia, como la que se lista a continuación, la velocidad puede llegar a quintuplicarse en un hipotético RED2.ASM que la invocara. ; en (CX, DX) de color AL (CGA 640x200)
SHL
DX,1
; sólo se corrompe AX
SHL
DX,1
; DX = («cy» / 2) * 64
BX
ADD
BX,DX
; BX = BX + («cy» / 2) * 80
PUSH
CX
MOV
CL,AH
; recuperar parte baja de «cx»
PUSH
DX
AND
CL,7
; dejar nº de bit a pintar (0..7)
MOV
BX,0B800h ; segmento de pantalla CGA
XOR
CL,7
; invertir orden de numeración
MOV
DS,BX
MOV
AH,1
; bit a borrar de la pantalla en AH
MOV
AH,CL
; preservar parte baja de «cx»
SHL
AX,CL
; AH = bit a borrar, AL = bit a pintar
XCHG
BX,CX
; BX = «cx»
NOT
AH
MOV
CL,3
AND
[BX],AH
; borrar punto anterior
SHR
BX,CL
; BX = «cx» / 8
OR
[BX],AL
; ubicar nuevo punto (1/0)
SHR
DX,1
; DX = int («cy» / 2)
POP
DX
JNC
no_add
POP
CX
ADD
BX,8192
; BX = «cx» / 8 + («cy» MOD 2) * 8192
POP
BX
INC
CL
; CL = 4
POP
DS
SHL
DX,CL
; DX = («cy» / 2) * 16
RET
ADD
BX,DX
; BX = BX + («cy» / 2) * 16
punto640x200_C PROC
no_add:
PUSH
DS
PUSH
punto640x200_C ENDP
Para estudiar el funcionamiento de la pantalla CGA el lector puede hacer un programa que recorra la memoria de vídeo para comprender la manera en que está organizada, un tanto peculiar pero no demasiado complicada. Sin embargo, con EGA y VGA no es tan sencillo realizar operaciones sobre la pantalla debido a la presencia de planos de bit; salvo contadas excepciones como la del siguiente apartado. 7.4.5. - EJEMPLO DE GRÁFICOS ACCEDIENDO AL HARDWARE. El siguiente programa de ejemplo accede directamente al segmento de vídeo de la VGA (0A000h) para trazar los puntos. Dibuja un vistoso ovillo basado en circunferencias con centro ubicado en una circunferencia base imaginaria, aprovechando los 256 colores de la VGA estándar en el modo 320x200. Como la paleta establecida por defecto es poco interesante, se define previamente una paleta con apoyo directo en el hardware (el método empleado es sencillo pero no recomendable, provoca nieve con algunas tarjetas). Se emplea el color verde, único visualizable en monitores monocromos (aunque cambiando la paleta con las funciones de la BIOS no hubiera sido necesario). La VGA en modo 13h asocia cada punto de pantalla a un byte, por lo que la pantalla es una matriz de 64000 bytes en el segmento 0A000h. Recordar que la fórmula para calcular el desplazamiento para un punto (cx,cy) es 320*cy+cx. Si se sustituye la rutina «punto», que traza el punto, por otra que lo haga llamando a la BIOS, en una VGA Paradise (BIOS de 14/7/88) se emplean 4 segundos y 8 centésimas en generar la imagen, mientras que tal y como está el programa lo dibuja en 40,4 centésimas (10,1 veces más rápido); todos estos datos cronometrados con precisión sobre un 386-25 sin memoria caché teniendo instalada la opción de «SHADOW ROM» (la lenta ROM copiada en RAM, incluida la BIOS de la VGA, por tanto no compite con desventaja). El algoritmo empleado para trazar la circunferencia es de J. Michener, quien se basó a su vez en otro de J. Bresseham desarrollado para plotter. La versión que incluyo genera circunferencias en pantallas de relación de aspecto 1:1, en otras (ej., de 640 x 200) produciría elipses. No entraré en su demostración matemática, que nada tiene que ver con el ensamblador; baste decir que la rutina se basa exclusivamente en la aritmética entera calculando un solo octante de la circunferencia (los demás los obtiene por simetría). ; ********************************************************************
modo
EQU
13h
; *
*
max_x
EQU
320
; *
OVILLO.ASM - Demostración de gráfica en VGA utilizando hardware *
max_y
EQU
200
; *
*
max_color
EQU
256
oviseg
SEGMENT
; ********************************************************************
; modo de vídeo
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
ASSUME CS:oviseg, DS:oviseg
otro_reg:
circunferencia
INC
AL
100h
ADD
CX,SI
MOV
AX,modo
ADD
CX,SI
INT
10h
CALL
circunferencia
CALL
paleta_verde
INC
AL
MOV
CX,max_x
SUB
CX,SI
SHR
CX,1
ADD
DX,DI
MOV
DX,max_y
ADD
CX,DI
SHR
DX,1
ADD
DX,SI
MOV
BX,DX
CALL
circunferencia
SHR
BX,1
; BX = ma_y / 4
INC
AL
CALL
ovillo
; en (CX, DX) de radio BX
SUB
CX,DI
MOV
AH,0
SUB
CX,DI
INT
16h
CALL
circunferencia
MOV
AX,3
INC
AL
INT
10h
; volver a modo texto
SUB
DX,SI
INT
20h
; fin de programa
SUB
DX,SI
CALL
circunferencia
INC
AL
; CX = max_x / 2
; DX = max_y / 2
; esperar pulsación de tecla
PROC MOV
CX,256
ADD
CX,DI
MOV
DX,3C8h
ADD
CX,DI
MOV
AL,CL
CALL
circunferencia
OUT
DX,AL
INC
AL
INC
DX
SUB
CX,DI
XOR
AL,AL
ADD
DX,SI
OUT
DX,AL
CMP
BP,0
MOV
AL,CL
JG
ovillo_decx
REPT
max_x/320
ADD
BP,DI
SHR
AL,1
ADD
BP,DI
ADD
BP,DI
ADD
BP,DI
ADD
BP,6
JMP
ovillo_incy
; los 256 registros
; registro a programar
; componente roja
ENDM OUT
DX,AL
XOR
AL,AL
OUT
DX,AL
DEC
DX
LOOP
otro_reg
; componente verde
; componente azul
RET paleta_verde
ENDP
ovillo
PROC
ovillo_acaba:
DX,DI
CALL ORG inicio:
paleta_verde
SUB
; circunferencia de circunferencias
MOV
BP,BX
MOV
AL,0
; en (CX, DX) con radio BX y color AL
MOV
SI,BX
XOR
DI,DI
SHL
BP,1
SUB
BP,3
NEG
BP
CMP
DI,SI
JG
ovillo_ok
ADD
CX,SI
ADD
DX,DI
CALL
circunferencia
INC
AL
SUB
CX,SI
SUB
CX,SI
CALL
circunferencia
INC
AL
SUB
DX,DI
; BP = 3 - 2 * BX
; ovillo completado
; en (x+SI, y+DI)
; en (x-SI, y+DI)
; en (x-SI, y-DI)
; en (x+SI, y-DI)
; en (x+DI, y+SI)
; en (x-DI, y+SI)
; en (x-DI, y-SI)
; en (x+DI, y-SI)
; CX = x, DX = y
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
ovillo_decx:
ovillo_incy:
DEC
SI
ADD
BX,DI
PUSH
AX
ADD
BX,DI
MOV
AX,DI
ADD
BX,6
SUB
AX,SI
JMP
circunf_incy
SHL
AX,1
DEC
SI
SHL
AX,1
PUSH
AX
ADD
BP,AX
MOV
AX,DI
POP
AX
SUB
AX,SI
ADD
BP,10
SHL
AX,1
INC
DI
SHL
AX,1
JMP
ovillo_acaba
ADD
BX,AX
circunf_decx:
ovillo_ok:
RET
POP
AX
ovillo
ENDP
ADD
BX,10
INC
DI
JMP
circunf_acaba
POP
DI
circunf_incy: circunferencia PROC
; en (CX,DX) con radio BX y color AL circunf_ok:
PUSH
BX
PUSH
CX
POP
SI
PUSH
DX
POP
DX
PUSH
SI
POP
CX
PUSH
DI
POP
BX
MOV
SI,BX
RET
XOR
DI,DI
SHL
BX,1
SUB
BX,3
NEG
BX
circunf_acaba: CMP
circunferencia ENDP
punto ; BX = 3 - 2 * BX
DI,SI
PROC
; trazar punto en 320x200 con 256 col.
PUSH
DS
PUSH
CX
; en (CX, DX) con color AL
JG
circunf_ok
PUSH
DX
ADD
CX,SI
XCHG
DH,DL
; DX = «cy» * 256
ADD
DX,DI
ADD
CX,DX
; CX = «cy» * 256 + «cx»
CALL
punto
SHR
DX,1
SUB
CX,SI
SHR
DX,1
; DX = «cy» * 64
SUB
CX,SI
ADD
CX,DX
; CX = «cy» * 320 + «cx»
CALL
punto
MOV
DX,0A000h
SUB
DX,DI
MOV
DS,DX
; segmento VGA
SUB
DX,DI
XCHG
BX,CX
; preservar BX en CX, BX = offset
CALL
punto
MOV
[BX],AL
; pintar el punto
ADD
CX,SI
XCHG
BX,CX
; restaurar BX
ADD
CX,SI
POP
DX
; restaurar demás registros
CALL
punto
POP
CX
SUB
CX,SI
POP
DS
ADD
DX,DI
RET
ADD
CX,DI
ADD
DX,SI
CALL
punto
SUB
CX,DI
SUB
CX,DI
CALL
punto
SUB
DX,SI
SUB
DX,SI
CALL
punto
ADD
CX,DI
ADD
CX,DI
CALL
punto
SUB
CX,DI
ADD
DX,SI
CMP
BX,0
JG
circunf_decx
ADD
BX,DI
ADD
BX,DI
; circunferencia completada
; en (x+SI, y+DI)
; en (x-SI, y+DI)
; en (x-SI, y-DI)
; en (x+SI, y-DI)
; en (x+DI, y+SI)
punto
ENDP
oviseg
ENDS END
; en (x-DI, y+SI)
; en (x-DI, y-SI)
; en (x+DI, y-SI)
; CX = x, DX = y
inicio
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
7.4.6. - EL ESTÁNDAR GRÁFICO VESA. Debido a la anarquía reinante en el mundo de las tarjetas gráficas, en 1989 se reunieron un grupo importante de fabricantes (ATI, Genoa, Intel, Paradise, etc) para intentar crear una norma común. El resultado de la misma fue el estándar VESA. Este estándar define una interface software común a todas las BIOS para permitir a los programadores adaptarse con facilidad a las diversas tarjetas sin tener en cuenta sus diferencias de hardware. Actualmente, las principales tarjetas soportan la norma VESA. Las más antiguas pueden también soportarla gracias a pequeños programas residentes que el usuario puede instalar opcionalmente. Para desarrollar una aplicación profesional, es una buena norma soportar algún modo estándar de la VGA y, para obtener más prestaciones, algún modo VESA para los usuarios que estén equipados con dicho soporte. Intentar acceder directamente al hardware o a las funciones BIOS propias de cada tarjeta del mercado por separado, salvo para aplicaciones muy concretas, es ciertamente poco menos que imposible. Modos gráficos. El estándar VESA soporta multitud de modos gráficos, numerados a partir de 100h, si bien algunos de los más avanzados (con 32000 o 16 millones de colores) sólo están soportados por las versiones más recientes de la norma. Entre 100h y 107h se definen los modos más comunes de 16 y 256 colores de todas las SuperVGA, aunque el modo 6Ah también es VESA (800x600x16) al estar soportado por múltiples tarjetas. Una de las grandes ventajas del estándar VESA es la enorme información que pone a disposición del programador. Es posible conocer todos los modos y qué características de resolución, colores y arquitectura tienen. Además, hay funciones adicionales muy útiles para guardar y recuperar el estado de la tarjeta, de especial utilidad para programas residentes: así, estos pueden fácilmente conmutar a modo texto (con la precaución de preservar antes los 4 primeros Kbytes de la RAM de vídeo empleados para definir los caracteres) y volver al modo gráfico original dejando la pantalla en el estado inicial. El programa de ejemplo. En el apéndice donde se resumen las funciones del DOS y la BIOS aparecen también las funciones VESA de vídeo. Estas funciones se invocan vía INT 10h, con AX tomando valores por lo general desde 4F00h hasta 4F08h. Para realizar programas que utilicen la norma, el lector deberá consultar dicha información. Sin embargo, se expone aquí un sencillo programa de demostración que recoge prácticamente todos los pasos necesarios para trabajar con un modo VESA. El primer paso consiste en detectar la presencia de soporte VESA en el sistema, tarea que realiza la función testvesa(). La función getbest256() se limita a buscar el modo de mayor resolución de 256 colores soportado por la tarjeta gráfica de ese equipo, barriendo sistemáticamente todos los modos de pantalla desde el "mejor" hasta el "peor". Para comprobar la existencia de un determinado modo gráfico, existe_modo() invoca también a la BIOS VESA. La función setmode() establece un modo gráfico VESA, devolviendo además dos informaciones interesantes: la dirección de memoria de la rutina de conmutación de bancos (ya veremos para qué sirve) y el segmento de memoria de vídeo, que será normalmente 0A000h. Finalmente, getinfo() devuelve información sobre cualquier modo gráfico. En principio, los modos utilizados por este programa de demostración son conocidos. Sin embargo, la lista de modos de vídeo puede ser mayor en algunas tarjetas, sobre todo en el futuro. Por tanto, un esquema alternativo podría consistir no en buscar ciertos modos concretos sino en ir recorriendo todos y elegir el que cumpla ciertas características de resolución o colores, entre todos los disponibles. De toda la información que devuelve getinfo() es particularmente interesante el número de bancos que necesita ese modo de vídeo. Hay que tener en cuenta que todos los modos de 256 colores de más de 320x200 ocupan más de 64 Kb de memoria. De esta manera, por ejemplo, una imagen de 640x480 con 256 colores utiliza unos 256 Kb de RAM, dividida en 4 bancos. En un momento dado, sólo uno de los 4 bancos puede estar
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
direccionado en el segmento de memoria de vídeo. Para elegir el banco activo (más bien, el inicio de la ventana lógica sobre el total de la memoria de vídeo, aunque nuestro ejemplo es una simplificación) existe una función de la BIOS VESA o, mejor aún: podemos llamar directamente a una subrutina que realiza rápidamente esa tarea (sin tener que utilizar interrupciones) cuya dirección nos devolvió setmode(). De esta manera, el interface VESA evita que tengamos que hacer accesos directos al hardware. La rutina setbank() se limita a cargar el registro DX con el banco necesario antes de ejecutar el CALL. De todas maneras, esta modalidad de llamada no tiene por qué estar soportada por todas las BIOS VESA (en cuyo caso devuelven una dirección 0000:0000 para el CALL) aunque la inmensa mayoría, por fortuna, lo soportan. El único cometido de este programa de demostración es buscar el mejor modo de 256 colores, entre los normales de las SuperVGA, activarlo e ir recorriendo todos los bancos que componen la memoria de vídeo (excepto el último, que podría estar incompleto) para llenar la pantalla con bytes de valor 55h y 0AAh. Finalmente, antes de terminar, se imprime la resolución y cantidad de memoria consumida por ese modo.
/*********************************************************************
video_seg,
*
*
far *pantalla,
*
i, modo, max_x, max_y, vram, bancos, banco, limite;
*
ESTANDAR GRAFICO VESA: EJEMPLO DE USO DEL MEJOR MODO DE 256
*
COLORES EN CUALQUIER SUPERVGA.
*
/* dirección del segmento de vídeo */
* * if (!testvesa()) {
*********************************************************************/
printf ("\nNecesario soporte VESA para este programa.\n"); exit (1); }
#include #include #include
modo = getbest256();
#include
setmode (modo, &ConmutaBanco, &video_seg);
#include
getinfo (modo, &max_x, &max_y, &vram, &bancos);
for (banco=0; banco
0x100
#define M640x480x256
0x101
#define M800x600x256
0x103
#define M1024x768x256
0x105
setbank (ConmutaBanco, banco);
/* modos VESA normales de 256c */
/* direccionar banco */
pantalla=MK_FP(video_seg, 0);
/* normalmente 0xA000:0 */
if (banco!=bancos-1) limite=32768;
#define M1280x1024x256 0x107
/* todo el segmento de 64 Kb */
else limite=(vram-banco*64)*512;
/* palabras último banco */
unsigned testvesa (void),
/* Detectar soporte VESA */
for (i=0; i<=limite; i++) *pantalla++=0x55AA;
existe_modo (unsigned),
/* Comprobar si un modo es soportado */
}
getbest256 (void);
/* Obtener mejor modo de 256c */
/* pintar */
setbank (ConmutaBanco, 0);
void setbank (long, unsigned),
/* Conmutar banco de memoria */
setmode (unsigned, long *,
/* Establecer modo VESA */
printf ("Modo de %dx%dx256 con %d Kb\n\n", max_x, max_y, vram); }
unsigned *), getinfo (unsigned,
/* Obtener información del modo */ /* COMPROBAR QUE EXISTE SOPORTE VESA */
unsigned *, unsigned *, unsigned *, unsigned *);
unsigned testvesa(void) { /* DEMOSTRACION */
struct REGPACK r; char far *mem; unsigned vesa;
void main() {
mem = farmalloc (256L);
struct REGPACK r;
r.r_es = FP_SEG (mem);
long ConmutaBanco; unsigned
/* dirección FAR del conmutador de banco */
r.r_di = FP_OFF (mem);
r.r_ax = 0x4F00; intr (0x10, &r); mem[4]=0; if (strcmp (mem, "VESA")==0) vesa=1; else vesa=0;
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
farfree (mem);
/* BUSCAR EL MODO DE 256 COLORES DE MAYOR RESOLUCION */
return (vesa); }
unsigned getbest256 (void) { if (existe_modo (M1280x1024x256)) return (M1280x1024x256); if (existe_modo (M1024x768x256)) return (M1024x768x256); if (existe_modo (M800x600x256)) return (M800x600x256); if (existe_modo (M640x480x256)) return (M640x480x256); if (existe_modo (M640x400x256)) return (M640x400x256); return (0); }
/* COMPROBAR LA EXISTENCIA DE UN MODO GRAFICO */
unsigned existe_modo (unsigned modo) { struct REGPACK r; unsigned far *mem, far *array;
mem = farmalloc (256L); r.r_es = FP_SEG (mem);
r.r_di = FP_OFF (mem);
r.r_ax=0x4F00; intr (0x10, &r); array = MK_FP (mem[8], mem[7]); farfree (mem);
while ((*array!=0xFFFF) && (*array!=modo)) array++; return (*array==modo); }
/* ESTABLECER UN MODO GRAFICO VESA Y DEVOLVER LA DIRECCION DE */ /* LA RUTINA DE CONMUTACION DE BANCOS Y EL SEGMENTO DE VIDEO
void setmode (unsigned modo, long *conmutar, unsigned *videoseg) { struct REGPACK r; long far *mem;
mem = farmalloc (256L); r.r_es = FP_SEG (mem);
r.r_di = FP_OFF (mem);
r.r_ax = 0x4F01; r.r_cx = modo; intr (0x10, &r); *conmutar = *(mem+3); *videoseg = *(mem+2); farfree (mem); r.r_ax=0x4F02; r.r_bx=modo; intr (0x10, &r); }
/* OBTENER INFORMACION SOBRE UN MODO GRAFICO VESA */
void getinfo (unsigned modo, unsigned *max_x, unsigned *max_y, unsigned *vram, unsigned *bancos) { struct REGPACK r; unsigned far *mem;
mem = farmalloc (256L); r.r_es = FP_SEG (mem);
r.r_di = FP_OFF (mem);
*/
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
r.r_ax = 0x4F01; r.r_cx = modo; intr (0x10, &r);
*max_x = mem[9]; *max_y = mem[10]; *vram = (unsigned) ( (long) mem[8] * mem[10] / 1024L); farfree (mem); *bancos = *vram / 64; if (*vram % 64) (*bancos)++; }
/* CONMUTAR DE BANCO CON LA MAXIMA VELOCIDAD */
void setbank (long direccion, unsigned banco) { asm { mov
ax,4f02h
mov
dx,banco
mov
bx,0
call dword ptr direccion } }
103
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
7.5. - EL TECLADO. En este apartado se estudiará a fondo el funcionamiento del teclado en los ordenadores compatibles, a tres niveles: bajo, intermedio y alto. En el capítulo 12 se documenta el funcionamiento del hardware del teclado, interesante para ciertas aplicaciones concretas, aunque para la mayor parte de las labores de programación no es necesario llegar a tanto. 7.5.1. - BAJO NIVEL. Funcionamiento general del teclado. Al pulsar una tecla se genera una interrupción 9 (IRQ 1) y el código de rastreo que identifica la tecla pulsada puede leerse en el puerto de E/S 60h, tanto en XT como en AT (se corresponde en los AT con el registro de salida del 8042); si se suelta la tecla se produce otra interrupción y se genera el mismo código de rastreo+128 (bit 7 activo). Por ejemplo, si se pulsa la 'A' se generará una INT 9 y aparecerá en el puerto del teclado (60h) el byte 1Eh, al soltar la 'A' se generará otra INT 9 y se podrá leer el byte 9Eh del puerto del teclado (véase la tabla del apéndice V, donde se listan los códigos de rastreo del teclado). Bajo el sistema DOS, el teclado del AT es idéntico al del XT en los códigos de rastreo y comportamiento, debido a la traducción que efectúa el 8042 en el primero. No obstante, el teclado del AT posee unos comandos adicionales para controlar los LEDs. En otros sistemas operativos (normalmente UNIX) el teclado del AT es programado para trabajar en modo AT y pierde la compatibilidad con el del XT (los códigos de rastreo son distintos y al soltar una tecla se producen dos interrupciones) pero bajo DOS esto no sucede en ningún caso y la compatibilidad es casi del 100%. Las teclas expandidas -las que han sido añadidas al teclado estándar de 83/84 teclas- tienen un comportamiento especial, ya que pueden generar hasta 4 interrupciones consecutivas (con un intervalo de unos 1,5 milisegundos, ó 3 ms en los códigos dobles que convierte en uno el 8042) con objeto de emular, aunque bastante mal, ciertas combinaciones de las teclas no expandidas; en general es bastante deficiente la emulación por hardware y el controlador del teclado (KEYB) tiene que tratarlas de manera especial en la práctica. Así, por ejemplo, cuando está inactivo NUM LOCK y se pulsa el cursor derecho expandido, se generan dos interrupciones consecutivas: en la primera aparece un valor 0E0h en el puerto del teclado que indica que es una tecla expandida; en la segunda interrupción aparece el valor 4Dh: el mismo que hubiera aparecido pulsando el '6' del teclado numérico. Sin embargo, si NUM LOCK está activo, en un teclado normal de 83 teclas hay que pulsar el '6' del teclado numérico junto con shift para que el cursor avance. Esto se simula en el teclado expandido por medio de 4 interrupciones: En las dos primeras puede aparecer la secuencia 0E0h-2Ah ó bien 0E0h-36h (2Ah y 36h son los códigos de las teclas shift normales): con esto se simula que está pulsado shift aunque ello no sea realmente cierto (las BIOS más antiguas ignoran la mayoría de los bytes mayores de 128, entre ellos el 0E0h); después aparecen otras dos interrupciones con los valores 0E0h-4Dh (con objeto de simular que se pulsa el '6' del teclado numérico): como el estado NUM LOCK está activo y en teoría se ha pulsado shift y el 6 del teclado numérico, el cursor avanza a la derecha; al soltar la tecla aparecerá la secuencia de interrupciones 0E0h-CDh-0E0h-0AAh, o en su defecto la secuencia equivalente 0E0h-CDh-0E0h-0B6h. En general, estos códigos shift fantasma dan problemas cuando las teclas de SHIFT adquieren otro significado diferente que el de conmutar el estado NUM LOCK, lo que sucede en casi todos los editores de texto de los modernos compiladores. Por ello, la BIOS o el KEYB tratan de manera especial las teclas expandidas; en los ordenadores más antiguos (con BIOS -o al menos su tecnología- anterior a Noviembre de 1985), si no se carga el KEYB, el teclado expandido funcionará mal, incluso en Estados Unidos -aunque las teclas estén bien colocadas-. Cuando se lee un valor 0E0h en una interrupción de teclado, el KEYB o la BIOS activan el bit 1 (el que vale 2) de la posición de memoria 0040h:0096h; en la siguiente interrupción ese bit se borra y ya se sabe que el código leído es el de una tecla expandida. El bit 0 de esa misma posición de memoria indica si se leyó un byte 0E1h en lugar de 0E0h (la tecla expandida «pause» o «pausa» es un caso especial -por fortuna, el único- y genera un prefijo 0E1h en vez del 0E0h habitual; de hecho, esta tecla no genera códigos al ser soltada, pero al pulsarla aparece la secuencia E1-1D-45-E1-9D-C5). El buffer del teclado.
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
Cuando se pulsa una tecla normal, la rutina que gestiona INT 9 deposita en un buffer dos bytes con su código ASCII y el código de rastreo, para cuando el programa principal decida explorar el teclado -lo hará siempre consultando el buffer-. Si el código ASCII depositado es cero ó 0E0h, se trata de una tecla especial (ALT-x, cursor, etc.) y el segundo byte indica cuál (son los denominados códigos secundarios). El código ASCII 0E0h sólo es generado en los teclados expandidos por las teclas expandidas (marcadas como 'Ex' en la tabla de códigos de rastreo del apéndice V), aunque las funciones estándar de la BIOS y del DOS que informan del teclado lo convierten en cero para compatibilizar con teclados no expandidos. Así mismo, el código ASCII 0F0h está reservado para indicar las combinaciones de ALT-tecla que no fueron consideradas inicialmente en el software de soporte de los teclados no expandidos, pero sí actualmente (de esta manera, las rutinas de la BIOS saben si deben informar de estas teclas o no según se esté empleando una función avanzada u obsoleta, para compatibilizar). En todo caso, las secuencias introducidas por medio de ALT-teclado_numérico llevan asociado un código de rastreo 0, por lo que el usuario puede generar los caracteres ASCII 0E0h y 0F0h sin que se confundan con combinaciones especiales; además, según IBM, si el código ASCII 0 va acompañado de un código de rastreo 3 los programas deberían interpretarlo como un auténtico código ASCII 0 (esta secuencia se obtiene con Ctrl-2) lo que permite recuperar ese código perdido en indicar combinaciones especiales. Es importante señalar que aunque el buffer (organizado como cola circular) normalmente está situado entre 0040h:001Eh y 0040h:003Eh, ello no siempre es así; realmente el offset del inicio y el fin del buffer respecto al segmento 0040h lo determinan las variables (tamaño palabra) situadas en 0040h:0080h y 0040h:0082h en todos los ordenadores posteriores a 1981. Por ello, la inmensa mayoría de las pequeñas utilidades de las revistas y los ejemplos de los libros son, por desgracia, incorrectos: la manera correcta de colocar un valor en el buffer -para simular, por ejemplo, la pulsación de una tecla- o extraerlo del mismo es comprobando adecuadamente los desbordamientos de los punteros teniendo en cuenta las variables mencionadas. El puntero al inicio del buffer es una variable tamaño palabra almacenada en la posición 0040h:001Ah y el fin otra ubicada en 0040h:001Ch. El siguiente ejemplo introduce un carácter de código ASCII AL y código de rastreo AH (es cómodo y válido hacer AH=0) en el buffer del teclado:
no_desb:
fin_rutina:
MOV MOV CLI MOV MOV ADD CMP JB MOV CMP JE MOV MOV CMP STI
BX,40h DS,BX BX,DS:[1Ch] CX,BX CX,2 CX,DS:[82h] no_desb CX,DS:[80h] CX,DS:[1Ah] fin_rutina DS:[BX],AX DS:[1Ch],CX SP,0
; meter carácter AX en el buffer del teclado ; evitar conflictos con interrupciones ; puntero a la cola del buffer ; apuntar CX al siguiente dato ; más allá del fin del buffer ; ; ; ; ; ;
inicio de la cola circular puntero al inicio del buffer ZF = 1 --> buffer lleno introducir carácter ASCII (AL) en el buffer actualizar puntero al final del buffer ZF=0 (SP siempre <> 0) --> buffer no lleno
El valor 0 para el código de rastreo es usado para introducir también algunos caracteres especiales, como las vocales acentuadas, etc., aunque por lo general no es demasiado importante su valor (de hecho, los programas suelen comprobar preferentemente el código ASCII; de lo contrario, en un teclado español y otro francés, ¡la tecla Z tendría distinto código!). No estaría de más en este ejemplo comprobar si las variables 40h:80h y 40h:82h son distintas de cero por si el ordenador es demasiado antiguo, medida de seguridad que de hecho toma el KEYB del DR-DOS (en estas máquinas además no es conveniente ampliar el tamaño del buffer cambiándolo de sitio, por ejemplo; lo normal es que esté entre 40h:1Eh y 40h:3Eh). En el apéndice V se listan los códigos secundarios: son el segundo byte (el más significativo) de la palabra depositada en el buffer del teclado por la BIOS o el KEYB. Gestión de la interrupción del teclado.
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
He aquí un ejemplo de una subrutina que intercepta la interrupción del teclado apoyándose en el controlador habitual y limitándose a detectar las teclas pulsadas, espiando lo que sucede pero sin alterar la operación normal del teclado: nueva_int9:
STI PUSH IN PUSHF CALL ⋅ ⋅ ⋅ POP IRET
; ; ; código ; CS:anterior_int9 ; ; AX ; ;
AX AL,60h
permitir interrupción periódica preservar registros modificados de la tecla pulsada preparar la pila para IRET llamar a la INT 9 original hacer algo con esa tecla restaurar registros modificados volver al programa principal
Evidentemente, es necesario preservar y restaurar todos los registros modificados, como en cualquier otra interrupción hardware, dado que puede producirse en el momento más insospechado y no debe afectar a la marcha del programa principal, anterior_int9 es una variable de 32 bits que contiene la dirección de la interrupción del teclado antes de instalar la nueva rutina. Es necesario hacer PUSHF antes de llamar porque la subrutina invocada va a retornar con IRET y no con RETF. En general, el duo PUSHF/CALL es una manera alternativa de simular una instrucción INT. Si se implementa totalmente el control de una tecla en una rutina que gestione INT 9 -sin llamar al principio o al final al anterior gestor-, en los XT hay que enviar una señal de reconocimiento al teclado poniendo a 1 y después a 0 el bit 7 del puerto de E/S 61h (en AT no es necesario, aunque tampoco resulta perjudicial hurgar en ese bit en las máquinas fabricadas hasta ahora); es importante no enviar más de una señal de reconocimiento, algo innecesario por otra parte, de cara a evitar anomalías importantes en el teclado de los XT. Además, tanto en XT como AT hay que enviar en este caso una señal de fin de interrupción hardware (EOI) al 8259 (con un simple MOV AL,20h; OUT 20h,AL) al igual que cuando se gestiona cualquier otra interrupción hardware. El ejemplo anterior quedaría como sigue: nueva_int9:
fin:
STI PUSH IN CMP JNE PUSH IN OR OUT AND OUT POP ⋅ ⋅ ⋅ MOV OUT POP IRET POP JMP
AX AL,60h ; código de la tecla pulsada AL,tecla ; ¿es nuestra tecla? fin ; no AX ; vamos a «manchar» AX AL,61h AL,10000000b 61h,AL AL,01111111b 61h,AL ; señal de reconocimiento enviada AX ; AL = tecla pulsada ; gestionarla AL,20h 20h,AL ; EOI al 8259 AX ; AX del programa principal ; volver al programa principal AX ; AX del programa principal CS:anterior_int9 ; saltar al gestor previo de INT 9
Como se puede observar, esta rutina gestiona una tecla y las demás se las deja al KEYB o la BIOS. Sólo en el caso de que la gestione él es preciso enviar una señal de reconocimiento y un EOI al 8259. En caso contrario, se salta al controlador previo a esta rutina con un JMP largo (segmento:offset); ahora no es preciso el PUSHF, como en el caso del CALL, por razones obvias. La instrucción STI del principio habilita las interrupciones, siempre inhibidas al principio de una interrupción -valga la redundancia-, lo que es conveniente para permitir que se produzcan más interrupciones -por ejemplo, la del temporizador, que lleva nada menos que la hora interna del ordenador-. En el ejemplo, el EOI es enviado justo antes de terminar de gestionar esa tecla; ello significa que mientras se la procesa, las interrupciones hardware de menor prioridad -todas, menos el
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
103
temporizador- están inhibidas por mucho que se haga STI; el programador ha de decidir pues si es preciso enviar antes o no el EOI (véase la documentación sobre el controlador de interrupciones 8259 de los capítulos posteriores), aunque si la rutina es corta no habrá demasiada prisa. Es habitual en los controladores de teclado de AT (tanto la BIOS como el KEYB del MS-DOS) deshabilitar el teclado mientras se procesa la tecla recién leída, habilitándolo de nuevo al final, por medio de los comandos 0ADh y 0AEh enviados al 8042. Sin embargo, la mayoría de las utilidades residentes no toman estas precauciones tan sofisticadas (de hecho, el KEYB del DR-DOS tampoco). Lógicamente sólo se pueden enviar comandos al 8042 cuando el registro de entrada del mismo está vacío, lo que puede verificarse chequeando el bit 1 del registro de estado: no es conveniente realizar un bucle infinito que dejaría colgado el ordenador de fallar el 8042, de ahí que sea recomendable un bucle que repita sólo durante un cierto tiempo; en el ejemplo se utiliza la temporización del refresco de la memoria dinámica de los AT para no emplear más de 15 ms esperando al 8042. Además las interrupciones han de estar inhibidas en el momento crítico en que dura el envío del comando, aunque cuidando de que sea durante el menor tiempo posible: nueva_int9:
STI PUSH CALL MOV OUT CALL IN STI ... CALL MOV OUT POP IRET
; breve ventana para interrupciones AX espera AL,0ADh 60h,AL ; inhibir teclado espera AL,60h ; ¿tecla? ; permitir rápidamente interrupciones ; procesar tecla y enviar EOI al 8259 espera AL,0AEh 60h,AL ; desinhibir teclado AX ; no merece la pena hacer STI
espera:
PUSH PUSH MOV CLI IN AND CMP JZ MOV IN TEST LOOPNZ POP POP RET
AX CX CX,995 ; constante para 15 ms
testref:
AL,61h AL,10h ; método válido solo en AT AL,AH testref AH,AL AL,64h ; registro de estado del 8042 AL,2 ; ¿buffer de entrada lleno? testref ; así es CX AX
7.5.2. - NIVEL INTERMEDIO. Consulta de SHIFT, CTRL, ALT, etc (marcas de teclado). Estas teclas pueden ser pulsadas para modificar el resultado de la pulsación de otras. IBM no ha definido combinaciones con ellas (excepto CTRL-ALT, que sirve para reinicializar el sistema si se pulsa en conjunción con DEL) por lo que los programas residentes suelen precisamente emplear combinaciones de dos o más teclas de estas para activarse sin eliminar prestaciones al teclado; por defecto, si se pulsan dos o más teclas de estas la BIOS o el KEYB asignan prioridades y consideran sólo una de ellas: ALT es la tecla de mayor prioridad, seguida de CTRL y de SHIFT. Por otra parte, cabe destacar el hecho de que CTRL, ALT y SHIFT (al
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
igual que Num Lock, Caps Lock, Scroll Lock e Ins) no poseen la característica de autorepetición de las demás teclas debido a la gestión que realiza la BIOS o el KEYB. - Teclado no expandido. Llamando con AH=2 a la INT 16h (función 2 de la BIOS para el teclado), se devuelve en AL un byte con información sobre las teclas de control (SHIFT, CTRL, etc.) que es el mismo byte almacenado en 0040h:0017h (véase en el apéndice III el área de datos de la BIOS y las funciones de la BIOS para teclado). En 0040h:0018h, existe otro byte de información adicional, aunque no hay función BIOS para consultarlo en los teclados no expandidos, por lo que a menudo es necesario leerlo directamente. Por lo general es mejor emplear las funciones BIOS, si existen, que consultar directamente un bit, por razones de compatibilidad. Evidentemente, todas las funciones para teclados no expandidos pueden usarse también con los expandidos. - Teclado expandido. A partir de 0040h:0096h hay otros bytes con información adicional y específica sobre el teclado del AT y los teclados expandidos: parte de esta información, así como de la de 0040:0018h, puede ser consultada en los teclados expandidos con la función 12h de la BIOS del teclado expandido, que devuelve en AX una palabra: en AL de nuevo el byte de 0040h:0017h y en AH otro byte mezcla de diversas posiciones de memoria con información útil (consultar funciones de la BIOS para teclado). Los bits de 40h:96h sólo son fiables si está instalado el KEYB del MS-DOS o 99% compatible; por ejemplo, el KEYB del DR-DOS 5.0/6.0 (excepto en modo KEYB US) no gestiona correctamente el bit de AltGr, aunque sí los demás bits. Antes de usar esta función conviene asegurarse de que está soportada por la BIOS o el KEYB instalado. Lectura de teclas ordinarias. Con la función 0 de la INT 16h (AH=0 al llamar) se lee una tecla del buffer del teclado, esperando su pulsación si es preciso, y se devuelve en AX (AH código de rastreo y AL código ASCII); con la función 1 (AH=1 al llamar a INT 16h) se devuelve también en AX el carácter del buffer pero sin sacarlo (habrá que llamar de nuevo con AH=0), aunque en este caso no se espera a que se pulse una tecla (si el buffer estaba vacío se retorna con ZF=1 en el registro de estado). En los equipos con soporte para teclado expandido existen además las funciones 10h y 11h (correspondientes a la 0 y 1) que permiten detectar alguna tecla más (como F11 y F12) y diferenciar entre las expandidas y las que no lo son al no convertir los códigos 0E0h en 0, así como la función 5 (introducir caracteres en el buffer). Combinaciones especiales de teclas. - BREAK: se obtiene pulsando CTRL-PAUSE en los teclados expandidos (CTRL-SCROLL LOCK en los no expandidos). El controlador del teclado introduce una palabra a cero en el buffer e invoca la interrupción 1Bh. Los programas pueden interceptar esta interrupción para realizar ciertas tareas críticas antes de terminar su ejecución (ciertas rutinas del DOS, básicamente las de impresión por pantalla, detectan BREAK y abortan el programa en curso). - PAUSE: se obtiene con dicha tecla o bien con CTRL-NUM LOCK (teclados no expandidos); provoca que el ordenador se detenga hasta que se pulse una tecla no modificadora (ni SHIFT, ni ALT, etc.), tecla que será ignorada pero servirá para abandonar la pausa. La pausa es interna a la rutina de control del teclado. - PTR SCR (SHIFT con el (*) del teclado numérico en teclados no expandidos): vuelca la pantalla por impresora al ejecutar una INT 5. - SYS REQ: al pulsarla genera una INT 15h (AX=8500h) y al soltarla otra INT 15h (AX=8501h). - CTRL-ALT-DEL: el controlador del teclado coloca la palabra 1234h en 0040h:0072h (para evitar el chequeo
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
103
de la memoria) y salta a la dirección 0FFFFh:0 reinicializando el ordenador. - ALT-teclado_numérico: manteniendo pulsada ALT se puede teclear en el teclado numérico un valor numérico en decimal; al soltar ALT el código ASCII que representa se introducirá en el buffer. El controlador del teclado almacena en 40h:19h el número en proceso de formación: cada vez que llega un nuevo dígito multiplica el contenido anterior por 10 y se lo suma. Al soltar ALT, se hace 40h:19h=0. Detección de soporte para teclado expandido. Normalmente no será necesario distinguir entre un teclado expandido o estándar, aunque en algunos casos habrá que tener en cuenta la posible pulsación de una tecla expandida y su código 0E0h asociado. En todo caso, el bit 4 de 0040h:0096h indica si el teclado es expandido; sin embargo es suicida fiarse de esto y es más seguro chequear por otros medios la presencia de funciones de la BIOS para teclado expandido antes de usarlas. En teoría, las BIOS de AT del 15 de noviembre de 1985 en adelante soportan las funciones 5, 10h y 11h; los de XT a partir del 10 de enero de 1986 soportan la 10h y la 11h. Sin embargo, en la práctica todas ellas normalmente están disponibles también en cualquier máquina más antigua si tiene instalado un KEYB eficiente, venga equipada o no con teclado expandido. Por ello, lo ideal es chequear la presencia de estas funciones por otros procedimientos. Por ejemplo: llamar a la función 12h con AL=0. Por desgracia, si la función no está implementada no devuelve el acarreo activo para indicar el error. Pero hay un truco: si el resultado sigue siendo AX=1200h, las funciones de teclado expandido no están soportadas. Esto se debe a que al no estar implementada la función, nadie ha cambiado el valor de AX: además, en caso de estar implementada no podría devolver 1200h porque ello significaría una contradicción entre AH y AL. MOV INT CMP JE JMP
AX,1200h 16h ; invocar función teclado expandido AX,1200h no_expandido ; función no soportada si_expandido ; función soportada
Posibilidades avanzadas. La rutina de la BIOS del AT (y de los KEYB) que lee el buffer del teclado, cuando no hay teclas y tiene que esperar por las mismas ejecuta de manera regular la función 90h (AH=90h) de la interrupción 15h indicando una espera de teclado al llamar (AL=2). De esta manera, un hipotético avanzado sistema operativo podría aprovechar ese tiempo muerto para algo más útil. Así mismo, cuando un carácter acaba de ser introducido en el buffer del teclado, se ejecuta la función 91h para indicar que ya ha finalizado la entrada y hay caracteres disponibles. En general, estas características no son útiles en el entorno DOS y, por otra parte, han sido deficientemente normalizadas. Por ejemplo, al acentuar incorrectamente se generan dos caracteres (además del familiar pitido): el KEYB del MS-DOS sólo ejecuta una llamada a la INT 15h con la función 91h (pese a haber introducido dos caracteres en el buffer) y el de DR-DOS hace las dos llamadas... Lo que sí puede resultar más interesante es la función de intercepción de código del teclado: las BIOS de AT no demasiado antiguas y el programa KEYB, tras leer el código de rastreo en AL, activan el acarreo y ejecutan inmediatamente la función 4Fh de la INT 15h para permitir que alguien se de por enterado de la tecla y opcionalmente aproveche para manipular AL y simular que se ha pulsado otra tecla: ese alguien puede devolver además el acarreo borrado para indicar al KEYB que no continúe procesando esa tecla y que la ignore (en caso contrario se procedería a interpretarla normalmente). Para verificar si esta función está disponible en la BIOS basta con ejecutar la función 0C0h de la INT 15h que devuelve un puntero en ES:BX y comprobar que el bit 4 de la posición direccionada por ES:[BX+5] está activo. Alternativamente, puede verificarse la presencia del programa KEYB, lo que también permite emplear esta función en los PC/XT, aunque es más arriesgado. Para detectar la presencia del KEYB del MS-DOS en memoria basta con llamar a la interrupción 2Fh con AX=0AD80h y comprobar que devuelve AL=0FFh (esta función devuelve la versión del KEYB en BX y un puntero a un área de datos en ES:DI). [DR-DOS usa AX=0AD00h]. Consideraciones finales.
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Conviene señalar que los teclados de AT pueden generar interrupciones aunque no se pulsen teclas, normalmente para devolver una señal de reconocimiento cuando alguien les ha enviado algo -por ejemplo, la BIOS puede enviar un comando para cambiar los led's-; por ello, en el momento más insospechado puede producirse una INT 9 con el código de rastreo 0FAh, y la secuencia de interrupciones generada por las teclas que tienen asociado un led en los AT, debido a los códigos 0FAh, no es exactamente idéntica a la de los XT, aunque se trata de un detalle poco relevante -incluso para quienes pretendan hacer algo especial con estas teclas. También es conveniente indicar que en los AT se puede leer puerto del teclado, para averiguar la última tecla pulsada o soltada, en casi cualquier momento -por ejemplo, periódicamente desde la interrupción del temporizador-. De todas formas, esta práctica tiene efectos secundarios debidos al mal diseño del software del sistema de los AT (tales como teclas shift que se enganchan, como si se quedaran pulsadas, numeritos que aparecen al pulsar los cursores expandidos, etc.). Además, en los XT sólo se obtendrá una lectura correcta inmediatamente después de producirse la interrupción del teclado y antes de enviar la correspondiente señal de reconocimiento al mismo -por tanto, no desde una interrupción periódica-. Todo esto desaconseja la lectura del puerto del teclado desde cualquier otro sitio que no sea INT 9, salvo contadas excepciones. Por último indicar que en los AT se puede modificar el estado de CAPS LOCK, NUM LOCK o SCROLL LOCK por el simple procedimiento de alterar el bit correspondiente en 40h:17h; dicho cambio se verá reflejado en los led's cuando el usuario pulse una tecla o el programa lea el teclado con cualquier función -en la práctica, de manera casi instantánea-. Sin embargo, para aplicar esta técnica es aconsejable verificar que se trata de un AT porque en los PC/XT el led -si existe- no se actualiza y pasa a indicar una información incorrecta. Realmente, en los XT, el control de los led lo lleva la propia circuitería del teclado de manera independiente al ordenador. 7.5.3. - ALTO NIVEL. El acceso al teclado a alto nivel puede realizarse a través de las funciones 1, 6, 7, 8 y 0Ah del DOS, considerándolo como dispositivo de entrada estándar. Algunas de estas funciones, si devuelven un 0, se trata de una tecla especial y la siguiente lectura devuelve el código secundario. El DOS utiliza las funciones BIOS. 7.6. - LOS DISCOS. 7.6.1. - ESTRUCTURA FISICA. Los discos son el principal medio de almacenamiento externo de los ordenadores compatibles. Pueden ser unidades de disco flexible, removibles, o discos duros -fijos-. Constan básicamente de una superficie magnética circular dividida en pistas concéntricas, cada una de las cuales se subdivide a su vez en cierto número de sectores de tamaño fijo. Como normalmente se emplean ambas caras de la superficie, la unidad más elemental posee en la actualidad dos cabezas de lectura/escritura, una para cada lado del disco. Los tres parámetros comunes a todos los discos son, por tanto: el número de cabezas, el de pistas y el de sectores. El término cilindro i hace referencia a la totalidad de las pistas i de todas las caras. Bajo DOS, los sectores tienen un tamaño de 512 bytes (tanto en discos duros como en disquetes) que es difícil cambiar (aunque no imposible). Los sectores se numeran a partir de 1, mientras que las pistas y las caras lo hacen desde 0. El DOS convierte esta estructura física de tres parámetros a otra: el número de sector lógico, que se numera a partir de 0 (los sectores físicos les denominaremos a partir de ahora sectores BIOS para distinguirlos de los sectores lógicos del DOS). Para un disco de SECTPISTA sectores BIOS por pista y NUMCAB cabezas, los sectores lógicos se relacionan con la estructura física por la siguiente fórmula: Sector lógico = (sector_BIOS - 1) + cara * SECTPISTA + cilindro * SECTPISTA * NUMCAB - X1
Es decir, el DOS recorre el disco empezando la pista 0 (la exterior, la más alejada del centro) y por la cara o cabezal 0, recorriendo todos los sectores; luego avanza una cara y recorre de nuevo todos los sectores; después pasa al siguiente cilindro... y repite de nuevo el proceso. De esta manera, varios cabezales podrían hipotéticamente- leer bloques de información consecutivos simultáneamente. En los disquetes, X1=0, pero en
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
los discos duros se resta un cierto factor de compensación X1, ya que éstos pueden estar divididos en varias particiones y la que usa el DOS puede no estar al principio del mismo. En general, un disco duro dividido en varias particiones de tipo DOS determina varias unidades lógicas de disco, cada una de las cuales dispone de un conjunto de sectores lógicos numerados a partir de 0 y un factor de compensación propio para la fórmula. Las siguientes fórmulas transforman sectores DOS en sus correspondientes BIOS: Sector_BIOS = (sector MOD SECTPISTA) + 1 Cara = (sector / SECTPISTA) MOD
NUMCAB
Cilindro = sector / (SECTPISTA * NUMCAB) + X2
Como la partición del DOS no suele empezar en el cilindro 0 (reservado en gran parte para la tabla de particiones) sino más bien en el 1 ó en otro posterior (cuando hay más particiones antes que la del DOS) será necesario añadir un cierto valor adicional de compensación X2 a la última fórmula para calcular el cilindro efectivo; esto es así porque en la práctica las particiones suelen empezar y acabar ocupando cilindros enteros y exactos (aunque en realidad, y dada la arquitectura de la tabla de partición, podrían empezar y acabar no sólo en un determinado cilindro sino también en cierto sector y cara del disco, pero no es frecuente). X1 y X2 se obtienen consultando e interpretando la tabla de particiones o el sector de arranque. 7.6.2. - CABEZA 0. PISTA 0. SECTOR 1. El primer sector físico de todos los discos contiene información especial (el sector_BIOS 1 del cilindro 0 y cabezal 0). Tanto en disquetes como en discos duros, contiene un pequeño programa que se encarga de poner en marcha el ordenador: es el sector de arranque de los disquetes, o bien el código de la tabla de particiones de los discos duros. En este último caso, ese programa realiza una tarea muy sencilla: consulta la tabla de particiones ubicada en ese mismo sector, determina cuál es la partición activa y dónde empieza y acaba; a continuación carga el sector lógico 0 de esa partición (sector de arranque) y lo ejecuta. En los disquetes no existe este paso intermedio: el sector físico 0 del disquete, en terminos absolutos, es ya el sector de arranque y no el de partición. Esto es así porque los disquetes contienen poca información y son baratos, no siendo preciso particionarlos para compartirlos con varios sistemas operativos. El programa ubicado en el sector de arranque busca el fichero oculto del sistema IBMBIO.COM o IO.SYS, lo carga y le entrega el control. El programa contenido en este fichero cargará a su vez IBMDOS.COM o MSDOS.SYS, el cual a su vez cargará finalmente el intérprete de comandos (normalmente, COMMAND.COM). Formato de la tabla de partición de los discos duros: ┌─────────────────────────────────────────────────────────────────────────────┐
Esta tabla comienza en un offset 1BEh del sector (al principio está el código ejecutable); cada partición de las 4 posibles ocupa 16 bytes; al final de las cuatro está la marca 0AA55h, ubicada en el offset 1FEh, que indica que la tabla es válida. Los 16 bytes que la forman se interpretan como indica el cuadro de la derecha:
│ byte 0: 0 para partición inactiva, 80h en la de arranque.
│
│ byte 1: cabeza donde comienza la partición.
│
│ byte 2: bits 0 al 5: sector de inicio de la partición; 6, 7: parte alta del │ │
│
número de cilindro.
│ byte 3: parte baja del número de cilindro de inicio de la partición.
│
│ byte 4: tipo de partición, las más comunes son 0: No usada; 1: DOS-12 (FAT
│
│
12 bits); 4: DOS-16 (FAT 16 bits); 5: DOS Extendida; 6:BIGDOS (más
│
│
de 32Mb); 7: OS/2 HPFS ó WinNT NTFS; 0Ah: OS/2 Boot Manager;
0Bh:
│
│
32-bit FAT Win95 (0Ch con LBA);
(como 06 y 05 pero con
│
│
LBA);
81h Linux; 82h Linux swap; 83h: Linux native; 0A5h: FreeBSD
│
│
o BSD/386; 0F2h: partición secundaria (no estudiada en este libro). │
0Eh y 0Fh
│ byte 5: cabeza donde termina la partición.
│
│ byte 6: bits 0 al 5: sector de fin de la partición; 6, 7: parte alta del
│
│
│
número de cilindro.
│ byte 7: parte baja del número de cilindro de fin de la partición.
│
│ bytes 8 al 11: Doble palabra que indica el sector relativo (en todo el
│
│
│
disco) en que comienza la partición, expresado en sectores.
│ bytes 12 al 15: Doble palabra con el tamaño de esa partición en sectores.
│
└─────────────────────────────────────────────────────────────────────────────┘
Formato de la TABLA DE PARTICIÓN Habitualmente, las particiones suelen empezar en el segundo cabezal del cilindro 0, con lo que toda la
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
primera pista física del disco duro está vacía. Lugar ideal para virus, algunos fabricantes han utilizado esta interesante característica para mejorar el arranque, colocando una falsa tabla de partición que muestre un menú en pantalla y cargue después la partición de verdad, permitiendo también más de 4 particiones. Sin embargo, estas maniobras suelen reducir la compatibilidad. Existen también código de particiones sofisticado que permite seleccionar una de las 4 particiones manteniendo pulsada una tecla en el arranque, sin tener que andar ejecutando FDISK para seleccionar la partición activa... ¡lo que se puede hacer con 400 bytes de código!. Realmente, la arquitectura global de las particiones de un equipo (en particular si tiene más de 4, una mezcla de sistemas operativos y/o varios discos duros), puede llegar a ser compleja: practíquese con un buen editor de disco para aprender más (ej. el DISKEDIT de las Norton Utilities o las PC-Tools). Las particiones extendidas llevan su propio sector de partición adicional, en el que no hay código de programa sino, en su lugar, una lista de dispositivos. Hay dos entradas por cada dispositivo: la primera indica el tipo (1-FAT12, 4-FAT16); la segunda entrada apunta al siguiente dispositivo (caso de existir) o es 0 (no hay más dispositivos). El DOS 4.0 y posteriores eliminaron la limitación de los 32 Mb en las particiones y el software actual, ya actualizado, no da problemas con los discos de más de 32 Mb. Por ello, en discos de más de 32 ó 40 Mb lo normal es instalar DOS 4.0 ó superior. Formato del sector de arranque: En el sector de arranque, además del sencillo programa de puesta en marcha del sistema, hay cierta información útil acerca de las características del disco o partición. Los primeros 3 bytes no son significativos: contienen el código de operación de una instrucción JMP que salta a donde realmente comienza el código, aunque conviene que dicha instrucción de salto esté al principio del sector de arranque para que algunos sistemas validen dicho sector (es válido un salto corto seguido de NOP o un salto completo de 3 bytes). A partir del cuarto (offset 3) se puede encontrar la información válida. En el sector de arranque del disquete está contenido el BPB (Bios Parameter Block) que analizaremos más tarde. ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │
offset
│
offset 11 (1 palabra):
Bytes por sector, ej. 512.
│
│
offset 13 (1 byte):
Sectores por cluster (ej. 2)
│
│
offset 14 (1 palabra):
Sectores reservados al principio (1 en diquettes)
│
│
offset 16 (1 byte):
Número de copias de la FAT (2 normalmente)
│
│
offset 17 (1 palabra):
Número de entradas al directorio raíz (112 en discos de 360 Kb)
│
│
offset 19 (1 palabra):
Número total de sectores del disco (0 en discos de más de 32 Mb)
│
│
offset 21 (1 byte):
Byte de tipo de disco (véase tabla más adelante)
│
│
offset 22 (1 palabra):
Número de sectores ocupados por cada FAT
│
│
offset 24 (1 palabra):
Número de sectores por pista
│
│
offset 26 (1 palabra):
Número de cabezas (2 en disquetes de doble cara)
│
│
offset 28 (2 palabras):
Número de sectores especiales reservados. Nota: sólo se debe considerar la primera mitad de │
3 (8 bytes):
Identificación del sistema (ej., "IBM
│
3.3")
│
esta doble palabra en versiones del sistema 3.30 o anteriores (no hay problemas con DR-DOS, │
│
que en todas sus versiones, hasta la 6.0 incluida, es un DOS 3.31).
│
depende de la posición relativa que ocupe la partición dentro del disco duro (será 0 en los │
│
disquetes),
│
un número de sector de la BIOS.
│
Número total de sectores del disco en discos de más de 32 Mb (esta información sólo debe
│
│
obtenerse de aquí si la palabra ubicada en el offset 19 es cero).
│
│ offset 36 (1 byte):
Número de unidad física (a partir del DOS 4.0).
│
│ offset 37 (1 byte):
Reservado.
│
│ offset 38 (1 byte):
valor 29h desde DOS 4.0 (marca de validación que indica que los bytes ubicados desde el
│
│
offset 36 al offset 61 están definidos).
│
│ offset 39 (2 palabras):
Número de serie del disco (a partir de DOS 4.0).
│ offset 43 (11 bytes):
Título del disco (desde DOS 4.0); por defecto se inicializa con "NO NAME
│
el DOS 4.0 como el 5.0 y 6.X siguen empleando además las tradicionales etiquetas de volumen.│
│ offset 54 (8 bytes):
Sistema de ficheros (a partir de DOS 4.0): puede ser "FAT12
│
offset 32 (2 palabras):
El valor de este campo │
este valor ha de sumarse al del número de sector del DOS antes de traducirlo a │
│
" o
"FAT16
", aunque tanto │
".
│
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Formato del SECTOR DE ARRANQUE
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
El byte del tipo de disco (offset 21) intenta identificar el tipo de disco, aunque no lo consigue en muchos casos dada la ilógica utilización que se ha hecho de él. La recomendación es hacer lo que viene haciendo el DOS desde la 3.30: no hacer caso de lo que dice este byte para identificar los discos. La única excepción tal vez sea el valor 0F8h que identifica a los dispositivos no removibles:
┌─────────────────────────────────────────────────────────────────────┐ │ 0FEh - discos de 5¼-160 Kb (1 cara, 8 sectores/pista, 40 pistas)
│
│ 0FFh - discos de 5¼-320 Kb (2 caras, 8 sectores/pista, 40 pistas)
│
│ 0FCh - discos de 5¼-180 Kb (1 cara, 9 sectores/pista, 40 pistas)
│
│ 0FDh - discos de 5¼-360 Kb (2 caras, 9 sectores/pista, 40 pistas)
│
│ 0F9h - discos de 5¼-1,2 Mb (2 caras, 15 sectores/pista, 80 pistas)
│
│ 0F9h - discos de 3½-720 Kb (2 caras, 9 sectores/pista, 80 pistas)
│
│ 0F8h - discos duros y algunos virtuales
│
│ 0F0h - discos de 3½-1,44 Mb (2 caras, 18 sectores/pista, 80 pistas) │ │ 0F0h - discos de 3½-2,88 Mb (2 caras, 36 sectores/pista, 80 pistas) │ │ 0F0h - restantes formatos de disco
│
└─────────────────────────────────────────────────────────────────────┘
Tipos de Discos 7.6.3. - LA FAT. Después del sector de arranque, aparecen en el disco una serie de sectores que constituyen la Tabla de Localización de Ficheros (File Alocation Table o FAT). Consiste en una especie de mapa que indica qué zonas del disco están libres, cuáles ocupadas, dónde están los sectores defectuosos, etc. Normalmente hay dos copias consecutivas de la FAT (véase el offset 16 del sector de arranque), ya que es el área más importante del disco de la que dependen todos los demás datos almacenados en él. No deja de resultar extraño que ambas copias de la FAT estén físicamente consecutivas en el disco: si accidentalmente se estropeara una de ellas (por ejemplo, rayando con un bolígrafo el disco) lo más normal es que la otra también resultara dañada. En general, muchos programas de chequeo de disco no se molestan en verificar si ambas FAT son idénticas (empezando por algunas versiones de CHKDSK). Por otra parte, hubiera sido mejor elección haberla colocado en el centro del disco: dada la frecuencia de los accesos a la misma, de cara a localizar los diferentes fragmentos de los ficheros, ello mejoraría notablemente el tiempo de acceso medio. Aunque cierto es que los cachés de disco y los buffers del config.sys pueden hacer casi milagros... a costa de memoria. Antes de seguir adelante, conviene hacer un pequeño paréntesis y explicar el concepto de cluster: un cluster es la unidad mínima de información a la que accede el DOS, desde el punto de vista lógico. Normalmente consta de varios sectores (ver offset 13 del sector de arranque): dos en un disquete de 360 Kb, uno en un disquete de alta densidad, y entre 4 y 16 -normalmente- en un disco duro. El disco queda dividido, por tanto, en un cierto número de clusters. La FAT es realmente un mapa que contiene 12 ó 16 bits -como veremos- por cada cluster, indicando su estado: cluster libre: valor 0 cluster defectuoso: valores 0FF7h (ó 0FFF7h). cluster no utilizable: valores 0FF5 al 0FF6h (ó 0FFF5 al 0FFF6h). último cluster del fichero: valor 0FF8 al 0FFFh (ó 0FFF8h al 0FFFFh). otro valor: puntero al siguiente cluster del fichero.
Los ficheros en disco no siempre ocupan posiciones contiguas: normalmente están más o menos fragmentados debido a que se aprovechan los huecos dejados por otros ficheros borrados, de ahí el auge de los programas que compactan los discos con objeto de acelerar el acceso a los datos. Por tanto, cada fichero consta de un cluster inicial indicado en la entrada del directorio -como se verá- que inicia una cadena tan larga como la longitud del mismo (expresada en clusters), existiendo normalmente un valor 0FFFh ó 0FFFFh en el último cluster para señalar el final (del 0FF8h al 0FFEh y del 0FFF8h al 0FFFEh no se emplean). Consultando la FAT se puede determinar la ubicación de los fragmentos en que están físicamente divididos los ficheros en los discos,
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
así como qué zonas están aún disponibles y cuáles son defectuosas en el mismo. Los cluster se numeran a partir de 2, ya que las dos primeras entradas en la FAT están reservadas para el sistema. Los clusters hacen referencia exclusiva a la zona de datos: el área que va detrás del sector de arranque, la FAT y el directorio. Por ello, en un disquete de 360 Kb, con clusters de 1 Kb y 354 Kb libres para datos, hay 354 clusters (numerados de 2 a 355) y los 6 Kb misteriosos que faltan son el sector de arranque, las dos FAT y -como veremos después- el directorio raíz. Puede ser válida, por ejemplo, la siguiente FAT de 12 bits habiendo un fichero A que ocupe los clusters 2, 3, 5 y 6: Elemento de la FAT 0
Valor FFD
Interpretación El disco es de tipo 0FDh (despreciar restantes
FFF 003 005 FF7 006 FFF 013 ...
Entrada no utilizada El siguiente cluster del fichero A es El siguiente cluster del fichero A es Cluster defectuoso El siguiente cluster del fichero A es Este es el último cluster del fichero El siguiente cluster del fichero B es
bits) 1 2 3 4 5 6 7 ...
el 3 el 5 el 6 A el 013
Como se ve, el primer byte de la primera entrada a la FAT es inicializado con el mismo valor que el byte de tipo de disco del sector de arranque. Los restantes bits de las dos primeras entradas suelen estar todos a 1. Para determinar el número de clusters del disco, ha de restarse del número total de sectores la cifra correspondiente al número de sectores reservados (normalmente 1 en los disquetes, correspondiente al sector de arranque), los que ocupa la FAT y los empleados por el directorio raíz (que se verá más adelante); a continuación se divide ese número de sectores de datos resultante por el número de sectores por cluster. El hecho de emplear FAT's de 12 bits es debido a que con menos bits (ej., un byte) sólo podría haber unos 250 clusters en el disco. En un disco de 1,2 Mb ello significaría que la unidad mínima de información sería 1200/250 = 5 Kb: el fichero más pequeño (de 1 byte) ocuparía ¡5 Kb!. Empleando FAT's de 16 bits se podrían hacer clusters incluso de tamaño menor que el sector (menos de 512 bytes), aprovechando más el espacio del disco. Sin embargo, ello haría que la propia FAT ocupase demasiado espacio en el disco. Por ello, en los disquetes se emplean FAT's de 12 bits (1 byte y medio): para un programa en código máquina ello no ralentiza los cálculos (aunque al ser humano no se le de muy bien trabajar con medios bytes). En la práctica, se toman palabras de 16 bits y se desprecian los 4 bits más significativos en los clusters pares y los 4 menos significativos en los impares. A continuación se listan dos rutinas que permiten acceder a una FAT de 12 bits previamente cargada en memoria, con objeto de consultar o modificar alguna entrada. Evidentemente, después habrá que volver a grabar la FAT en disco, tantas veces como copias de la misma existan en éste. Las rutinas necesitan que la FAT esté completamente cargada en memoria, lo cual no es un requerimiento demasiado costoso, habida cuenta de que no puede ocupar más de 4085 * 1,5 = 6128 bytes. POPF
; ************ Escribir un elemento en una FAT de 12 bits
JC
poke_fat_imp
;
DS:BX = FAT completamente cargada en memoria
AND
AX,1111000000000000b
;
DX
JMP
poke_fat_ok
AND
AX,0000000000001111b
PUSH
CX
; Entrada: AX
= posición de dicho elemento
= nuevo valor de dicho elemento poke_fat_imp:
poke_fat
PROC
; preservar la otra entrada
; preservar la otra entrada
PUSH
AX
MOV
CL,4
PUSH
BX
SHL
DX,CL
PUSH
DX
POP
CX
ADD
BX,AX
; BX = BX + cluster
OR
AX,DX
; «mezclar»
SHR
AX,1
; AX = cluster / 2
MOV
[BX],AX
; nuevo valor en la FAT
; CF = 1 si impar
POP
DX
PUSHF
; preservar registros
poke_fat_ok:
ADD
BX,AX
; BX = BX + cluster * 1,5
POP
BX
MOV
AX,[BX]
; AX = palabra con dato 12 bits
POP
AX
; colocarlo: 4 bits a la izda
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
RET poke_fat
; retorno sin alterar registros
; ************ Leer un elemento de una FAT de 12 bits
ENDP
; Entrada: AX
peek_fat
= posición de dicho elemento
;
DS:BX = FAT completamente cargada en memoria
; Salida:
DX
= valor de dicho elemento
PROC PUSH
AX
PUSH
BX
ADD
BX,AX
; BX = BX + cluster
SHR
AX,1
; AX = cluster / 2
PUSHF
; preservar registros
; CF = 0 si par
ADD
BX,AX
MOV
DX,[BX]
; BX = BX + cluster * 1,5
POPF
peek_fat_par:
JNC
peek_fat_par
PUSH
CX
MOV
CL,4
SHR
DX,CL
POP
CX
AND
DH,00001111b
POP
BX
POP
AX
RET peek_fat
; DX=DX/16: si DX=xyz0, DX=0xyz
; borrar posible dígito izdo
; retornar sólo DX modificado
ENDP
Tal vez, en futuros disquetes de elevada capacidad sea necesario pasar a una FAT de 16 bits, aparecida con el DOS 3.0, que es la usada por todos los discos duros excepto el de 10 Mb del XT original de IBM. Con una FAT de 12 bits el nº de cluster más alto posible es 4085, que se corresponde con un disco de 4084 clusters (numerados de 2 a 4085). En principio, no existe ninguna manera sencilla de averiguar el tipo de FAT de un disco, ya que el fabricante olvidó incluir un byte de identificación al efecto. La documentación publicada es contradictoria en las diversas fuentes que he consultado, y en todas es por desgracia incorrecta (unos dicen que la FAT 16 comienza a partir de 4078 clusters, otros que a partir de 4086, otros confunden el número de clusters con el número más alto de cluster...). Sin embargo, todas las versiones del DOS comprobadas (MS-DOS 3.1, 3.3, 4.0, 5.0 y DR-DOS 5.0 y 6.0) operan con una FAT de 16 bits en discos de 4085 clusters (inclusive) en adelante; esto es, a partir de 4086 como número de cluster más alto. Esto puede verificarse fácilmente creando discos virtuales con 4084/4085 clusters, copiando algunos ficheros y mirando la FAT con algún programa de utilidad (a simple vista se distingue si las entradas son de 12 ó 16 bits). Por desgracia, salvo en MS-DOS 3.3 y en DR-DOS 6.0, los comandos CHKDSK del sistema consideran erróneamente que los discos de 4085, 4086 y 4087 clusters ¡poseen una FAT de 12 bits!, lo cual resulta además completamente absurdo, dado que 4087 (0FF7h) es la marca de cluster defectuoso en una FAT de 12 bits y ¡en ningún caso podría ser un número de cluster cualquiera!. Sin embargo, pese a este problema de CHKDSK, los discos con más de 4084 clusters han de ser diseñados con una FAT de 16 bit, ya que es mucho más grave tener problemas con el DOS que con CHKDSK. Otra solución es procurar no crear discos de ese número crítico de clusters, o confiar que el usuario no ejecute el casi olvidado CHKDSK sobre ellos. Por fortuna, los discos normales no están por ahora en la frontera crítica entre la FAT de 12 y la de 16 bits, aunque con los discos virtuales sí se pueden crear unidades con esos tamaños críticos: la casi totalidad de los discos virtuales del mercado tienen problemas en estos casos. En algunos discos duros se puede determinar también el tipo de FAT consultando la tabla de particiones, aunque no es el método más conveniente. Debe tener en cuenta el lector que manipular una FAT sin conocer su tipo supone destrozar la información almacenada en el disco. Sin embargo, tampoco hay que tener tanto miedo: lo que sí puede resultar peligroso es llegar al extremo de preguntar al usuario el tipo de FAT... Ahora puede surgir la pregunta: si la FAT mantiene una cadena que indica cómo está distribuido un fichero en el disco, ¿dónde se almacena el inicio de esa cadena, esto es, la primera entrada en la FAT del fichero?. 7.6.4.- EL DIRECTORIO RAÍZ.
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Inmediatamente después de la FAT y su(s) réplica(s) de seguridad viene el directorio raíz. Detrás de éste ya vienen los clusters conteniendo la información del disco propiamente dicha. El directorio consta de 32 bytes por cada fichero/subdirectorio (los subdirectorios no son más que un tipo especial de fichero). En los discos de 360 Kb, por ejemplo, el directorio se extiende a lo largo de 7 sectores (3584 bytes = 112 entradas como máximo). El tamaño y ubicación del directorio pueden obtenerse del sector de arranque, como se vio al principio. La información almacenada en los 32 bytes es la siguiente: ┌───────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────┐ │ offset 0 (8 bytes):
Nombre del fichero
│ │ bit 0: activo si el fichero es de sólo lectura
│
│ offset 8 (3 bytes):
Extensión del nombre del fichero
│ │ bit 1: activo si el fichero es oculto
│
│ offset 11 (1 byte):
Byte de atributos
│ │ bit 2: activo si el fichero es de sistema
│ offset 12 (10 bytes): Reservado (PASSWORD cifrada DR-DOS) │ │ bit 3: activo si esa entrada de directorio es │ offset 22 (2 bytes):
Hora*2048 + minutos*32 + segundos/2 │ │
│ offset 24 (2 bytes):
(año-1980)*512 + mes*32 + día
│ │ │
la etiqueta de volumen
│ │ bit 4: activo si es un subdirectorio
│
│ offset 26 (2 bytes):
Primera entrada en la FAT
│ │ bit 5: bit de archivo usado por BACKUP y RESTORE │
│ offset 28 (4 bytes):
Tamaño del fichero en bytes
│ │ bits 6,7: no utilizados
│
└───────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────┘ ENTRADA DE DIRECTORIO
BYTE DE ATRIBUTOS
En el byte de atributos, varios bits pueden estar activos a un tiempo. El atributo de sistema no tiene un significado en particular, es una reliquia heredada del CP/M (los ficheros ocultos del sistema lo tienen activo). En un mismo disco sólo puede haber una entrada con el bit 3 activo; además, en este caso se interpretan el nombre y la extensión como un único conjunto de 11 caracteres. Las entradas de tipo subdirectorio (bit 4 del byte de atributos activo) tienen un valor cero en el campo de tamaño (offset 28): el tamaño de un fichero subdirectorio está determinado por el número de entradas que ocupa en la FAT (en la práctica, esto sucede con cualquier otro fichero, aunque si no es de directorio en el offset 28 esta información se indica con precisión de bytes). El nombre del fichero puede comenzar por 0E5h, lo que indica que el fichero que estuvo ahí ha sido borrado. Si empieza por 2Eh (código ASCII del punto (.)) ó por 2Eh, 2Eh (dos puntos consecutivos) se trata de una entrada que referencia a un fichero subdirectorio. 7.6.5. - LOS SUBDIRECTORIOS. Como hemos visto, un subdirectorio en principio puede ser una simple entrada del directorio raíz. El subdirectorio, físicamente, es a su vez un fichero un tanto especial: contiene datos binarios ... que son nada más y nada menos que otras entradas de directorio para otros ficheros, de 32 bytes como siempre. Dentro de cada subdirectorio hay al menos dos entradas especiales: un fichero con un nombre punto (.) que referencia al propio subdirectorio -que así puede autolocalizarse- y otro con doble punto (..) que referencia al directorio padre -del que cuelga- siendo posible, gracias a ello, retroceder cuanto se desee por el árbol de directorios sin necesidad de que todos los caminos partan del raíz. Si la primera entrada en la FAT del fichero (..) es un 0, quiere decir que ese subdirectorio cuelga del raíz, de lo contrario apuntará al primer cluster del fichero subdirectorio padre. El tamaño de un fichero subdirectorio es ilimitado -sin exceder, evidentemente, la capacidad del disco-. Por ello, en un subdirectorio puede haber una gran cantidad de ficheros (muchos más de 112 ó 500) sin problemas. Cada fichero que se crea en un subdirectorio aumenta el tamaño del fichero subdirectorio en 32 bytes. Por ello, en un disco de 360 Kb (354 Kb libres) se puede crear un subdirectorio y en él se pueden introducir, en caso extremo, 11326 ficheros (más el (.) y el (..)) de tamaño cero que paradójicamente llenarían el disco (recordar que cada entrada al directorio ocupa 32 bytes). Normalmente nadie suele cometer esos excesos. Si en un subdirectorio había demasiados ficheros y se borra una buena parte de los mismos, el tamaño del fichero subdirectorio debería reducirse, pero en la práctica el DOS no se ocupa de estas pequeñeces, habida cuenta de que los ficheros subdirectorio son unos pequeños islotes en el gran océano disco (los usuarios más tacaños siempre pueden optar por crear un nuevo subdirectorio y mover todos los ficheros a él, borrando el anterior para recuperar el espacio libre).
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
Considerando el nombre completo de un fichero, con toda la trayectoria de directorios, el proceso a seguir para localizarlo en el disco es ir recorriendo los ficheros subdirectorio de uno en uno, hasta llegar al fichero subdirectorio donde está registrado el fichero y, en la posición correspondiente, obtener su punto de entrada en la FAT. Dicho sea de paso, tal vez sea una pena que el disco no conste de un único «fichero raíz» privilegiado de directorio, que podríamos denominar «subdirectorio raíz». Ello permitiría también un número ilimitado de entradas (en vez de 112, 224, etc.) y sería más lógico que una ristra de sectores. Sin embargo, esta peculiar circunstancia también aparece en otros sistemas operativos, como el UNIX. Sus motivos tendrá. 7.6.6. - EL BPB Y DPB. El BPB (Bios Parameter Block) es una estructura de datos que contiene información relativa a la unidad de disco. El BPB es una pieza vital en los controladores de dispositivo de bloques, como veremos en un futuro capítulo, por lo que a continuación se expone su contenido (idéntico a una parte del sector 0): ┌───────────────────────────────────────────────────────────────────────────┐ │
offset
0
DW bytes_por_sector
│
│
offset
2
DB sectores_por_cluster
│
│
offset
3
DW sectores_reservados_al_comienzo_del_disco
│
│
offset
5
DB número_de_FATs
│
│
offset
6
DW número_de_entradas_en_el_directorio_raíz
│
8
│
offset
│
offset 10
DB byte_descriptor_de_medio
DW número_total_de_sectores (0 con nº de sector de 32 bits) │ │
│
offset 11
DW numero_de_sectores_por_FAT
│
│
-- A partir del DOS 3.0:
│
│
offset 13
│
DW sectores_por_pista
│
offset 15
DW número_de_cabezas
│
│
offset 17
DD número_de_sectores_ocultos
│
│
-- A partir del DOS 4.0 (más bien DOS 3.31)
│
│
offset 21
DD número_de_sectores (unidades con direccionamiento de
│
offset 25
DB 6 DUP (?)
│ │
sector de 32 bits) (6 bytes no documentados)
El DOS convierte internamente el BPB en DPB (Drive Parameter Block), una estructura similar con más información útil. Para obtener el DPB de una unidad determinada, puede utilizarse la función 32h del DOS, Get Drive Parameter Block (indocumentada); la cadena de DPBs del DOS puede recorrerse a partir del primer DPB (obtenido con la función 52h del DOS, Get List of Lists, también indocumentada).
│ │
│
offset 31
DW número_de_cilindros
│
│
offset 33
DB tipo_de_dispositivo
│
│
offset 34
DW atributos_del_dispositivo
│
└───────────────────────────────────────────────────────────────────────────┘
7.6.7. - LA BIOS Y LOS DISQUETES. Resulta interesante conocer el comportamiento de la BIOS en relación a los disquetes, ya que las aplicaciones desarrolladas bajo DOS de una u otra manera habrán de cooperar con la BIOS por razones de compatibilidad (o al menos respetar ciertas especificaciones). El funcionamiento del disquete se controla a través de funciones de la INT 13h, aunque esta interrupción por lo general acaba llamando a la INT 40h que es quien realmente gestiona el disco en las BIOS modernas de AT. Las funciones soportadas por esta interrupción son: reset del sistema de disco (reset del controlador de disquetes, envío del comando specify y recalibramiento del cabezal), consulta del estado del disco (obtener resultado de la última operación), lectura, escritura y verificación de sectores, formateo de pistas, obtención de información del disco y las disqueteras, detección del cambio de disco, establecimiento del tipo de soporte para formateo... algunas de estas últimas funciones no están disponibles en las máquinas PC/XT. La BIOS se apoya en varias variables ubicadas en el segmento 40h de la memoria. Estas variables son las siguientes (para más información, consultar el apéndice al final del libro): Byte 40h:3EhEstado de recalibramiento del disquete. Esta variable indica varias cosas: si se ha producido una interrupción de disquete, o si es preciso recalibrar alguna disquetera debido a un reset anterior. Byte 40h:3FhEstado de los motores. En esta variable se indica, además del estado de los motores de las 4 posibles disqueteras (si están encendidos o no), la última unidad que fue seleccionada y la operación en curso sobre la misma. Byte 40h:40hCuenta para la detención del motor. Este byte es decrementado por la interrupción periódica del temporizador; cuando llega a 0
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
todos los motores de las disqueteras (realmente, el único que estaba girando) son detenidos. Dejar el motor girando unos segundos tras la última operación evita tener que esperar a que el motor acelere antes de la siguiente (si esta llega poco después). Byte 40h:41hEstado de la última operación: se actualiza tras cada acceso al disco, indicando los errores producidos (0 = ninguno). Bytes 40h:42hA partir de esta dirección, 7 bytes almacenan el resultado de la última operación de disquete o disco duro. Se trata de los 7 bytes que devuelve el NEC765 tras los principales comandos. Byte 40h:8BhControl del soporte (AT). Esta variable almacena, entre otros, la última velocidad de transferencia seleccionada. Byte 40h:8FhInformación del controlador de disquete (AT). Se indica si la unidad soporta 80 cilindros (pues sí, la verdad) y si soporta varias velocidades de transferencia. Byte 40h:90hEstado del soporte en la unidad A. Se indica la velocidad de transferencia a emplear en el disquete introducido en esta unidad, si precisa o no saltos dobles del cabezal (caso de los disquetes de 40 cilindros en unidades de 80), y el resultado de los intentos de la BIOS (la velocidad puede ser correcta o no, según se haya logrado determinar el tipo de soporte). Byte 40h:91hLo mismo que el byte anterior, pero para la unidad B. Byte 40h:92hEstado del soporte en la unidad A al inicio de la operación. Byte 40h:93hEstado del soporte en la unidad B al inicio de la operación. Byte 40h:94hNúmero de cilindro en curso en la unidad A. Byte 40h:95hNúmero de cilindro en curso en la unidad B.
Además de estas variables, la BIOS utiliza también una tabla de parámetros apuntada por la INT 1Eh. Los valores para programar ciertas características del FDC según el tipo de disco pueden variar, aunque algunos son comunes. Esta tabla determina las principales características de operación del disco. Dicha tabla está inicialmente en la ROM, en la posición 0F000h:0EFC7h de todas las BIOS compatibles (prácticamente el 100%), aunque el DOS suele desviarla a la RAM para poder actualizarla. El formato de la misma es: byte 0:Se corresponde con el byte 1 del comando 'Specify' del 765, que indica el step rate (el tiempo de acceso cilindrocilindro, a menudo es 0Dh = 3 ó 6 ms) y el head unload time (normalmente, 0Fh = 240 ó 480 ms). byte 1:Es el byte 2 del comando 'Specify': los bits 7..1 indican el head load time (normalmente 01h = 2 ó 4 ms) y el bit 0 suele estar a 0 para indicar modo DMA. byte 2:Tics de reloj (pulsos de la interrupción 8) que transcurren tras el acceso hasta que se para el motor. byte 3:Bytes por sector (0=128, 1=256, 2=512, 3=1024).
byte 4:Sectores por pista. byte 5:Longitud del GAP entre sectores (normalmente 2Ah en unidades de 5¼ y 1Bh en las de 3½). byte 6:Longitud de sector (ignorado si el byte 3 no es 0). byte 7:Longitud del GAP 3 al formatear (80 en 5¼ y 3½-DD, 84 en 5¼-HD y 108 en 3½-HD). byte 8:Byte de relleno al formatear (normalmente 0F6h). byte 9:Tiempo de estabilización del cabezal en ms. byte 10:Tiempo de aceleración del motor (en unidades de 1/8 de segundo).
El tiempo de estabilización del cabezal es el tiempo que hay que esperar tras mover el cabezal al cilindro adecuado, hasta que éste se asiente, con objeto de garantizar el éxito de las operaciones futuras; esta breve pausa es establecida en 25 milisegundos en la BIOS del PC original, aunque otras BIOS y el propio DOS suelen bajarlo a 15. Del mismo modo, el tiempo de aceleración del motor (byte 10) es el tiempo que se espera a que el motor adquiera la velocidad de rotación correcta, nada más ponerlo en marcha. En cualquier caso, es norma general intentar tres veces el acceso a disco (con resets de por medio) hasta considerar que un error es real. En general, pese a estos valores usuales, la flexibilidad del sistema de disco es extraordinaria y suele responder favorablemente con unos altísimos niveles de tolerancia en las temporizaciones. Una excepción quizá la constituye el valor de GAP empleado al formatear, al ser un parámetro demasiado importante. 7.6.8. - DISQUETES FLOPTICAL 3½ DE 20 MB. Las unidades que soportan estos disquetes, que también admiten los de 720K y 1.44M (aunque a menudo no los de 2.88M) trabajan con controladoras SCSI e incorporan una BIOS propia para dar soporte a estos dispositivos. El secreto de estos disquetes está en el posicionamiento óptico del cabezal, lo que permite elevar notablemente el número de pistas. Por ejemplo, las unidades de 20 Mb parecen estar equipadas con 753 cilindros y 27 sectores/pista. Aunque en el sector de arranque indica que posee 251 cilindros y 6 cabezales, el sentido común nos permite deducir que esto no puede ser así. Lo de los 27 sectores por pista parece indicar que la velocidad de transferencia de estos disquetes es exactamente un 50% mayor que la de los convencionales de 1.44M (750 Kbit/seg frente a 500 Kbit/seg). El FORMAT del DOS 5.0 y posteriores puede formatear los disquetes floptical, pero lo hace a bajo
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
nivel, con lo que tarda cerca de 30-45 minutos en inicializarlos. Como ya vienen formateados de fábrica, en realidad basta con añadirles un sector de arranque e inicializar la FAT y el directorio raíz. También se puede verificar la superficie magnética para detectar posibles sectores defectuosos. Los programas de utilidad que acompañan estas unidades realizan todas estas tareas en unos 4 minutos. El tipo de FAT asignado puede ser seleccionado por el usuario (12 ó 16 bits), así como otros parámetros técnicos (tamaño de clusters, etc.). Las tarjetas controladoras suelen permitir un cierto grado de flexibilidad, de cara a seleccionar la letra de unidad que se desea asignar al floptical. Configurándolo como A: se puede incluso arrancar desde un disquete de éstos. 7.6.9. - EJEMPLO DE ACCESO AL DISCO A ALTO NIVEL. Se puede acceder a varios niveles, siendo mejor el más alto por razones de compatibilidad: 1) Programando directamente el controlador de disquetes/disco duro para acceder a sectores físicos. 2) Llamando a la BIOS para leer cierto sector, de cierta cara y cierto cilindro. 3) Llamando al DOS para leer un sector lógico determinado en la unidad que se le indique. 4) Llamando al DOS para acceder a un fichero por su nombre y ruta. El método (1) es apropiado para realizar formateos especiales en sistemas de protección anticopia; el (2) es útil para acceder a otras particiones de otros sistemas operativos o a disquetes formateados por otros sistemas operativos; las opciones (3) y (4) son las más cómodas e interesantes. En general, en la medida de lo posible es conveniente no bajar del nivel (3); de lo contrario se pierde la posibilidad de acceder a ciertas unidades (por ejemplo, un disco virtual no existe en absoluto para la BIOS). A continuación se muestra un programa de ejemplo que solicita el nombre de un fichero y lo visualiza por pantalla, cargándolo por fragmentos y apoyándose en las funciones del DOS que se comentan en el apéndice que resume las funciones del sistema operativo. Paradójicamente, el acceso se realiza a alto nivel pese a tratarse de un programa en ensamblador. Como se puede observar, al final del programa se definen dos buffers de datos de 80 y 2048 bytes. Si no se desea que estos buffers alarguen el tamaño del programa ejecutable, pueden definirse de la siguiente manera: fichnomEQU buffer
$ EQU
$+80
Sin embargo, si se procede de esta última manera convendría asegurarse primero de que existen 2128 bytes de memoria libres tras el código del programa, ya que de esta manera el DOS no realiza la comprobación por nosotros (se limita a cargar cualquier programa que quepa en memoria). De todas maneras, normalmente suele haber más de 2128 bytes libres de memoria tras cargar cualquier programa... Conviene hacer notar que si en lugar de DUP (0) se coloca DUP (?), el linkador de Borland (TLINK 3.0), al contrario que el LINK de Microsoft, TAMPOCO reserva espacio efectivo para esas variables. Esto sólo sucede, lógicamente, cuando el DUP (?) está al final del programa y no hay nada más a continuación -ni más código ni datos que no sean DUP (?)-. ; ********************************************************************
MOV
AH,9
; función de impresión
; *
*
INT
21h
; llamar al DOS
*
LEA
DX,fichnom
; dirección para el «input»
*
MOV
BYTE PTR [fichnom],60
; ********************************************************************
MOV
AH,10
; función de entrada de teclado
; *
MIRA.ASM
-
Utilidad para visualizar ficheros de texto.
; *
mira
; no más de 60 caracteres
INT
21h
; llamar al DOS
SEGMENT
MOV
BL,[fichnom+1]
; longitud efectiva tecleada
ASSUME CS:mira, DS:mira
MOV
BH,0
; en BX
ADD
BX,OFFSET fichnom ; apuntar al final
ORG
100h
; programa de tipo .COM
MOV
BYTE PTR [BX+2],0 ; poner un cero al final
LEA
DX,input_txt
; mensaje
LEA
DX,fichnom+2
inicio: ; offset a cadena ASCIIZ nombre
103
trocito:
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
MOV
AL,0
; modo de lectura
MOV
CX,AX
; bytes leídos realmente
MOV
AH,3Dh
; función para abrir fichero
JCXZ
cerrar
; no hay nada que imprimir
INT
21h
; llamar al DOS
PUSH
AX
; preservarlos
JC
error
; CF=1 --> error
LEA
BX,buffer
; imprimir buffer ...
MOV
handle,AX
; código de acceso al fichero
MOV
DL,[BX]
; carácter a carácter
MOV
AH,2
; ir llamando al servicio 2 del
imprime:
MOV
BX,handle
; código de acceso al fichero
INT
21h
; DOS para imprimir en pantalla
MOV
CX,2048
; número de bytes a leer
INC
BX
; siguiente carácter
LEA
DX,buffer
; dirección del buffer
LOOP
imprime
; acabar caracteres
MOV
AH,3Fh
; función para leer del fichero
POP
AX
; recuperar nº de bytes leídos
INT
21h
; llamar al DOS
CMP
AX,2048
; ¿leidos 2048 bytes?
JC
error
; CF=1 --> error
JE
trocito
; sí, leer otro trocito más
MOV
BX,handle
; código de acceso al fichero
MOV
AH,3Eh
; cerrar fichero
INT
21h
; llamar al DOS
JC
error
; CF = 1 --> error
INT
20h
; fin del programa
LEA
DX,fallo_txt
; mensaje de error
MOV
AH,9
; función de impresión
INT
21h
; llamar al DOS
CMP
handle,0
; ¿fichero abierto?
JNE
cerrar
; sí: cerrarlo
INT
20h
; fin del programa
cerrar:
error:
; ------------ datos y variables
handle
DW
0
input_txt
DB
13,10,"Nombre del fichero: $"
fallo_txt
DB
13,10,"*** Error ***",13,10,10,"$"
fichnom
DB
80 DUP (0)
buffer
DB
2048 DUP (0) ;
mira
ENDS END
; handle de control del fichero
; buffer para leer desde el teclado "
"
"
"
el disco
inicio
7.6.10. - EJEMPLO DE ACCESO AL DISCO A BAJO NIVEL. El programa de ejemplo desarrollado requiere un adaptador VGA ya que utiliza el modo de 640 por 480 con 16 colores para obtener una representación gráfica de alta calidad del contenido del disco, en lugar de la tradicional y pobre representación habitual en modo texto. Además, se reprograman los registros de paleta y el DAC de la VGA para elegir colores más atractivos. El funcionamiento del programa se basa en acceder a la FAT y crear una imagen gráfica de la misma. Para ello, calcula cuantos puntos de pantalla debe trazar por cada cluster de disco (utiliza una ventana de 636x326 = 207336 puntos). Aunque este número no es entero, por razones de eficiencia se trabaja con fracciones para evitar el empleo de coma flotante. Muchas veces el ensamblador no es suficiente para asegurar la velocidad: la primera versión del programa tardaba 18 segundos en dibujar un mapa en un 386-25, con una rutina escrita en su mayor parte en ensamblador. Tras mejorar el algoritmo y optimizar el código en la zona crítica donde se trazan los puntos, se redujo a menos de 0,66 segundos el tiempo necesario (¡314000 puntos por segundo a 25 MHz!). Para leer los sectores del disco no se utiliza la función absread() del Borland C 2.0, ya que posee una errata por la que falla con unidades de más de 32767 clusters. En su lugar, una rutina en ensamblador se encarga de llamar a la interrupción 25h teniendo cuidado con el tipo de disco (particiones de más de 32 Mb o de menos de esa cantidad). La FAT se lee en una matriz, ya que no ocupa más de 128 Kb en el peor de los casos. Se lee de tres veces para evitar que en un sólo acceso a disco, vía INT 25h, se rebasen los 64 Kb permitidos si la FAT ocupa más de 64 Kb (el puntero al buffer apunta al inicio del segmento al ser de tipo HUGE). A continuación, se interpreta la FAT (según sea de 12 ó 16 bits) y se crea otra matriz de tamaño equivalente al número de clusters del disco. Esta última matriz -
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
que indica los clusters libres, ocupados y defectuosos- es la que se volcará en pantalla adecuadamente. El programa también imprime información general sobre el disco, utilizando la función de impresión de la BIOS. Se imprime todo lo necesario antes de dibujar ya que para trazar los puntos es preciso programar el adaptador de vídeo de una manera diferente a la que emplea la BIOS (por razones de velocidad): después de ejecutar prepara_punto(), la BIOS no es capaz de escribir en pantalla. La inclusión de ensamblador en los programas en C se verá con detalle en un capítulo posterior. /********************************************************************
int
existe_vga(), info_disco(), leesect(), HablaSp();
int
sp, unidad, tamcluster, sectfat,
/ /*
tsect, scr_ok=0, modo, cb, pag, cur_x, cur_y;
*/ /*
DMAP
2.1
-
Utilidad
de
información
gráfica
de
discos.
unsigned long numsect, inifat, tamfat;
*/
unsigned numclusters, clusters_datos, clusters_malos;
/*
unsigned char huge *boot, huge *fat, huge *bitfat, far *scrbuf;
*/ /*
(c) Julio 1994 Ciriaco García de Celis.
*/
void main(int argc, char **argv)
/*
{ sp=HablaSp();
*/ /*
/* determinar idioma del país */
Compilar con Borland C++ en modelo large con cb=0;
*/ /*
la
opción
«Jump
optimization»
if (!strcmp(strupr(argv[argc-1]),"/I")) cb++;
desactivada.
/* parámetro /I */
*/ /*
sp^=cb;
*/
if (argc>cb+1) unidad=(*argv[1] | 0x20)-'a';
/********************************************************************
else
/
unidad=getdisk();
#include
preservar_pantalla (&scrbuf,&modo,&pag,&cur_x,&cur_y,&scr_ok,&cb);
#include
if (!existe_vga()) salida_error (1);
#include
if ((boot=farmalloc(2048L))==NULL) salida_error (2);
#include
if (leesect(unidad, 1, 0L, boot)!=0) salida_error (3);
#include
if (!info_disco (boot, &numsect, &numclusters, &tamcluster, &inifat, §fat, &tamfat, &tsect)) salida_error(5); if ((fat=farmalloc(tamfat))==NULL) salida_error (2);
#define C_PACIENCIA
78
/* colores */
#define C_PACIENCIAM
9
#define C_NEGRO
0
/* VGA negro */
aviso_espera();
#define C_CABECERA
1
/* VGA oro */
carga_fat (fat, inifat, sectfat, tsect);
#define C_TITULOS
2
/* VGA rojo */
genera_bitfat (fat, bitfat, numclusters);
#define C_INFO
3
/* VGA naranja */
#define C_LEYENDA
4
/* VGA azul claro */
#define C_MARCO
5
/* VGA amarillo */
init_video();
#define C_OCUPADA
6
/* VGA verde oscuro */
prepara_paleta();
#define C_LIBRE
7
/* VGA verde claro */
informe_disco (unidad, boot, numsect,
#define C_ERRONEA
8
/* VGA verde muy oscuro */
if ((bitfat=farmalloc((long)numclusters))==NULL) salida_error (2);
analiza_fat
(bitfat,
numclusters,
&clusters_datos,
&clusters_malos);
clusters_datos, clusters_malos); leyendas (numclusters, clusters_datos, clusters_malos); prepara_punto();
#define MODO
0x12
#define MIN_X
2
/* modo de vídeo */
#define MAX_X
637
#define MIN_Y
152
pinta_fat (bitfat, numclusters);
#define MAX_Y
477
if (!getch()) getch();
marco(); while (kbhit()) getch();
/* ventana de dibujo de FAT */
restaurar_pantalla (scrbuf,modo,pag,cur_x,cur_y,scr_ok,cb); } void preservar_pantalla(), restaurar_pantalla(), init_video(), aviso_espera(), carga_fat(), escribir(), salida_error(), dec2str(), porc2str(), genera_bitfat(), analiza_fat(),
void preservar_pantalla(char far **scrbuf, int *modo, int *pag, int
informe_disco(), leyendas(), marco(), pinta_fat(), prepara_punto(), punto(), prepara_paleta();
*colorbits)
*cx,
int
*cy,
int
*scr_ok,
int
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
{ *scr_ok=0;
/* supuesto que no va a ser posible */
*modo=peekb(0x40, 0x49);
void prepara_paleta()
if (((*modo<=3)||(*modo==7))&&((*scrbuf=farmalloc(4096L))!=NULL)) {
{
*scr_ok=1;
struct REGPACK r;
if (*modo==7)
char i, paleta[17]; static unsigned char dac[][3] = {
movedata(0xb000,0,FP_SEG(*scrbuf),FP_OFF(*scrbuf),4096);
/* R
G
B */
0,
0},
/* VGA negro */
{63, 42,
0},
/* VGA oro */
*pag=peekb(0x40,0x62);
{63, 16,
0},
/* VGA rojo */
*cx=peekb(0x40,0x50+(*pag)*2); *cy=peekb(0x40,0x51+(*pag)*2);
{63, 32,
0},
/* VGA naranja */
*colorbits=peek(0x40, 0x10) & 0x30;
{ 0, 40, 63},
/* VGA azul claro */
}
{63, 63,
0},
/* VGA amarillo */
{ 0, 48,
0},
/* VGA verde oscuro */
{ 0, 63,
0},
/* VGA verde claro */
{ 0, 28,
0}
/* VGA verde muy oscuro */
else
{ 0,
movedata(0xb800,peek(0x40,0x4e), FP_SEG(*scrbuf),FP_OFF(*scrbuf),4096);
}
};
void restaurar_pantalla(char far *scrbuf, int modo, int pag, int cx, int cy, int scr_ok, int colorbits)
r.r_ax=0x1013; r.r_bx=0x0100;
{ struct REGPACK r;
intr (0x10, &r);
/* DAC: 16 bloques de 16 elementos */
poke (0x40, 0x10, peek(0x40, 0x10) & 0xFFCF | colorbits);
r.r_ax=0x1013; r.r_bx=1;
if (scr_ok) {
intr (0x10, &r);
/* página 0: paleta en elementos 0..15 del DAC */
if (modo!=peekb(0x40,0x6c)) { r.r_ax=modo; intr (0x10, &r); } r.r_ax=0x500+pag; intr (0x10, &r);
/* restaura página activa
*/
for (i=0; i<16; i++) paleta[i]=i;
/* índices correctos */
paleta[16]=0;
/* borde negro */
if (modo==7) r.r_es=FP_SEG(paleta); r.r_dx=FP_OFF(paleta);
movedata(FP_SEG(scrbuf),FP_OFF(scrbuf),0xb000,0,4096);
r.r_ax=0x1002; intr (0x10, &r);
else
/* establecer paleta y borde */
movedata(FP_SEG(scrbuf),FP_OFF(scrbuf), r.r_bx=0;
/* primer elemento del DAC */
r.r_ax=0x200; r.r_bx=pag<<8; r.r_dx=cy<<8+cx; intr (0x10, &r);
r.r_cx=9;
/* número de elementos a definir */
farfree(scrbuf);
r.r_es=FP_SEG(dac); r.r_dx=FP_OFF(dac);
0xb800,peek(0x40,0x4e),4096);
r.r_ax=0x1012; intr (0x10, &r);
}
/* programar elementos del DAC */
}
else { r.r_ax=modo; intr (0x10, &r); } /* imposible reponer pantalla */
void aviso_espera()
}
{ int cx;
int existe_vga()
if (modo>1) cx=25; else cx=4;
/* devolver condición cierta si hay VGA */
escribir (cx, 12, C_PACIENCIA,"╔══════════════════════════════╗");
{
escribir (cx, 13, C_PACIENCIA,
struct REGPACK r;
sp?"║ ANALIZANDO AREAS DEL SISTEMA ║": "║
r.r_ax=0x1A00; intr (0x10, &r);
PROCESSING SYSTEM AREAS
║");
escribir (cx+32, 13, C_PACIENCIAM, "█");
return ((r.r_ax & 0xFF)==0x1A);
escribir (cx, 14, C_PACIENCIA,"╚══════════════════════════════╝");
}
escribir (cx+32, 14, C_PACIENCIAM, "█"); escribir void init_video()
(cx+1,15,C_PACIENCIAM,"████████████████████████████████");
{
} struct REGPACK r;
/* forzar modo color */
void carga_fat (unsigned char huge *fat, long inifat, int sectfat, int tsect)
poke (0x40, 0x10, peek (0x40, 0x10) & 0xFFCF | 0x20); /* establecer modo 640x480x16 */ r.r_ax=MODO; intr (0x10, &r); }
{ int parte1, parte2, parte3;
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
nclus = nsect / boot[0x0D];
parte1=(sectfat+2)/3;
if (nclus>65535L) salida_error (4);
parte2=(sectfat-parte1)/2; parte3=sectfat-parte1-parte2;
*numclusters = nclus;
/* la FAT se carga de tres veces */
*tamcluster = (*tamsect) * boot[0x0D];
if (parte1)
*bytesfat=(long) (*sectfat) * (*tamsect);
if (leesect(unidad, parte1, inifat, fat)!=0) salida_error (3);
return (1);
if (parte2)
/* retorno correcto */
}
if (leesect(unidad, parte2, inifat+parte1, }
fat + (unsigned long) parte1 * tsect)!=0) salida_error (3); if (parte3) if (leesect(unidad, parte3, inifat+parte1+parte2, fat + (unsigned long) (parte1+parte2) * tsect)!=0) salida_error (3);
void salida_error(int error) {
}
restaurar_pantalla (scrbuf,modo,pag,cur_x,cur_y,scr_ok,cb); switch (error) { case 1: printf (sp?"\n
void escribir (int cx, int cy, int color, unsigned char *cadena)
"\n
{
Este programa requiere adaptador VGA.\n": This program requires VGA adaptor.\n");
break;
struct REGPACK r; unsigned char *p, pagina;
case 2: printf (sp?"\n
Memoria insuficiente.\n":
"\n
Insufficient memory.\n");
unsigned char far *cursor_x; break;
case 3: printf (sp?"\n
pagina = peekb(0x40, 0x62); r.r_ax=0x200; r.r_bx = (pagina << 8); r.r_dx=0xFF00;
de red.\n":
intr (0x10, &r);
drive.\n");
Unidad incorrecta, no preparada, HPFS o
"\n /* eliminar cursor de la pantalla */
Incorrect, not ready, HPFS or network
break; case 4: printf (sp?"\n
cursor_x = MK_FP (0x40, 0x50 + (pagina <<1) );
"\n
poke (0x40, 0x50 + (pagina << 1), (cy << 8) + cx);
Sólo soportados sistemas FAT12/FAT16.\n":
Only supported FAT12/FAT16 filesystems.\n");
break; case 5: printf (sp?"\n
p=cadena;
Sector de arranque dañado, imposible
informar.\n":
while (*p) {
"\n
r.r_ax=0x900 | *p; r.r_bx = (pagina << 8) | color; r.r_cx=1;
Boot record damaged, impossible to analyze
drive.\n");
intr (0x10, &r);
break;
(*cursor_x)++; }
p++;
exit (error);
} }
}
void dec2str (char *cadena, unsigned long num, int longitud)
int info_disco (unsigned char *boot, unsigned long *numsect,
{
unsigned *numclusters, int *tamcluster, unsigned long *inifat, int *sectfat,
unsigned long div;
unsigned long *bytesfat, int *tamsect)
int i, coma;
{ switch (longitud) {
unsigned long nclus, nsect; *tamsect = boot[0x0B] | ((int) boot[0x0C] << 8);
case 13: coma=1; div=1000000000L; break;
*numsect = boot[0x13] | ((unsigned long) boot[0x14] << 8);
case
if (!*numsect) *numsect=(long) boot[0x20] | (long) boot[0x21]<<8
}
6: coma=2; div=10000L; break;
for (i=0; i
| (long) boot[0x22]<<16 | (long) boot[0x23]<<24;
if (i==coma) { cadena[i]='.'; coma+=4; i++;
*sectfat=boot[0x16] | (int) boot[0x17] << 8;
}
*inifat=boot[0x0E] | (int) boot[0x0F] << 8;
cadena[i]=num/div+'0'; num%=div; if
((*tamsect<32)
||
(numsect==0)
||
(boot[0x0D]==0)
}
||
cadena[i]=0;
(*sectfat==0)) return (0);
while (((*cadena=='0') || (*cadena=='.')) && (*(cadena+1)))
/* retorno con error */
*cadena++=' ';
else { }
nsect=*numsect - (*inifat) - (*sectfat) * boot[0x10] (boot[0x11] *tamsect;
|
(int)
boot[0x12]
<<
8)
*
32
/
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
else
void porc2str (char *cadena, int num)
if (elemento == C_ERRONEA) (*clusters_malos)++;
{
*clusters_datos=numclusters-libres-(*clusters_malos);
cadena[0]=num/10000 | '0'; num%=10000; cadena[1]=num/1000
| '0'; num%=1000;
cadena[2]=num/100
| '0'; num%=100;
}
if (sp) cadena[3]=','; else cadena[3]='.'; cadena[4]=num/10
void informe_disco (int unidad, unsigned char *boot,
| '0';
unsigned long numsect, unsigned datos, unsigned malos)
if (cadena[0]=='0') { {
cadena[0]=' '; if (cadena[1]=='0') cadena[1]=' ';
char id[17], c;
}
int tamsect, sectpista, numcaras, sectfat, sectcluster, i;
} tamsect = boot[0x0B] | (int) boot[0x0C] << 8; sectpista = boot[0x18] | (int) boot[0x19] << 8; void genera_bitfat (unsigned char huge *fat, unsigned char huge *bitfat, unsigned numclusters)
numcaras = boot[0x1A] | (int) boot[0x1B] << 8; sectfat = boot[0x16] | (int) boot[0x17] << 8; sectcluster = boot[0x0D];
{ unsigned int fat16=0, elemento, pos;
escribir (0, 0, C_CABECERA, sp?
unsigned i;
"─────────
DMAP
2.1
if (numclusters>4084) fat16++;
unidad A: ─────────":
for (i=2; i
report ─────────");
"───────── DMAP 2.1
(c)
Julio
1994
CiriSOFT
────────
Informe
(c) July 1994 CiriSOFT ─────────── Drive A:
if (fat16) { elemento = fat[(long)i<<1] | (fat [((long)i<<1)|1] << 8);
id[0]=(char) unidad + 'A'; id[1]=0;
if (!elemento)
escribir (sp?68:61, 0, C_CABECERA, id);
bitfat[i-2]=C_LIBRE;
/* cluster libre */ escribir (0, 1, C_TITULOS, sp?"ID sistema:
else
bitfat[i-2]=C_ERRONEA;
":"System ID:
/* cluster defectuoso */
escribir (15, 1, C_INFO, id); escribir (0, 2, C_TITULOS, sp?"Byte de Medio: ":"Media byte:
else bitfat[i-2]=C_OCUPADA;
");
for (i=3; i<11; i++) id[i-3]=boot[i]; id[8]=0;
if (elemento == 0xFFF7)
/* cluster ocupado */
");
c=boot[0x15] >> 4 | '0'; if (c>'9') c+=7; id[0]=c; c=boot[0x15] & 0x0F | '0'; if (c>'9') c+=7; id[1]=c; id[2]=0;
}
escribir (19, 2, C_INFO, id);
else /* FAT12 */ { pos = (i*3L) >> 1;
escribir (0, 3, C_TITULOS, "Bytes/sector:
if (i & 1)
dec2str (id, tamsect, 6);
elemento = (fat[pos] >> 4) | (fat[pos+1L] << 4);
");
escribir (15, 3, C_INFO, id); escribir (0, 4, C_TITULOS, sp?"Cilindros:
else elemento = fat[pos] | ((fat[pos+1L] & 0x0F) << 8);
");
escribir (15, 4, C_INFO, id);
if (!elemento) bitfat[i-2]=C_LIBRE;
":"Cylinders:
dec2str (id, (numsect/sectpista/numcaras*256+255) >> 8, 6);
/* cluster libre */
escribir (0, 5, C_TITULOS, sp?"Caras:
":"Sides:
");
":"Tracks:
");
dec2str (id, numcaras, 6);
else
escribir (15, 5, C_INFO, id);
if (elemento == 0xFF7) bitfat[i-2]=C_ERRONEA;
/* cluster defectuoso */
bitfat[i-2]=C_OCUPADA;
escribir (0, 6, C_TITULOS, sp?"Pistas: dec2str (id, numsect/sectpista, 6);
else /* cluster ocupado */
escribir (15, 6, C_INFO, id); escribir
}
(26,
1,
C_TITULOS,
sp?"Sectores/pista:":"Sectors/track:
");
}
dec2str (id, sectpista, 6); escribir (43, 1, C_INFO, id); void analiza_fat (unsigned char huge *bitfat, unsigned numclusters, unsigned *clusters_datos, unsigned *clusters_malos)
escribir
(26,
2,
C_TITULOS,
sp?"Sectores/cluster:":"Sectors/cluster: "); dec2str (id, sectcluster, 6);
{ unsigned i, elemento, libres=0;
escribir (43, 2, C_INFO, id); escribir
for (i=0; i
(26,
3,
C_TITULOS,
"); dec2str (id, sectfat, 6); escribir (43, 3, C_INFO, id);
sp?"Sectores/FAT:
":"Sectors/FAT:
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
escribir
(26,
4,
C_TITULOS,
sp?"Número
de
FATs:":"Number
escribir (55, 8, C_LEYENDA, sp?"Area defectuosa (":"Damaged area
of
(");
FATs:"); dec2str (id, boot[0x10], 6);
porc=malos*10000L/numclusters+5;
escribir (43, 4, C_INFO, id); escribir
(26,
5,
C_TITULOS,
sp?"Sectores
porc2str (cad, porc); escribir (sp?72:69, 8, C_LEYENDA, cad);
reserv.:":"Reserved }
sectors:"); dec2str (id, boot[0x0E] | (int) boot[0x0F] << 8, 6); escribir (43, 5, C_INFO, id); escribir
(26,
6,
C_TITULOS,
sp?"Entradas
en
raiz:":"Root
dir
void marco() {
entries:");
int x, y;
dec2str (id, boot[0x11] | (int) boot[0x12] << 8, 6); escribir (43, 6, C_INFO, id); escribir (52, 1, C_TITULOS, sp?"Sectores:
for (y=MIN_Y; y<=MAX_Y; y++) {
":"Sectors:
");
punto (MIN_X-2, y, C_MARCO); punto (MIN_X-1, y, C_MARCO);
dec2str (id, numsect, 13);
punto (MAX_X+1, y, C_MARCO); punto (MAX_X+2, y, C_MARCO); }
escribir (67, 1, C_INFO, id); escribir (52, 2, C_TITULOS, "Clusters:
for (x=MIN_X-2; x<=MAX_X+2; x++) {
");
numsect = numsect - (boot[0x0E] | (int) boot[0x0F] << 8) -
punto (x, MIN_Y-2, C_MARCO); punto (x, MIN_Y-1, C_MARCO);
(sectfat) * boot[0x10] -
punto (x, MAX_Y+2, C_MARCO); punto (x, MAX_Y+1, C_MARCO); }
(boot[0x11] | (int) boot[0x12] << 8) * 32 / tamsect; }
dec2str (id, numsect/sectcluster, 13); escribir (67, 2, C_INFO, id); escribir (52, 3, C_TITULOS, "Total bytes:");
void pinta_fat (unsigned char huge *bitfat, unsigned numclusters)
dec2str (id, (long)numclusters*tamsect*sectcluster, 13);
{
escribir (67, 3, C_INFO, id); escribir (52, 4, C_TITULOS, sp?"Bytes libres:":"Bytes free:
");
unsigned long factor; unsigned x, y,
dec2str (id, (((long)numsect/sectcluster-datos-malos)
ant_pixel_l=0, ant_pixel_h=0, coord_x=2, coord_y=MIN_Y*80;
*tamsect*sectcluster), 13); escribir (67, 4, C_INFO, id); escribir
(52,
5,
C_TITULOS,
sp?"Bytes
ocupados:":"Bytes
used:
factor=(long) (MAX_X-MIN_X+1)*(MAX_Y-MIN_Y+1); factor=factor*16384L/numclusters;
"); dec2str (id, (long)datos*sectcluster*tamsect, 13); escribir (67, 5, C_INFO, id);
asm { push
ax; push bx; push cx; push dx; push si; push di; push es;
mov
cx,numclusters
dec2str (id, (long)malos*sectcluster*tamsect, 13);
les
bx,bitfat
escribir (67, 6, C_INFO, id);
mov
si,bx }
escribir (52, 6, C_TITULOS, sp?"Bytes erróneos:":"Bytes damaged: ");
strcpy (id, "────────────────");
/* SI --> posición del primer cluster */
proc_fat: asm { mov
for (i=0; i<5; i++)
al,es:[bx] }
cuenta: asm {
escribir (i<<4, 7, C_CABECERA, id); }
inc
bx
cmp
al,es:[bx]
loope cuenta void leyendas (unsigned numclusters, unsigned datos, unsigned malos)
mov
{
sub int porc;
di,bx di,si
/* DI --> número de cluster hasta donde avanzar
*/ push
si
mov
ax,word ptr factor
escribir (sp?2:4, 8, C_OCUPADA, "██");
mul
di
escribir (sp?5:7, 8, C_LEYENDA, sp?"Area ocupada (":"Used area (");
mov
si,ax
mov
ax,di
porc=datos*10000L/numclusters+5;
mov
di,dx
porc2str (cad, porc); escribir (sp?19:18, 8, C_LEYENDA, cad);
mul
char *cad="100,0%)";
escribir (28, 8, C_LIBRE, "██");
word ptr [factor+2]
/* DX:AX segundo producto parcial
*/ add
ax,di
adc
dx,0
porc=(numclusters-datos-malos)*10000L/numclusters+5;
shl
si,1
porc2str (cad, porc); escribir (sp?43:42, 8, C_LEYENDA, cad);
rcl
ax,1
escribir (52, 8, C_ERRONEA, "██");
rcl
dx,1
escribir (31, 8, C_LEYENDA, sp?"Area libre (":"Free area (");
/* DI:SI producto parcial */
/* DX:AX:SI producto */
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
shl
si,1
rcl
ax,1
rcl
dx,1
dec_msb: asm {
/* DX:AX = DX:AX:SI / 16384 = pixel
*/
pop
si
sub
si,1
push
si
mov
si,dx
mov
si,80
mov
di,ax
jnc
incy
sub
di,ant_pixel_l
pop
si
sbb
si,ant_pixel_h
pop
bx
mov
ax,bp
/* SI:DI = nº de pixels a pintar
*/ mov
ant_pixel_l,ax
pop
bp
mov
ant_pixel_h,dx
pop
ds
push
bx; push cx; push ds; push bp;
mov
coord_x,bx
mov
ch,es:[bx-1]
mov
coord_y,ax
mov
bx,coord_x
pop
cx
mov
bp,coord_y
pop
bx
mov
dx,3CEh
pop
si
mov
ax,0A000h
jcxz
fin_proc
mov
ds,ax
jmp
proc_fat }
mov
al,8
mov
cl,bl
and
cl,7
mov
ah,80h
shr
ah,cl
push
bx
mov
cl,3
shr
bx,cl
add
bx,bp
push
si
push
ax; push dx
mov
si,80
mov
dx,3CEh
out
dx,ax }
mov
ax,205h
out
dx,ax
fin_proc: asm { /*
BX = cx,
BP = cy*80
pop
*/
es; pop di; pop si; pop dx; pop cx; pop bx; pop ax;
} } /*
AH = bit a pintar en su sitio
*/
void prepara_punto() { /*
BX = cy*80+cx/8
asm {
*/
pinta_mas: asm {
/*
preparar la VGA para punto()
*/
/*
registro de modo (5): escr. 2 lect. 0
/*
cambiar AH para hacer OR/XOR/AND
mov
cl,[bx]
/* acceso en lectura */
mov
ax,3
mov
[bx],ch
/* pintar punto */
out
dx,ax
sub
di,1
pop
dx; pop ax
jc
dec_msb }
*/
}
/* evitar salto la mayoría de las veces */ }
incy: asm { add
bx,si
add
bp,si
cmp
bp,(MAX_Y+1)*80
void punto (int coord_x, int coord_y, int color)
jb
pinta_mas
{
ror
ah,1
out
dx,ax
push
ds
pop
si
push
ax; push bx; push cx; push dx;
pop
bx
mov
cx,coord_x
inc
bx
mov
dx,coord_y
push
bx
xchg
bx,cx
push
si
mov
cx,0A000h
mov
si,80
mov
ds,cx
mov
bp,MIN_Y*80
mov
cl,4
mov
cl,3
shl
dx,cl
shr
bx,cl
mov
ax,dx
add
bx,bp
shl
ax,1
push
ax
shl
mov
ah,1
add
int
16h
mov
al,bl
pop
ax
dec
cl
jz
pinta_mas
shr
pop
si; pop bx; pop bp; pop ds; pop cx; pop bx; pop si;
add
jmp
fin_proc }
and
al,7
/* siguiente pixel en el eje X */
/*
BX = cy*80+cx/8
*/
/* tecla pulsada */
asm {
/* rutina rápida sólo para modos de 640x???x16 */
/*
BX = cx, DX = cy
*/
/*
DX = cy * 16
*/
ax,1
/*
CX = cy * 64
*/
dx,ax
/*
DX = cy * 80
*/
bx,cl
/*
CL = 3
bx,dx
/*
BX = cy * 80 + cx / 8
*/ */
*/
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
push
mov
cl,al
mov
ah,80h
shr
ah,cl
/*
AH = bit a pintar en su sitio
mov
dx,3CEh
/*
registro de direcciones
mov
al,8
out
dx,ax
mov
al,[bx]
mov
ax,color
mov
[bx],al
pop
dx; pop cx; pop bx; pop ax;
pop
ds
*/
*/
/* acceso en lectura */
} }
int leesect(int unidad, int nsect, unsigned long psect, void *buffer) { struct fatinfo fatdisco; static anterior_unidad=0xFFFF, tipo_disco; unsigned buffer_s, buffer_o, psectl, psecth, flags;
if (unidad!=anterior_unidad)
/* ahorrar tiempo si mismo disco */
{ getfat(unidad+1, &fatdisco); if (((unsigned)fatdisco.fi_nclus * (unsigned long)fatdisco.fi_sclus) > 0xFFFFL) tipo_disco=1;
/* unidad de más de 65535 sectores */
else tipo_disco=0;
/* unidad de menos de 65536 sectores */
anterior_unidad=unidad; }
buffer_o=FP_OFF(buffer); buffer_s=FP_SEG(buffer); psectl=psect & 0xFFFF; psecth=psect >> 16;
if (tipo_disco)
/* unidades con más de 65535 sectores */
asm { push
ax; push bx; push cx; push dx; push
push
bp; push ds;
si; push di;
push
buffer_s
/* segmento del buffer */
push
buffer_o
/* offset */
push
nsect
/* número de sectores */
push
psecth
/* sector inicial (parte alta) */
push
psectl
/* (parte baja) */
mov
ax,unidad
/* unidad */
mov
bx,sp
mov
dx,ss
mov
ds,dx
/* DS:BX = SS:SP */
mov
cx,0ffffh
/* sectores de 32 bits */
int
25h
/* acceso al disco */
pop
flags
/* resultado de la operación */
add
sp,12
/* equilibrar pila */
pop
ds; pop bp;
pop
di; pop si; pop dx; pop cx; pop bx; pop ax
pushf
} else
/* unidades con menos de 65536 sectores */
asm { push
ax; push bx; push cx; push dx; push
si; push di;
bp; push ds;
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
mov
ax,unidad
/* unidad */
mov
dx,psectl
/* sector inicial */
mov
cx,nsect
/* número de sectores */
mov
bx,buffer_o
/* offset del buffer */
mov
ds,buffer_s
/* segmento */
int
25h
/* acceso al disco */
pop
flags
/* resultado de la operación */
add
sp,2
/* equilibrar pila */
pop
ds; pop bp;
pop
di; pop si; pop dx; pop cx; pop bx; pop ax
pushf
} return (flags & 1); }
int HablaSp()
/* devolver 1 si mensajes en castellano */
{ union REGS r; struct SREGS s; char info[64]; int i, idioma, spl[]={54, 591, 57, 506, 56, 593, 503, 34, 63, 502, 504, 212, 52, 505, 507, 595, 51, 80, 508, 598, 58, 3, 0};
idioma=0;
/* supuesto el inglés */
if (_osmajor>=3) { r.x.ax=0x3800; s.ds=FP_SEG(info); r.x.dx=FP_OFF(info); intdosx (&r, &r, &s); i=0; while (spl[i++]) if (spl[i-1]==r.x.bx) idioma=1; } return (idioma); }
7.7. - EL PSP. Como se vio en el capítulo anterior, antes de que el COMMAND.COM pase el control al programa que se pretende ejecutar, se crea un bloque de 256 bytes llamado PSP (Program Segment Prefix), cuya descripción detallada se da a continuación. La dirección del PSP en los programas COM viene determinada por la de cualquier registro de segmento (CS=DS=ES=SS) nada más comenzar la ejecución del mismo. Sin embargo, en los programas de tipo EXE sólo viene determinada por DS y ES. En cualquier caso, existe una función del DOS para obtener la dirección del PSP, cuyo uso recomienda el fabricante del sistema en aras de una mayor compatibilidad con futuras versiones del sistema operativo. La función es la 62h y está disponible a partir del DOS 3.0. En la siguiente información, los campos del PSP que ocupen un byte o una palabra han de interpretarse como tal; los que ocupen 4 bytes deben interpretarse en la forma segmento:offset. En negrita se resaltan los campos más importantes. - offsets 0 al 1: palabra 20CDh, correspondiente a la instrucción INT 20h. En CP/M se podía terminar un programa ejecutando un salto a la posición 0. En MS-DOS, un programa COM ¡también!. - offsets 2 al 3: una palabra con la dirección de memoria (segmento) del último párrafo disponible en el sistema. Teniendo en cuenta dónde acaba la memoria y el punto en que está cargado nuestro programa, no es difícil saber la memoria que queda libre. Supuesto ES apuntando al PSP:
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
MOV
AX,ES:[2]
; párrafo más alto disponible
MOV
CX,ES
; segmento del PSP
SUB
AX,CX
; AX = párrafos libres
MOV
CX,16
MUL
CX
; DX:AX bytes libres
- offset 4: no utilizado. - offsets 5 al 9: salto al despachador de funciones del DOS (en CP/M se ejecutaba un CALL 5, el MS-DOS ¡también lo permite!). No es recomendable llamar al DOS de esta manera. Los PSP creados por la función 4Bh en algunas versiones del DOS no tienen correctamente inicializado este campo. - offsets 0Ah al 0Dh: contenido previo del vector de terminación (INT 22h). - offsets 0Eh al 11h: contenido previo del vector de Ctrl-Break (INT 23h). - offsets 12h al 15h: contenido previo del vector de manipulación de errores críticos (INT 24h). - offsets 16h al 17h: segmento del PSP padre. - offsets 18h al 2Bh: tabla de trabajo del sistema con los ficheros (Job File Table o JFT) : un byte por handle (a 0FFh si cerrado; los primeros son los dispositivos CON, NUL, ... y siempre están abiertos). Sólo hasta 20 ficheros (si no, véase offset 32h). - offsets 2Ch al 2Dh: desde el DOS 2.0, una palabra que apunta al segmento del espacio de entorno, donde se puede encontrar el valor de variables de entorno tan interesantes como PATH, COMSPEC,... y hasta el nombre del propio programa que se está ejecutando en ese momento y el directorio de donde se cargó (no siempre es el actual; el programa pudo cargarse, apoyándose en el PATH, en cualquier otro directorio diferente del directorio en curso). Véase el capítulo 8 para más información de las variables de entorno. - offsets 2Eh al 31h: desde el DOS 2.0, valor de SS:SP en la entrada a la última INT 21h invocada. - offsets 32h al 33h: desde el DOS 3.0, número de entradas en la JFT (por defecto, 20). - offsets 34h al 37h: desde el DOS 3.0, puntero al JFT (por defecto, PSP:18h). Desde el DOS 3.0 puede haber más de 20 ficheros abiertos a la vez gracias a este campo, que puede ser movido de sitio. Sin embargo, es sólo a partir del DOS 3.3 cuando en un PSP hijo (por ejemplo, creado con la función EXEC) se copia la información de más que de los 20 primeros ficheros, si hay más de 20. Se puede saber si un fichero es remoto (en la MS-net) comprobando si el byte de la JFT está comprendido entre 80h-0FEh, aunque es mejor siempre acceder antes a las funciones del DOS. - offsets 38h al 3Bh: desde el DOS 3.0, puntero al PSP previo (por defecto, 0FFFFh:0FFFFh en las versiones del DOS 3.x); es utilizado por SHARE en el DOS 3.3. - offsets 3Ch al 3Fh: no usados hasta ahora. - offsets 40h al 41h: desde el DOS 5.0, versión del sistema a devolver cuando se invoca la función 30h. - offsets 42h al 47h: no usados hasta ahora. - offset 48h: desde Windows 3, el bit 0 está activo si la aplicación es no-Windows. - offsets 49h al 4Fh: no usados hasta ahora. - offsets 50h al 52h: código de INT 21h/RETF. No recomendado hacer CALL PSP:5Ch para llamar al DOS.
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
- offsets 53h al 5Bh: no usados hasta ahora. - offsets 5Ch al 7Bh: apuntan a los dos FCB's (File Control Blocks) usados antaño para acceder a los ficheros (uno en 5Ch y el otro en 6Ch). Es una reliquia en desuso, y además este área no se inicializa si el programa es cargado en memoria superior con el comando LOADHIGH del MS-DOS 5.0 y posteriores, por lo que no conviene usarlo ni siquiera para captar parámetros, al menos en programas residentes -susceptibles de ser instalados con LOADHIGH-. Si se utiliza el primer FCB se sobreescribe además el segundo. - offsets 7Ch al 7Fh: no usados hasta ahora. - offsets 80h al 0FFh: es la zona donde aparecen los parámetros suministrados al programa. El primer byte indica la longitud de los parámetros, después vienen los mismos y al final un retorno de carro (ASCII 13) que es un tanto redundante -a fin de cuentas, ya se sabe la longitud de los parámetros-. Ese retorno de carro, sin embargo, no «se cuenta» en el byte que indica la longitud. Téngase en cuenta que no son mayusculizados automáticamente (están tal y como los tecleó el usuario), y además los parámetros pueden estar separados por uno o más espacios en blanco o tabuladores (ASCII 9). En general, comprobar los valores que recibe el PSP cuando se carga un programa es una tarea que se realiza de manera sencilla con el programa DEBUG/SYMDEB. Para ello basta una orden tal como "DEBUG PROGRAMA.COM HOLA /T": al entrar en el DEBUG (o SYMDEB) basta con hacer «D 0» para examinar el PSP de PROGRAMA. Para ver los parámetros (HOLA /T en el ejemplo) se haría «D 80». 7.8. - EL PROCESO DE ARRANQUE DEL PC. Al conectar el PC éste comienza a ejecutar código en los 16 últimos bytes de la memoria (dirección 0FFFF0h en PC/XT, 0FFFFF0h en 286 y 0FFFFFFF0h en 386 y superiores). En esa posición de memoria, en la que hay ROM, existe un salto a donde realmente comienza el código de la BIOS. Este salto suele ser de tipo largo (segmento:offset) con objeto de cargar en CS un valor que referencie al primer mega de memoria, donde también está direccionada la ROM (todos los microprocesadores arrancan en modo real). El programa de la ROM inicialmente se limita a chequear los registros de la CPU, primero el de estado y luego los demás (en caso de fallo, se detiene el sistema). A continuación, se inicializan los principales chips (interrupciones, DMA, temporizador...); se detecta la configuración del sistema, accediendo directamente a los puertos de E/S y también consultando los switches de configuración de la placa base (PC/XT) o la CMOS (AT); se establecen los vectores de interrupción y se chequea la memoria RAM si el contenido de la dirección 40h:72h es distinto de 1234h (el contenido de la memoria es aleatorio inicialmente). Por último, se entrega el control sucesivamente a las posibles memorias ROM adicionales que existan (la de la VGA, el disco duro en XT, etc.) con objeto de que desvíen los vectores que necesiten. Al final del todo, se intenta acceder a la primera unidad de disquetes: si no hay disquete, se procede igualmente con el primer disco duro (en los PC de IBM, si no hay disco duro ni disquete se ejecuta la ROM BASIC). Se carga el primer sector en la dirección 0:7C00h y se entrega el control a la misma. Ese sector cargado será el sector de arranque del disquete o la tabla de partición del disco duro (el código que contiene se encargará de cargar el sector de arranque del propio disco duro, según la partición activa). El programa del sector de arranque busca el fichero del sistema IO.SYS (o IBMBIO.COM en PC-DOS) y lo carga, entregándole el control (programa SYSINIT) o mostrando un mensaje de error si no lo encuentra. Las versiones más modernas del DOS no requieren que IO.SYS ó IBMBIO.COM comience en el primer cluster de datos del disco, aunque sí que se encuentre en el directorio raíz. Puede que también se cargue al principio el fichero MSDOS.SYS (o IBMDOS.COM) o bien puede que el encargado de cargar dicho fichero sea el propio IO.SYS o IBMBIO.COM. El nombre de los ficheros del sistema depende de si éste es PC-DOS (o DR-DOS) o MS-DOS. Teniendo en cuenta que el MS-DOS y el PC-DOS son prácticamente idénticos desde la versión 2.0 (PC-DOS funciona en máquinas no IBM), la existencia de las dos versiones se explica sólo por razones comerciales. El fichero IO.SYS o IBMBIO.COM en teoría debería ser entregado por el vendedor del ordenador: este fichero provee soporte a las diferencias específicas que existen en el hardware de las diferentes máquinas. Sin embargo, como todos los PC compatibles son casi idénticos a nivel hardware (salvo algunas de las primeras máquinas que intentaron imitar al PC) en la práctica es el fabricante del DOS (Microsoft o Digital Research)
103
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
quien entrega dicho fichero. Ese fichero es como una capa que se interpone entre la BIOS del PC y el código del sistema operativo contenido en MSDOS.SYS o IBMDOS.COM. Este último fichero es el encargado de inicializar los vectores 20h-2Fh y completar las tablas de datos internas del sistema. También se interpreta el CONFIG.SYS para instalar los controladores de dispositivo que den soporte a las características peculiares de la configuración del ordenador. Finalmente, se carga el intérprete de mandatos: por defecto es COMMAND.COM aunque no hay razón para que ello tenga que ser así necesariamente (pruebe el lector a poner en CONFIG.SYS la orden SHELL C:\DOS\QBASIC.EXE; aunque si se abandona QBASIC algunas versiones modernas del DOS son aún capaces de cargar el COMMAND por sus propios medios, después del error pertinente, en vez de bloquear el ordenador). En las versiones más recientes del DOS, el sistema puede residir en memoria superior o en el HMA: en ese caso, el proceso de arranque se complica ya que es necesario localizar el DOS en esa zona después de cargar los controladores de memoria. 7.9. - FORMATO DE LAS EXTENSIONES ROM. Las memorias ROM que incorporan diversas tarjetas (de vídeo, controladoras de disco duro, de red) pueden estar ubicadas en cualquier punto del área 0C0000h-0FFFFFh. La ROM BIOS del ordenador se encarga de ir recorriéndolas y entregándolas el control durante la inicialización, con objeto de permitirlas desviar vectores de interrupción y ejecutar otras tareas propias de su inicialización. La BIOS recorre este área en incrementos de 2 Kb buscando la signatura 55h, 0AAh: estos dos bytes consecutivos tienen que aparecer al principio para considerar que ahí hay una ROM. El tercer byte, que va detrás de éstos, indica el tamaño de esa extensión ROM en bloques de 512 bytes. Por razones de seguridad, se realiza una suma de comprobación de toda la extensión ROM y si el resultado es 0 se considera una auténtica ROM válida. En ese caso, se entrega el control (con un CALL entre segmentos) al cuarto byte de la extensión ROM. Ahí habrá de estar ubicado el código de la extensión ROM (habitualmente un salto a donde realmente comienza). Al final del todo, el código de la extensión ROM debe devolver de nuevo el control a la BIOS del sistema, por medio de un retorno lejano (RETF). El código almacenado en estas extensiones ROM puede contener accesos directos al hardware y llamadas a la ROM BIOS del sistema. Sin embargo, conviene recordar que el DOS no ha sido cargado aún y no se pueden emplear sus funciones. La ventaja de las extensiones ROM es que aumentan las prestaciones del sistema antes de cargar el DOS. El inconveniente es que en otros sistemas operativos (UNIX, etc.) que emplean el modo protegido, estas memorias ROM en general no son accesibles. En la actualidad, con la disponibilidad de memoria superior bajo DOS, resulta más conveniente que las extensiones de hardware vengan acompañadas de drivers para DOS, WINDOWS, OS/2,... que no con una ROM, mucho más difícil de actualizar. Un ejemplo de memoria ROM podría ser: bios
fin_bios
DB DB JMP ... ... ...
55h, 0AAh 32 inicio
; 16 Kb de ROM
; la suma de todos los bytes = 0
Los primeros ordenadores de IBM incorporaban una memoria ROM con el BASIC. El COMMAND de aquellas versiones del DOS (desconozco si el actual también) era capaz de ejecutar comandos internos definidos en estas ROM, al igual que un CLS o un DIR, vamos. El formato era, por ejemplo: bios_basic
DB DB JMP DB DB JMP DB
55h, 0AAh 64 inicio 5 "BASIC" basic 6
; 32 Kb de ROM-BASIC ; longitud del siguiente comando ; salto al comienzo del BASIC ; longitud del siguiente comando
103
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
basic fin_bios
DB JMP DB ... ... ...
"BASICA" basic 0
; salto al comienzo (el mismo del BASIC) ; no más comandos
; la suma de todos los bytes = 0
Si esto le parece una tontería al lector, es que no ha visto lo que vamos a ver ahora. Resulta que también se pueden almacenar programas en BASIC (el código fuente, aunque tokenizado) en las BIOS. ¡Sí, un listado en ROM!: mortgagebas
fin_bios
DB DB RETF DB ... ...
55h, 0AAh 48
; ; ; ; ;
0AAh, 55h
24 Kb de contabilidad nada que hacer esto es un listado BASIC aquí, el programa la suma de todos los bytes = 0
7.10. - FORMATO FÍSICO DE LOS FICHEROS EXE. Los ficheros EXE poseen una estructura en el disco distinta de su imagen en memoria, al contrario que los COM. Es conveniente conocer esta estructura para ciertas tareas, como por ejemplo la creación de antivirus y también la de virus-, que requiere modificar un fichero ejecutable ya ensamblado o compilado. Analizaremos como ejemplo de programa EXE el del capítulo 6, que reúne las principales características necesarias para nuestro estudio. Se comentarán los principales bytes que componen el fichero ejecutable en el disco (1088 en total). A continuación se lista un volcado del fichero ejecutable a estudiar. Todos los datos están en hexadecimal (parte central) y ASCII (derecha); la columna de la izquierda es el offset del primer byte de la línea. Donde hay puntos suspensivos, se repite la línea de arriba tantas veces como sea preciso: 0000 0010 0020 0030 0040 0050
4D 00 6A 00 02 00
01F0 0200 0210 0220 0230 0240
00 0D 69 1E CB 70
0430
70
5A 40 02 00 72 00 00 00 00 00 00 00 . 00 00 0A 54 72 0D 33 C0 00 00 69 6C . 69 6C
00 00 00 00 00 00 . 00 65 0A 50 00 61 . 61
03 00 00 00 00 00 . 00 78 24 B8 00 70 . 70
00 00 00 00 00 00 . 00 74 00 00 00 69 . 69
01 02 00 00 00 00 . 00 6F 00 00 00 6C . 6C
00-20 00-3E 00-00 00-00 00-00 00-00 . . 00-00 20-61 00-00 8E-D8 00-00 61-70 . . 61-70
00 00 00 00 00 00 . 00 20 00 BA 00 69 . 69
00 00 00 00 00 00 . 00 69 00 00 00 6C . 6C
00 00 00 00 00 00 . 00 6D 00 00 00 61 . 61
FF 01 00 00 00 00 . 00 70 00 B4 00 70 . 70
FF 00 00 00 00 00 . 00 72 00 09 00 69 . 69
04 FB 00 05 00 00 . 00 69 00 CD 00 6C . 6C
00 MZ@..... ....... 30 ........>.....{0 00 jr.............. 00 ................ 00 ................ 00 ................ . . . . 00 ................ 6D ..Texto a imprim 00 ir..$........... 21 [email protected]:..4.M! 00 K............... 61 pilapilapilapila . . . . 61 pilapilapilapila
Los ficheros EXE constan de una cabecera, seguida de los segmentos de código, datos y pila; esta cabecera se carga en un buffer auxiliar y no formará parte de la imagen definitiva del programa en memoria. A continuación se explica el contenido de los bytes de la cabecera: Offset 0 (2 bytes): Valores fijos 4Dh y 5Ah (en ASCII, 'MZ') ó 5Ah y 4Dh ('ZM'); esta información indica que el fichero es realmente de tipo EXE y no lleva esa extensión por antojo de nadie. Offset 2 (2 palabras): Tamaño del fichero en el disco. La palabra más significativa (offset 4) da el número total de sectores que ocupa: 3 en este caso (3 * 512 = 1536). El tercer sector no está totalmente lleno, pero para eso está la palabra menos significativa (offset 2) que indica que el último sector sólo tiene ocupados los primeros 40h bytes. Por tanto, el tamaño efectivo del fichero es de 1024 + 64 = 1088
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS
103
bytes, lo que se corresponde con la realidad. Offset 6 (1 palabra): Número de reubicaciones a realizar. Indica cuántas veces se hace referencia a un segmento absoluto: el montador del sistema operativo tendrá que relocalizar en memoria todas las referencias a segmentos absolutos según en qué dirección se cargue el programa para su ejecución. En el ejemplo sólo hay 1 (correspondiente a la instrucción MOV AX,datos). Offset 8 (1 palabra): Tamaño de esta cabecera del fichero EXE. La cabecera que estamos analizando y que precede al código y datos del programa será más o menos larga en función del tamaño de la tabla de reubicaciones, como luego veremos. En el ejemplo son 200h (=512) bytes, el tamaño mínimo, habida cuenta que sólo hay una reubicación (de hecho, aún cabrían muchas más). Offset 0Ah (1 palabra): Mínima cantidad de memoria requerida por el programa, en párrafos, en adición tamaño del mismo. En el ejemplo es 0 (el programa se conforma con lo que ocupa en disco).
al
Offset 0Ch (1 palabra): Máxima cantidad de memoria requerida (párrafos). Si es 0, el programa se cargará lo más alto posible en la memoria (opción /H del LINK de Microsoft); si es 0FFFFh, como en el ejemplo, el programa se cargará lo más abajo posible en la memoria -lo más normal-. Offset 0Eh (2 palabras): Valores para inicializar SS (offset 0Eh) y SP (offset 10h). Evidentemente, el valor para SS está aún sin reubicar (habrá de sumársele el segmento en que se cargue el programa). En el ejemplo, el SS relativo es 4 y SP = 200h (=512 bytes de tamaño de pila definido). Offset 12h (1 palabra): Suma de comprobación: son en teoría los 16 bits de menos peso de la negación de la suma de todas las palabras del fichero. El DOS debe hacer poco caso, porque TLINK no se molesta ni en inicializarlo (El LINK de Microsoft sí). Olvidar este campo. Offset 14h (2 palabras): Valores para inicializar CS (offset 16h) e IP (offset 14h). El valor para CS está aún sin reubicar y habrá de sumársele el segmento definitivo en que se cargue el programa. En el ejemplo, el valor relativo de CS es 2, siendo IP = 0. Offset 18h (1 palabra): Inicio de la tabla de reubicación, expresado como offset. En el ejemplo es 3Eh, lo que indica que la tabla comienza en el offset 3Eh. Cada entrada en la tabla ocupa 4 bytes. La única entrada de que consta este programa tiene el valor 0002:0005 = 25h, lo que indica que en el offset 200h+25h (225h) hay una palabra a reubicar -se suma 200h que es el tamaño de la cabecera-. En efecto, en el offset 225h hay una palabra a cero, a la que habrá de sumársele el segmento donde sea cargado el programa. Esta palabra a cero es el operando de la instrucción MOV AX,datos (el código de operación de MOV AX,n es 0B8h). Offset 1Ah (1 palabra): Número de overlay (0 en el ejemplo, es un programa principal). Offset 1Ch al 3Dh: Valores desconocidos (dependientes de la versión de LINK o TLINK).
LA GESTIÓN DE MEMORIA DEL DOS
143
Capítulo VIII: LA GESTIÓN DE MEMORIA DEL DOS
8.1. - TIPOS DE MEMORIA EN UN PC. Daremos un breve repaso a los tipos de memoria asociados a los ordenadores compatibles en la actualidad. Conviene también echar un vistazo al apéndice I, donde se describe de manera más esquemática, para completar la explicación. 8.1.1. - Memoria convencional. Es la memoria RAM comprendida entre los 0 y los 640 Kb; es la memoria utilizada por el DOS para los programas de usuario. Los 384 Kb restantes hasta completar el megabyte se reservan para otros usos, como memoria para gráficos, BIOS, etc. En muchas máquinas, un buen fragmento de esta memoria está ocupado por el sistema operativo y los programas residentes, quedando normalmente no más de 560 Kb a disposición del usuario. 8.1.2. - Memoria superior. Este término, de reciente aparición, designa el área comprendida entre los 640 y los 1024 Kb de memoria del sistema. Entre 1989 y 1990 aparecieron programas capaces de gestionar este área para aprovechar los huecos de la misma que no son utilizados por la BIOS ni las tarjetas gráficas. La memoria superior no se toma de la memoria instalada en el equipo, sino que está en ciertos chips aparte relacionados con la BIOS, los gráficos, etc. Por ello, un AT con 1 Mb de RAM normalmente posee 640 Kb de memoria convencional y 384 Kb de memoria extendida. Los segmentos A0000 y B0000 están reservados para gráficos, aunque rara vez se utilizan simultáneamente. El segmento C0000 contiene la ROM del disco duro en XT (en AT el disco duro lo gestiona la propio BIOS del sistema) y/o BIOS de tarjetas gráficas. El segmento D0000 es empleado normalmente para el marco de página de la memoria expandida. El segmento E0000 suele estar libre y el F0000 almacena la BIOS del equipo. Los modernos sistemas operativos DOS permiten (en los equipos 386 ó 386sx y superiores) colocar memoria física extendida en el espacio de direcciones de la memoria superior; con ello es factible rellenar los huecos vacíos y aprovecharlos para cargar programas residentes. Ciertos equipos 286 también soportan esta memoria, gracias a unos chips de apoyo, pero no es frecuente. 8.1.3. - Memoria de vídeo. El primer adaptador de vídeo de IBM era sólo para texto y empleaba 4 Kb. Después han ido apareciendo la CGA (16 Kb), EGA (64-256 Kb), VGA (256 Kb) y SVGA (hasta 2 Mb). Como sólo hay 128 Kb reservados para gráficos en el espacio de direcciones del 8086, las tarjetas más avanzadas tienen paginada su memoria y con una serie de puertos de E/S se indica qué fragmento del total de la memoria de vídeo está siendo direccionado (en la VGA, sólo 64 Kb en A0000). 8.1.4. - Memoria expandida. Surgió en los PC/XT como respuesta a la necesidad de romper el límite de los 640 Kb, y se trata de un sistema de paginación. Consiste en añadir chips de memoria en una tarjeta de expansión, así como una cierta circuitería que permita colocar un fragmento de esa memoria extra en lo que se denomina marco de página de memoria expandida, que normalmente es el segmento D0000 del espacio de direcciones del 8086 (64 Kb). Este marco de página está dividido en 4 bloques de 16 Kb. Allí se pueden colocar bloques de 16 Kb extraídos de esos chips adicionales por medio de comandos de E/S enviados a la tarjeta de expansión. Para que los
143
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
programas no tengan que hacer accesos a los puertos y para hacer más cómodo el trabajo, surgió la especificación LIM-EMS (Lotus-Intel-Microsoft Expanded Memory System) que consiste básicamente en un driver instalable desde el config.sys que pone a disposición de los programas un amplio abanico de funciones invocables por medio de la interrupción 67h. La memoria expandida está dividida en páginas lógicas de 16 Kb que pueden ser colocadas en las normalmente 4 páginas físicas del marco de página. Los microprocesadores 386 (incluido obviamente el SX) permiten además convertir la memoria extendida en expandida, gracias a sus mecanismos de gestión de memoria: en estas máquinas la memoria expandida es emulada por EMM386 o algún gestor similar. 8.1.5. - Memoria extendida. Es la memoria ubicada por encima del primer mega en los procesadores 286 y superiores. Sólo se puede acceder a la mayoría de esta memoria en modo protegido, por lo que su uso queda relegado a programas complejos o diversos drivers que la aprovechen (discos virtuales, cachés de disco duro, etc.). Hace ya bastante tiempo se diseñó una especificación para que los programas que utilicen la memoria extendida puedan convivir sin conflictos: se trata del controlador XMS. Este controlador implementa una serie de funciones normalizadas que además facilitan la utilización de la memoria extendida, optimizando las transferencias de bloques en los 386 y superiores (utiliza automáticamente palabras de 32 bits para acelerar el acceso). La especificación XMS viene en el programa HIMEM.SYS, HIDOS.SYS y en algunas versiones del EMM386. El controlador XMS también añade funciones normalizadas para acceder a la memoria superior. 8.1.6. - Memoria caché. Desde el punto de vista del software, es memoria (convencional, expandida o extendida) empleada por un controlador de dispositivo (driver) para almacenar las partes del disco de más frecuente uso, con objeto de acelerar el acceso a la información. A nivel hardware, la memoria caché es una pequeña RAM ultrarrápida que acompaña a los microprocesadores más avanzados; los programas no tienen que ocuparse de la misma. También incorporan memorias caché algunos controladores de disco duro, aunque se trata básicamente de memoria normal y corriente para acelerar los accesos. 8.1.7. - Memoria shadow RAM. Los chips de ROM no han evolucionado tanto como las memorias RAM; por ello es frecuente que un 486 a 66 MHz tenga una BIOS de sólo 8 bits a 8 Mhz. A partir de los procesadores 386 (también 386sx) y superiores, existen unos mecanismos de gestión de memoria virtual que permiten colocar RAM en el espacio lógico de direcciones de la ROM. Con ello, es factible copiar la ROM en RAM y acelerar sensiblemente el rendimiento del sistema, especialmente con los programas que se apoyan en la BIOS. También los chipset de la placa base pueden añadir soporte para esta característica. La shadow RAM normalmente son 384 Kb que reemplazan cualquier fragmento de ROM ubicado entre los 640-1024Kb de RAM durante el proceso de arranque (boot) del sistema. En ocasiones, el usuario puede optar entre 384 Kb de shadow ó 384 Kb más de memoria extendida en el programa SETUP de su ordenador. 8.1.8. - Memoria CMOS RAM. Son 64 bytes de memoria (128 en algunas máquinas) ubicados en el chip del reloj de tiempo real de la placa base de los equipos AT y superiores. A esta memoria se accede por dos puertos de E/S y en ella se almacena la configuración y fecha y hora del sistema, que permanecen tras apagar el ordenador (gracias a las pilas). Evidentemente no se puede ejecutar código sobre la RAM CMOS (Ni pueden esconderse virus, al contrario de lo que algunos mal informados opinan. Otra cosa es que utilicen algún byte de la CMOS para controlar su funcionamiento). 8.1.9. - Memoria alta o HMA. Se trata de los primeros 64 Kb de la memoria extendida (colocados entre los 1024 y los 1088 Kb). Normalmente, cuando se intentaba acceder fuera del primer megabyte (por ejemplo, con un puntero del tipo
LA GESTIÓN DE MEMORIA DEL DOS
143
FFFF:1000 = 100FF0) un artificio de hardware lo impedía, convirtiendo esa dirección en la 0:0FF0 por el simple procedimiento de poner a cero la línea A20 de direcciones del microprocesador en los 286 y superiores. Ese artificio de hardware lo protagoniza el chip controlador del teclado (8042) ya que la línea A20 pasa por sus manos. Si se le insta a que conecte los dos extremos (enviando un simple comando al controlador del teclado) a partir de ese momento es el microprocesador quien controla la línea A20 y, por tanto, en el ejemplo anterior se hubiera accedido efectivamente a la memoria extendida. Los nuevos sistemas operativos DOS habilitan la línea A20 y, gracias a ello, están disponibles otros 64 Kb adicionales. Para ser exactos, como el rango va desde FFFF:0010 hasta FFFF:FFFF se puede acceder a un total de 65520 bytes (64 Kb menos 16 bytes) de memoria. Téngase en cuenta que las direcciones FFFF:0000 a la FFFF:000F están dentro del primer megabyte. En el HMA se cargan actualmente el DR-DOS 5.0/6.0 y el MS-DOS 5.0 y posteriores; evidentemente siempre que el equipo, además de ser un AT, disponga como mínimo de 64 Kb de memoria extendida. En ciertos equipos poco compatibles es difícil habilitar la línea A20, por lo que el HIMEM.SYS de Microsoft dispone de un parámetro que se puede variar probando docenas de veces hasta conseguirlo, si hay suerte (además, hay BIOS muy intervencionistas que dificultan el control de A20). 8.2. - BLOQUES DE MEMORIA. Vamos ahora a conocer con profundidad la manera en que el sistema operativo DOS gestiona la memoria; un tema poco tratado, ya que esta información no está oficialmente documentada por Microsoft. Los bloques de memoria en el DOS son agrupaciones de bytes siempre múltiplos enteros de 16 bytes: en realidad son agrupaciones de párrafos. La memoria de un PC -siempre bajo DOS- está, por tanto, dividida en grupos de párrafos. Por tanto, una palabra de 16 bits permite almacenar la dirección del párrafo de cualquier posición de memoria dentro del megabyte direccionable por el 8086. Todo bloque de memoria tiene asociado un propietario, que bien puede ser el DOS o un programa residente que haya solicitado al DOS el control de dicho bloque. Cuando se ejecuta un programa, el sistema crea dos bloques para el mismo: el bloque de memoria del programa y el bloque de memoria del entorno. 8.2.1. - El bloque de memoria del programa. Cuando se ejecuta un programa, el DOS busca el mayor bloque de memoria disponible (convencional o superior, según sea el caso) y se lo asigna -y no el bloque más cercano a la dirección 0, como algunos afirman-. Este área recibe el nombre de bloque de programa o segmento de programa. La dirección del primer párrafo del mismo es de suma importancia y se denomina PID (Process ID, identificador de proceso). En los primeros 256 bytes de este área el DOS crea el PSP ya conocido -256 bytes- formado por varios campos de información relacionada con el programa. Tras el PSP viene el código del programa ejecutable. Para los objetivos de este capítulo basta con conocer dos campos del PSP: el primero está en su offset 0 y son dos bytes (por tanto, los primeros dos bytes del PSP) que contienen la palabra 20CDh (ó 27CDh en algunos casos). Esto se corresponde con el código de operación de la instrucción ensamblador INT 20h (o INT 27h); esto es así por razones históricas heredadas del CP/M. Por ello, cuando un programa finaliza, puede hacerlo con un salto al inicio del PSP (un JMP 0 en los programas COM) donde se ejecuta el INT 20h, aunque normalmente el programador ejecuta directamente el INT 20h que es más seguro. El otro campo del PSP que nos interesa es el offset 2Ch: en él hay una palabra que indica el párrafo donde comienza el bloque de entorno asociado al programa. 8.2.2. - El bloque del entorno. El espacio de entorno del COMMAND.COM es el bloque de entorno del COMMAND.COM (que podemos considerar como un programa residente). Es una zona de memoria donde se almacenan las variables de entorno definidas con el mandato SET del sistema, así como con algunos comandos como PATH, PROMPT, etc. Por ejemplo, la orden PATH C:\DOS es análoga a SET PATH=C:\DOS. Las variables de entorno pueden consultarse con SET (sin parámetros). las variables de entorno sirven para crear información que puedan usar múltiples programas, aunque se usan poco en la realidad. Cuando un programa es cargado, además del bloque de memoria del programa se crea el bloque del entorno. Se trata de una vulgar copia del espacio de entorno del COMMAND.COM; de esta manera, el programa en ejecución tiene acceso a las variables de entorno del
143
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
sistema aunque no las puede modificar (estaría modificando una mera copia). Las variables de entorno se almacenan en formato ASCIIZ ordinario (esto es, terminadas por un byte a cero) y tienen una sintaxis del tipo VARIABLE=SU VALOR. Tras la última de las variables hay otro byte más a cero para indicar el final. Después de esto, y sólo a partir del DOS 3.0, viene una palabra que indica el número de cadenas ASCIIZ especiales que vienen a continuación: normalmente 1, que contiene una información muy útil: la especificación completa del nombre del programa que está siendo ejecutado -incluida la unidad y ruta de directorios- lo que permite a los programas saber su propio nombre y desde qué directorio están siendo ejecutados y, por tanto, dónde deben abrir sus ficheros (por educación no es conveniente hacerlo en el directorio raíz o en el actual). En el espacio de entorno del COMMAND, este añadido del DOS 3.0 y posteriores parece no estar definido. 8.2.3. - Los bloques de control de memoria (MCB's). Todos los bloques de memoria (tanto programa como entorno) vienen precedidos por una cabecera de un párrafo (16 bytes) que almacena información relativa al mismo. Esta cabecera recibe el nombre técnico de MCB (Memory Control Block) y tiene la siguiente estructura: offset 0 1 3 5 8 15 ┌───────┬─────────────┬────────┬─────┬─────────────────────────────┐ │ byte │ PID │ │ │ Nombre del propietario │ │ de │ propietario │ Tamaño │ ... │ (sólo en bloque de programa │ │ marca │ │ │ │ y MS-DOS ≥4.0 ó DRDOS ≥5.0) │ └───────┴─────────────┴────────┴─────┴─────────────────────────────┘
En el offset 0 se sitúa el byte de marca (4Dh si no es el último MCB de la cadena de MCB's en memoria, 5Ah si es el último), en el offset 1 hay una palabra que indica el PID del programa propietario del bloque, en el offset 3 otra palabra indica el tamaño (como siempre, párrafos) del bloque, sin incluir este párrafo del MCB. Los bytes que van del 5 al 7 están reservados. Entre el 8 y el 15 se sitúa el nombre del programa propietario, aunque esta información sólo existe en los bloques de programa y con MS-DOS 4.0 ó posterior (también en DR-DOS 5.0/6.0, aunque este operativo es aparentemente un DOS 3.31). El nombre acaba con un cero si tiene menos de 8 caracteres (en DR-DOS 5.0 acaba siempre con un cero, truncándose el 8º carácter si lo había; esta errata ha sido corregida en DR-DOS 6.0). 8.2.4. - La cadena de los bloques de memoria. Cuando un programa finaliza su ejecución, normalmente el DOS libera su bloque de memoria y de entorno. Sin embargo, los programas residentes permanecen con el bloque de memoria y de entorno en la RAM del sistema, hasta que se les desinstale o se reinicialice el equipo. Los buenos programas residentes suelen liberar el bloque de memoria del entorno antes de terminar, con objeto de economizar una memoria que normalmente no usan (entre otras razones porque tiene un tamaño variable e impredecible). Como mínimo existen dos programas residentes en todo momento: el núcleo (kernel) del sistema operativo y el COMMAND.COM, aunque los usuarios suelen añadir el KEYB y, en muchos casos, el PRINT, APPEND, GRAPHICS, GRAFTABL, NLSFUNC, SHARE, etc. Como todos los bloques de memoria están ubicados unos tras otros, y además se conoce el tamaño de los mismos, es factible hacer un programita que recorra la cadena de bloques de memoria hasta que se encuentre uno cuyo byte de marca valga 5Ah (último MCB), pudiéndose identificar los programas residentes cargados y la memoria que emplean. La dirección del primer MCB era al principio un secreto de Microsoft, aunque hoy casi todo el mundo sabe que las siguientes líneas: MOV AH,52h INT 21h MOV AX,ES:[BX-2]
devuelven en AX la dirección del primer MCB de la cadena, utilizando la función indocumentada 52h del sistema operativo.
LA GESTIÓN DE MEMORIA DEL DOS
143
8.2.5. - Relación entre bloque de programa y de entorno. El siguiente esquema aclarará la relación existente entre el bloque de programa y el de entorno. Los valores numéricos que figuran son arbitrarios (pero correctos). ┌────────────────────┐ Bloque del entorno │ │ │ ╔═══════╤═══════╤════════╤═════════════════════════════════════╗ │ 1DB7 ║ Marca │ PID │ Tamaño │ (reservados) ║ │ ║ 4Dh │ 316F │ 000B │ ║ │ ╠═══════╧═══════╪════╤═══╧═══════════╤════╤═══════════════╤════╣ ┌─│─¾ 1DB8 ║ variable 1 │ 00 │ variable 2 │ 00 │ variable 3 │ 00 ║ │ │ ╟───────────────┴────┴───────────────┴────┼───────────────┼────╢ │ │ ║ ... (más variables terminadas en 0) ... │última variable│ 00 ║ │ │ ╟────┬──────┬─────────────────────────────┴───────────────┼────╢ │ │ ║ 00 │ 0001 │ C:\UTIL\VARIOS\PROGRAMA.EXE │ 00 ║ │ │ ╚════╧══════╧═════════════════════════════════════════════╧════╝ │ │ │ │ │ │ ┌──────────────────┐ Bloque del programa │ │ │ │ │ │ │ ╔═══════╤═══════╤════════╤══════════════╤══════════════════════╗ │ │ │ 316E ║ Marca │ PID │ Tamaño │ (reservados) │ (nombre propietario) ║ │ │ │ ║ 4Dh │ 316Fh │ 1C70 │ │ P R O G R A M A ║ │ │ │ ╠═══════╧════╤══╧════════╧══════╤═══════╧══════╤═══════════════╣ │ │ │ 316F ║ (offset 0) │ │ (offset 2Ch) │ ║ │ └─┴────¾ ║ 20CDh │ ... │ 1DB8 │ ... ║ │ ╟────────────┴──────────────────┴──────────────┴───────────────╢ │ │ └─────────────────────────────────────────────────┘
8.2.6. - Tipos de bloques de memoria. Básicamente existen cinco tipos de bloques de memoria: bloques de programa, de entorno, del sistema, bloques de datos y bloques libres. Los dos primeros ya han sido ampliamente explicados. Los bloques del sistema se corresponden con el kernel o núcleo del sistema operativo o los dispositivos instalables; normalmente tienen su PID como 0008. En los nuevos sistemas operativos y en las máquinas donde la cadena de bloques de memoria puede avanzar por encima de los 640 Kb, las zonas correspondientes a RAM de vídeo y extensiones BIOS suelen tener un PID 0007 en DR-DOS (que indica área excluida) ó 0008 (MS-DOS 5.0) y son consideradas como bloques de memoria ordinarios, aunque sólo sea para saltarlos de alguna manera. Los bloques libres tienen un PID 0000. El PID 0006 (sólo aparece en DR-DOS) indica que se trata de un bloque de memoria superior XMS. Los bloques de datos aparecen en raras ocasiones, debido al uso de las funciones del sistema operativo para localizar bloques de memoria. Cuando un programa se ejecuta, tiene asignada la mayor parte de la memoria para sí, pero es perfectamente factible que solicite al DOS una reducción de la memoria asignada (función 4Ah) y, con los Kb que haya liberado, puede volver a llamar al DOS para crear bloques de memoria (función 48h) o destruirlos (con la función 49h). A la hora de recorrer la cadena de bloques de memoria, si se sigue el siguiente orden de evaluación el resultado será siempre correcto: en primer lugar, si aparece un PID 0000 significa que es un bloque libre. Si el PID no apunta a un PSP (no apunta a un área que empieza por 20CDh ó 27CDh) se trata entonces de un bloque del sistema. Si el PID apunta al MCB+1, se trata de un bloque del programa (recuérdese que el MCB lo precede inmediatamente). Si el PID apunta a un PSP en cuyo offset 2Ch una palabra apunta al MCB+1, se trata del bloque del entorno de ese PSP. Si no es ninguno de estos últimos bloques, por eliminación ha de ser un bloque de datos.
143
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
8.2.7. - Liberar el espacio de entorno en programas residentes. Resulta triste ver como algunos sofisticados programas residentes llegan incluso a autorrelocalizarse en memoria machacando parte del PSP con objeto de economizar algunos bytes; después un alto porcentaje de los mismos se olvida de liberar el espacio de entorno, que para nada utilizan y que suele ocupar incluso más memoria que todo el PSP. La manera de liberar el espacio de entorno antes de que un programa quede residente es la siguiente (necesario DOS 3.0 como mínimo si se obtiene la dirección del PSP utilizando la función 62h): MOV INT MOV MOV MOV INT
AH,62h 21 ES,BX ES,ES:[2Ch] AH,49h 21h
; obtener dirección del PSP en BX ; dirección del espacio de entorno ; función para liberar bloque ; bloque destruido
Alternativamente, se puede liberar directamente el bloque de memoria del entorno poniendo directamente un 0 en su PID, aunque es menos elegante. Si ES apunta al PSP: MOV DEC MOV MOV
AX,ES:[2Ch] AX ES,AX WORD PTR ES:[1],0
; dirección del espacio de entorno ; apuntar a su MCB ; liberar bloque (PID=0)
8.2.8. - Peculiaridades del MS-DOS 4.0 y posteriores. La información siguiente explica las particularidades de los bloques de memoria con MS-DOS 4.0 y posteriores; no es válida para DR-DOS aunque algunos aspectos concretos puedan ser comunes. Desde el MSDOS 3.1, el primer bloque de memoria es un segmento de datos del sistema, que contiene los drivers instalados desde el CONFIG.SYS. A partir del DOS 4.0, este bloque de memoria está dividido en subbloques, cada uno de ellos precedidos de un bloque de control de memoria con el siguiente formato: offset
0:
Byte, indica el tipo de subsegmento: "D" - controlador de dispositivo "E" - extensión de controlador de dispositivo "I" - IFS (Installable File System) driver "F" - FILES= (área de almacenamiento de estas estructuras, si FILES>5) "X" - FCBS= (área de almacenamiento de estas estructuras) "C" - BUFFERS= /X (área de buffers en memoria expandida) "B" - BUFFERS= (área de buffers) "L" - LASTDRIVE= (área de almacenamiento de las CDS) "S" - STACKS= (zona de código y datos de las pilas del sistema) "T" - INSTALL= (área transitoria de este mandato) offset 1: Palabra, indica dónde comienza el subsegmento (normalmente a continuación) offset 3: Palabra, indica el tamaño del subsegmento (en párrafos) offset 8: 8 bytes: en los tipos "D" e "I", nombre del fichero que cargó el driver. Por tanto, desde el DOS 4.0, una vez localizado el primer MCB, puede despreciarse y tomar el que viene inmediatamente a continuación (párrafo siguiente) para recorrer los subsegmentos conectados. En el DOS 5.0 y siguientes, los bloques propiedad del sistema tienen el nombre "SC" (System Code, código del sistema o áreas de memoria superior excluidas) o bien "SD" (System Data, con controladores de dispositivo, etc.). Desde la versión 5.0 del DOS, estos bloques "SD" contienen subbloques con las mismas características que los del DOS 4.0.
LA GESTIÓN DE MEMORIA DEL DOS
143
Adicionalmente, el DOS 5.0 introdujo los bloques denominados UMB que recorren la memoria superior, en las diferentes áreas en que puede estar fragmentada. Acceder a estos bloques de control de memoria es bastante complicado: el segmento donde empiezan está almacenado en el offset 1Fh de la tabla de información sobre buffers de disco, cuya dirección inicial a su vez se obtiene en el puntero largo que devuelve en ES:BX+12h la función indocumentada Get List of Lists (52h): normalmente el resultado es el segmento 9FFFh. En general, es más sencillo ignorar la memoria superior como una entidad independiente y recorrer toda la memoria sin más. Sin embargo, para poder acceder a los bloques de memoria superior éstos han de estar ligados a los de la memoria convencional: para conectarlos, si no lo están, puede emplearse la función, tradicionalmente indocumentada (aunque recientemente ha dejado de serlo) Get or Set Memory Allocation Strategy (58h) del DOS: es conveniente preservarla antes y volver a restaurar esta información después de alterarla. En cualquier caso, el formato de los bloques de control UMB es el siguiente: offset 0: Byte con valor 5Ah para el último bloque y 4Dh en otro caso. offset 1: Palabra con el PID. offset 3: Palabra con el tamaño del bloque en párrafos. offset 8: 8 Bytes: "UMB" si es el primer bloque UMB y "SM" si es el último. 8.2.9. - Cómo recorrer los bloques de memoria. La organización de la memoria varía según la versión del sistema operativo instalada. En líneas generales, todo lo comentado hasta ahora -excepto lo del apartado anterior- es válido para cualquier versión del DOS. Sin embargo, en las máquinas que tienen memoria superior, las cosas pueden cambiar un poco en esta zona de memoria: si tienen instalado algún gestor de memoria extraño, este área puede estar desconectada por completo de los primeros 640 Kb. Con DR-DOS el usuario puede utilizar el comando MEMMAX para habilitar o inhibir el acceso a la memoria superior; desde el MS-DOS 5.0 existen funciones específicas del sistema para estas tareas. El programa de ejemplo listado más abajo recorre toda la memoria sin adentrarse en las particularidades de ningún sistema operativo. Tan sólo se toma la molestia de intentar detectar si existe memoria superior y, en ese caso, mostrar también su contenido. Este algoritmo puede no enseñar todo lo que podría enseñar gracias a las últimas versiones del DOS, pero sí gran parte, y funciona en todas las versiones. Para comprobar si existe memoria superior utiliza una técnica muy sencilla: al alcanzar el último bloque de memoria, se comprueba si el siguiente empezaría en el segmento 9FFFh en vez del A000h como cabría esperar en una máquina de 640Kb (sólo suelen tener memoria superior las máquinas que al menos tienen 640 Kb). Si esto es así no se considera que el bloque sea el último y se prosigue con el siguiente, saltando la barrera de los 640 Kb. En este caso, obviamente, los 16 bytes que faltan para completar los 640 Kb de memoria son precisamente un MCB. Esta técnica funciona sólo a partir del MS-DOS 5.0; en DR-DOS 6.0, si la memoria superior está inhibida con MEMMAX -U, no funciona (DR-DOS 6.0 se encarga de machacar el último MCB de la memoria convencional y no deja ni rastro) aunque sí con MEMMAX +U. También se imprime el nombre de los programas, aunque en DOS 3.30 y versiones anteriores salga basura. Además, el PID de tipo 6 se interpreta como un bloque de memoria superior XMS -que se estudiará en el siguiente apartado de este mismo capítulo- bajo DR-DOS 6.0, imprimiéndose también el nombre. La primera acción de MAPAMEM al ser ejecutado es rebajar la memoria que tiene asignada hasta el mínimo necesario; por ello en el resultado figura ocupando sólo 1440 bytes y teniendo tras de sí un gran bloque libre. Es conveniente que los programas rebajen al principio la memoria asignada con objeto de facilitar el trabajo bajo ciertos entornos pseudo-multitarea soportados por el DOS; de hecho, es norma común en el código generado por los compiladores realizar esta operación al principio. Sin embargo, no todo el mundo se preocupa de ello y, a fin de cuentas, tampoco es tan importante. Un ejemplo de la salida que puede producir este programa es el siguiente, tomado de una máquina con memoria superior y bajo los dos sistemas operativos más comunes (aunque en los ejemplos los espacios de entorno han coincidido junto al bloque de programa, ello no siempre sucede así). Las diferentes ocupaciones de memoria de los programas en ambos sistemas operativos se deben frecuentemente a que se trata de versiones distintas:
143
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
DR-DOS 6.0
MS-DOS 5.0
MAPAMEM 2.2
MAPAMEM 2.2
- Información sobre la memoria del sistema. Tipo
Ubicación Tamaño
PID
- Información sobre la memoria del sistema.
Propietario
Tipo
Ubicación Tamaño
PID
Propietario
-------- --------- ------- ----- ---------------
-------- --------- ------- ----- ---------------
Sistema
0000-003F
1.024
Interrupciones
Sistema
0000-003F
1.024
Interrupciones
Sistema
0040-004F
256
Datos del BIOS
Sistema
0040-004F
256
Datos del BIOS
Sistema
0050-023C
7.888
Sistema Operat.
Sistema
0050-0252
8.240
Sistema
023E-02FD
3.072
0008
Sistema
0254-045F
8.384
0008
Programa 02FF-031E
512
02FF
COMMAND
Sistema
0461-0464
64
0008
Entorno
0320-033F
512
02FF
COMMAND
Programa 0466-050E
2.704
0466
COMMAND
Datos
0341-0358
384
02FF
COMMAND
Libre
0510-0513
64
0000
Programa 035A-03EE
2.384
035A
MATAGAME
Entorno
0515-0544
768
0466
COMMAND
03F0-0408
400
040A
KEYRESET
Entorno
0546-0567
544
0569
MAPAMEM
Programa 040A-041D
320
040A
KEYRESET
Programa 0569-05C2
1.440
0569
MAPAMEM
Entorno
041F-0437
400
0439
MAPAMEM
Libre
05C4-9FFE 631.728
0000
Programa 0439-0492
1.440
0439
MAPAMEM
Sistema
A000-D800 229.392
0008
Libre
0494-9FFE 636.592
0000
Sistema
D802-E159
38.272
0008
Sistema
A000-DEFF 258.048
0007
Libre
E15B-E17F
592
0000
Sistema
DF01-E477
22.384
0008
Programa E181-E18D
208
E181
DOSVER
Sistema
E479-E483
176
0008
Programa E18F-E23C
2.784
E18F
NLSFUNC
Sistema
E485-E48D
144
0008
Programa E23E-E3AF
5.920
E23E
GRAPHICS
Sistema
E48F-E591
4.144
0008
Programa E3B1-E533
6.192
E3B1
SHARE
Sistema
E593-E7DA
9.344
0008
Programa E535-E637
4.144
E535
DOSKEY
Sistema
E7DC-E806
688
0008
Programa E639-E7E2
6.816
E639
PRINT
Sistema
E808-E810
144
0008
Programa E7E4-E840
1.488
E7E4
RCLOCK
Sistema
E812-E81A
144
0008
Programa E842-E862
528
E842
DISKLED
Sistema
E81C-E8DE
3.120
0008
Programa E864-ECF0
18.640
E864
DATAPLUS
Programa E8E0-EA51
5.920
E8E0
GRAPHICS
Programa ECF2-ED59
1.664
ECF2
HBREAK
Programa EA53-EA60
224
EA53
CLICK
Programa ED5B-ED7E
576
ED5B
ANSIUP
Programa EA62-EA6E
208
EA62
DOSVER
Programa ED80-ED8C
208
ED80
PATCHKEY
Programa EA70-EA7F
256
EA70
ALTDUP
Programa ED8E-ED93
96
ED8E
TDSK
Area XMS EA81-EA8F
240
0006
B1M92VAC
Datos
ED95-F6D4
37.888
ED8E
TDSK
Programa EA91-EAC0
768
EA91
VSA
Libre
F6D6-F6FF
672
0000
Area XMS EAC2-EB17
1.376
0006
RCLOCK
Area XMS EB19-EB30
384
0006
DISKLED
Programa EB32-EDB4
10.288
EB32
VWATCH
Area XMS EDB6-EEEC
4.976
0006
DATAPLUS
Area XMS EEEE-EF4F
1.568
0006
HBREAK
Libre
EF51-EFFE
2.784
0000
Sistema
F000-F5FF
24.576
0007
Sistema
F601-F6FF
4.080
0008
Entorno
Sistema Operat.
; ******************************************************************** 100h
; programa tipo COM
MOV
BX,tam_mapmem
; tamaño de este programa
MOV
AH,4Ah
; modificar memoria asignada
INT
21h
; ejecutar función del DOS
SEGMENT
LEA
DX,cabecera_txt
ASSUME CS:mapamem; DS:mapamem
CALL
print
; * ; *
ORG
* MAPAMEM 2.2
-
Utilidad para listar los bloques de memoria.
; *
* *
; ********************************************************************
mapamem
mapa
PROC
143
LA GESTIÓN DE MEMORIA DEL DOS
otro_mcb:
no_tipo_sys:
tipo_ok:
MOV
BX,64
MUL
BX
DEC
AX
ES,AX
MOV
BX,AX
DEC
AX
POP
AX
CALL
print16hex
INC
AX
CMP
AX,BX
; ¿hay RAM superior (DOS 5)?
SUB
AX,50h
JE
otro_mcb
; así es
MOV
DX,16
MOV
AX,4C00h
MUL
DX
INT
21h
MOV
CL,8+16
CALL
print_32
LEA
DX,cabx_txt
CALL
print
MOV
BX,WORD PTR ES:[1]
MOV
DL,0
CMP
BX,0
JE
tipo_ok
MOV
DL,1
CMP
BX,6
JE
tipo_ok
; lo es (PID = 6)
MOV
DL,2
; supuesta zona del sistema
PUSH
DS
MOV
DS,BX
MOV
AX,WORD PTR DS:[0]
; AX = [PID:0000]
MOV
AX,ES
MOV
CX,WORD PTR DS:[2Ch]
; CX = [PID:002C]
INC
AX
POP
DS
CALL
print16hex
CMP
AX,20CDh
MOV
AL,'-'
JE
no_tipo_sys
CALL
printAL
CMP
AX,27CDh
MOV
AX,ES
JNE
tipo_ok
; no es un PSP
ADD
AX,ES:[3]
MOV
DL,3
; supuesta zona de programa
CALL
print16hex
MOV
AX,ES
MOV
AX,ES:[3]
INC
AX
MOV
DX,16
CMP
BX,AX
; ¿PID=MCB+1?
MUL
DX
JE
tipo_ok
; lo es
MOV
CL,8+16
MOV
DL,4
; supuesta zona de entorno
CALL
print_32
CMP
CX,AX
JE
tipo_ok
INC
DL
MOV
pid,BX
MOV
tipo,DL
MOV
AL,' '
CALL
imprime_tipo
; tipo del bloque
CALL
printAL
CALL
imprime_rango
; ubicación y tamaño
CALL
printAL
CALL
imprime_pid
MOV
AX,pid
CALL
imprime_nombre
CALL
print16hex
MOV
AL,13
MOV
AL,' '
CALL
printAL
CALL
printAL
MOV
AL,10
CALL
printAL
CALL
printAL
MOV
AX,ES
; MCB ya tratado
ADD
AX,ES:[3]
; tamaño del bloque
INC
AX
; apuntar al siguiente MCB
CMP
BYTE PTR ES:[0],5Ah
; ¿es el último?
PUSH
ES
MOV
ES,AX
; puntero al siguiente MCB
LEA
DX,libre_txt
JNE
otro_mcb
CMP
tipo,0
; ¿bloque libre?
JNE
no_libre
; no ; imprimirlo
MOV
AH,52h
INT
21h
MOV
AX,ES:[BX-2]
MOV
; función "Get List of Lists"
; segmento del primer M.C.B.
; imprimir dónde acaba el DOS
; pasar párrafos a bytes mapa
ENDP
imprime_tipo
PROC
; fin del programa
; imprimir tamaño zona del DOS
LEA
SI,tabla_tipos
MOV
AL,tipo
XOR
AH,AH
SHL
AX,1
; lo es (PID = 0)
ADD
SI,AX
; supuesto bloque XMS de DR-DOS
MOV
DX,[SI]
; dirección del mensaje
CALL
print
; imprimirlo
; P.I.D. (Process ID)
; supuesta zona libre (tipo DL)
; AX = tipo * 2
RET imprime_tipo
ENDP
imprime_rango
PROC
; es un PSP
; imprimir inicio del bloque
; imprimir guión
; imprimir final del bloque
; pasar bytes a párrafos
; imprimir tamaño del bloque
RET imprime_rango
ENDP
imprime_pid
PROC
; por eliminación zona de datos
; retorno de carro
; salto de línea
RET
; no, no era el último
imprime_pid
ENDP
imprime_nombre PROC
PUSH
AX
CALL
print
INT
12h
JMP
nombre_ok
143
no_libre:
nombre_listo:
otra_letra:
cod_normal:
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
CMP
tipo,1
CALL
print4hex
; imprimir nibble más significativo
JE
nombre_listo ; bloque XMS: nombre de ES:8 a ES:16
POP
AX
; restaurar AL
CMP
tipo,2
PUSH
AX
; y preservarlo de nuevo
JE
nombre_ok
; nombre del propietario desconocido
AND
AL,1111b
; dejar nibble menos significativo
MOV
BX,ES:[1]
; segmento del PSP dueño del bloque
CALL
print4hex
; e imprimirlo
DEC
BX
; apuntar al MCB
POP
AX
MOV
ES,BX
POP
CX
MOV
BX,7
; nombre de ES:BX+1 a ES:BX+9
MOV
CX,8
; máximo tamaño del nombre
print8hex
ENDP
INC
BX
MOV
AL,ES:[BX]
; carácter del nombre
print16hex
PROC
AND
AL,AL
JZ
nombre_ok
CMP
AL,' '
JAE
cod_normal
MOV
AL,'?'
CALL
printAL
LOOPNZ otra_letra nombre_ok:
POP
RET
AX
MOV
AL,AH
CALL
print8hex
POP
AX
; evitar códigos raros en DOS < 4.0
CALL
print8hex
; imprimirlo
RET
; es cero: fin del nombre
; a por otro (8 como máximo)
print16hex
; imprimir parte baja
ENDP
; -------------------------- PRINT-32 v3.1 --------------------------
RET
; ;
PROC
; imprimir cadena en DS:DX con
;
; el final delimitado por un '$'
Subrutina para imprimir nº decimal de 32 bits en DXAX formateado.
PUSH
AX
;
No requiere ningún registro de segmento apuntándola; se apoya en
PUSH
CX
;
la rutina «print» para imprimir la cadena DS:DX delimitada por '$'.
MOV
AH,9
;
INT
21h
; Entradas:
POP
CX
;
Si bit 4
POP
AX
;
bits
0-3 = nº total de dígitos (incluyendo separadores de
bits
5-7 = nº de dígitos de la parte fraccional (cuantos
RET
;
print
ENDP
;
printAL
PROC
; imprimir carácter en AL
= 1 --> se imprimirán signos separadores de millar
millar y parte fraccional)
;
dígitos de DXAX, empezando por la derecha, se
;
consideran parte fraccional, e irán precedidos
;
del correspondiente separador)
PUSH
AX
PUSH
DX
; registros usados preservados
;
MOV
AH,2
; función de impresión del DOS
; Salidas:
MOV
DL,AL
; carácter a imprimir
;
INT
21h
; llamar al sistema
;
POP
DX
POP
AX
RET printAL
; imprimir parte alta
ES
imprime_nombre ENDP
print
; imprimir palabra hexadecimal (AX)
PUSH
nº impreso, ningún registro modificado.
; * Ejemplo, si DXAX=9384320 y ; recuperar registros
;
; retornar
; ;
ENDP
CL=010 1 1011
se imprimirá ( '_' representa un espacio en blanco ):
__93.843,20
Tener cuidado al especificar la plantilla para que ésta se adapte
; al número a imprimir. Si se especifican, por ej., pocos dígitos en print4hex
no_sup9:
print4hex
print8hex
; imprimir carácter hexadecimal (AL)
; la parte entera (=demasiados en la fraccional) no tiene sentido
PUSH
AX
; preservar AX
; imprimir el separador de millares. Si se intenta, la rutina podría
ADD
AL,'0'
; pasar binario a ASCII
; colgarse porque no valida el formato.
CMP
AL,'9'
JBE
no_sup9
; no es letra
print_32
ADD
AL,'A'-'9'-1 ; lo es
PUSHF
CALL
printAL
; imprimir dígito hexadecimal
PUSH
AX
POP
AX
; restaurar AX
PUSH
BX
RET
PUSH
CX
ENDP
PUSH
DX
PUSH
SI
PUSH
DI
PROC
PROC
; imprimir byte hexadecimal en AL
PROC
PUSH
CX
PUSH
DS
PUSH
AX
PUSH
ES
MOV
CL,4
MOV
BX,CS
SHR
AL,CL
MOV
DS,BX
; pasar bits 4..7 a 0..3
; preservar registros
143
LA GESTIÓN DE MEMORIA DEL DOS
digit_pr32:
factor_pr32:
hecho_pr32:
rep_sub_pr32:
SUB
CX,ent_frac_pr32
ADD
CX,3
BX,OFFSET tabla_pr32
MOV
SI,final_pr32
MOV
CX,10
MOV
DI,SI
PUSH
CX
INC
DI
PUSH
AX
REP
MOVSB
PUSH
DX
MOV
AL,millares_pr32
XOR
DI,DI
MOV
[DI],AL
MOV
SI,1
; DISI = 1
INC
final_pr32
DEC
CX
; CX - 1
MOV
ent_frac_pr32,SI
JCXZ
hecho_pr32
SUB
SI,OFFSET tabla_pr32
SAL
SI,1
CMP
SI,3
RCL
DI,1
JAE
entera_pr32
MOV
DX,DI
MOV
BX,final_pr32
MOV
AX,SI
MOV
BYTE PTR [BX+1],"$"
SAL
SI,1
MOV
BX,OFFSET tabla_pr32
RCL
DI,1
MOV
principio_pr32,BX ; inicio de cadena
SAL
SI,1
MOV
AL,[BX]
RCL
DI,1
CMP
AL,'0'
ADD
SI,AX
JE
blanco_pr32
; cero a la izda --> poner " "
ADC
DI,DX
; DISI=DISI*8+DISI*2=DISI*10
CMP
AL,millares_pr32
; separador millares a la izda
LOOP
factor_pr32
; DISI=DISI*(10^(CX-1))
JE
blanco_pr32
POP
DX
CMP
AL,fracc_pr32
POP
AX
JNE
acabar_pr32
MOV
CL,0FFh
MOV
BYTE PTR [BX-1],'0' ; reponer 0 antes de la coma
INC
CL
DEC
principio_pr32
SUB
AX,SI
MOV
AL,formato_pr32
SBB
DX,DI
; DXAX = DXAX - DISI
AND
AL,00001111b
JNC
rep_sub_pr32
; restar factor cuanto se pueda
XOR
AH,AH
ADD
AX,SI
; subsanar el desbordamiento:
MOV
DX,final_pr32
ADC
DX,DI
; DXAX = DXAX + DISI
SUB
DX,AX
ADD
CL,'0'
; pasar binario a ASCII
INC
DX
MOV
[BX],CL
AND
AX,AX
POP
CX
JNZ
format_pr32
INC
BX
MOV
DX,principio_pr32 ; longitud obtenida del número
LOOP
digit_pr32
MOV
ES,BX
MOV
formato_pr32,CL
MOV
STD
no_frac_pr32:
entera_pr32:
; byte del formato de impresión
; DISI * 2 poner_pr32:
limpiar_pr32: ; DISI * 8
; CX se recuperará más tarde
acabar_pr32:
; CX se recupera ahora
CALL
print
; transferencias hacia atrás
POP
ES
; próximo dígito del número
format_pr32:
DEC
BX
; BX apunta al último dígito
POP
DS
MOV
final_pr32,BX
; último dígito
POP
DI
MOV
ent_frac_pr32,BX
; frontera parte entera/fracc.
POP
SI
MOV
CL,5
POP
DX
MOV
AL,formato_pr32
POP
CX
SHR
AL,CL
POP
BX
AND
AL,AL
POP
AX
JZ
no_frac_pr32
MOV
CL,AL
XOR
CH,CH
MOV MOV INC
DI
REP
MOVSB
INC
; AL = nº de decimales
; cadena arriba (hacer hueco)
; poner separador de millares
; usar la variable como puntero
; próximo separador
; delimitador fin de cadena
; imprimir
; DX = offset 'principio'
; longitud solicitada
; imprimir cadena en DS:DX
; restaurar todos los registros
POPF
; ninguno
RET blanco_pr32:
; salida del procedimiento
MOV
BYTE PTR [BX],' ' ; quitar 0 / separador millares
SI,final_pr32
INC
BX
DI,SI
INC
principio_pr32
CMP
BX,final_pr32
JB
limpiar_pr32
final_pr32
MOV
DX,BX
MOV
AL,fracc_pr32
JMP
SHORT acabar_pr32 ; imprimir
MOV
[DI],AL
; separador de parte fraccional
DB
0
MOV
ent_frac_pr32,SI
; indicar nueva frontera
DB
5 DUP (' ')
MOV
AL,formato_pr32
DT
0
TEST
AL,16
; interpretar el formato
DW
0,0
JZ
poner_pr32
; imprimir como tal
millares_pr32
EQU
'.'
; separador de millares
MOV
CX,final_pr32
; añadir separadores de millar
fracc_pr32
EQU
','
;
; cadena arriba (hacer hueco)
formato_pr32
tabla_pr32
; sustituyendo por espacios
; es el número 0.000.000.00X
; área de trabajo
"
parte fraccional
143
final_pr32
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
DW
0
; offset último byte a imprimir
principio_pr32 DW
0
;
ent_frac_pr32
DW
0
; offset frontera entero-fracc.
print_32
ENDP
"
"
primer
"
"
; ------------ Datos
" cabecera_txt
LABEL BYTE
DB
13,10,"MAPAMEM 2.2"
DB
13,10,"
DB
"Tipo
DB
"-------- --------- ------- ----- ---------------"
DB
13,10,"Sistema
0000-003F
1.024
Interrupciones"
DB
13,10,"Sistema
0040-004F
256
Datos del BIOS"
DB
13,10,"Sistema
0050-$"
- Información sobre la memoria del sistema.",13,10,10 Ubicación Tamaño
PID
Propietario",13,10
cabx_txt
DB
"
tabla_tipos
DW
tipo_libre, tipo_xms, tipo_sistema
DW
tipo_programa, tipo_entorno, tipo_datos
tipo_libre
DB
"Libre
tipo_xms
DB
"Area XMS $"
tipo_sistema
DB
"Sistema
tipo_programa
DB
"Programa $"
tipo_entorno
DB
"Entorno
$"
tipo_datos
DB
"Datos
$"
libre_txt
DB
"$"
tipo
DB
0
pid
DW
0
tam_mapmem
EQU
($-OFFSET mapamem)/16+1
mapamem
ENDS END
Sistema Operat.",13,10,"$"
$"
$"
; tamaño de MAPAMEM
mapa
8.3. - MEMORIAS EXTENDIDA Y SUPERIOR XMS. El controlador XMS implementa una serie de funciones para acceder de manera sencilla a la memoria extendida. En principio, hay funciones para asignar y liberar el HMA (frecuentemente ya estará ocupado por el sistema operativo), para controlar la línea A20 (en la actualidad suele estar permanentemente habilitada), para averiguar la memoria extendida disponible, para asignar dicha memoria a los programas que la solicitan (a los que devuelve un handle de control, igual que cuando se abre un fichero), liberarla, devolver la dirección física para quien desee realizar transferencias directas y lo más interesante: para mover bloques, bien sea entre zonas de la memoria extendida o entre la memoria convencional y la extendida, de la manera más óptima y rápida según el tipo de CPU que se trate. Digamos que la memoria extendida XMS es como un gran banco o almacén de memoria torpe, del que podemos traer o llevar datos y nada más. Adicionalmente, el controlador XMS añade funciones para gestionar la memoria superior. Los bloques de memoria superior no son accesibles de manera directa por los programas, a menos que éstos sean expresamente cargados en este área con HILOAD ó LOADHIGH. Sin embargo, los programas pueden solicitar zonas de memoria superior al controlador XMS, que además de la memoria extendida gestiona también estas áreas. Estos bloques de memoria son gestionados de manera independiente a los de la memoria convencional, existiendo funciones específicas del controlador XMS para localizar y liberar los bloques. Con DR-DOS 6.0 y algunos gestores de memoria, en la memoria superior pueden residir tanto bloques de memoria DOS gestionados por el sistema (normalmente, como consecuencia de un HILOAD para instalar programas residentes), así como auténticos bloques de memoria XMS. Realmente, las zonas que emplea el DR-DOS no son sino bloques de este tipo de memoria. El MS-DOS 5.0 y posteriores, sin embargo, reservan toda la memoria superior para sus propios usos -cargar programas residentes- cuando se indica DOS=UMB en el CONFIG.SYS; por lo que si alguna aplicación
143
LA GESTIÓN DE MEMORIA DEL DOS
solicita memoria superior XMS no la encontrará. Pero se puede emplear la función 58h para conectar la memoria superior y a continuación, con la misma función, cambiar la estrategia de asignación de memoria para que el sistema asigne memoria superior en respuesta a las funciones ordinarias de asignación de memoria. Después es conveniente restaurar la estrategia de asignación y el estado de la memoria superior a la situación inicial (también se puede consultar previamente con la función 58h). La hecho de que un programa pueda solicitar memoria superior al sistema es una posibilidad interesante: ello permite a los programas residentes auto-relocalizarse de una manera sencilla a estas zonas, anticipándose a la actuación de usuarios inexpertos que podrían olvidarse del HILOAD o el LOADHIGH. Por otra parte, se economiza algo de memoria al poder suprimirse el PSP en la copia. Con MS-DOS 5.0 y posteriores, no obstante, el programa deberá dejar algo residente en memoria convencional (si no se termina residente, el sistema libera los bloques asignados en memoria superior) o bien modificar el PID de los bloques en memoria superior para que al terminar sin quedar residente el DOS no los libere. Para poder emplear los servicios del controlador XMS hay que verificar primero que está instalado el programa HIMEM.SYS o alguno equivalente (el EMM386 del DR-DOS 6.0 integra también las funciones del HIMEM.SYS, así como el QEMM386). Para ello se chequea la entrada 43h en la interrupción Multiplex, comprobando si devuelve 80h en el registro AL (y no 0FFh como otros programas residentes): MOV INT MOV CMP JE MOV INT CMP JE JNE
AX,352Fh 21h AX,ES AX,0 no_hay_XMS AX,4300h 2Fh AL,80h hay_XMS no_hay_XMS
; obtener vector de INT 2Fh en ES:BX
; en DOS 2.x la INT 2Fh está indefinida ; chequear presencia de XMS ; interrupción Multiplex
Antes de llamar a la INT 2Fh se comprueba que esta interrupción está apuntando a algún sitio (con el segmento distinto de 0) ya que en algunas versiones 2.x del DOS está sin inicializar y el sistema se cuelga si se invoca sin precauciones. Las funciones del controlador XMS no se invocan por medio de ninguna interrupción, como sucede con las del DOS o la BIOS. En su lugar, una vez detectada la presencia del mismo se le debe interrogar preguntándole dónde está instalado, por medio de la subfunción 10h: MOV INT MOV MOV
AX,4310h 2Fh XMS_seg,ES XMS_off,BX
; preguntar dirección del controlador ; almacenarla
donde XMS_seg y XMS_off es una estructura del tipo: gestor_XMS XMS_off XMS_seg
LABEL DWORD DW 0 DW 0
Posteriormente, cuando haya que utilizar un servicio o función del controlador XMS se colocará el número del mismo en AH y se ejecutará un CALL gestor_XMS. Para utilizar las llamadas al XMS es preciso que en la pila queden al menos 256 bytes libres. En un apéndice al final del libro se listan y documentan todas las funciones XMS. Si por cualquier motivo fuera necesario en un programa residente interceptar las llamadas al controlador XMS realizadas por los programas de aplicación, hay que decir que ello es posible. Por supuesto, no es tan sencillo como desviar un vector de interrupción: hay que modificar el código del propio controlador. Por fortuna, todos los controladores XMS suelen comenzar con una instrucción de salto larga o corta (JMP
143
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
XXXX:XXXX, JMP XXXX, JMP SHORT XX) y, si ésta ocupa menos de 5 bytes, los restantes están cubiertos de instrucciones NOP (código de operación 90h). Se pueden modificar los primeros bytes del mismo para poner un salto hacia nuestra propia rutina, que luego acabe llamando a su vez al controlador previo (el RAMDRIVE de Microsoft, por ejemplo, realiza esta complicada maniobra). 8.4.- MEMORIA EXPANDIDA EMS. La memoria expandida, como se comentó al principio del capítulo, es una técnica de paginación para solventar la limitación de 640 Kb de memoria de los PC. Hasta la versión 3 del controlador de memoria expandida, esta extensión consiste en un segmento de memoria de 64 Kb (en la dirección 0D0000h o 0E0000h, a veces otras como 0C8000h, etc.) dividido en cuatro páginas adyacentes de 16 Kb. Ese segmento se denomina marco de página de la memoria expandida. Las cuatro páginas son las páginas físicas numeradas entre 0 y 3. Cuando un programa solicita memoria expandida, se le asigna un handle de control (un número de 16 bits) que la referencia, así como cierto número de páginas lógicas asociado al mismo. A partir de ese momento, cualquier página lógica puede ser mapeada sobre una de las cuatro páginas físicas. De este modo, es factible acceder simultáneamente a cuatro páginas lógicas entre todas las disponibles. Por ello es posible incluso asignar la misma página lógica a más de una página física, aunque es un tanto absurdo. La principal utilidad de la memoria expandida es de cara a almacenar grandes estructuras de datos evitando en lo posible un acceso a disco. La memoria expandida se implementa con una extensión del hardware, aunque algunos equipos 286 ya la tienen integrada en la placa base. En los 386 y superiores, la CPU puede ser colocada en modo virtual 86, una variante del modo protegido en la que la memoria expandida puede ser emulada por las técnicas de memoria virtual de este microprocesador, sin necesidad de una extensión hardware. Algunos sistemas de memoria expandida real (no emulada) pueden soportar incluso una reinicialización del PC sin perder el contenido de esa memoria.
─ ─ ─ 16 Kb ─ ─ ─
DFFFF DC000 D8000 D4000 D0000
│ │ ├─────────────┤ │ 3 │ ├─ ─ ─ ─ ─ ─ ─┤ │ 2 │ ├─ ─ ─ ─ ─ ─ ─┤ │ 1 │ ├─ ─ ─ ─ ─ ─ ─┤ │ 0 │ ├─────────────┤
½─ ─ ┐ │ ┌ ½─ ─ ┤ │ │½──────────────┤ ½─ ─ ┤ │ │ │ ½─ ─ ┘ │ └
MARCO DE PÁGINA DE MEMORIA EXPANDIDA (PÁGINAS FÍSICAS)
½─ ½─ ½─ ½─ ½─ ½─ ½─
─ ─ ─ ─ ─ ─ ─
─ ─ ─ ─ ─ ─ ─
─ ─ ─ ─ ─ ─ ─
┌─────────────┐ │ ├─┐ A└─┬───────────┘ ├─┐ ─ B└─┬───────────┘ ├─┐ ─ ─ C└─┬───────────┘ ├─┐ ─ ─ ─ D└─┬───────────┘ ├─┐ ─ ─ ─ ─ E└─┬───────────┘ ├─┐ ─ ─ ─ ─ ─ F└─┬───────────┘ │ ─ ─ ─ ─ ─ ─ G└─────────────┘
PÁGINAS DE MEMORIA EXPANDIDA ASIGNABLES (PÁGINAS LÓGICAS)
En este ejemplo se ha solicitado al EMM 8 páginas (numeradas en el gráfico A-G) y cualquiera de ellas puede ser «colocada» (paginada) en cualquiera de las 4 páginas físicas, a elegir.
Para utilizar la memoria expandida hay que invocar la interrupción 67h. Para detectar la presencia del controlador hay dos métodos. El primero consiste en buscar un dispositivo "EMMXXXX0", ya que el gestor de memoria expandida se carga desde el CONFIG.SYS y define un controlador de dispositivo de caracteres con ese nombre. Es tan sencillo como intentar abrir un fichero con ese nombre y comprobar si existe. Desde la línea de comandos del DOS se puede hacer así: IF EXIST EMMXXXX0 ECHO HAY CONTROLADOR EMS Existe el riesgo de que en lugar de un controlador con ese nombre se trate ¡de un fichero que algún
LA GESTIÓN DE MEMORIA DEL DOS
143
gracioso haya creado!: para cerciorarse, hay unas funciones de control IOCTL en el DOS para asegurar que se trata de un dispositivo y no de un fichero. Sin embargo, no es recomendable este método para detectar el EMM en los programas residentes y en los controladores de dispositivo: existe otro medio más conveniente para esos casos, que también puede ser empleado de manera general en cualquier otra aplicación. Consiste en buscar la cadena "EMMXXXX0" en el offset 10 del segmento apuntado por el vector 67h (despreciando el offset de dicho vector) ¡así de sencillo!. Las funciones del EMM se invocan colocando en AH el número de función y ejecutando la INT 67h: a la vuelta, AH normalmente valdrá 0 para indicar que todo ha ido bien. En un apéndice al final del libro se listan y documentan todas las funciones EMS. Estas funciones se numeran a partir de 40h, aunque desde la 4Fh sólo están disponibles a partir de la versión 4.0 del controlador, si bien en muchos casos no son necesarias. Las principales funciones (soportadas por EMS 3.2) son: 40h - Obtener el estado del controlador (ver si es operativo y la memoria EMS puede funcionar bien). 41h - Obtener el segmento del marco de página (no tiene por qué se 0D000h ni 0E000h). 42h - Preguntar el número de páginas libres que aún no están asignadas. 43h - Asignar páginas (esta función devuelve un handle de control, igual que cuando se abre un fichero). 44h - Mapear páginas (colocar una cierta página lógica 0..N en una de las físicas 0..3). 45h - Liberar las páginas asignadas, para que puedan usarlas futuros programas (¡es vital!). 46h - Preguntar la versión del controlador de memoria expandida. 47h - Salvar el contexto del mapa de páginas (usado por los TSR para no alterar el marco de página). 48h - Restaurar el contexto del mapa de páginas (usado por los TSR para no alterar el marco de página). 4Dh - Obtener información de todos los handles que hay y las páginas que tienen asignadas.
La memoria expandida, lejos de ser sólo un invento obsoleto para superar los 640K en los viejos ordenadores, es una de las memorias más versátiles disponibles bajo DOS. Muchos programas pueden ver incrementado notablemente el rendimiento si se desarrollan empleando esta memoria en lugar de la XMS. La razón es que, con la memoria extendida, hay que traerla (copiarla) a la memoria convencional, procesarla y volverla a copiar a la memoria extendida. Sin embargo, con la memoria expandida EMS, una rapidísima función coloca en el espacio de direcciones del 8086 la memoria que va a ser accedida: allí mismo puede ser procesada sin necesidad de movimiento físico. Esto es debido a que la conmutación páginas de memoria expandida se hace, dicho entre comillas, seleccionando el chip de RAM que se utiliza, sin existir movimiento físico de datos. En algunos casos, sin embargo, la EMS no aumenta el rendimiento: por ejemplo, al construir un disco virtual, habrá que transferir datos desde la memoria convencional a la XMS ó la EMS; en cualquier caso se va a producir un movimiento físico (¿qué mas da que sea hacia la EMS que hacia la XMS?). En los modernos sistemas operativos, la memoria expandida soportada a partir de las versiones 4.0 del EMM (Expanded Memory Manager) cubre un amplio espectro del espacio de direcciones dentro del megabyte gestionado por el MS-DOS. Aquí, las páginas no han de ser necesariamente consecutivas; son más de 4 y tampoco tienen que ser necesariamente de 16 Kb. Sin embargo, por defecto -y por razones de compatibilidadlas cuatro primeras páginas físicas están colocadas adyacentemente por encima de los 640K y son de 16 Kb, no siendo recomendable modificar esta especificación. Por ejemplo, en el sistema 386 en que se escribieron las primeras versiones de este libro, con un EMM 4.0, las páginas físicas 0 a la 3 estaban ubicadas a partir de la dirección 0C8000h; las páginas 4 a la 27h estaban ubicadas entre la dirección 10000h a la 9FFFFh, cubriendo también los primeros 640 Kb (excepto los primeros 64 Kb). Si alguien está pensando en desviar la interrupción 67h desde un programa residente, para interceptar y manipular las llamadas de los programas de aplicación a esa interrupción, ya puede ir olvidándose. La razón es que los 386 y superiores están en modo virtual 86 con los controladores EMS instalados. Esto significa que cuando un programa invoca una interrupción, como la INT 67h, la CPU -de la manera que está programadapasa inmediatamente a continuación a ejecutar una rutina en modo protegido fuera del espacio de direcciones del MS-DOS. Con algunos gestores de memoria, como el EMM386 del DR-DOS 6.0, no sucede nada: ese programa supervisor retorna a la tarea virtual y ejecuta el código ubicado en el espacio de direcciones del MSDOS. Sin embargo, con QEMM386, el controlador de memoria está ubicado fuera de ese espacio de direcciones, y ya no vuelve a él. Si se mira con el DEBUG a donde apunta la INT 67h en una máquina con QEMM (por ejemplo, traceando una llamada a la interrupción), se verá que este vector apunta al siguiente
143
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
código: INT IRET
28h
Evidentemente, ¡ese no es el controlador de memoria!. Para acceder a él hay que ejecutar una interrupción de verdad. Supongo que a través de la especificación VCPI (Virtual Control Program Interface) que regula el acceso a los modos extendidos del 386, habrá algún medio de poder acceder al código del controlador EMS, o interceptar las llamadas. Sin embargo, no es tan fácil como cambiar un vector...
SUBPROCESOS, RECUBRIMIENTOS Y FILTROS
157
Capítulo IX: SUBPROCESOS, RECUBRIMIENTOS Y FILTROS
9.1. - LLAMADA A SUBPROCESOS Y RECUBRIMIENTOS U OVERLAYS. La función EXEC del DOS (4Bh) es el pilar que sustenta la ejecución de programas desde dentro de otros programas, así como la carga de subrutinas de un mismo programa desde disco (overlays). Si no existiera la función EXEC, el proceso sería arduo: habría que reservar memoria, cargar el fichero ejecutable en memoria, relocalizarlo si es de tipo EXE, crear su PSP y demás áreas de datos (entorno, etc)... por fortuna, la función EXEC se ocupa de todo ello. Además, esta función posee una característica no documentada hasta el DOS 5.0 (sí ha sido documentada desde dicha versión), que es la posibilidad de cargar un programa sin ejecutarlo, lo cual puede ser interesante de cara a la creación de depuradores de código. Para llamar a la función EXEC para cargar y ejecutar un programa se pone un 0 en AL. Hay que apuntar DS:DX a la dirección del nombre del programa (una cadena ASCIIZ, esto es, terminada por cero) que puede incluir la ruta de directorios y debe incluir la extensión. También hay que apuntar en ES:BX a una estructura de datos (bloque de parámetros) que se interpreta de la siguiente forma: offset 0: Segmento donde está el entorno a copiar para crear el del programa cargado. A 0 si es el del programa padre. Los programas hijos siempre accederán a una copia y no al original. offset 2: Doble palabra que apunta a los parámetros del programa a ejecutar (los que ese programa admite, por sí solo, en la línea de comandos). Tiene el mismo formato que el contenido de PSP:80h. offset 6: Doble palabra que apunta al primer FCB a copiar en el proceso hijo. offset 10: Doble palabra que apunta al segundo FCB a copiar en el proceso hijo. offset 14: Si se carga sin ejecutar, devuelve el SS:SP inicial del subprograma. offset 18: Si se carga sin ejecutar, devuelve el CS:IP inicial del subprograma. El subprograma cargado hereda los ficheros abiertos del programa padre. Antes de llamar a esta función, el ordenador debe tener suficiente memoria libre. Cuando se ejecuta un programa COM ordinario, toda la memoria del sistema está asignada al mismo (el mayor bloque en realidad, lo que en la práctica significa toda la memoria). Por tanto, un programa COM que desee cargar otros programas debe primero rebajar la memoria que el DOS le ha asignado y quedarse sólo con la que necesita. Con los programas EXE, la cantidad de memoria que les asigna el DOS inicialmente depende del compilador y las opciones de compilación; en ensamblador suele ser también toda la memoria, por lo que es deber de éste liberar la que no necesita. Para ello, se calcula cuanta memoria necesita el programa y se llama a la función del sistema para modificar el tamaño del bloque de memoria del propio programa (función 4Ah del DOS, pasando en ES la dirección del PSP). En los programas COM, la pila está apuntando al final del segmento (SP está próximo a 0FFFEh). Por ello, si el programa va a ocupar menos de 64 Kb, será preciso mover SP más abajo para que no se salga del futuro bloque de memoria del programa. Si no se toma esta precaución, SP apuntará dentro del siguiente bloque de memoria, que es más que probablemente el que utilizará EXEC, con lo que el ordenador debería colgarse a no ser que haya mucha suerte. Tras llamar a la función EXEC, en teoría todos los registros son destruidos, según la documentación oficial, incluidos SS:SP. Esto significa que antes de llamar a EXEC deben apilarse los registros que no se desee alterar y guardar en un par de variables SS y SP. Tras llamar a EXEC, inmediatamente a continuación y antes de hacer nada se deben recargar SS y SP, para proceder después a recuperar de la pila los demás registros. Este comportamiento de EXEC parece romper la tónica habitual de comportamiento del DOS. Sin embargo, lo cierto es que esto sólo sucedía en el DOS 2.X: aunque Microsoft no lo diga oficialmente, las versiones posteriores del
157
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
sistema sólo corrompen DX y BX al llamar a EXEC. El siguiente programa de ejemplo, de tipo COM, realiza todas las tareas necesarias para cargar otro programa. Como ejemplo, he decidido cargar el COMMAND.COM, aunque el programa a ejecutar podría ser cualquier otro; la ventaja de COMMAND es que crea una nueva sesión de intérprete de comandos y permite comprobar con comodidad qué ha sucedido con la memoria. ; ********************************************************************
MOV
WORD PTR [BX+6],5Ch
; *
*
MOV
WORD PTR [BX+8],CS
*
MOV
WORD PTR [BX+0Ah],6Ch
*
MOV
WORD PTR [BX+0Ch],CS
; ********************************************************************
LEA
DX,nombre
MOV
AX,4B00h
INT
21h
PUSH
CS
SEGMENT
POP
DS
ASSUME CS:shell, DS:shell
LEA
DX,adios_txt
MOV
AH,9
INT
21h
MOV
AX,4C00h
INT
21h
nombre
DB
"C:\DOS\COMMAND.COM",0
exec_info
DB
22 DUP (0)
hola_txt
DB
13,10
DB
"Estás dentro de SHELL.COM ...",13,10,"$"
DB
13,10
DB
"... Acabas de abandonar SHELL.COM",13,10,"$"
; *
SHELL.ASM 1.0
-
Demostración de carga de subprograma.
; *
TAMTOT
EQU
shell
ORG
1024
; este programa y su pila caben en 1 Kb.
100h
inicio: MOV
SP,TAMTOT
MOV
AH,4Ah
; redefinir la pila
MOV
BX,TAMTOT/16
INT
21h
LEA
DX,hola_txt
MOV
AH,9
INT
21h
LEA
BX,exec_info
MOV
WORD PTR [BX],0
MOV
WORD PTR [BX+2],80h
MOV
WORD PTR [BX+4],CS
; redimensionar bloque memoria
; mensaje de bienvenida
; PSP
adios_txt
shell
; FCB 0
; FCB 1
; cargar y ejecutar programa
; DS = CS
; mensaje de despedida
; terminar
; programa a ejecutar
ENDS END
inicio
Al ejecutar el programa anterior, y suponiendo que el ordenador tenga el COMMAND.COM en C:\DOS (es más cómodo que andar buscando la variable de entorno COMSPEC), se puede generar una sesión de trabajo como la que se muestra a continuación, en la que la utilidad MAPAMEM permite verificar la estructura de la memoria tras la ejecución de SHELL.COM: C:\COMPILER\86\AREA>shell
Estás dentro de SHELL.COM ...
Microsoft(R) MS-DOS(R) Versión 5.00 (C)Copyright Microsoft Corp 1981-1991.
C:\COMPILER\86\AREA>mapamem
MAPAMEM 2.2 - Información sobre la memoria del sistema.
Tipo
Ubicación Tamaño
PID
Propietario
-------- --------- ------- ----- --------------Sistema
0000-003F
1.024
Interrupciones
Sistema
0040-004F
256
Datos del BIOS
Sistema
0050-0B59
45.216
Sistema
0B5B-0CF1
6.512
0008
Programa 0CF3-0E1C
4.768
0CF3
COMMAND
64
0000
Libre
0E1E-0E21
Sistema Operat.
SUBPROCESOS, RECUBRIMIENTOS Y FILTROS
Entorno
0E23-0E52
768
0CF3
COMMAND
Entorno
0E54-0E6D
416
0E6F
SHELL
Programa 0E6F-0EAE
1.024
0E6F
SHELL
0EB0-0EC8
400
0ECA
COMMAND
Programa 0ECA-0F72
2.704
0ECA
COMMAND
Datos
Entorno
0F74-0F8B
384
0ECA
COMMAND
Entorno
0F8D-0FA5
400
0FA7
MAPAMEM
Programa 0FA7-0FFA
1.344
0FA7
MAPAMEM
Libre
0FFC-9FFE 589.872
0000
Sistema
A000-D800 229.392
0008
Sistema
D802-E159
38.272
0008
Libre
E15B-E179
496
0000
Programa E17B-E187
208
E17B
DOSVER
Programa E189-E5B7
17.136
E189
BUFFERS
Programa E5B9-E617
1.520
E5B9
FILES
Programa E619-E663
1.200
E619
LASTDRIV
Programa E665-E712
2.784
E665
NLSFUNC
Programa E714-E885
5.920
E714
GRAPHICS
Programa E887-EA09
6.192
E887
SHARE
Programa EA0B-EB0D
4.144
EA0B
DOSKEY
Programa EB0F-ECB8
6.816
EB0F
PRINT
Programa ECBA-ED17
1.504
ECBA
RCLOCK
Programa ED19-ED39
528
ED19
DISKLED
Programa ED3B-F1C7
18.640
ED3B
DATAPLUS
Programa F1C9-F230
1.664
F1C9
HBREAK
Programa F232-F255
576
F232
ANSIUP
Programa F257-F25C
96
F257
TDSK
Datos
F25E-F65D
16.384
F257
TDSK
Libre
F65F-F6FF
2.576
0000
157
C:\COMPILER\86\AREA>exit
... Acabas de abandonar SHELL.COM
C:\COMPILER\86\AREA>_
La subfunción EXEC para cargar un programa sin ejecutarlo se selecciona con AL=1; ES:BX apunta al bloque de parámetros que se definió para el caso normal de carga+ejecución. Esta subfunción asigna el PID, no obstante, al PSP del subprograma cargado. La subfunción de EXEC para cargar un overlay o recubrimiento, se llama con los mismos valores en los registros que la anterior, exceptuando AL (que ahora vale 3). Sin embargo el bloque de parámetros apuntado por ES:BX es ahora mucho más sencillo: Offset 0: Segmento donde cargar el overlay (la memoria ha de asignarla el programa principal). Offset 2: Factor de reubicación, si se trata de un fichero EXE (normalmente el mismo valor que el anterior, si el subprograma va a correr en el mismo segmento en que es cargado). El overlay puede haber sido ensamblado, por ejemplo, con un desplazamiento relativo nulo (ORG 0) de manera que para llamarlo hay que hacer un CALL FAR al segmento donde ha sido cargado, con un offset 0. Claro que también se puede calcular la distancia que hay entre el segmento del programa principal y el del overlay, multiplicarlo por 16 y utilizarlo como offset en la llamada al mismo segmento del programa principal. Sin embargo, esto requiere que el overlay sea ensamblado con cierto offset ... a calcular. Quienes proponen este segundo método -que los hay- andaban ese día más bien despistados. En general, la programación con overlays es compleja, y más aún si los overlays constan de varios segmentos internos. Para conocer si la función EXEC se ha realizado correctamente o ha fracasado, se puede utilizar la
157
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
función 4Dh del DOS (Obtener código de retorno), que devuelve en AH: 0 (terminación normal), 1 (programa abortado por Ctrl-Break), 2 (terminación por error crítico) ó 3 (terminación residente). Al llamar a la función 4Dh, se borra la información que devuelve (sólo funciona la primera llamada). En AL se devuelve el valor que retorna el programa que finaliza (valor de ERRORLEVEL). 9.2. - FILTROS. El DOS es un sistema operativo que soporta el redireccionamiento. Las posibilidades son, sin embargo, muy limitadas. La razón es la ineficiencia del sistema en las operaciones de entrada y salida, que obliga a las aplicaciones a hacer accesos directos al hardware. Por ejemplo: con el comando interno CTTY, a través de un puerto serie es factible poner a un PC como servidor remoto de otro. Esto permite operar en la línea de comandos desde el terminal remoto ubicado a varios metros de distancia. Sin embargo, nada más ejecutar un programa, el teclado del PC con el emulador de terminal dejará de funcionar y será preciso utilizar ¡el del propio servidor!: la razón es que muy pocos programas usan el DOS para leer el teclado; no digamos para escribir en la pantalla... Sin embargo, aún en la actualidad muchos usuarios de PC trabajan en la línea de comandos, donde sí es posible, como se ha mencionado, utilizar el DOS como un sistema con dispositivos de entrada y salida estándar que soportan el redireccionamiento. El redireccionamiento bajo DOS es empleado sobre todo para procesar ficheros de texto. Un filtro es un programa normal que lee datos de la entrada estándar (por defecto, el teclado), los procesa de alguna manera y los deposita en la salida estándar (por defecto, la pantalla). Tanto la entrada como la salida estándar, popularmente conocidas como STDIN y STDOUT, respectivamente, así como la salida estándar para errores (STDERR) son dispositivos permanentemente abiertos en el DOS. Tienen asociados un handle de control, como cualquier fichero: 0 para STDIN (denominado CON), 1 para STDOUT (también conocido por CON), 2 para STDERR (también CON), 3 para la salida serie (denominada AUX) y 4 para la impresora (conocida por PRN). Por tanto, un filtro normal debe limitarse a leer, con las funciones de manejo de ficheros ordinarias, información procedente del handle 0; tras procesarla debe escribirla en el handle 1. Si se produce un error en el proceso, o hay una salida de log que no deba mezclarse con la salida deseada por el usuario, se puede escribir el mensaje en el handle 2. El redireccionamiento y el sistema de ficheros por handle fue incluido a partir del DOS 2.0 (en versiones anteriores no hay siquiera subdirectorios). Cuando se ejecuta una orden del tipo COMANDO | FILTRO, el intérprete de comandos cierra la salida estándar y crea un fichero auxiliar (de nombre extraño); a continuación abre ese fichero para salida: como al cerrar la salida estándar se había liberado el handle 1, ese handle será asignado al nuevo fichero. Esto significa que toda la salida de COMANDO no irá a la pantalla (CON) sino al fichero auxiliar. Cuando se acabe de ejecutar COMANDO, el intérprete de mandatos cerrará el fichero auxiliar y volverá a abrir la salida estándar, restaurando el sistema al estado normal. Pero la cosa no queda ahí, evidentemente: a continuación se cierra la entrada estándar y se abre como entrada el fichero auxiliar recién creado, que pasará a ser el nuevo dispositivo de entrada por defecto. Seguidamente, se carga y ejecuta FILTRO, que tomará los datos del fichero auxiliar en lugar del teclado. Al final, el fichero auxiliar es cerrado y borrado, abriéndose y restaurándose la entrada por defecto normal. Si se ejecuta DIR | SORT, aparte del directorio ordenado aparecerán dos extraños ficheros con 0 bytes (este era su tamaño cuando se ejecutó DIR): el DOS crea dos ficheros auxiliares para sustituir la entrada y salida estándar, aunque en este ejemplo sólo se emplee uno de ellos. Actuarán los dos si se utilizan filtros encadenados que obliguen a redireccionar simultáneamente tanto la entrada como la salida a ficheros auxiliares, en una orden del tipo DIR | SORT | MORE. A partir del DOS 5.0, si está definida la variable de entorno TEMP los ficheros auxiliares se crean donde ésta indica y no en el directorio activo, por lo que a simple vista podrían no verse dichos ficheros. Cuando se utilizan los redirectores habituales ('<', '>', '<<' y '>>') suceden procesos similares, todos ellos desencadenados por COMMAND.COM, con objeto de alterar la salida y entrada por defecto para trabajar con
157
SUBPROCESOS, RECUBRIMIENTOS Y FILTROS
un fichero en su lugar. Por tanto, los filtros son programas que no tienen que preocuparse de cual es la entrada o salida; su codificación es extremadamente sencilla y puede realizarse en cualquier lenguaje de alto o bajo nivel. El siguiente programa en C estándar, NULL.C, es un filtro nulo que no realiza tarea alguna: se limita a enviar todo lo que recibe (por tanto, DIR es lo mismo que DIR | NULL): #include
void main() { int c;
do putchar(c=getchar()); while (c!=EOF); }
El siguiente filtro, algo más útil, transforma en minúsculas todo lo que pasa por él, teniendo cuidado con los caracteres españoles (Ñ, Ü, Ç, etc.). Lee bloques de medio Kbyte de una sola vez para reducir el número de llamadas al DOS y ganar velocidad. Si se ejecuta sin más (sin emplear '|' ni '<' ni ningún símbolo de redireccionamiento o filtro) se limita a leer líneas del teclado y a reescribirlas en minúsculas, hasta que se acaba la entrada estándar (teclear Ctrl-Z y Return al final). ; ********************************************************************
INT
; *
RET
; *
* MIN.ASM 1.0
-
Filtro para poner en minúsculas ASCII Español.
; *
*
escribe_salida ENDP
*
; ********************************************************************
segmento
21h
pon_minusculas PROC
SEGMENT ASSUME CS:segmento, DS:segmento
procesa_car:
PUSH
CX
LEA
BX,buffer
MOV
AL,[BX]
CMP
AL,'A'
STDIN
EQU 0
JB
car_ok
STDOUT
EQU 1
CMP
AL,128
JAE
car8
CMP
AL,'Z'
JA
car_ok
OR
AL,32
MOV
[BX],AL
INC
BX
LOOP
procesa_car
POP
CX
ORG
100h
inicio: CALL
lee_entrada
; leer de STDIN
JCXZ
fin_filtro
; en CX, bytes leídos
car_ok:
PUSHF CALL
pon_minusculas
CALL
escribe_salida
; escribir en STDOUT
RET
POPF
fin_filtro:
lee_entrada
lee_entrada
JNC
inicio
MOV
AX,4C00h
INT
21h
car8: ; CF = 1 si fin de fichero
PROC
MOV
AH,'ñ'
CMP
AL,'Ñ'
JE
trad_ok
MOV
AH,'ç'
CMP
AL,'Ç'
LEA
DX,buffer
JE
trad_ok
MOV
CX,512
MOV
AH,'ü'
MOV
BX,STDIN
CMP
AL,'Ü'
MOV
AH,3Fh
JE
trad_ok
INT
21h
MOV
AH,'é'
MOV
CX,AX
CMP
AL,'É'
RET
JE
trad_ok
ENDP
MOV
AH,AL
MOV
AL,AH
JMP
car_ok
; leer
trad_ok: escribe_salida PROC LEA
DX,buffer
MOV
BX,STDOUT
MOV
AH,40h
pon_minusculas ENDP
buffer
DB
512 DUP (?)
; escribir
157
segmento
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
ENDS END
inicio
161
PROGRAMAS RESIDENTES
Capítulo X: PROGRAMAS RESIDENTES
En este capítulo vamos a abordar uno de los temas más estrechamente relacionados con la programación de sistemas: la creación de programas residentes. El DOS es un sistema monousuario y monotarea, diseñado para atender sólo un proceso en un momento dado. Los programas residentes, aquellos que permanecen en memoria tras ser ejecutados, surgieron como intento de superar esta limitación. Algunos de estos programas residentes proporcionan en la práctica multitarea real (tales como colas de impresión o relojes), pero otros están muertos a menos que el usuario los active. A la hora de construir programas residentes el ensamblador es el lenguaje más apto: es el más potente, el programador controla totalmente la máquina sin depender de facetas ocultas del compilador y, además, es el lenguaje más sencillo para crear programas residentes (en inglés, TSR: Terminate and Stay Resident). Para los programas más complejos puede ser necesario, en cambio, utilizar algún lenguaje de alto nivel próximo a la máquina. Sin duda, los programas residentes que pretendan captar gran número de usuarios, deben cumplir dos requisitos: por un lado, ocupar poca memoria; por otro, estar disponibles rápidamente cuando son requeridos y, también, ser fiables y crear pocos conflictos. Esto último es importante, ya que un programa residente puede funcionar más o menos bien pero no del todo: si bien la máquina puede resistirse a colgarse, pueden aparecer anomalías o conflictos con algunas aplicaciones. En particular, es muy común la circunstancia de que dos programas residentes sean incompatibles entre sí. 10.1. - PRINCIPIOS BÁSICOS. Un programa residente o TSR es un programa normal y corriente que, tras ser cargado, permanece parcial o totalmente en memoria al finalizar su ejecución. Ello es posible utilizando una función específica del sistema operativo. Los programas residentes pueden ser activados mediante una combinación de teclas o bien actuar con cierta periodicidad, asociados a la interrupción del temporizador. También pueden interceptar funciones del DOS o de la BIOS para cambiar o modificar su funcionamiento. Al final, casi siempre resulta totalmente inevitable desviar alguna interrupción hacia una nueva rutina que la gestione, con objeto de activar el programa residente. Como en casi todos los aspectos de la programación, existen unos cuantos principios fundamentales que conviene respetar: 1) Los programas residentes no deben alterar el funcionamiento normal del resto del ordenador. Esto significa que deben preservar el estado de todo lo que van a modificar durante su ejecución, restaurándolo después antes de retornar al programa principal, lo cual no se limita por supuesto a los registros de la CPU, sino que incluye también la pantalla, los discos, el estado de la memoria expandida y extendida, etc. Cuando se produce la interrupción que activa el programa residente, los registros de la CPU pueden tener un valor que hay que interpretar o bien pueden ser aleatorios. Este último es el caso de la interrupción periódica del temporizador: el programa residente sólo puede fiarse de CS:IP, los demás registros deberán ser inicializados antes de empezar a operar (lógicamente, habrán de ser primero preservados para ser restaurados al final). 2) No se pueden invocar libremente desde un programa residente los servicios del sistema operativo. Si el lector es la primera vez que oye esto, quizá se quede extrañado. Tal vez se pregunte qué sucedería si desde un programa residente se llama (pongamos por ejemplo, una vez cada segundo) a la función de impresión del DOS para sacar una 'A' por la pantalla. Lo que puede suceder -y acabará sucediendo, si no a la primera 'A', a la segunda o la tercera- es que el ordenador se cuelgue. Esto es debido a que el DOS es un sistema operativo no reentrante, entre otras razones porque conmuta a una pila propia al ser invocado. Por ello, si se llama a un servicio del DOS desde un programa residente, es posible que en ese momento el DOS ya estuviese realizando otra función del programa principal y lo que vamos a conseguir es que se vuelva loco y pierda el control cuando
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
se acabe la tarea residente (el contenido previo de la pila ha sido destrozado). Para utilizar el DOS desde un programa residente hay que conocer cómo están organizadas las pilas del sistema operativo, así como determinar el estado del DOS para saber si se puede interrumpir en ese momento o si hay que esperar. Utilizar el DOS es prácticamente indispensable a la hora de acceder al disco, por lo que más adelante en este capítulo lo veremos con detenimiento. Para utilizar el DOS hay que emplear funciones más o menos secretas del sistema no documentadas por Microsoft, si bien esto no es peligroso: esta empresa las utiliza y las ha utilizado siempre profusamente en sus propios programas, por lo que resulta más que seguro esperar que futuras versiones del DOS sigan soportándolas. 3) La BIOS no es tampoco completamente reentrante. Por fortuna, la BIOS utiliza la pila del programa que le llama. Por ello, para utilizar funciones de la BIOS desde un programa residente basta con asegurar que el sistema no está ya ejecutando una función BIOS incompatible (normalmente, una interrupción 10h en el caso de las funciones de vídeo o la 13h en las de disco). 4) El hardware puede ser accedido sin limitaciones desde los programas residentes, si bien el nivel de uso que puede hacerse está limitado por el sentido común (puede haber problemas, por ejemplo, si un programa residente cambia la posición del cabezal de un disquete cuando el programa principal estaba ejecutando una función del DOS o la BIOS para acceder al disquete). 5) Los programas residentes tienen una causa que provoca su activación. Si cuando ya están activos, se vuelve a reproducir la causa, estamos ante un problema de reentrada que compete exclusivamente al programador. Por lo general, se suele denegar una demanda de activación cuando el programa residente ya estaba activo (si el programa tiene pila propia esto es además obligatorio). Pongamos por caso que se pulsa CTRL-ALT-R para mostrar un reloj residente en pantalla, ¿qué sucederá si se vuelve a pulsar CTRL-ALT-R con el reloj ya activado?. Para solucionar esto, existen dos caminos: uno de ellos es utilizar una variable que indique que el programa ya está activo. El otro, es utilizar para desactivar el programa la misma secuencia de teclas que para activarlo. Lógicamente, los programas que realicen algo periódicamente (pongamos por caso 18,2 veces por segundo) basta con que se limiten a no pillarse los dedos, esto es, utilizar menos de 1/18,2 segundos de tiempo de CPU para sus tareas. 10.2. - UN EJEMPLO SENCILLO. El siguiente programa residente no realiza tarea alguna, tan sólo es una demostración de la manera general de proceder para crear un programa residente. En principio, el código de instalación está colocado al final, con objeto de no dejarlo residente y economizar memoria. La rutina de instalación (MAIN) se encarga de preservar el vector de la interrupción periódica y desviarlo para que apunte a la futura rutina residente. También se instala una rutina de control de la interrupción 10h. Finalmente, se libera el espacio de entorno para economizar memoria y se termina residente. El procedimiento CONTROLA_INT8 puede ser modificado por el lector para que el programa realice una tarea útil cualquiera 18,2 veces por segundo: de la manera que está, se limita a llamar al anterior vector de la INT 8 y a comprobar que no se está ejecutando ninguna función de vídeo de la BIOS (que no se ha interrumpido la ejecución de una INT 10h). Esto significa que el lector podrá utilizar libremente los servicios de vídeo de la BIOS, si bien para utilizar por ejemplo los de disquetes habría que desviar y monitorizar también INT 13h; por supuesto además que no se puede llamar al DOS en este TSR (no se puede hacer INT 21h directamente desde el código residente). Por cierto, si se fija el lector en la manera de controlar la INT 10h verá que al final se retorna al programa principal con IRET: los flags devueltos son los del propio programa que llamó y no los de la INT 10h real. Con la INT 10h se puede hacer esto, ya que los servicios de vídeo de la BIOS no utilizan el registro de estado para devolver ninguna condición. Sin embargo, con otras interrupciones BIOS (ej. 16h) o las del DOS habría que actuar con más cuidado para que la rutina de control no altere nada el funcionamiento normal. Puede que el lector haya visto antes programas residentes que no toman la precaución de monitorizar la interrupción 10h o la 13h de la BIOS, y tal vez se pregunte si ello es realmente necesario. La respuesta es tajantemente que sí. Como se verá en el futuro en otro programa de ejemplo, reentrar a la BIOS sin más puede provocar conflictos.
161
PROGRAMAS RESIDENTES
demores
main:
PUSH
ES
MOV
AX,3508h
INT
21h
MOV
ant_int08_seg,ES
MOV
ant_int08_off,BX
MOV
AX,3510h
INT
21h
MOV
ant_int10_seg,ES
MOV
ant_int10_off,BX
POP
ES
LEA
DX,controla_int08
MOV
AX,2508h
INT
21h
; Colocar aquí el proceso a ejecutar 18,2 veces/seg.
LEA
DX,controla_int10
; que puede invocar funciones de INT 10h
MOV
AX,2510h
INT
21h
PUSH
ES
MOV
ES,DS:[2Ch]
MOV
AH,49h
INT
21h
POP
ES
SEGMENT ASSUME CS:demores, DS:demores
ORG
100h
JMP
main
inicio:
controla_int08 PROC PUSHF CALL
CS:ant_int08
; llamar al gestor normal de INT 8
; obtener vector de INT 8
; obtener vector de INT 10h
STI CMP
CS:in10,0
JNE
fin_int08
; estamos dentro de INT 10h
; nueva rutina de INT 8
;
fin_int08:
; nueva rutina de INT 10h
IRET controla_int08 ENDP
controla_int10 PROC INC
CS:in10
; indicar entrada en INT 10h
PUSHF CALL
CS:ant_int10
DEC
CS:in10
; dirección del entorno
; liberar espacio de entorno
LEA
DX,main
; fin del código residente
IRET
ADD
DX,15
; redondeo a párrafo
controla_int10 ENDP
MOV
CL,4
SHR
DX,CL
; bytes -> párrafos
MOV
AX,3100h
; terminar residente
INT
21h
in10
DB
0
ant_int08
LABEL DWORD
ant_int08_off
DW
?
ant_int08_seg
DW
?
ant_int10
LABEL DWORD
ant_int10_off
DW
?
ant_int10_seg
DW
?
; fin de la INT 10h
; mayor de 0 si hay INT 10h
demores
ENDS END
inicio
; Dejar residente hasta aquí.
10.3. - LOCALIZACIÓN DE UN PROGRAMA RESIDENTE. Un programa residente que ya está instalado en memoria puede volver a ser cargado desde disco y esto hay que tenerlo en cuenta. Puede que el programa sea de éstos que se cargan una sola vez y carecen de parámetros. En ese caso, no sucederá nada porque sea creada en memoria una nueva copia del mismo: es problema del usuario. Sin embargo, si una recarga posterior puede provocar un cuelgue del sistema o, simplemente, el programa tiene opciones y se pretende modificar los parámetros de la copia ya residente, entonces se hace necesario que el programa tenga capacidad para buscarse en memoria y encontrarse a sí mismo en el caso de que ya estuviera cargado. 10.3.1 - MÉTODO DE LOS VECTORES DE INTERRUPCIÓN. El método más simple es también el más simplón -inútil- y consiste en apoyarse en los vectores de interrupción. Por ejemplo, si el programa quedó residente interceptando la interrupción 9, basta con mirar a dónde apunta dicha interrupción y comprobar un grupo de bytes o alguna identificación que permita determinar si el programa que la gestiona es ya una copia de él mismo. El inconveniente de este método, fácil de deducir, es que si se carga más de un programa residente que emplee la INT 9, sólo el último cargado será capaz de
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
encontrarse a sí mismo en memoria. 10.3.2. - MÉTODO DE LA CADENA DE BLOQUES DE MEMORIA. Otro método alternativo es rastrear la cadena de bloques de memoria del sistema operativo buscando programas residentes y comprobándolos uno por uno. Este método es bastante rápido, habida cuenta de que no van a existir más de 20-50 bloques de memoria. Sin embargo, la organización de la memoria en los PCs es a veces tan anárquica que este método (que debería ser el más elegante) es un poco peligroso en cuanto a la seguridad, aunque mucho menos que el anterior. Lo cierto es que puede ser difícil intentar recorrer la memoria superior, habida cuenta del desigual tratamiento que recibe en las diversas versiones del DOS y con los diversos controladores de memoria que pueden estar instalados. Por cierto, la idea de rastrear toda la memoria (1 Mb), buscando desesperadamente una cadena de identificación, no es nueva. Sin embargo es tremendamente lenta llevada a la práctica. Es incómoda (hay que considerar el caso de que el propio programa que busca se encuentre a sí mismo, en particular en áreas como los buffers de transferencia con disco del DOS) y bastante salvaje. 10.3.3. - MÉTODO DE LA INTERRUPCIÓN MULTIPLEX. Finalmente, existe la posibilidad de utilizar el mismo sistema que emplea el DOS para comprobar la presencia de sus propios programas residentes (como el KEYB, GRAPHICS, GRAFTABL, SHARE, PRINT, etc) basado en la interrupción Multiplex (2Fh). Este sistema es el más seguro, aunque un tanto laborioso. Consiste en llamar a la INT 2F con un valor en el registro AH que indica quién está llamando, y otro valor en AL para decir por qué está llamando (normalmente 0). Los valores 00-BFh en AH están reservados para el DOS, y de C0h-FFh para las aplicaciones. A la vuelta, AL devuelve un valor 0 para indicar que el programa no está instalado pero está permitida la instalación, un valor 1 para decir que no está instalado ni tampoco está permitida la instalación. Si devuelve FFh, significa que el programa ya estaba instalado. Por ejemplo, el KEYB del DOS llama a INT 2Fh con AX=AD80h, donde ADh significa que quien pregunta es el KEYB -y no otro programa- para conocer si ya está instalado o no. En caso de que lo esté (AL=FFh a la vuelta), también se devuelve en ES:DI la dirección del KEYB ya residente (que es lo solicitado con AL=80h). En el caso concreto del KEYB, si a la vuelta AL<>FFh se interpreta que el programa no está aún residente, por lo que se procede a su instalación (en este caso, curiosamente incluso aunque AL=1). Esta técnica cuenta con la complicación que supone decidir qué valor emplear en la interrupción multiplex. Es evidente que dos programas residentes no pueden utilizar el mismo. Los programas menos eficientes utilizan un valor fijo predeterminado, con lo que limitan las posibilidades del usuario. Sin embargo, para solucionarlo existen varias alternativas, que se verán más adelante. Aviso: Aunque no es frecuente, algunas versiones 2.X del sistema no tienen inicializado el vector de la INT 2Fh. Por ello, es una buena práctica asegurarse de que esta interrupción apunta a algo antes de llamarla (por ejemplo, verificando que el segmento es distinto de cero). Por otro lado, el comando PRINT del DOS en las versiones 2.X del sistema gestiona de tal manera la INT 2Fh que ninguna otra aplicación puede emplearla. Por ello, el método de la interrupción Multiplex está más bien reservado para versiones 3.0 o superiores (también la 2.X si el usuario prescinde de PRINT). 10.4. - EXPULSIÓN DE UN PROGRAMA RESIDENTE DE LA MEMORIA Se trata de una tarea bastante sencilla en sí, aunque hay que tener en cuenta una serie de factores. En primer lugar, el programa debe restaurar todos los vectores de interrupción que había interceptado. Ello significa que si ha sido instalado tras él otro programa residente que modifica uno de los vectores que él interceptaba, ya no es posible restaurarlo. Por ello, un primer requisito para permitir la desinstalación es que sea el último programa residente cargado que utiliza un vector de interrupción dado. Esto es fácil de verificar, basta con comprobar que todas las interrupciones interceptadas siguen apuntando a una copia de él. Si esta prueba es superada satisfactoriamente, puede procederse a restaurar los vectores de interrupción y liberar la memoria
PROGRAMAS RESIDENTES
161
ocupada de una de las dos siguientes maneras: 1) Pasando en ES el segmento donde está cargado el programa y llamando a la función 49h del DOS para liberar el bloque de memoria. 2) Liberando directamente el bloque de memoria al colocar una palabra a cero en los bytes del MCB que identifican al propietario del bloque. Este método puede ser más seguro si está instalado un gestor de memoria expandida extraño, aunque es menos elegante y quizá menos recomendable. Por lo general, no tiene mucho sentido que un usuario elimine un programa residente después de haber cargado otro -aunque ello sea posible- ya que se origina un hueco en la memoria que normalmente no se utilizará para nada -el DOS asigna siempre el mayor bloque disponible al cargar cualquier aplicación-, aunque esto es realmente problema exclusivo del usuario. Como se verá después, ciertos programas residentes sofisticados permiten ser desinstalados aún sin ser los últimos instalados; sin embargo, estos programas residentes tienen que tener algo en común: comportarse de la misma manera y actuar también de una manera definida. Ello significa que si entre dos programas residentes que cumplen el mismo convenio el usuario instala un programa que no lo respeta, se pierden todas las posibilidades. 10.5.- GESTIÓN AVANZADA DE LA INTERRUPCIÓN MULTIPLEX. 10.5.1. - EL CONVENIO BMB COMPUSCIENCE. Para solucionar el problema de que dos programas residentes no pueden utilizar el mismo valor de identificación en la interrupción Multiplex, los señores de BMB Compuscience Canada pensaron un buen sistema, publicado en el INTERRUP.LST de Ralf Brown, que expongo a continuación. La idea consiste en asignar dinámicamente el valor del registro AH empleado al llamar a la interrupción Multiplex. Para ello se empieza, por ejemplo, con AH=0C0h. Se coloca un 0 en AL para solicitar chequeo de instalación y se hace que los registros ES:DI valgan 0EBEBh:0BEBEh (porque sí), llamando a continuación a la INT 2Fh. A la vuelta se devuelve en 0 en AL para indicar programa no instalado, un 1 para señalar además que no se debe instalar, y FFh para decir que ya está instalado... ¿quién?: un programa cuyo nombre de fabricante abreviado (MMMM), nombre de producto (PPPPPPPP) y versión (NNNN) están en ES:DI de la forma "BMB MMMMPPPPPPPPvNNNN". Si se comprueba que ese programa no es el buscado, se incrementa AH y si AH es menor o igual a 0FFh se repite el proceso. De este bucle puede salirse de dos maneras: encontrando el programa buscado (y su ubicación en memoria) o sin encontrarle, en cuyo caso también se habrá localizado algún valor de AH aún no utilizado por ninguna tarea residente (a no ser que el usuario haya instalado ya 64 programas residentes con esta técnica). Lógicamente, el programa residente debe interceptar también INT 2Fh y devolver (cuando alguien pregunta por él) un valor FFh en AL y, si además el que preguntaba llamaba con ES:DI=0EBEBh:0BEBEh entonces debe devolver en ES:DI la información antes mencionada. Lo de emplear 0EBEBh y 0BEBEh constituye un mecanismo similar a un password, para evitar que al programa que llama a INT 2Fh se le modifique ES:DI sin que lo sepa. 10.5.2. - EL CONVENIO CiriSOFT. El convenio anterior adolece de un defecto importante: ya puestos a determinar con tanto detalle el fabricante, nombre y versión del programa, ¿por qué no colocar más información útil?. Por ejemplo, sería interesante disponer de información sobre los contenidos previos de los vectores de interrupción que el programa ha desviado, lo cual permitiría su desinstalación aunque no sea el último cargado, ser desinstalado por parte de otros programas o incluso emplear ciertas técnicas de relocalización en memoria para evitar la fragmentación de la misma cuando es desinstalado. Con objeto de aumentar la eficacia, el autor de este libro desarrolló un método nuevo, extensión del expuesto en el apartado anterior, que permitiera sacar mayor partido de la interrupción Multiplex. Al igual que el anterior, el nuevo convenio también está publicado en el
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
INTERRUP.LST, lo que garantiza su difusión y la inversión de quienes decidan emplearlo. El método es similar al anterior, con la diferencia de que en ES:DI está almacenado en el momento de llamar el valor 1492h:1992h. En AH se indica, como siempre, el número de entrada de la interrupción Multiplex y en AL se coloca un 0 solicitando chequeo de instalación. Tras llamar, si AL devuelve un 1 ó un 0FFh significa que esa entrada ya está empleada, si devuelve un 0 significa que está libre y que puede ser utilizada. Hasta ahora, todo sucede como es costumbre en los programas que utilizan la interrupción Multiplex. Sin embargo, por el hecho de haber llamado con ES:DI=1492h:1992h, el programa residente sabe que quien lo llama es alguien que respeta el convenio. Por ello, además de devolver un 0FFFFh en AX, modifica ES y DI para apuntar a una tabla con la siguiente información: Offset -16
Tamaño WORD
-14
WORD
-12
WORD
-10
BYTE
-9
BYTE
-8 -6 -4 00h
WORD WORD 4 BYTEs ???
Descripción segmento donde realmente comienza el código del TSR (CS en programas con PSP, segmento de memoria superior XMS si instalado como UMB...) offset donde realmente comienza el código del TSR (frecuentemente 100h en programas *.COM y 0 en TSR's en memoria superior). memoria empleada por el TSR (en párrafos). Conociendo la memoria que emplea el TSR es posible determinar si los vectores que intercepta están aún apuntándolo (y si es seguro el proceso de desinstalación). de características bits 0-2: 000 programa normal (con PSP) 001 bloque de memoria superior XMS (se necesita función de HIMEM.SYS para liberar la memoria al desinstalar) 010 device driver (*.SYS) 011 device driver en formato EXE 1xx otros (reservados) bits 3-6 reservados bit 7 activo si tabla_extra definida y soportada número de entrada en la interrupción Multiplex (redefinible por un agente externo). Notar que el TSR debe usar ESTA variable en su rutina de control de INT 2Fh. offset a la tabla area_vectores (se verá después) offset a la tabla area_extra (ver bit 7 en offset -10) "*##*" (asegurar que el TSR verifica el convenio) "AUTOR:NOMBRE_DEL_PROGRAMA:VERSION",0 (longitud variable, este área es empleada de cara a determinar si el TSR está ya residente y su versión; el carácter ':' se utiliza como delimitador).
El valor ubicado en ES:DI-14 puede ser útil de cara a deducir el tamaño de la parte del PSP que permanece residente, ya que se considera que la ubicación del programa comienza en el offset 0 relativo al segmento definido en ES:DI-16 y, por tanto, el tamaño del programa definido en ES:DI-12 es relativo también con offset 0 a ese segmento. Si bien se puede opinar que son demasiados campos, son sólo poco más de 16 bytes los que se añaden al programa residente. Además, muchas de las variables anteriores han de estar definidas necesariamente: ¿por qué no juntarlas de una manera convenida?. En la tabla anterior se define un puntero a una estructura con información sobre los vectores interceptados. No se respeta sin embargo el formato de los encabezamientos de interrupción propuesto en la BIOS del PS/2 (la intención de IBM es buena, pero ha llegado demasiado tarde). Formato Offset -1 00h 01h 05h 06h . .
de la tabla area_vectores: Tamaño Descripción BYTE número de vectores interceptados por el TSR BYTE número del primer vector DWORD puntero al primer vector antes de instalar el TSR BYTE número del segundo vector DWORD puntero al segundo vector antes de instalar el TSR . (y así sucesivamente). Notar que el TSR debe usar ESTAS variables para invocar las anteriores rutinas de control de esas interrupciones, ya que un . agente externo podría actualizarlas.
161
PROGRAMAS RESIDENTES
En las primeras versiones de este convenio ya no existían más reglas. Sin embargo, al final comprendí la necesidad de ampliar las prestaciones. Por ello, el convenio fue ampliado con dos tablas más, opcionales, que es conveniente rellenar incluso también en aquellos TSR más sencillos que ocupan menos de 64 Kb y son totalmente reubicables (no contienen referencias absolutas a segmentos). Estas tablas permitirían a un hipotético sistema operativo mover los programas residentes para evitar la fragmentación de la memoria, tarea que mientras tanto puede realizar algún programa de utilidad. Aquellos TSR que contengan referencias en su propio código o datos cambiando el segmento (sólo puede ocurrir normalmente en los programas EXE) el convenio establece que deben soportar el parámetro /SR: ante él, al ser recargados en memoria desde disco (necesario para la reubicación) deben instalarse silenciosamente sin chitar, autoinhibiéndose a continuación. En general, la mayoría de los programas residentes escritos en ensamblador son relocalizables, así como los elaborados en el modelo Tiny del C, por lo que no es muy complejo realizar esta tarea. La única pega que se puede poner es que, por desgracia, ¡pocos programas usan este convenio!. Formato Offset 00h 02h
de la tabla area_extra (opcional): Tamaño Descripción WORD offset a la tabla control_externo (0 si no soportada) WORD reservado para futuro uso (0)
Formato de la tabla control_externo (opcional): Offset Tamaño Descripción 00h BYTE bit 0: activo si el TSR es relocalizable (sin referencias a segmentos) 01h WORD offset a una variable que puede inhibir o activar el TSR ---Si el bit 0 en el offset 00h está a 0: 03h DWORD puntero a cadena ASCIIZ con el nombre del fichero ejecutable que soporta el parámetro /SR (instalación e inhibición silenciosa) 07h DWORD puntero a la primera variable a inicializar en la copia recargada de disco desde el TSR aún residente. 0Bh DWORD puntero a la última variable (todas están en el mismo bloque).
La variable que activa o inhibe el TSR permite paralizarlo momentáneamente antes de realizar ciertas tareas críticas, si bien no está pensada su utilización de cara a relocalizarlo en memoria o a desinstalarlo. A continuación se listan dos rutinas que habrá de incorporar todo programa que desee emplear este convenio (u otras equivalentes). Las rutinas las he denominado mx_get_handle y mx_find_tsr. La primera permite buscar un valor para la interrupción Multiplex aún no empleado por otra tarea residente, tanto si ésta es del convenio como si no. La segunda sirve para que el programa residente se busque a sí mismo en la memoria. En esta segunda rutina se indica el tamaño de la cadena de identificación (la que contiene el nombre del fabricante, programa y versión) en CX. Si no se encuentra el programa residente en la memoria, puede repetirse la búsqueda con CX indicando sólo el tamaño del nombre del fabricante y el programa, sin incluir el de la versión: así se podría advertir al usuario que tiene instalada ya otra versión distinta.
; ------------ Buscar entrada no usada en la interrupción Multiplex. ;
A la salida, CF=1 si no hay hueco (ya hay 64 programas
;
residentes instalados con esta técnica). Si CF=0, se
;
devuelve en AH un valor de entrada libre en la INT 2Fh.
mx_no_hueco:
RET mx_si_hueco:
CLC RET
mx_get_handle mx_get_handle
STC
ENDP
PROC AH,0C0h
; ------------ Buscar un TSR por la interrupción Multiplex. A la
AX
;
entrada, DS:SI cadena de identificación del programa
MOV
AL,0
;
(CX bytes) y ES:DI protocolo de búsqueda (normalmente
INT
2Fh
;
1492h:1992h). A la salida, si el TSR ya está instalado,
CMP
AL,0FFh
;
CF=0 y ES:DI apunta a la cadena de identificación del
POP
AX
;
mismo. Si no, CF=1 y ningún registro alterado.
JNE
mx_si_hueco
INC
AH
mx_find_tsr
PROC
JNZ
mx_busca_hndl
MOV mx_busca_hndl: PUSH
MOV
AH,0C0h
161
mx_rep_find:
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
PUSH
AX
PUSH
DS
PUSH
CX
PUSH
ES
PUSH
SI
PUSH
DI
MOV
AL,0
PUSH
CX
INT
2Fh
POP
CX
CMP
AL,0FFh
JNE
mx_skip_hndl
; no hay TSR ahí
CLD
mx_skip_hndl:
PUSH
DI
REP
CMPSB
POP
DI
JE
mx_tsr_found
POP
DI
POP
ES
POP
DS
POP
SI
POP
CX
POP
AX
INC
AH
JNZ
mx_rep_find
; comparar identificación
; programa buscado hallado
STC RET mx_tsr_found:
ADD
SP,4
POP
DS
POP
SI
POP
CX
POP
AX
; «sacar» ES y DI de la pila
CLC RET mx_find_tsr
ENDP
La rutina mx_unload desinstala un programa residente que verifique el convenio; basta con indicar el número de interrupción Multiplex que emplea el TSR. El proceso de desinstalación falla si se ha instalado después un TSR que no verifica el convenio y tiene alguna interrupción en común, ya que la rutina no puede en ese caso recorrer la cadena de vectores para modificarla anulando la tarea residente. Para que un TSR se autodesinstale basta con que suministre a esta rutina su propio número de identificación. El método empleado por la rutina para cambiar los vectores de interrupción no es muy ortodoxo, pero simplifica el algoritmo y posee un nivel de seguridad razonable. Esta rutina da dos pasadas: el objeto de la primera es sólo asegurar que el TSR puede ser desinstalado antes de empezar a cambiar ningún vector. En la segunda, se cambian los enlaces entre los vectores y se libera la memoria, bien llamando al DOS o al controlador XMS (según quién la haya asignado). Hay una maniobra más o menos complicada para hacer que el vector 2Fh sea el último restaurado, con objeto de poder seguir la cadena de interrupciones hasta el propio TSR invocando la INT 2Fh.
RET
; ------------ Eliminar TSR del convenio si es posible. A la entrada, ;
en AH se indica la entrada Multiplex; a la salida, CF=1
mx_ul_able:
XOR
AL,AL
;
si fue imposible y CF=0 si se pudo. Se corrompen todos
XCHG
AH,AL
;
los registros salvo los de segmento. En caso de fallo
MOV
BP,AX
;
al desinstalar, AL devuelve el vector «culpable».
MOV
CX,2
PUSH
CX
mx_unload
PROC
LEA
SI,tabla_vectores
mx_ul_pasada:
PUSH
ES
MOV
CL,ES:[SI-1]
CALL
mx_ul_tsrcv?
MOV
CH,0
JNC
mx_ul_able
POP
ES
mx_ul_masvect: POP PUSH
; BP=entrada Multiplex del TSR
; siguiente pasada
; CX = nº vectores
AX AX
; pasada en curso
161
PROGRAMAS RESIDENTES
mx_ul_2f:
DEC
AL
POP
AX
PUSH
CX
JNE
mx_ul_chain
; no
MOV
AL,ES:[SI]
POP
ES
; sí: ¡posible reponer vector!
JNZ
mx_ul_pasok
POP
CX
CMP
CX,1
POP
BX
JNE
mx_ul_noult
PUSH
BX
MOV
AL,2Fh
PUSH
CX
LEA
SI,tabla_vectores
PUSH
ES
DEC
BX
mx_ul_busca2f: CMP
mx_ul_noult:
mx_ul_pasok:
mx_ul_masmx:
mx_ul_tsrcv:
mx_ul_buscav:
ES:[SI],AL
; vector en curso
; ¿último vector?
; ¿INT 2Fh?
JE
mx_ul_pasok
JNZ
mx_ul_norest
; no es la segunda pasada
ADD
SI,5
POP
ES
; segunda pasada...
JMP
mx_ul_busca2f
PUSH
ES
CMP
AL,2Fh
PUSH
DS
JNE
mx_ul_pasok
MOV
BX,CS:mx_ul_tsroff ; restaurar INT's
ADD
SI,5
MOV
DS,CS:mx_ul_tsrseg
JMP
mx_ul_2f
CLI
PUSH
ES
MOV
CX,ES:[SI+1]
PUSH
AX
MOV
[BX+1],CX
MOV
AH,0
MOV
CX,ES:[SI+3]
SHL
AX,1
MOV
[BX+3],CX
SHL
AX,1
STI
DEC
AX
MOV
CS:mx_ul_tsroff,AX
MOV
; ¿restaurar INT 2Fh?
POP
DS
POP
ES
CS:mx_ul_tsrseg,0 ; apuntar a tabla vectores
POP
CX
POP
AX
ADD
SI,5
PUSH
AX
DEC
CX
MOV
AH,35h
JZ
mx_unloadable
INT
21h
JMP
mx_ul_masvect
POP
AX
MOV
CS:mx_ul_tsroff,DI ; ES:DI almacena la dirección
MOV
CL,4
MOV
CS:mx_ul_tsrseg,ES ; de la variable vector
SHR
BX,CL
MOV
DX,ES:[DI+1]
MOV
DX,ES
MOV
CL,4
ADD
DX,BX
SHR
DX,CL
MOV
AH,0C0h
CALL
mx_ul_tsrcv?
JNC
mx_ul_tsrcv
JMP
mx_ul_otro
PUSH
ES:[DI-16]
PUSH
ES:[DI-12]
MOV
DI,ES:[DI-8]
MOV
CL,ES:[DI-1]
MOV
CH,0
CMP
AL,ES:[DI]
JE
mx_ul_usavect
ADD
DI,5
LOOP
mx_ul_buscav
ADD
SP,4
JMP
mx_ul_otro
mx_ul_norest:
; vector en ES:BX mx_ul_chain:
; INT xx en DX (aprox.)
; ...TSR del convenio en ES:DI
; offset a la tabla de vectores
; número de vectores en CX
; este TSR usa vector analizado
; no lo usa
mx_ul_usavect: POP
CX
; tamaño del TSR
POP
BX
; segmento del TSR
CMP
DX,BX
JB
mx_ul_otro
ADD
BX,CX
CMP
DX,BX
JA
mx_ul_otro
PUSH
AX
XOR
AL,AL
XCHG
AH,AL
CMP
AX,BP
; la INT xx no le apunta
; la INT xx le apunta
; ¿es el propio TSR?
; siguiente vector
; no más, ¡desinstal-ar/ado!
161
mx_ul_otro:
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
MOV
CX,ES:[DI+3]
ADD
DX,CX
MOV
AH,0BFh
INC
AH
; a por otro TSR
JZ
mx_ul_exitnok
; ¡se acabaron!
JMP
mx_ul_masmx
mx_ul_exitnok: ADD POP
SP,6
; INT xx en DX (aprox.)
; equilibrar pila
ES
STC RET
; imposible desinstalar
mx_unloadable: POP
CX
DEC
CX
JZ
mx_ul_exitok
; desinstalado
JMP
mx_ul_pasada
; 1ª pasada exitosa: por la 2ª
TEST
ES:info_extra,111b
MOV
ES,ES:segmento_real ; segmento real del bloque
JZ
mx_ul_freeml
CMP
xms_ins,1
JNE
mx_ul_freeml
MOV
DX,ES
MOV
AH,11h
CALL
gestor_XMS
POP
ES
mx_ul_exitok:
; ¿tipo de instalación?
; cargado en RAM convencional
; no hay controlador XMS (¿?)
; liberar memoria superior
CLC RET mx_ul_freeml:
MOV
AH,49h
INT
21h
POP
ES
; liberar bloque de memoria ES:
CLC RET mx_ul_tsrcv?:
PUSH
AX
PUSH
ES
; ¿es TSR del convenio?...
PUSH
DI
MOV
DI,1492h
MOV
ES,DI
MOV
DI,1992h
INT
2Fh
CMP
AX,0FFFFh
JNE
mx_ul_ncvexit
CMP
WORD PTR ES:[DI-4],"#*"
JNE
mx_ul_ncvexit
CMP
WORD PTR ES:[DI-2],"*#"
JNE
mx_ul_ncvexit
ADD
SP,4
POP
AX
; CF=0
RET mx_ul_ncvexit: POP
DI
POP
ES
POP
AX
STC
; ...no es TSR del convenio
; CF=1
RET mx_ul_tsroff
DW
0
mx_ul_tsrseg
DW
0
mx_unload
ENDP
Los dos programas siguientes constituyen dos pequeñas utilidades de apoyo a los TSR de este convenio. TSRLIST lista los TSR del convenio que están instalados en el ordenador, con información detallada; TSRKILL permite eliminar uno o todos los TSR que estén instalados en cualquier orden, no sólo
161
PROGRAMAS RESIDENTES
necesariamente el último que fue cargado. Lógicamente, si entre varios programas que respetan el convenio hay uno que lo viola, TSRKILL puede no ser capaz de desinstalar un TSR del convenio. En ese caso, se informa de qué vector ha sido el culpable. Ejemplo de salida de TSRLIST /V:
TSRLIST 1.3 (c) Febrero 1994 CiriSOFT. Listado de tareas residentes normalizadas: Programa Ver. Dirección Tamaño Mx. ID Vectores interceptados -------- ----- --------- ------ -------- ------------------------------------RCLOCK 2.3 E8A3:0000 1424 192 08 09 10 2F KEYBFIX 1.0 E15B:0000 208 193 09 2F DISKLED 2.1 E8FD:0060 528 194 08 09 13 2F DATAPLUS 2.4 E91F:0060 18640 195 09 2F ANSIUP 1.0 EDAD:0060 576 196 29 2F HBREAK 4.1 EDD2:0000 1584 197 08 09 20 21 27 2F 70 SCRCAP 1.0 F23E:0100 2144 198 08 09 13 28 2F - ID de programas residentes que incumplen convenio: 210;
La entrada multiplex 210 (0D2h) de que informa TSRLIST es utilizada por QEMM386; TSRLIST también informa de las entradas que están siendo utilizadas por programas que no respetan el convenio, aunque lógicamente no da más información. primera_vez=0;
/********************************************************************/ /* /*
}
*/ TSRLIST 1.3 - Utilidad de listado de TSR's normalizados - BC++
/*
else tsr_raro[entrada-0xc0]=raro=1; /* TSR no del convenio */
*/ }
*/ }
/********************************************************************/
if (raro) { #include
printf("\n- ID de programas residentes que incumplen convenio: ");
#include
for (entrada=0; entrada<64; entrada++) if (tsr_raro[entrada]) printf("%2d; ", entrada+0xc0); if (vect) printf("\n"); }
void cabecera(),
if (!vect) printf("\n- Ejecute con /V para listado de vectores.\n");
listar_tsr(), }
obtener_item();
void main (int argc, char *argv[])
int hay_tsr (int entrada)
{
{ int
entrada,
/* para rastrear entradas de INT 0x2F */
struct REGPACK r;
vect=0,
/* a 1 si se detecta parámetro /V
r.r_ax=entrada << 8;
primera_vez=1,
/* a 0 cuando no lo sea */
raro=0;
/* a 1 si detectado TSR no del convenio */
char tsr_raro[64];
*/
/* flags de TSRs que no respetan el convenio */
/* función booleana: 1 si hay TSR */
intr (0x2f, &r); return ((r.r_ax & 0xff)==0xff); }
if ((argc>1) && (!strcmp(strupr(argv[1]),"/V"))) vect=1; int tsr_convenio (int entrada) printf("\nTSRLIST 1.3 printf("
(c) Febrero 1994 CiriSOFT.\n");
{ struct REGPACK r;
Listado de tareas residentes normalizadas:\n\n");
r.r_ax=entrada << 8;
for (entrada=0xc0; entrada<=0xff; entrada++) { tsr_raro[entrada-0xc0]=0;
r.r_es=0x1492; r.r_di=0x1992;
if (hay_tsr(entrada)) {
intr (0x2f, &r); return ((r.r_ax==0xFFFF) &&
if (tsr_convenio (entrada)) { if (primera_vez) cabecera(vect);
/* encabezamiento */
listar_tsr (entrada, vect);
/* informar del TSR */
(peek(r.r_es,r.r_di-4)==9002) && (peek(r.r_es,r.r_di-2)==10787)); }
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
void cabecera(int vect) { printf("Programa
Ver. Dirección Tamaño
######################################################################
Mx. ID ");
if (vect) printf ("
Vectores interceptados\n");
else printf ("
Autor/fabricante\n");
printf("-------- ----- --------- ------ -------- ");
/********************************************************************/
printf("-----------------------------------\n");
/* /*
}
*/ TSRKILL 1.3 - Utilidad de desinstalación de TSRs normalizados.
/*
Compilar en el modelo «Large» de Borland C.
/*
*/
{ struct REGPACK r; char cad[40];
#include
unsigned int base, cont;
#include
char huge *info;
#include #include
r.r_ax=entrada << 8; r.r_es=0x1492; r.r_di=0x1992; intr (0x2f, &r); info=MK_FP(r.r_es, r.r_di); struct tsr_info { unsigned segmento_real;
/* elemento 1: nombre */
unsigned offset_real;
printf("%-8s", cad); obtener_item (2, 3, info, cad); printf("
unsigned ltsr;
/* elemento 2: versión */
unsigned char info_extra;
%-4s %04X:%04X ",
unsigned char multiplex_id;
cad, peek(r.r_es, r.r_di-16), peek(r.r_es, r.r_di-14)); printf("%6u
%03u
unsigned vectores_id;
",
unsigned extension_id;
peek(r.r_es, r.r_di-12)*16, peekb(r.r_es, r.r_di-9) & 0xff);
unsigned long validacion; char autor_nom_ver[80];
if (vect) /* listado de vectores */ {
};
base=peek(r.r_es, r.r_di-8); for (cont=0; cont
");
int
tsr_convenio(), mx_unload(),
printf("%02X ", peekb(r.r_es, base+cont*5));
existe_xms();
}
void liberar_umb(),
}
desinstalar();
else /* imprimir autor */ { obtener_item (0, 37, info, cad);
/* elemento 0: autor */
printf("%s", cad); void main (int argc, char **argv)
}
{ printf("\n"); }
int
mxid;
struct
tsr_info far *tsr;
printf ("\nTSRKILL 1.3\n"); void obtener_item (int posicion, int max_long, char huge *info, char *cad)
if ((((mxid=atoi(argv[1]))<0xc0) || (mxid>0xFF)) && (mxid!=-1)) { printf ("
- Indicar número Mx. ID (TSRLIST) entre 192 y 255");
printf (" (-1 todos los TSR).\n");
{ int i;
for (i=0; i
exit (1); }
if (mxid==-1) { for (mxid=0xc0; mxid<=0xFF; mxid++) if (tsr_convenio(mxid, &tsr)) desinstalar (mxid);
cad[i]=cad[max_long]=0; /* fin de cadena y controlar tamaño */ }
*/
/********************************************************************/
void listar_tsr (int entrada, int vect)
obtener_item (1, 8, info, cad);
*/
} else
161
PROGRAMAS RESIDENTES
nofincadena=1; mx=0xC0;
desinstalar (mxid);
while (posible && nofincadena) {
}
if (tsr_convenio (mx, &tsrx)) { iniciotsr=tsrx->segmento_real;
/* el OFFSET se desprecia */
void desinstalar (int mxid)
i=peekb(FP_SEG(tsrx), tsrx->vectores_id-1);
{
while ((peekb(FP_SEG(tsrx),tsrx->vectores_id+5*(i-1))!=vector) int
vector, correcto;
char
far *nombre, *p,
&& i) i--; if (i && (intptr>=iniciotsr)&&(intptr<=iniciotsr+tsrx->ltsr)) if (mx==mxid) nofincadena=0;
cadena [80], cadaux[80];
else { tablaptr[vx][0]=FP_SEG(tsrx);
correcto=mx_unload (mxid, &vector, &nombre);
tablaptr[vx][1]=tsrx->vectores_id+5*(i-1)+1; intptr=peek(tablaptr[vx][0],tablaptr[vx][1]+2) +
if (correcto || (vector<0x100)) {
((unsigned) peek(tablaptr[vx][0],tablaptr[vx][1]) >>4);
strcpy (cadaux, nombre); p=cadaux; while (*p) if ((*p++)==':') *(p-1)=0; p=cadaux;
mx=0xBF;
while (*p++); strcpy (cadena, p);
}
/* nombre programa */ }
strcat (cadena, " "); while (*p++); strcat (cadena, p);
if (mx==0xFF) posible=0; else mx++;
/* versión */
}
strcat (cadena, " de "); strcat (cadena, cadaux);
/* compensar incremento posterior */
}
/* autor */
} *interrupción = vector; *tsrnombre = tsr->autor_nom_ver;
if (correcto) printf("
- Desinstalado el %s\n", cadena); if (strstr(*tsrnombre, "HBREAK")!=NULL) {
else {
posible=0; *interrupción=0x101; }
if (vector==0x100) printf ("
- No hay TSR %u o no es del convenio.\n", mxid); if (strstr(*tsrnombre, "2MGUI")!=NULL) {
else if (vector==0x101) printf ("
posible=0; *interrupción=0x102; }
- HBREAK es «demasiado fuerte» para TSRKILL.\n");
else if (vector==0x102) printf ("
if (posible) {
- 2MGUI es «demasiado fuerte» para TSRKILL.\n");
for (i=0; i
else { printf ("
vector = peekb(FP_SEG(tsr), tsr->vectores_id+5*i);
- El %s no se puede desinstalar: ", cadena);
printf ("fallo en el vector %02X.\n", vector);
sgm = peek(FP_SEG(tsr), tsr->vectores_id+5*i+3);
}
ofs = peek(FP_SEG(tsr), tsr->vectores_id+5*i+1); if ((tablaptr[i][0]==0) && (tablaptr[i][1]==0)) {
}
interr=MK_FP(sgm, ofs);
}
setvect (vector, interr); } else {
int mx_unload (int mxid, int *interrupción, char far **tsrnombre)
asm cli
{ int
poke (tablaptr[i][0], tablaptr[i][1], ofs);
mx, posible, vx, vector, i, nofincadena;
unsigned intptr, iniciotsr, tablaptr[256][2], sgm, ofs;
poke (tablaptr[i][0], tablaptr[i][1]+2, sgm);
char
numvect;
asm sti
struct
tsr_info far *tsr, far *tsrx;
struct
REGPACK r;
void
interrupt (*interr)();
} }
switch (tsr->info_extra & 3) { case 0: r.r_es=tsr->segmento_real; r.r_ax=0x4900;
if (!tsr_convenio (mxid, &tsr)) {
intr (0x21, &r); break;
*interrupción=0x100;
case 1: if (existe_xms()) liberar_umb (tsr->segmento_real);
return (0);
break;
} } }
numvect = peekb(FP_SEG(tsr), tsr->vectores_id-1);
return (posible);
for (i=0; i<256; i++) tablaptr[i][0]=tablaptr[i][1]=0; } for (posible=1, vx=0; posible && (vxvectores_id+5*vx); intptr = FP_SEG(getvect(vector)) + (FP_OFF(getvect(vector)) >> 4);
int tsr_convenio (int entrada, struct tsr_info far **info)
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
int existe_xms ()
{ struct REGPACK r;
{ struct REGPACK r;
r.r_ax=entrada << 8; r.r_ax=0x4300; intr (0x2F, &r); return ((r.r_ax & 0xFF)==0x80);
r.r_es=0x1492; r.r_di=0x1992; intr (0x2f, &r);
}
*info = MK_FP(r.r_es, r.r_di-16); return ((r.r_ax==0xFFFF) && (peek(r.r_es,r.r_di-4)==9002) && (peek(r.r_es,r.r_di-2)==10787)); }
void liberar_umb (unsigned segmento) { long controlador;
asm { push
es; push si; push di;
mov
ax,4310h
int
2Fh
mov
word ptr controlador,bx
mov
word ptr controlador+2,es
mov
ah,11h
mov
dx,segmento
call
controlador
pop
di; pop si; pop es;
} }
10.5.3.- LA PROPUESTA AMIS. La interrupción Multiplex presenta un elevado nivel de polución debido al gran número de programas que la utilizan incorrectamente. En algunos casos se soluciona el problema instalando primero los programas conflictivos y después los que trabajan bien. Lo mínimo que se puede exigir a un programa residente que utilice esta interrupción es que soporte el chequeo de instalación (la llamada con AL=0) y devuelva una señal de reconocimiento afirmativo (AL=0FFh) si está empleando esa entrada en cuestión. Sin embargo, algunos no llegan ni a eso. Por fortuna, son tan malos que casi nadie los emplea. Sin embargo, con objeto de solucionar estos casos, Ralf Brown -autor del INTERRUP.LST- ha desarrollado un método alternativo basado en la interrupción 2Dh. Esta interrupción no ha sido empleada hasta ahora por el DOS ni por ninguna aplicación importante. La propuesta AMIS (Alternate Multiplex Interrupt Specification) implementa un sistema estandarizado de interface con los programas residentes. Habida cuenta de que las principales empresas desarrolladoras de software de sistemas ojean el INTERRUP.LST antes de utilizar una interrupción, para evitar conflictos entre aplicaciones, es de esperar que la propia Microsoft no utilice tampoco la INT 2Dh para sus propósitos en futuras versiones del DOS. Por tanto, no es muy arriesgado seguir este convenio. La información que expongo a continuación se corresponde con la versión 3.4 de la especificación. Los programas que emplean la INT 2Dh deben interceptarla e implementar una serie de funciones. Como luego veremos, no es necesario que soporten todas las que propone el convenio. A la hora de llamar a la INT 2Dh se indicará en AH, tal como se hacía con la interrupción Multiplex, el número de entrada y en AL la función. Todo el funcionamiento se basa en invocar funciones en el programa residente. El inconveniente de ejecutar código en la copia residente es que ocupa algo más de memoria, y la necesidad de implementar dichas funciones. La ventaja de ejecutar código en la copia residente es que ésta puede, en donde sea procedente, restaurar el estado del sistema de manera más completa o realizar tareas específicas que sean necesarias. Por citar un ejemplo, TSRKILL no puede desinstalar las conocidas utilidades HBREAK o 2MGUI, que, en cambio, con la propuesta AMIS podrían haber soportado una función de desinstalación accesible por cualquier agente externo. Existen las siguientes funciones: - Función 0: Chequeo de instalación. Si no hay un TSR utilizando ese número se devuelve un 0 en AL. En caso contrario se devuelve un 0FFh en AL; en CX se devuelve además el número de versión del interface AMIS que soporta el TSR (ej. CX=340h para la v3.4); en DX:DI se entrega la dirección de la cadena de
PROGRAMAS RESIDENTES
161
identificación, con el siguiente formato: Offset 0 (8 bytes): Nombre del fabricante (rellenado con espacios al final). Offset 8 (8 bytes): Nombre del programa (rellenado con espacios si hace falta). Offset 16 (hasta 64 bytes): Cadena ASCIIZ (terminada en 0) con la descripción del producto; este campo puede constar simplemente de un cero si no se desea inicializarlo. - Función 1: Obtener punto de entrada. Como llamar a la INT 2Dh puede ser relativamente lento (debido al elevado número de programas residentes que puede haber instalados) con esta función se solicita al TSR un punto de entrada alternativo para poder llamarlo de una manera más directa sin la INT 2Dh. Si devuelve un 0 en AL, significa que el TSR debe ser invocado obligatoriamente vía INT 2Dh. Si devuelve un 0FFh en AL ello implica que soporta una llamada directa, cuyo punto de entrada devuelve en DX:BX. - Función 2: Desinstalación. A la entrada, se indica al TSR en DX:BX el punto donde deberá saltar tras su autodesinstalación (si la soporta). A la vuelta, el TSR devuelve un código en AL que se interpreta: 0 - Función no implementada. 1 - Fallo. 2 - No es posible desinstalar ahora, el TSR lo intentará cuando pueda. 3 - Es seguro desinstalar, pero el TSR no dispone de rutina al efecto. El TSR está aún habilitado y devuelve en BX el segmento del bloque de memoria donde reside. 4 - Es seguro desinstalar, pero el TSR no dispone de rutina al efecto. El TSR está inhibido y devuelve en BX el segmento del bloque de memoria donde reside. 5 - No es seguro desinstalar ahora. Intentar de nuevo más tarde. 0FFh - Todo ha ido bien, TSR desinstalado: retorna con AX corrompido a la dirección DX:BX. - Función 3: Solicitud de POP-UP. Esta función está diseñada sólo para los programas residentes que muestran menús en pantalla al ser activados (normalmente con una combinación de teclas). El valor que devuelve en AL se interpreta: 0 - Función no implementada, el TSR no es de tipo POP-UP. 1 - No es posible el POP-UP ahora, intentar solicitud más tarde. 2 - No es posible el POP-UP en este preciso instante, el TSR lo reintentará en breve. 3 - El TSR ya está POP-UPado. 4 - Imposible hacer POP-UP, se requiere intervención del usuario. En BX se devuelve la causa genérica del fallo: 0-Desconocido, 1-La cadena de interrupciones se solapa con memoria que debe ser desalojada para el POP-UP, 2-Fallo en las operaciones de swapping necesarias para el POP-UP. Además, en CX se devuelve un código de error exclusivo de la aplicación que se trate. 0FFh - El TSR fue correctamente POP-UPado y posteriormente abandonado por el usuario. A la vuelta, BX entrega un 0 para no indicar nada, un 1 para indicar que el TSR fue descargado por el usuario y los valores 2 al 0FFh están reservados para futuros usos. Los valores 100h al 0FFFFh en BX están a disposición del programa que se trate. - Función 4: Determinar los vectores interceptados. A la entrada se indica en BL el número de la interrupción (excepto 2Dh). A la vuelta, AL devuelve un código: 0 - Función no implementada. 1 - Imposible determinar. 2 - La interrupción indicada ha sido interceptada. 3 - La interrupción indicada ha sido interceptada, DX:BX apunta a la rutina que la gestiona. 4 - Se devuelve en DX:BX la lista de interrupciones interceptadas. 0FFh - Esa interrupción no ha sido interceptada. Esto en principio significa que el TSR puede hacer casi lo que le da la gana cuando le preguntan qué interrupciones controla. Los valores 1 al 3 sólo están definidos por compatibilidad con versiones anteriores de la
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
especificación (v3.3), el autor del convenio avisa que no serán quizá soportados en otras versiones. Por tanto, lo más normal es que el TSR devuelva un valor 4 sin hacer caso del valor de BL (de lo contrario, el programa que llama tendría que hacer un molesto bucle comprobando todas las interrupciones). Sería una lástima que un TSR devolviera un valor 0. El formato de la lista de interrupciones interceptadas es: Offset 0 (1 bytes): Número del vector (el último de la lista es siempre 2Dh). Offset 1 (2 bytes): Offset a la rutina de control de interrupción. La rutina de control de interrupción respeta este formato, propuesto por IBM en las BIOS de PS/2: Offset 0 (2 bytes): Salto corto a donde realmente empieza la rutina de control (10EBh). Offset 2 (4 bytes): Dirección previa de ese vector de interrupción. Offset 6 (2 bytes): Valor 424Bh (consejo de IBM). Offset 8 (1 byte): Banderín de EOI, 0 si es interrupción software o controlador secundario de la interrupción hardware, 80h si es el controlador primario de la interrupción hardware (debe enviar un comando EOI al controlador de interrupciones 8259). Offset 9 (2 bytes): Salto corto a la rutina de reset hardware (que retornará con RETF). Offset 0Bh (7 bytes): Reservados (a 0). Offset 12h: Rutina que controla la interrupción. - Funciones 5 y siguientes: Reservadas para futuras versiones del convenio, devuelven 0 al no estar implementadas. Por supuesto, los programas que cumplan la propuesta AMIS deben asignar dinámicamente el número de entrada que van a utilizar en la INT 2Dh, buscando uno libre. Para chequear su instalación han de emplear los 16 bytes que indican el nombre del fabricante y el programa. Como dije al principio, no es preciso que un programa soporte todas estas funciones: para cumplir con la versión 3.4 de la especificación basta con implementar las funciones 0, 2 (sin obligación de disponer de rutina de desinstalación) y la 4 (devolviendo un valor 4). 10.5.4.- COMPARACIÓN ENTRE MÉTODOS. Cualquiera de los tres métodos expuestos es válido para lograr una correcta localización del programa residente en memoria. El más sencillo es el primero (aunque ES:DI puede estar asignado de la manera que el lector considere oportuna, por supuesto). Sin embargo, son los dos últimos los más recomendables, por las prestaciones que ofrecen. El más completo es la propuesta AMIS. 10.6. - MÉTODOS ESPECIALES PARA ECONOMIZAR MEMORIA. De cara a aumentar el número potencial de usuarios de un programa residente es fundamental considerar el aspecto de la ocupación de memoria. El método más sencillo es implementar el programa como falso controlador de dispositivo (se verán en el capítulo siguiente) con objeto de evitar el PSP; sin embargo, estos programas sólo pueden ser ejecutados una vez en el momento de arranque del sistema. No obstante, con los programas COM y EXE normales también se pueden tomar una serie de medidas para reducir la ocupación de memoria: la primera y más efectiva es no dejar residente el inservible espacio de entorno, como se vio en capítulos anteriores. Otra de ellas consiste en emplear el PSP para almacenar datos; esto último sólo debe hacerse después de finalizada la ejecución del programa -después de haber entregado el control al sistema-, ya que el PSP es utilizado por el DOS al terminar la ejecución. En todo caso conviene respetar al menos los dos primeros bytes (y a ser posible también los dos situados en el offset 2Ch) con objeto de que no se vuelvan locos los programas del sistema que informan sobre el estado de la memoria (fundamentalmente el comando MEM). Si el programa utiliza pocos datos como para cubrir el PSP, cabe la posibilidad de colocar código en el mismo, para lo cual el programa puede auto-relocalizarse hacia atrás en la memoria, machacando los 171 últimos bytes del PSP que no son vitales para el sistema: en efecto, en el offset 5Ch comienza el primer FCB; los 7 bytes anteriores corresponden al FCB extendido -circunstancia que poco suelen poner de relieve los libros técnicos-
PROGRAMAS RESIDENTES
161
por lo que el único área que es obligatorio respetar es la zona 00-54h: 85 bytes (incluso este área podría ser también casi totalmente ocupada, como se dijo antes, pero después de finalizar la ejecución del programa). Por comodidad, se respetarán los primeros 96 bytes, justo 6 párrafos: moviendo el programa hacia atrás un número entero de párrafos, al final resulta sencillo desviar los vectores de interrupción decrementando su segmento en 6 unidades menos antes de desviarlos. Esta treta sólo es factible, por supuesto, en programas de un solo segmento, tipo COM. Los de tipo EXE normalmente dejarán residente todo el PSP, ya que es un segmento previo al programa (de hecho, al terminar residente hay que añadir el tamaño del PSP) y sería complicada la reubicación. Es cierto que estas técnicas, con programas que se mueven a si mismos dando vueltas por la memoria, automodificándose ... no son consideradas elegantes por los programadores conservadores, y no se pueden hacer estas salvajadas en entornos con protección de memoria (UNIX, etc.); de hecho, Niklaus Wirth se llevaría sin duda las manos a la cabeza. Sin embargo el DOS y el 8086 las permiten y pueden ser bastante útiles, en especial para los programadores de sistemas. Además, escondiendo bien los fuentes, lo más probable es que nadie se entere de ello... 10.7. - PROGRAMAS AUTOINSTALABLES EN MEMORIA SUPERIOR. Los TSR más eficientes deben detectar la presencia de memoria superior e instalarse automáticamente en ella, por varios motivos. Por un lado, se mejora el rendimiento en aquellas máquinas con usuarios inexpertos que no emplean el HILOAD o el LOADHIGH del sistema. Por otro, un programa residente puede ocupar mucho más espacio en disco que lo que luego ocupará en memoria. Si se utiliza LOADHIGH o HILOAD, el sistema intenta reservar memoria para poder cargar el fichero desde disco. Esto significa que puede haber casos en que no tenga suficiente memoria para cargar el programa, con lo que lo cargará en memoria convencional. Sin embargo, ese TSR tal vez hubiera cabido en la memoria superior: si es el propio TSR el que se autorelocaliza (copiándose a sí mismo) hacia la memoria superior, este problema desaparece. Tratándose de programas de un solo segmento real, como los COM, no es problema alguno realizar la operación de copia. Con DR-DOS y, en general, con ciertos controladores de memoria (tales como QEMM) la memoria superior es gestionada por la especificación de memoria extendida XMS (véase apartado 8.3). Para utilizar la memoria superior en estos sistemas hay que detectar la presencia del controlador XMS y pedirle la memoria (también habrá que llamarle después para liberarla). Con MS-DOS 5.0 y posteriores sólo existe memoria superior XMS si NO se indica DOS=UMB en el CONFIG.SYS; sin embargo, la mayoría de los usuarios suelen indicar esta orden con objeto de que el MS-DOS permita emplear LOADHIGH y DEVICEHIGH. Por desgracia, con MS-DOS, cuando el DOS gestiona la memoria superior, se la roba toda al controlador XMS. Por tanto, habrá que pedírsela al DOS. Con MS-DOS, el procedimiento general es el siguiente: Primero, preservar el estado de la estrategia de asignación de memoria y el estado de los bloques de memoria superior (si están o no conectados con los de la memoria convencional). A continuación, se conectan los bloques de memoria superior con los de la convencional, por si no lo estaban. Seguidamente, se modifica la estrategia de asignación de memoria, estableciendo -por ejemplo- un best fit en memoria superior. Finalmente, se asigna memoria utilizando la función convencional de asignación (48h). Tras estas operaciones, habrá de ser restaurada la estrategia de asignación de memoria y el estado de los bloques de memoria superior. Es conveniente intentar primero asignar memoria superior XMS: si falla, se puede comprobar si la versión del DOS es 5 (o superior) y aplicar el método propio que requiere este sistema. De esta manera, los TSR podrán asignar memoria superior sea cual sea el sistema operativo, controlador de memoria o configuración del sistema activos. Sin embargo, con el método propio del DOS 5.0 hay un inconveniente: al acabar la ejecución del código de instalación del TSR, el DOS ¡libera el bloque de memoria que se asignó con la función 48h!. Para evitar esto, hay dos métodos: uno, consiste en terminar residente (aunque sea dejando sólo los primeros 96 bytes del PSP) con objeto de que el sistema respete el bloque de memoria creado. Si no se desea este ligero derroche de memoria convencional, hay un método más contundente. Consiste en engañar al DOS y, tras asignar el bloque de memoria, modificar en su correspondiente bloque de control la información del propietario (PID), haciéndole apuntar -por ejemplo- a sí mismo. De esta manera, al acabar el programa, el DOS recorrerá la cadena de bloques de memoria y no encontrará ninguno que pertenezca al programa que finaliza... conviene también, en este caso, que los dos primeros bytes del bloque de memoria superior contengan la palabra 20CDh (ubicada
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
al inicio de los PSP), con objeto de que algunos programas de diagnóstico lo confundan con un programa (no obstante, el comando MEM del DOS no requiere este detalle y lo tomaría directamente por un programa). También hay que crear el nombre del programa en los 8 últimos bytes del MCB manipulado. Las siguientes rutinas asignan memoria superior XMS (UMB_alloc) o memoria superior DOS 5 (UPPER_alloc): ; ------------ Reservar bloque de memoria superior del nº párrafos AX,
MOV
AX,5802h
;
devolviendo en AX el segmento donde está. CF=1 si no
INT
21h
;
está instalado el gestor XMS (AX=0) o hay un error (AL
MOV
umb_state,AL
;
devuelve el código de error del controlador XMS).
MOV
AX,5803h
MOV
BX,1
UMB_alloc
PROC
INT
21h
; preservar estado UMB
; conectar cadena UMB's
PUSH
BX
MOV
AX,5801h
PUSH
CX
MOV
BX,41h
PUSH
DX
INT
21h
; High Memory best fit
CMP
xms_ins,1
POP
BX
; ...párrafos requeridos
JNE
no_umb_disp
; no hay controlador XMS
MOV
AH,48h
MOV
DX,AX
; número de párrafos
INT
21h
; asignar memoria
MOV
AH,10h
; solicitar memoria superior
PUSHF
CALL
gestor_XMS
PUSH
AX
; guardado el resultado
CMP
AX,1
; ¿ha ido todo bien?
MOV
AX,5801h
MOV
AX,BX
; segmento UMB/código de error
MOV
BX,alloc_strat
JNE
XMS_fallo
; fallo
INT
21h
POP
DX
; ok
MOV
AX,5803h
POP
CX
MOV
BL,umb_state
POP
BX
XOR
BH,BH
CLC
INT
21h
RET
POP
AX
no_umb_disp:
MOV
AX,0
POPF
XMS_fallo:
POP
DX
JC
UPPER_fin
POP
CX
PUSH
DS
POP
BX
; restaurar estrategia
; restaurar estado cadena UMB
; hubo fallo
DEC
AX
STC
MOV
DS,AX
RET
INC
AX
ENDP
MOV
WORD PTR DS:[1],AX
; manipular PID
MOV
WORD PTR DS:[16],20CDh
; simular PSP
; ------------ Reservar memoria superior, con DOS 5.0, del tamaño
PUSH
ES
;
solicitado (AX párrafos). Si no hay bastante CF=1,
MOV
CX,DS
;
en caso contrario devuelve el segmento en AX.
MOV
ES,CX
MOV
CX,CS
DEC
CX
UMB_alloc
UPPER_alloc
PROC PUSH
AX
MOV
DS,CX
MOV
AH,30h
MOV
CX,8
INT
21h
MOV
SI,CX
CMP
AL,5
MOV
DI,CX
POP
AX
CLD
JAE
UPPER_existe
REP
MOVSB
POP
ES DS
STC
UPPER_existe:
JMP
UPPER_fin
; necesario DOS 5.0 mínimo
POP
PUSH
AX
; preservar párrafos...
CLC
MOV
AX,5800h
UPPER_fin:
RET
INT
21h
UPPER_alloc
ENDP
MOV
alloc_strat,AX
; copiar nombre de programa
; preservar estrategia
La rutina UMB_alloc requiere una variable (xms_ins) que indique si está instalado el controlador de memoria extendida, así como otra (gestor_XMS) con la dirección del mismo. La rutina UPPER_alloc necesita una variable de palabra (alloc_strat) y otra de tipo byte (umb_state) en que apoyarse. El método expuesto consiste en modificar el PID para evitar que el DOS desasigne la memoria al acabar la ejecución del programa;
161
PROGRAMAS RESIDENTES
también se coloca oportunamente la palabra 20CDh para simular un PSP y se asigna al nuevo bloque de programa el mismo nombre que el del bloque de programa real. Los programas con autoinstalación en memoria superior deberían tener un parámetro (al estilo del /ML de los de DR-DOS) para forzar la instalación en memoria convencional si el usuario así lo requiere. 10.8. - PROGRAMAS RESIDENTES EN MEMORIA EXTENDIDA CON DR-DOS 6.0 El auténtico empleo de memoria extendida para instalar programas residentes, aprovechando el modo protegido en que está el ordenador con el controlador de memoria expandida instalado, no será tratado en este libro. En particular, algún emulador de coprocesador para 386 emplea esas técnicas. Aquí nos limitaremos a un objetivo más modesto, en los primeros 64 Kb de memoria extendida accesibles desde DOS. El DR-DOS 6.0 fue el primer sistema operativo DOS que permitía instalar programas residentes en los primeros 64 Kb de la memoria extendida, zona comúnmente conocida por HMA. La ventaja de cargar aquí las utilidades residentes es que no ocupan memoria, dicho entre comillas (al menos, no memoria convencional ni superior). El inconveniente principal es que este área es bastante limitada (en la práctica, algo menos de 20 Kb libres) y la instalación un tanto compleja. Ciertos programas del sistema (COMMAND, KEYB, NLSFUNC, SHARE, TASKMAX) se pueden cargar en esta zona -algunos incluso lo hacen automáticamente-. Otro inconveniente es la complejidad de la instalación: normalmente los programas se cargarán en el segmento 0FFFEh con un offset variable y dependiente de la zona en que sean instalados. Por ello, el primer requisito que han de cumplir es el de ser relocalizables: en la práctica, la rutina de instalación habrá de montar el código en memoria asignando posiciones absolutas a ciertos modos de direccionamiento. El MS-DOS 5.0 también utiliza el HMA para cargar programas residentes; sin embargo no está tan normalizado como en el caso del DR-DOS y es probable que en futuras versiones cambie el método. De una manera torpe, Microsoft eligió a DISPLAY.SYS para ocupar parte del área que el propio DOS deja libre en el HMA tras instalarse. Este fichero es utilizado en la conmutación de páginas de códigos (factible en máquinas con EGA y VGA) para adaptar el juego de caracteres a ciertas lenguas. Hubiera sido mucho más inteligente elegir el KEYB y otros programas similares que casi todo el mundo tiene instalados. Por consiguiente, limitaremos el estudio al caso del DR-DOS. La información que viene a continuación fue obtenida por la labor investigadora del autor de este libro, que la envió posteriormente a Ralf Brown para incluirla en el Interrupt List. Conviene hacer ahora hincapié en que esta manera de gestionar el HMA, a nivel de bloques de memoria, es propia del DR-DOS 6.0, y no de otras versiones anteriores de este sistema, aunque probablemente sí de las posteriores. Para comprobar que en una máquina está presente el DR-DOS puede verificarse la presencia de una variable de entorno del tipo «OS=DRDOS» y otra «VER=X.XX» con la versión. En todo caso, es mucho más seguro utilizar una función del sistema al efecto: MOV
AX,4452h
INT
21h
JC
no_es_drdos
CMP
AX,1063h
JE
drdos341
CMP
AX,1065h
JE
drdos5
CMP
AX,1067h
JE
drdos6
JA
drdos_futuro
; función exclusiva del DR-DOS ; probablemente es MS-DOS
El DR-DOS 6.0 implementa un nuevo servicio para gestionar la carga de programas en el HMA. Con las siguientes líneas: MOV
AX,4458h
INT
21h
MOV
SI,ES:[BX+10h] ; variable exclusiva de DR-DOS
MOV
DI,ES:[BX+14h] ; otra variable de DR-DOS
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
se obtiene en SI el offset al primer bloque libre de memoria en el HMA (ubicado en 0FFFFh:SI), y en DI el offset al primer bloque ocupado de memoria en el HMA (en 0FFFFh:DI). Si el offset al primer bloque de memoria libre es 0, significa que el DR-DOS no está instalado en el HMA o que no está instalado el EMM386.SYS, con lo que no es posible instalar programas en el HMA. Sólo si el kernel del DR-DOS reside en el HMA se puede utilizar esta técnica, para compartir la memoria con el sistema operativo. En el HMA los bloques de memoria forman una cadena pero mucho más simple que en los demás tipos de memoria. En concreto, tienen una cabecera de sólo 5 bytes: los dos primeros apuntan al offset del siguiente bloque de memoria (cero si éste era el último) y los dos siguientes el tamaño de este bloque. Téngase en cuenta que los bloques no han de estar necesariamente seguidos, por lo que la información del tamaño no debe emplearse para direccionar al siguiente bloque: ¡para algo están los primeros dos bytes!. El quinto byte puede tomar un valor entre 0 y 5 para indicar el tipo de programa, por este orden: System, KEYB, NLSFUNC, SHARE, TaskMAX, COMMAND. Como se ve, no se almacena el nombre en formato ASCII sino con un código. Los programas creados por el usuario pueden utilizar cualquiera de los códigos, aunque quizá el más recomendable sea el 0 (de todas maneras, puede haber varios bloques con el mismo código). Para cargar un programa residente aquí, primero se recorre la cadena de bloques libres hasta encontrar uno del tamaño suficiente -si lo hay, claro está-. A continuación, se rebaja el tamaño de este bloque modificando su cabecera. Después, se crea una cabecera para el nuevo bloque (que se sitúa al final del bloque libre empleado, siempre tendiendo hacia direcciones altas) y se consulta la variable del DOS que indica el primer bloque ocupado: el nuevo bloque creado habrá de apuntarle; a su vez, esta variable del DOS ha de ser actualizada ya que desde ahora el primer bloque ocupado (bueno, en realidad el último) es el recién creado. Ha de tenerse en cuenta que si lo que sobra del bloque libre que va a ser utilizado son menos de 16 bytes, se le debe desechar -porque así lo establece el sistema-, eliminándolo de la lista encadenada por el simple procedimiento de hacer apuntar su predecesor a su sucesor. Lógicamente, si el bloque no tenía predecesor -si era el primer bloque- lo que hay que hacer es modificar la variable del DOS que indica el primer bloque libre para que apunte a su sucesor. En general, se trata de gestionar una lista encadenada, lo que más que un problema de ensamblador lo es de sentido común. No eliminar los posibles bloques libres de menos de 16 bytes es saltarse una norma del sistema operativo y podría tener consecuencias imprevisibles con futuros programas cargados. Una vez reservado espacio para el nuevo programa, habrá de copiarse este desde la memoria convencional hacia el HMA, con una simple instrucción de transferencia. Allí -o antes de realizar la transferencia- habrá de relocalizarse el código. Lo normal en los programas del sistema -y, por consiguiente, lo más recomendable- es que nuestras aplicaciones corran en la dirección 0FFFEh:XXXX y no la 0FFFFh:XXXX como en principio podría suponerse, aunque quizá se trate de un detalle irrelevante. Por último, se han de desviar los correspondientes vectores de interrupción a las nuevas rutinas del programa residente. Obviamente, el programa principal instalador deberá acabar normalmente -y no residente-. En general, la gestión del HMA es engorrosa porque el sistema realiza poco trabajo sucio, delegándoselo al programa que quiera emplear este área. 10.9. - EJEMPLO DE PROGRAMA RESIDENTE QUE UTILIZA LA BIOS. El programa de ejemplo es un completo reloj-alarma residente. No posee intuitivas ventanas de configuración ni cientos de opciones, pero es sencillo y muy económico en cuanto a consumo de memoria se refiere. Admite la siguiente sintaxis: RCLOCK [/A=hh:mm:ss | OFF] [ON|OFF] [/T=n] [/X=nn] [/Y=nn] [/C=nn] [/ML] [/U] [/?|H] La opción /A permite indicar una hora concreta para activar la alarma sonora o bien desactivar una alarma (/A=OFF) previamente programada -por defecto, no hay alarma definida-. Los parámetros ON y OFF, por sí solos, se emplean para controlar la aparición en pantalla o no del reloj -por defecto aparece nada más ser instalado-. El parámetro /T puede tomar un valor 1 para activar la señal horaria -por defecto-, 2 para avisar a las
PROGRAMAS RESIDENTES
161
medias, 4 para pitar a los cuartos y 5 para avisar cada cinco minutos; si vale 0 no se harán señales de ninguna clase. Los parámetros opcionales X e Y permiten colocarlo en la posición deseada dentro de la pantalla: si /X=72 (valor por defecto), el reloj no aparecerá realmente en esa coordenada sino lo más a la derecha posible en cada tipo de pantalla activa. Con /C se puede modificar el valor del byte de atributos empleado para colorear el reloj. /ML fuerza la instalación en memoria convencional. Por último, con /U se puede desinstalar de la memoria, en los casos en que sea posible. Es posible ejecutarlo cuando ya está instalado con objeto de cambiar sus parámetros o programar la alarma. Si las coordenadas elegidas están fuera de la pantalla -ej., al cambiar a un modo de menos columnas o filas- el resultado puede ser decepcionante (esto no sucede si /X=72). Si se produce un cambio de modo de pantalla o una limpieza de la misma, el reloj seguirá apareciendo correctamente casi al instante -se refresca su impresión 4 veces por segundo-. Una vez cargado, se puede controlar la presencia o no en pantalla pulsado Ctrl-Alt-R o AltGr-R (sin necesidad de volver a ejecutar el programa con los parámetros ON u OFF). Cuando se expulsa el reloj de la pantalla, se restaura el contenido anterior a la aparición del reloj. Por ello, si se han producido cambios en el monitor desde que apareció el reloj, el fragmento de pantalla restaurado puede quedar feo, aunque también quedaría feo de todas maneras si se rellenara de espacios en blanco. De hecho, esto último es lo que sucede cuando se trabaja con pantallas gráficas. Cuando comienza a sonar la alarma, estando o no el reloj en pantalla, se puede pulsar Ctrl-Alt-R o AltGr-R para cancelarla; de lo contrario avisará durante 15 segundos. Este es el único caso en que AltGr-R o Ctrl-Alt-R no servirá para activar o desactivar el reloj (una posterior pulsación, sí). Después de haber sonado, la alarma quedará desactivada y no volverá a actuar, ni siquiera al cabo de 24 horas. El programa utiliza el convenio CiriSOFT para detectar su presencia en memoria, por lo que es desinstalable incluso aunque no sea el último programa residente cargado, siempre que tras él se hayan instalado sólo programas del convenio (o al menos otros que no utilicen las mismas interrupciones). Posee su propia rutina de desinstalación (opción /U), con lo que no es necesario utilizar la utilidad general de desinstalación. También está equipado con las rutinas que asignan memoria superior XMS o, en su defecto, memoria superior solicitada al DOS 5.0: por ello, aunque el fichero ejecutable ocupa casi 6 Kb, sólo hacen falta 1,5 Kb libres de memoria superior para instalarlo en este área, lo que se realiza automáticamente en todos los entornos operativos que existen en la actualidad. Evidentemente, también se instala en memoria convencional y sus requerimientos mínimos son un PC/XT y (recomendable) DOS 3.0 o superior. Se utiliza la función de impresión en pantalla de la BIOS, con lo cual el reloj se imprime también en las pantallas gráficas (incluida SuperVGA). Por ello, es preciso desviar la INT 10h con objeto de detectar su invocación y no llamarla cuando ya se está dentro de ella (el reloj funciona ligado a la interrupción periódica y es impredecible el estado de la máquina cuando ésta se produce). Si se anula la rutina que controla INT 10h, en los modos gráficos SuperVGA de elevada resolución aparecen fuertes anomalías al deslizarse la pantalla (por ejemplo, cuando se hace DIR) e incluso cuando se imprime; sin embargo, la BIOS es dura como una roca (no se cuelga el ordenador, en cualquier caso). En los modos de pantalla normales no habría tanta conflictividad, aunque conviene ser precavidos. La impresión del reloj se produce sólo 4 veces por segundo para no ralentizar el ordenador; aunque se realizara 18,2 veces por segundo tampoco se notaría un retraso perceptible. La interrupción periódica es empleada no sólo para imprimir el reloj sino también para hacer sonar la música, enviando las notas adecuadamente al temporizador a medida que se van produciendo las interrupciones. No se utiliza INT 1Ch porque la considero menos segura y fiable que INT 8; sin embargo se toma la precaución de llamar justo al principio al anterior controlador de la interrupción. De la manera que está diseñado el programa, es sencillo modificar las melodías que suenan, o crear una utilidad de música residente por interrupciones para amenizar el uso del PC. Los valores para programar el temporizador, según la nota que se trate, se obtienen de una tabla donde están ya calculados, ya que sería difícil utilizar la coma flotante al efecto. Al leer el teclado, se tiene la precaución de comprobar si al pulsar Ctrl-Alt-R o AltGr-R la BIOS o el KEYB han colocado un código Alt-R en el buffer. Esto suele suceder a menos que el KEYB no sea demasiado compatible (Ctrl-Alt equivale, en teoría, a Alt a secas). Si así es, ese carácter se saca del buffer para que no lo detecte el programa principal (si se sacara sin cerciorarse de que realmente está, en caso de no estar el ordenador se quedaría esperando una
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
pulsación de tecla). El método utilizado para detectar la pulsación de AltGr en los teclados expandidos no funciona con el KEYB de DR-DOS 5.0/6.0 (excepto en modo KEYB US), aunque esto es un fallo exclusivo de dicho controlador. Sin duda, la parte más engorrosa del programa es la interpretación de los parámetros en la línea de comandos, tarea incómoda en ensamblador. Aún así, el programa es bastante flexible y se puede indicar, por ejemplo, un parámetro /A=000020:3:48 para programar la alarma a las 20:03:48. Sin embargo, el uso del ensamblador para este tipo de programas es más que recomendable: además de aumentar la fiabilidad del código, el consumo de memoria es más que asequible, incluso en máquinas modestas.
;*********************************************************************
;
001: bloque UMB XMS
;*
*
;
010: *.SYS
*
;
011: *.SYS formato EXE
;*
RCLOCK v2.3
;*
(c) Septiembre 1992 CiriSOFT
;* ;*
; bit 7 a 1: «extension_id» definida
(c) Grupo Universitario de Informática - Valladolid *
»»» Utilidad de reloj-alarma residente «««
;*
*
multiplex_id
DB
0
*
vectores_id
DW
tabla_vectores
*
extension_id
DW
tabla_extra
DB
"*##*"
DB
"CiriSOFT:RCLOCK:2.3",0
DB
4
;********************************************************************* autor_nom_ver
; número Multiplex de este TSR
; ------------ Macros de propósito general
XPUSH
tabla_vectores EQU
MACRO RM
DB
IRP reg, PUSH reg ENDM ENDM
XPOP
MACRO RM IRP reg, POP reg
; ------------ Programa
rclock
ini_residente
8
ant_int08
LABEL DWORD
ant_int08_off
DW
0
ant_int08_seg
DW
0
DB
9
ant_int09
LABEL DWORD
ant_int09_off
DW
0
ant_int09_seg
DW
0
DB
10h
ENDM ENDM
; número de vectores de interrupción usados
$
ant_int10
LABEL DWORD
ant_int10_off
DW
0
ant_int10_seg
DW
0
DB
2Fh
SEGMENT
ant_int2F
LABEL DWORD
ASSUME CS:rclock, DS:rclock
ant_int2F_off
DW
0
ant_int2F_seg
DW
0
tabla_extra
LABEL BYTE
ORG
100h
EQU
$
; INT 8 ; dirección original
; INT 9 ; dirección original
; INT 10h ; dirección original
; INT 2Fh ; dirección original
DW
ctrl_exterior ; permitido control exterior
DW
0
; campo reservado
; **************************************** ; * ; *
D A T O S
R E S I D E N T E S
; *
*
ctrl_exterior
*
reubicabilidad DB
LABEL BYTE 1
*
activacion
visibilidad
DW
; programa 100% reubicable
; **************************************** ; ------------ Tabla de períodos de las notas inicio:
JMP
;
main
; Datos para el período de las 89 notas, tomando como base un reloj de ; ------------ Identificación estandarizada del programa
; 1,19318 MHz (el del 8253). Las notas están ordenadas ascendentemente ; como las de un piano, aunque las de código 0 al 6 son «silenciosas». ; Los datos (para notas mayores de 6) se han calculado con la fórmula:
program_id
LABEL BYTE
segmento_real
DW
0
; segmento real donde será cargado
;
offset_real
DW
0
; offset real
;
longitud_total DW
0
; zona de memoria ocupada (párrafos)
info_extra
80h ; bits 0, 1 y 2-> 000: normal, con PSP
DB
"
"
"
; ;
1193180/(36.8*(2^(1/12))^(nota-6))
161
PROGRAMAS RESIDENTES
; ;
41
43
46
48
50
53
55
58
60
; ------------ Parámetros básicos del reloj
62
─┬──▄▄▄─▄▄▄──┬──▄▄▄─▄▄▄─▄▄▄──┬──▄▄▄─▄▄▄──┬──▄▄▄─▄▄▄─▄▄▄──┬─
;
. . │
███ ███
│
███ ███ ███
│
███ ███
│
███ ███ ███
│
;
│
███ ███
│
███ ███ ███
│
███ ███
│
███ ███ ███
│
; ;
. . │ 40│ 42│ 44│ 45│ 47│ 49│ 51│ 52│ 54│ 56│ 57│ 59│ 61│ 63│
. .
. .
alarm_enable
DB
hora_alarma
LABEL BYTE
alarm_h
DW
"0 "
DB
":"
DW
"00"
DB
":"
alarm_s
DW
"00"
─┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴─ alarm_m
;
tabla_periodos LABEL WORD
0
; por defecto, alarma OFF
DW
37,37,37,37,37,37,37,30603
visibilidad
DB
1
; por defecto, reloj aparece
DW
28885,27264,25734,24290,22926,21640,20425,19279
tipo_aviso
DB
1
; 1 -> señal horaria; 2 -> a las medias
DW
18197,17175,16211,15301,14442,13632,12867,12145
DW
11463,10820,10212,9639,9098,8587,8105,7650
DW
7221,6816,6433,6072,5731,5410,5106,4819
c_x
DB
72
; coordenada X para el reloj
DW
4549,4293,4052,3825,3610,3408,3216,3036
c_y
DB
0
; coordenada Y
DW
2865,2705,2553,2409,2274,2146,2026,1912
color
DB
14+4*16
; tinta amarilla y fondo rojo
DW
1805,1704,1608,1518,1432,1352,1276,1204
refresco
DB
4
; cada 4/18,2 sg. se reimprime el reloj
DW
1137,1073,1013,956,902,852,804,759
DW
716,676,638,602,568,536,506,478
DW
451,426,402,379,358,338,319,301
DW
284
; 4 -> a los cuartos; 5 -> cada 5 min., ; 0 -> sin señal
; ------------ Variables de control general
in10
DW
0
; flag contador de entradas en INT 10h
cont_refresco
DB
1
; contador de INT's 8 a «saltar»
pagina
DB
0
; página de vídeo activa
modo_video
DB
255
; modo de vídeo activo (valor imposible
operacion
DB
0
visible
DB
1
; 1 si el reloj está en pantalla
; hacer pausas; si al byte de duración se le suma 128,
c_xx
DB
0
; coordenada X real del reloj
; se produce una pausa de 1/18,2 segundos antes de que
musica_sonando DB
0
; a 1 si música sonando
; suene otra nota. El final se indica con un 255.
puntero_notas
DW
0
; apunta a la siguiente nota musical
; fragmento del preludio 924 de Bach:
contador_nota
DB
0
DB
47,2,52,2,56,3,1,1,47,2,52,2,56,3,1,1
turno_blanco
DB
0
DB
47,2,52,2,54,3,1,1,51,2,54,2,59,3,1,1
DB
49,2,54,2,59,3,1,1,49,2,54,2,57,3,1,1
parando
DB
0
DB
49,2,52,2,56,3,1,1,52,2,56,2,61,3,1,1
DB
51,2,56,2,61,3,1,1,51,2,56,2,59,3,1,1
DB
51,2,54,2,57,3,1,1
DB
255
; ------------ Sonido
; para provocar inicialización)
; formato de la música: ;
número de nota (0-88), duración (en 1/18,2 seg.)
;
; 8/9 para preservar/restaurar la zona ; de pantalla ocupada por el reloj
; Las primeras 7 notas son inaudibles y sirven para
; que va a sonar ; INT's 8 que le quedan por sonar a la ; nota que está en curso musica_alarma
; típica música de las iglesias:
musica_horas
; de notas ; contador para detener el sonido
; ------------ Cadenas para imprimir
hora_actual
LABEL BYTE
horasH
DB
0
horasL
DB
0
DB
":"
DB
61,10,57,10,59,10,52,20,1,7,52,10,59,10,61,10,57
minutosH
DB
0
DB
20,255
minutosL
DB
0
DB
":"
segundosH
DB
0
segundosL
DB
0
DB
0
DB
8 DUP (' ')
; para almacenar el contenido previo
DB
8 DUP (7)
; de la pantalla (sólo modo texto)
; tres pitidos descendentes
musica_medias
; a 1 si se procesa la nota separadora
DB
47,7,54,7,56,7,52,7,255
; tres pitidos ascendentes:
musica_cuartos DB
restaurar
52,7,56,7,59,10,255
; un par de dobles pitidos:
; *************************************** ; *
musica_5min
DB
57,3+128,57,3+128,1,8,57,3+128,57,3+128,255
; * ; *
* C O D I G O
R E S I D E N T E
* *
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
; ***************************************
; ------------ Rutina de gestión de INT 2Fh no_sonando: ges_int2F
PROC
FAR ret_int09:
STI
preguntan:
ret_no_info:
MOV
alarm_enable,AH
; desactivar alarma
CALL
chiton
; silenciar altavoz
JMP
ret_int09
XOR
visibilidad,AH
; invertir visibilidad reloj
MOV
cont_refresco,AH
; acelerar presencia/ausencia
XPUSH
CMP
AH,CS:multiplex_id
MOV
AH,1
JE
preguntan
INT
16h
; consultar estado del buffer
JMP
CS:ant_int2F
JZ
no_hay_alt_r
; no se colocó Alt-R en buffer
CMP
DI,1992h
MOV
AH,0
; este KEYB es más compatible:
JNE
ret_no_info
INT
16h
; sacar código Alt-R del buffer
MOV
AX,ES
no_hay_alt_r:
XPOP
CMP
AX,1492h
fin_int09ds:
POP
DS
JNE
ret_no_info
fin_int09:
POP
AX
PUSH
CS
POP
ES
LEA
DI,autor_nom_ver
MOV
AX,0FFFFh
; saltar al gestor de INT 2Fh
; no llama alguien del convenio
; no llama alguien del convenio
IRET ; sí llama: darle información
ges_int09
ENDP
; "entrada multiplex en uso"
; ------------ Rutina de gestión de INT 8
IRET ges_int2F
ges_int08
ENDP
PROC
FAR
PUSHF ; ------------ Rutina de control INT 10h. No se imprimirá en pantalla
CALL
;
cuando se ejecute una INT 10h para no reentrar al BIOS.
STI
ges_int10
PROC
FAR
INC
CS:in10
CS:ant_int08
; llamar al controlador previo
XPUSH MOV
AX,CS
MOV
DS,AX
MOV
ES,AX
CALL
avisos_sonoros
; darlos si es necesario
DEC
cont_refresco
; contador de INTs 8 a «saltar»
IRET
JNZ
fin_int08
; no han pasado las suficientes
ENDP
MOV
AL,refresco
MOV
cont_refresco,AL
CMP
CS:in10,0
; indicar entrada en INT 10h
PUSHF
ges_int10
CALL
CS:ant_int10
DEC
CS:in10
; fin de la INT 10h
; ------------ Rutina de gestión de INT 9
ges_int09
JNE
fin_int08
; estamos dentro de INT 10h
PROC
FAR
CALL
obtiene_hora
; crear cadena con la hora
PUSH
AX
CMP
visibilidad,1
; ¿reloj visible?
IN
AL,60h
JNE
restaurar?
; no
CMP
visible,1
; sí, ¿acaba de aparecer?
; espiar código de rastreo
PUSHF
ctrl_alt:
; recargar cuenta
CALL
CS:ant_int09
; llamar al KEYB
JE
scr_getted
; no
CMP
AL,13h
; ¿tecla «R»?
MOV
visible,1
; en efecto: es preciso
JNE
fin_int09
; no
MOV
operacion,8
; entonces tomar el contenido
PUSH
DS
CALL
bios_scr_proc
; previo de la pantalla
MOV
AX,40h
CALL
gestiona_fondo
; detectar cambio en pantalla
MOV
DS,AX
CALL
print_reloj
; imprimir reloj
MOV
AL,DS:[17h]
JMP
fin_int08
XOR
AL,12
CMP
visible,1
; reloj oculto ¿recientemente?
TEST
AL,12
JNE
fin_int08
; no, ya había desaparecido
JZ
ctrl_alt
MOV
visible,0
; sí:
TEST
BYTE PTR DS:[96h],8
MOV
operacion,9
JZ
fin_int09ds
CALL
bios_scr_proc
XPOP
scr_getted:
; invertir bits de Ctrl y Alt
restaurar?:
; pulsado Ctrl-Alt
; no pulsado AltGr fin_int08:
STI
; reponer contenido de pantalla
IRET
PUSH
CS
POP
DS
MOV
AH,1
CMP
musica_sonando,AH
JNE
no_sonando
DEC
AH
MOV
parando,19
MOV
musica_sonando,AH ; parar música
ges_int08
ENDP
; ------------ Controlar la generación de señales sonoras ; no hay sonido avisos_sonoros PROC ; en 1 segundo, no más notas
CMP
parando,0
; ¿"callar" durante 1 segundo?
JE
avisos_on
; no
161
PROGRAMAS RESIDENTES
DEC
parando
JNE
cuarto?
JMP
fin_avisos
LEA
AX,musica_medias-2
CMP
musica_sonando,1
CMP
CL,2
; ¿avisar a las medias?
JNE
no_mas_notas
JAE
fin_avisando
; en efecto
DEC
contador_nota
CMP
SI,"51"
; ¿15 ó 45 minutos exactos?
JNZ
misma_nota
; sigue sonando todavía la nota
JE
cuar_quiza?
CMP
turno_blanco,0
; ¿pausa entre notas?
CMP
SI,"54"
JE
otra_nota
; no
JNE
cinco_min?
MOV
turno_blanco,0
; sí, sólo una vez
CMP
DI,"00"
MOV
contador_nota,1
; y durante una interrupción
JNE
cinco_min?
MOV
AX,0
; período inaudible
LEA
AX,musica_cuartos-2
CALL
programar_8253
CMP
CL,4
; ¿avisar a los cuartos?
misma_nota:
JMP
fin_avisos
JAE
fin_avisando
; en efecto
otra_nota:
MOV
BX,puntero_notas
CMP
minutosL,'5'
; ¿minutos múltiplos de 5?
INC
BX
JE
cinc_quiza?
INC
BX
CMP
minutosL,'0'
MOV
puntero_notas,BX
; actualizarlo
JNE
fin_avisos
MOV
BX,[BX]
; siguiente nota
CMP
DI,"00"
MOV
AL,BH
JNE
fin_avisos
AND
AL,128
; aislar bit más significativo
LEA
AX,musica_5min-2
; minutos múltiplo exacto de 5
ROL
AL,1
; ahora el menos significativo
CMP
CL,5
; ¿avisar cada 5 minutos?
MOV
turno_blanco,AL
; bit de separación entre notas
JB
fin_avisos
; pues no
AND
BH,127
; el resto de BH es la duración
MOV
puntero_notas,AX
; inicio de la melodía
CMP
BL,255
; ¿se acabaron las notas?
MOV
contador_nota,1
; compensar futuro decremento
JNE
sonar
; no, luego tocar esta nota
MOV
musica_sonando,1
; activar música
MOV
musica_sonando,0
; sí
fin_avisos:
MOV
alarm_enable,0
; desactivar alarma
avisos_sonoros ENDP
CALL
chiton
; acallar altavoz
JMP
no_mas_notas
INC
BH
MOV
contador_nota,BH
; INT's 8 que dura esa nota
XOR
BH,BH
; BX = posición en la tabla
IN
AL,61h
SHL
BX,1
; la tabla es de palabras
AND
AL,0FCh
MOV
AX,[BX+tabla_periodos]
JMP
SHORT $+2
CALL
programar_8253
JMP
SHORT $+2
JMP
fin_avisos
OUT
61h,AL
CMP
alarm_enable,0
RET
JE
no_alarma
LEA
SI,hora_actual
LEA
DI,hora_alarma
MOV
CX,8
avisos_on:
sonar:
no_mas_notas:
; sí
; no hay sonido en curso cuarto?:
; puntero a la siguiente nota
media?:
cinco_min?:
cinc_quiza?:
fin_avisando:
; 15 ó 45 minutos exactos
RET
; ------------ Detener sonido por el altavoz
chiton
; período del sonido
; alarma desactivada
chiton
PROC
; altavoz silenciado
ENDP
; ------------ Preparar la producción de sonido
programar_8253 PROC
CLD
no_alarma:
cuar_quiza?:
; 30 minutos exactos
REP
CMPSB
; ¿hora actual = hora alarma?
PUSH
AX
JNE
no_alarma
; no es la hora de la alarma
MOV
AL,182
LEA
AX,musica_alarma-2
OUT
43h,AL
JMP
fin_avisando
POP
AX
MOV
CL,tipo_aviso
JMP
SHORT $+2
MOV
SI,WORD PTR minutosH
JMP
SHORT $+2
MOV
DI,WORD PTR segundosH
OUT
42h,AL
CMP
SI,"00"
MOV
AL,AH
JNE
media?
JMP
SHORT $+2
CMP
DI,"00"
JMP
SHORT $+2
JNE
media?
OUT
42h,AL
LEA
AX,musica_horas-2
JMP
SHORT $+2
CMP
CL,1
; ¿avisar a las horas?
JMP
SHORT $+2
JAE
fin_avisando
; en efecto
IN
AL,61h
CMP
SI,"03"
; ¿30 minutos exactos?
OR
AL,3
JNE
cuarto?
JMP
SHORT $+2
CMP
DI,"00"
JMP
SHORT $+2
; sí lo es
; ¿hora en punto?
; hora en punto
; preparar canal 2
; canal #2 del 8253 programado
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
OUT
61h,AL
; activar sonido
print_reloj
ENDP
RET programar_8253 ENDP
; ------------ Crear cadena de caracteres con la hora actual
; ------------ Controlar posible cambio de modo de pantalla o página
obtiene_hora
PROC
;
de visualización activa, que afectan al fragmento de
PUSH
DS
;
pantalla preservado antes de imprimir el reloj.
XOR
AX,AX
MOV
DS,AX
MOV
SI,DS:[46Ch]
MOV
DI,DS:[46Eh]
gestiona_fondo PROC MOV
AH,15
INT
10h
; modo de vídeo AL y página BH
POP
DS
CMP
AL,modo_video
; ¿ha cambiado modo de vídeo?
MOV
AX,1080
JNE
clr_fondo?
; en efecto
CALL
mult32x16
CMP
BH,pagina
; ¿ha cambiado la página?
MOV
AX,19663
JNE
clr_fondo?
; así es
CALL
divi48x15
; no ha cambiado nada
PUSH
DI
; actualizar nuevos parámetros
PUSH
SI
MOV
AX,3600
RET clr_fondo?:
dejar_c_x:
fondo_clr_ar:
; DXDISI = DXDISI / 19663
; DISI = tics/18,2065 = seg.
modo_video,AL
MOV
pagina,BH
MOV
BL,c_x
; coordenada X teórica
CALL
divi48x15
CMP
BL,72
; ¿es la 72?
MOV
AX,SI
JNE
dejar_c_x
; no: se deja como tal
MOV
CL,10
MOV
BL,AH
; sí: ajustar posición lo más
DIV
CL
; pasar a BCD no empaquetado
SUB
BL,8
;
OR
AX,"00"
; pasar BCD a ASCII
MOV
c_xx,BL
; coordenada X real
CMP
AL,'0'
CMP
AL,3
; ¿modo de texto de color?
JNE
no_cero_izda
JBE
get_fondo
; sí: preservar área pantalla
MOV
AL,' '
CMP
AL,7
; ¿modo de texto monocromo?
MOV
horasH,AL
JE
get_fondo
; sí: preservar área pantalla
MOV
horasL,AH
MOV
CX,8
; modo gráfico: no preservar,
MOV
AX,3600
LEA
BX,restaurar
; cubrir con espacios en blanco
MUL
SI
MOV
BYTE PTR DS:[BX],' '
POP
SI
MOV
BYTE PTR DS:[BX+8],7
POP
DI
INC
BX
SUB
SI,AX
LOOP
fondo_clr_ar
SBB
DI,DX
MOV
AX,SI
MOV
CL,60
DIV
CL
PUSH
AX
MOV
AH,0
MOV
CL,10
DIV
CL
; pasar binario a BCD
OR
AX,"00"
; pasar BCD a ASCII
MOV
minutosH,AL
a la derecha posible
no_cero_izda:
; y atributos blancos
; acabar buffer
MOV
operacion,8
CALL
bios_scr_proc
; preservar zona de la pantalla
RET gestiona_fondo ENDP
; ------------ Imprimir reloj en pantalla
print_reloj
; DXDISI = DISI * 1080
MOV
RET get_fondo:
; contador de hora del BIOS
PROC
; AX = SI = horas
; evitar cero a la izda en hora
; DXAX = horas*3600
; DISI = segundos+minutos*60
; AL = minutos
MOV
AH,3
MOV
minutosL,AH
MOV
BH,pagina
POP
AX
INT
10h
; coordenadas del cursor en DX
MOV
CL,60
PUSH
DX
; guardarlas para restaurarlas
MUL
CL
MOV
AH,2
SUB
SI,AX
MOV
DL,c_xx
MOV
AX,SI
MOV
DH,c_y
MOV
CL,10
MOV
BH,pagina
DIV
CL
; pasar binario a BCD
INT
10h
; ubicar cursor
OR
AX,"00"
; pasar BCD a ASCII
LEA
BX,hora_actual
; cadena a imprimir
MOV
segundosH,AL
CALL
bios_print
; imprimir reloj
MOV
segundosL,AH
POP
DX
; recuperar posición del cursor
RET
MOV
BH,pagina
; y página activa
obtiene_hora
MOV
AH,2
INT
10h
; restaurar posición del cursor
; ------------ Imprimir en color usando BIOS; sería más rápido acceder
RET
; coordenadas del reloj
;
; SI = segundos restantes
ENDP
a la memoria de vídeo, pero así también funciona en los
161
PROGRAMAS RESIDENTES
;
modos gráficos y en cualquier tarjeta (incluído SVGA).
;
La cadena ASCIIZ se entrega en DS:BX.
bios_print
opcont:
PROC
MOV
[SI+8],AH
; y su atributo
CALL
cursor_derecha
; siguiente posición
INC
SI
; próximo carácter
POP
CX
MOV
AL,[BX]
LOOP
proximo_car
; acabar caracteres
INC
BX
POP
DX
; recuperar coordenadas
AND
AL,AL
MOV
BH,pagina
JZ
fin_print
MOV
AH,2
PUSH
BX
INT
10h
MOV
AH,9
MOV
BH,pagina
MOV
BL,color
MOV
CX,1
INT
10h
CALL
cursor_derecha
POP
BX
JMP
bios_print
; primer carácter a imprimir
; byte 0 -> fin de cadena
bios_scr_proc
; número de caracteres
; y reponer posición del cursor
RET
; función de impresión
ENDP
; ------------ Rutina para multiplicar números de 32 por números de 16 ;
bits generando resultado de 48 bits: DISI * AX = DXDISI
mult32x16
PROC
; avanzar cursor
PUSH
AX
fin_print:
RET
XCHG
SI,AX
; multiplicador en SI
bios_print
ENDP
MUL
SI
; AX (parte baja) * SI --> DXAX
PUSH
DX
; preservar resultado parcial
PUSH
AX
MOV
AX,DI
MUL
; siguiente carácter
; ------------ Avanzar cursor a la derecha
SI
; AX (parte alta) * SI --> DXAX
MOV
BH,pagina
POP
SI
; parte baja del resultado
MOV
AH,3
POP
DI
; parte media del resultado
INT
10h
; DX = coordenadas actuales
ADD
DI,AX
; acumular resultado intermedio
INC
DL
; incrementar X (sin controlar
ADC
DX,0
; arrastrar posible acarreo
MOV
AH,2
; posible desbordamiento)
POP
AX
MOV
BH,pagina
INT
10h
cursor_derecha PROC
RET ; actualizar posición cursor
mult32x16
ENDP
RET ; ------------ Rutina para dividir números de 48 por números de 15
cursor_derecha ENDP
;
bits sin desbordamientos y con cociente de 48 bits.
; ------------ Procesar fragmento de pantalla empleado por el reloj:
;
DXDISI/AX --> cociente en DXDISI y resto en AX.
;
si «operacion» es 8 se copiará de la pantalla a un
;
No se modifican otros registros. No se comprueba si
;
buffer y si es 9 se hará la operación inversa.
;
el divisor es cero o excede los 15 bits.
bios_scr_proc
PROC
divi48x15
PROC
proximo_car:
MOV
AH,3
PUSH
BX
MOV
BH,pagina
PUSH
CX
INT
10h
; obtener posición del cursor
XOR
BX,BX
PUSH
DX
; y preservarla para el final
MOV
CX,49
MOV
AH,2
divi48_15_cmp: CMP
AX,BX
MOV
DL,c_xx
MOV
DH,c_y
MOV
BH,pagina
INT
10h
; mover cursor
RCL
SI,1
LEA
SI,restaurar
; dirección del buffer
RCL
DI,1
MOV
CX,8
; 8 caracteres
RCL
DX,1
PUSH
CX
MOV
AH,operacion
MOV
BH,pagina
MOV
BL,[SI+8]
; preparar BL por si AH=9
POPF
MOV
AL,[SI]
; preparar AL por si AH=9
RCL
MOV
CX,1
; preparar CX por si AH=9
PUSHF
INT
10h
; leer/escribir carácter
CMP
operacion,8
; ¿se trataba de leer?
LOOP
divi48_15_cmp
JNE
opcont
; no
MOV
AX,BX
MOV
[SI],AL
; sí, guardar carácter leído
POP
CX
; coordenadas del reloj
JA
divi48_nosub
SUB
BX,AX
; rotar 49 veces
STC divi48_nosub:
PUSHF ; 8 ->preservar, 9 ->restaurar
divi48_resto:
CMP
CX,1
JE
divi48_resto
BX,1
POPF
; ¡no rotar el resto al final!
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
JNE
instalar_ml
RET
MOV
AX,parrafos_resid ; párrafos de memoria precisos
ENDP
CALL
UMB_alloc
; pedir memoria superior XMS
JNC
instalar_umb
; hay la suficiente
MOV
AX,parrafos_resid
CALL
UPPER_alloc
; pedir memoria superior DOS 5
JC
instalar_ml
; no hay la suficiente
POP
divi48x15
BX
fin_residente
EQU
$
; fin del área residente
bytes_resid
EQU
fin_residente-ini_residente
STC
; indicar que usa memoria DOS
MOV
ES,AX
; segmento del bloque UMB
MOV
DI,0
; ES:0 zona a donde reubicar
CALL
inicializa_id
; inicializar identificación
; *****************************
CALL
reubicar_prog
; reubicar el programa a ES:DI
; *
*
CALL
activar_ints
; interceptar vectores
*
JMP
fin_noresid
; programa instalado «arriba»
parrafos_resid EQU
; *
(bytes_resid+15)/16
I N S T A L A C I O N
; *
instalar_umb:
; en efecto
instalar_ml:
*
; *****************************
main
params_ok:
desinst:
mens_ok:
no_residente:
instalable:
instalar:
handle_ok:
PROC ; nombre del programa
STC
; indicar que usa memoria DOS
MOV
DI,60h
; instalación mem. convencional
CALL
inicializa_id
; inicializar identificación
CALL
reubicar_prog
; reubicar programa a ES:DI
CALL
activar_ints
; interceptar vectores
CALL
free_environ
; liberar espacio de entorno
LEA
DX,rclock_txt
CALL
print
CALL
obtener_param
; analizar posibles parámetros
MOV
DX,parrafos_resid ; tamaño zona residente, desde
JNC
params_ok
; son correctos
ADD
DX,6
CALL
print_err
; no: informar del error/ayuda
MOV
AX,3100h
JMP
fin_noresid
INT
21h
CALL
inic_XMS
; considerar presencia de XMS
MOV
AX,4C00h
CALL
residente?
; ¿programa ya residente?
INT
21h
JC
no_residente
; todavía no
CMP
param_u,1
; sí: ¿solicitan desinstalarlo?
JE
desinst
; así es
CALL
adaptar_param
; parámetros en copia residente
JMP
fin_noresid
;*
MOV
ES,tsr_seg
;*
CALL
rclock_off
;*
MOV
AH,ES:multiplex_id
;*********************************************************
CALL
mx_unload
LEA
DX,des_ok_txt
JNC
mens_ok
; ha sido posible
LEA
DX,des_no_ok_txt
; es imposible
CALL
print
JMP
fin_noresid
CMP
AX,0
JE
fin_noresid:
main
; PSP:60h bytes (6 párrafos)
; terminar residente
; terminar no residente
ENDP
;********************************************************* * SUBRUTINAS DE PROPOSITO GENERAL PARA LA INSTALACION
* *
; desinstalarlo: ; ------------ Admitir posibles parámetros en la línea de comandos
obtener_param
PROC MOV
BX,81h
; apuntar a zona de parámetros
CALL
saltar_esp
; saltar delimitadores
; ¿reside una versión distinta?
JNC
otro_pmt
; quedan más parámetros
instalable
; no: se admite instalación
JMP
fin_proc_pmt
; no más parámetros
CALL
error_version
; error de versión incompatible
CMP
AL,'/'
JMP
fin_noresid
JE
pmt_barrado
CMP
param_u,1
; no residente: ¿desinstalar?
CMP
AL,'?'
JNE
instalar
; no lo piden
MOV
DH,128
LEA
DX,imp_desins_txt ; lo piden, ¡serán despistados!
JNE
pmt_nobarrado
CALL
print
JMP
mal_proc_pmt
JMP
fin_noresid
CALL
mx_get_handle
JNC
handle_ok
LEA
DX,nocabe_txt
CALL
print
JMP
fin_noresid
MOV
multiplex_id,AH
LEA
DX,instalado_txt
CALL
print
CALL
preservar_ints
CMP
param_ml,0
otro_pmt_mas:
otro_pmt:
pmt_nobarrado: OR
WORD PTR [BX],"
; parámetro precedido por '/'
; código de «error» para ayuda
; «error» de solicitud de ayuda "
; pasar a minúsculas
CMP
WORD PTR [BX],"no"
JNE
pmt_off?
MOV
visibilidad,1
MOV
visible,1
MOV
param_onoff,1
; entrada multiplex para RCLOCK
ADD
BX,2
; mensaje de instalación
JMP
otro_pmt_mas
CMP
WORD PTR [BX],"fo"
; ¿parámetro OFx?
; tomar nota de vectores
MOV
DH,0
; código de error
; ¿se indicó parámetro /ML?
JNE
mal_proc_pmt
; obtener entrada Multiplex
; no quedan entradas
pmt_off?:
; ¿parámetro ON?
161
PROGRAMAS RESIDENTES
pmt_barrado:
pmt_no_A:
no_ml:
mal_proc_pmt:
JMP
otro_pmt_mas
MOV
param_x,1
mal_proc_pmt
MOV
c_x,AL
MOV
visibilidad,0
CMP
AX,124
MOV
visible,0
MOV
DH,4
MOV
param_onoff,1
JA
mal_proc_pmt
ADD
BX,3
JMP
otro_pmt_mas
JMP
otro_pmt_mas
MOV
param_y,1
INC
BX
MOV
c_y,AL
MOV
AL,[BX]
; letra del parámetro
CMP
AX,59
CMP
AL,13
; ¿fin de mandatos?
MOV
DH,5
MOV
DH,0
JA
mal_proc_pmt
JE
mal_proc_pmt
JMP
otro_pmt_mas
CMP
AL,'?'
MOV
param_c,1
MOV
DH,128
MOV
color,AL
JE
mal_proc_pmt
CMP
AX,255
OR
AL,' '
MOV
DH,6
CMP
AL,'h'
JA
mal_proc_pmt
JE
mal_proc_pmt
JMP
otro_pmt_mas
CMP
AL,'a'
PUSH
BX
JNE
pmt_no_A
CALL
get_num
JMP
pmt_A
JNC
bien_pmt_A
CMP
AL,'u'
POP
BX
JE
pmt_U
ADD
BX,2
MOV
SI,[BX]
; ¿parámetro de dos caracteres?
OR
WORD PTR [BX],"
OR
SI,"
; mayusculizar
CMP
WORD PTR [BX],"no"
CMP
SI,"lm"
; ¿parámetro /ML?
JNE
pmt_A_off?
JNE
no_ml
MOV
alarm_enable,1
MOV
param_ml,1
MOV
param_a_onoff,1
ADD
BX,2
ADD
BX,2
JMP
otro_pmt_mas
JMP
otro_pmt_mas
PUSH
AX
CMP
WORD PTR [BX],"fo"
; ¿parámetro OFx?
CALL
get_num
; obtener valor del parámetro
MOV
DH,0
; código de error
POP
CX
; CL tipo de parámetro
JNE
mal_proc_pm
MOV
DH,7
; código de error
OR
BYTE PTR [BX+2],' ' ; pasar a minúsculas
JC
mal_proc_pmt
; parámetro incorrecto
CMP
BYTE PTR [BX+2],'f' ; ¿parámetro OFF?
CMP
CL,'t'
JNE
mal_proc_pm
JE
pmt_T
MOV
alarm_enable,0
CMP
CL,'x'
MOV
param_a_onoff,1
JE
pmt_X
ADD
BX,3
CMP
CL,'y'
JMP
otro_pmt_mas
JE
pmt_Y
MOV
param_a,1
CMP
CL,'c'
ADD
SP,2
MOV
DH,2
CMP
AX,23
JE
pmt_C
JA
mal_pmtA
MOV
CL,10
DIV
CL
; pasar binario a BCD
ADD
AX,"00"
; pasar BCD a ASCII
CMP
AL,'0'
OR
BYTE PTR [BX+2],' ' ; pasar a minúsculas
CMP
BYTE PTR [BX+2],'f' ; ¿parámetro OFF?
JNE
pmt_Y:
; falta parámetro pmt_C: ; código de «error» para ayuda
; poner en minúsculas
pmt_A:
; parámetro /A=hh:mm:ss|ON|OFF
"
STC
; en efecto
pmt_A_off?:
bien_pmt_A:
; código de error
; error en parámetro(s)
RET fin_proc_pmt:
CLC
; parámetros procesados
RET pmt_U:
pmt_T:
pmt_X:
; admitir hasta 132 columnas
; y hasta 60 líneas
"
; pasar a minúsculas ; ¿parámetro ON?
; «sacar» BX de la pila
MOV
param_u,1
JNE
no_cero_izda2
INC
BX
MOV
AL,' '
JMP
otro_pmt_mas
MOV
param_t,1
MOV
BYTE PTR alarm_h+1,AH
MOV
tipo_aviso,AL
DEC
BX
CMP
AX,5
CALL
get_num
MOV
DH,3
JC
mal_pmtA
JA
mal_proc_pmt
CMP
AX,59
CMP
AL,3
JA
mal_pmtA
JE
mal_proc_pmt
MOV
CL,10
no_cero_izda2: MOV
; evitar cero a la izda. hora
BYTE PTR alarm_h,AL
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
DIV
CL
; pasar binario a BCD
JE
fin_num
ADD
AX,'00'
; pasar BCD a ASCII
CMP
AL,32
MOV
BYTE PTR alarm_m,AL
JE
fin_num
MOV
BYTE PTR alarm_m+1,AH
CMP
AL,9
DEC
BX
JE
fin_num
CALL
get_num
CMP
AL,'/'
JC
mal_pmtA
JE
fin_num
CMP
AX,59
CMP
AL,':'
JA
mal_pmtA
JE
fin_num
MOV
CL,10
INC
BX
DIV
CL
; pasar binario a BCD
MOV
AL,[BX]
ADD
AX,'00'
; pasar BCD a ASCII
JMP
obtener_num
MOV
BYTE PTR alarm_s,AL
MOV
SI,BX
MOV
BYTE PTR alarm_s+1,AH
DEC
SI
MOV
alarm_enable,1
XOR
DX,DX
JMP
otro_pmt_mas
MOV
AX,1
; AX = 10 elevado a la 0 = 1
mal_pmtA:
MOV
DH,1
DEC
BX
; próximo carácter a procesar
mal_proc_pm:
JMP
mal_proc_pmt
MOV
CL,[BX]
obtener_param
ENDP
CMP
CL,'='
JE
ok_num
CMP
CL,':'
JE
ok_num
CMP
CL,'.'
JNE
no_millar
CMP
AX,1000
JE
otro_car
JMP
mal_num
CMP
CL,'0'
JB
mal_num
CMP
CL,'9'
; puntero al primer carácter
JA
mal_num
; hay parámetro
SUB
CL,'0'
; pasar ASCII a binario
MOV
CH,0
; CX = 0 .. 9
PUSH
AX
; AX = 10 elevado a la N
AND
AX,AX
JNZ
multiplica
AND
CL,CL
JNZ
mal_num_pop
; a la izda sólo permitir ceros
PUSH
DX
; tras completar 5º dígito
fin_num:
otro_car:
; ------------ Saltar espacios, tabuladores, ... buscando un parámetro
saltar_esp:
MOV
AL,[BX]
INC
BX
CMP
AL,9
JE
saltar_esp
CMP
AL,32
JE
saltar_esp
CMP
AL,0Dh
JE
fin_param
DEC
BX
CLC
; carácter tabulador
; espacio en blanco no_millar: ; fin de zona de parámetros
RET fin_param:
STC
; no hay parámetro
RET
; ------------ Obtener número chequeando delimitadores /= y /:
get_num:
err_sintax:
multiplica:
INC
BX
MOV
AL,[BX]
MUL
CX
INC
BX
POP
DX
CMP
AL,'='
JC
mal_num_pop
JE
delimit_ok
ADD
DX,AX
CMP
AL,':'
JC
mal_num_pop
JE
delimit_ok
POP
AX
CMP
AX,10000
JNE
STC
; sintaxis incorrecta
; fin número
; fin número (otro parámetro)
; fin número (otro dato)
; delimitador: fin de número
; delimitador: fin de número
; saltar los puntos de millar
; separador millar descolocado
; DX = DX + digito (CX) * 10 ^ N (AX)
potencia
; AX*10 no se desbordará
MOV
AL,[BX]
MOV
AX,0
; como próximo dígito<>0 a
CALL
obtener_num
JMP
otro_car
; la izda ... pobre usuario
JC
err_sintax
MOV
DI,10
INC
BX
PUSH
DX
; no manchar DX al multiplicar
MUL
DI
; AX = AX elevado a la (N+1)
POP
DX
RET delimit_ok:
; fin número
potencia:
RET
JMP
otro_car
;
puntero (BX) apuntará al final del número y CF=1 si el
mal_num_pop:
POP
AX
; reequilibrar pila
;
número era incorrecto.
mal_num:
MOV
BX,SI
; número mayor de 65535
; ------------ Extraer nº de 16 bits y depositarlo en AX; al final, el
STC obtener_num
CMP
; condición de error
RET
PROC AL,0Dh
; fin zona parámetros y número
ok_num:
MOV
BX,SI
; número correcto
161
PROGRAMAS RESIDENTES
MOV
AX,DX
CLC
RET
; resultado ; condición de Ok.
error_version
ENDP
RET obtener_num
; ------------ Considerar presencia de controlador XMS
ENDP
; ------------ Imprimir errores en los parámetros
print_err
no_ayuda:
inic_XMS
PROC ; error: DH código de error
PROC MOV
AX,4300h
INT
2Fh
CMP
AL,80h
DH,128
JNE
no_ayuda
JNE
XMS_ausente
LEA
DX,ayuda_txt
PUSH
ES
JMP
pr_ret
MOV
AX,4310h
MOV
AH,DH
INT
2Fh
; sí: obtener su dirección
MOV
AL,CL
MOV
XMS_off,BX
; y preservarla
LEA
DX,ini_err_txt
MOV
XMS_seg,ES
CALL
print
MOV
xms_ins,1
LEA
BX,tabla_err
POP
ES
PUSH
AX
MOV
AL,AH
SHL
AL,1
; AL = AL * 2
XOR
AH,AH
; AX = AL
inic_XMS
ADD
BX,AX
MOV
DX,[BX]
; dirección del texto
; ------------ Comprobar si el programa ya reside en memoria. A la
CALL
print
POP
AX
CMP
AH,1
JBE
no_pr_pmt
MOV
DL,AL
MOV
AH,2
; CL=parámetro en errores 1..6
; tabla de mensajes de error
XMS_ausente:
; recuperar código y parámetro
; error 0 ó 1
; imprimir letra del parámetro
MOV
xms_ins,0
RET ENDP
;
salida, CF=0 si programa ya reside, con «tsr_seg» y
;
«tsr_off» inicializadas apuntando a la cadena de
;
identificación de la copia residente.
;
programa no reside aún (AX=0) o reside pero en otra
;
versión distinta (AX=1).
residente?
PROC
INT
21h
LEA
DX,fin_err_txt
PUSH
CX
pr_ret:
CALL
print
PUSH
SI
RET
PUSH
DI
ENDP
PUSH
ES
PUSH
AX
LEA
DI,autor_nom_ver
MOV
SI,DI
; ------------ Ya está instalada otra versión distinta del programa
error_version
; no instalado
RET
no_pr_pmt:
print_err
; chequear presencia XMS
CMP
Si CF=1, el
; identificación del programa
MOV
AL,0
PUSH
ES
MOV
CL,255
LEA
DX,mal_ver_txt1
CLD
CALL
print
REPNE SCASB
LES
DI,tsr_dir
SUB
DI,SI
MOV
AL,':'
MOV
CX,DI
MOV
CL,255
MOV
AX,1492h
CLD
MOV
ES,AX
REPNE SCASB
MOV
DI,1992h
; ES:DI protocolo de búsqueda
REPNE SCASB
CALL
mx_find_tsr
; buscar si está en memoria
MOV
tsr_off,DI
; anotar la dirección programa ; por si estaba instalado
PROC
MOV
DL,ES:[DI]
; número de versión
MOV
AH,2
MOV
tsr_seg,ES
INT
21h
POP
AX
MOV
DL,'.'
JNC
resid_ok
MOV
AH,2
POP
ES
INT
21h
PUSH
ES
MOV
DL,ES:[DI+2]
LEA
DI,autor_nom_ver
MOV
AH,2
MOV
SI,DI
INT
21h
MOV
AL,':'
LEA
DX,mal_ver_txt2
MOV
CL,255
CALL
print
REPNE SCASB
POP
ES
REPNE SCASB
; revisión
; tamaño autor+programa+versión
; CF=0 -> programa ya residente
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
CALL
espera_reloj
MOV
AH,c_y
AX,1492h
MOV
ES:c_y,AH
ES,AX
MOV
ES:visibilidad,AL ; restaurar visibilidad
CMP
param_c,1
JNE
param_adapted
; anotar dirección del programa
MOV
AL,color
; parámetro /C:
; por si instalada otra versión
MOV
ES:color,AL
; actualizar byte de atributos
SUB
DI,SI
MOV
CX,DI
MOV MOV MOV
DI,1992h
; ES:DI protocolo de búsqueda
CALL
mx_find_tsr
; buscar si está en memoria
MOV
tsr_off,DI
MOV
tsr_seg,ES
MOV
AX,0
JC
resid_ok
MOV
AX,1
STC resid_ok:
; tamaño autor+programa
param_c?:
; esperar a que se vaya
; actualizar coordenada Y
param_adapted: RET ; CF=1, AX=0 -> no residente
adaptar_param
ENDP
; CF=1, AX=1 -> sí: otra vers.
; ------------ Eliminar el RCLOCK de la pantalla
POP
ES
POP
DI
POP
SI
MOV
ES:visibilidad,0
POP
CX
CALL
espera_reloj
RET
MOV
ES:musica_sonando,0
ENDP
IN
AL,61h
AND
AL,0FCh
; ------------ Adaptar parámetros de un RCLOCK ya instalado.
JMP
SHORT $+2
;
Sólo se adaptan los indicados, testeando la variable
JMP
SHORT $+2
;
que indica si se han especificado.
OUT
61h,AL
residente?
rclock_off
PROC
; eliminarlo de la pantalla
; parar posible sonido
RET adaptar_param
param_a?:
rclock_off
PROC DX,ya_install_txt
CALL
print
; ------------ Esperar una INT 8 que refresque la impresión del reloj
MOV
ES,tsr_seg
;
en pantalla si ésta -la impresión- está habilitada.
CMP
param_onoff,1
JNE
param_a?
espera_reloj
PROC
MOV
AL,visibilidad
PUSH
DS
MOV
ES:visibilidad,AL ; adaptar visibilidad del reloj
PUSH
AX
CMP
param_a,1
PUSH
CX
JNE
param_aonoff?
MOV
CL,refresco
; nº tics suficientes para que
LEA
SI,alarm_enable
; parámetro /A=hh:mm:ss
MOV
CH,0
; aparezca en pantalla
MOV
DI,SI
; programar nueva alarma
ADD
CX,2
; redondear hacia arriba
MOV
CX,9
MOV
AX,40h
MOV
DS,AX
; parámetros ON u OFF:
CLD REP
param_x?:
param_y?:
STI
MOVSB param_a_onoff,1
espera_tics:
MOV
AX,DS:[6Ch]
JNE
param_t?
espera_tic:
CMP
AX,DS:[6Ch]
MOV
AL,alarm_enable
JE
espera_tic
MOV
ES:alarm_enable,AL
LOOP
espera_tics
CMP
param_t,1
POP
CX
JNE
param_x?
POP
AX
MOV
AL,tipo_aviso
; parámetro /T:
POP
DS
MOV
ES:tipo_aviso,AL
; actualizar byte
RET
CMP
param_x,1
JNE
param_y?
MOV
AL,ES:visibilidad ; parámetro /X:
MOV
ES:visibilidad,0
; eliminar reloj de pantalla
CALL
espera_reloj
; esperar a que se vaya
MOV
AH,c_x
MOV
ES:c_x,AH
MOV
param_aonoff?: CMP
param_t?:
ENDP
LEA
; parámetro /A=ON o /A=OFF: ; actualizar estado alarma
espera_reloj
ENDP
; ------------ Preservar vectores de interrupción previos
preservar_INTs PROC PUSH
ES
PUSH
DI
ES:c_xx,AH
LEA
DI,tabla_vectores
MOV
ES:visibilidad,AL ; restaurar visibilidad
MOV
CL,[DI-1]
CMP
param_y,1
MOV
CH,0
JNE
param_c?
PUSH
CX
MOV
AL,ES:visibilidad ; parámetro /Y:
PUSH
DI
MOV
ES:visibilidad,0
MOV
AH,35h
; actualizar coordenada X
otro_vector:
; eliminar reloj de pantalla
; CX vectores interceptados
161
PROGRAMAS RESIDENTES
PUSH
AX
MOV
AH,30h
DI
INT
21h
POP
CX
CMP
AL,5
MOV
[DI+1],BX
POP
AX
MOV
[DI+3],ES
JAE
UPPER_existe
ADD
DI,5
STC
LOOP
otro_vector
POP
DI
POP
ES
MOV
AL,[DI]
INT
21h
POP
; obtener vector de INT xx
; anotar donde apunta
JMP
UPPER_fin
; necesario DOS 5.0 mínimo
PUSH
AX
; preservar párrafos...
MOV
AX,5800h
INT
21h
MOV
alloc_strat,AX
MOV
AX,5802h
INT
21h
MOV
umb_state,AL
MOV
AX,5803h
MOV
BX,1
INT
21h
MOV
AX,5801h
MOV
BX,41h
INT
21h
; High Memory best fit
RET
POP
BX
; ...párrafos requeridos
ENDP
MOV
AH,48h
INT
21h
; asignar memoria
; guardado el resultado
; repetir con los restantes UPPER_existe:
RET preservar_INTs ENDP
; ------------ Liberar espacio de entorno
free_environ
free_environ
PROC PUSH
ES
MOV
ES,DS:[2Ch]
MOV
AH,49h
INT
21h
POP
ES
; dirección del entorno
; liberar espacio de entorno
; ------------ Reservar bloque de memoria superior del nº párrafos AX,
PUSHF
;
devolviendo en AX el segmento donde está. CF=1 si no
PUSH
AX
;
está instalado el gestor XMS (AX=0) o hay un error (AL
MOV
AX,5801h
;
devuelve el código de error del controlador XMS).
MOV
BX,alloc_strat
INT
21h
MOV
AX,5803h
UMB_alloc
PROC
; conectar cadena UMB's
; restaurar estrategia
BX
MOV
BL,umb_state
PUSH
CX
XOR
BH,BH
PUSH
DX
INT
21h
CMP
xms_ins,1
POP
AX
JNE
no_umb_disp
; no hay controlador XMS
POPF
MOV
DX,AX
; número de párrafos
JC
UPPER_fin
MOV
AH,10h
; solicitar memoria superior
PUSH
DS
CALL
gestor_XMS
DEC
AX
CMP
AX,1
; ¿ha ido todo bien?
MOV
DS,AX
MOV
AX,BX
; segmento UMB/código de error
INC
AX
JNE
XMS_fallo
; fallo
MOV
WORD PTR DS:[1],AX
; manipular PID
POP
DX
; ok
MOV
WORD PTR DS:[16],20CDh
; simular PSP
POP
CX
PUSH
ES
POP
BX
MOV
CX,DS
CLC
MOV
ES,CX
RET
MOV
CX,CS
MOV
AX,0
DEC
CX
XMS_fallo:
POP
DX
MOV
DS,CX
POP
CX
MOV
CX,8
POP
BX
MOV
SI,CX
STC
MOV
DI,CX
RET
CLD
ENDP
REP
MOVSB
POP
ES
; ------------ Reservar memoria superior, con DOS 5.0, del tamaño
POP
DS
;
solicitado (AX párrafos). Si no hay bastante CF=1,
CLC
;
en caso contrario devuelve el segmento en AX.
UPPER_alloc
; preservar estado UMB
PUSH
no_umb_disp:
UMB_alloc
; preservar estrategia
PROC
UPPER_fin:
RET
UPPER_alloc
ENDP
; restaurar estado cadena UMB
; hubo fallo
; copiar nombre de programa
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
; ------------ Inicializar área «program_id» del programa residente.
INT
21h
;
A la entrada, ES:DI = seg:off a donde será reubicado
ADD
SI,3
;
y CF=1 si se utiliza memoria superior XMS.
LOOP
desvia_otro
POP
DS
PROC
POP
CX
PUSHF
RET
inicializa_id
inicializa_id
segmento_real,ES
; anotar segmento del bloque
MOV
offset_real,DI
; ídem con el offset
MOV
longitud_total,parrafos_resid
; ------------ Buscar entrada no usada en la interrupción Multiplex.
MOV
CL,4
;
A la salida, CF=1 si no hay hueco (ya hay 64 programas
MOV
AX,DI
;
residentes instalados con esta técnica). Si CF=0, se
SHR
AX,CL
;
devuelve en AH un valor de entrada libre en la INT 2Fh.
ADD
longitud_total,AX ; consumirá desde offset=0
MOV
AL,1
mx_get_handle
PROC MOV
; CF=0: usar memoria UMB XMS
JNC
info_ok
DEC
AL
OR
info_extra,AL
mx_busca_hndl: PUSH
AX AL,0
INT
2Fh
RET
CMP
AL,0FFh
ENDP
POP
AX
JNE
mx_si_hueco
INC
AH
JNZ
mx_busca_hndl
mx_no_hueco:
PROC PUSH
DI
LEA
SI,ini_residente
MOV
CX,bytes_resid
STC RET
mx_si_hueco:
CLC RET
mx_get_handle
CLD
reubicar_prog
AH,0C0h
MOV
; usar memoria convencional
; ------------ Reubicar programa residente a su dirección definitiva.
reubicar_prog
ENDP
MOV
POPF
info_ok:
activar_INTs
ENDP
ADD
SI,2
; no copiar primera palabra
ADD
DI,2
; respetar primera palabra
SUB
CX,2
;
entrada, DS:SI cadena de identificación del programa
REP
MOVSB
;
(CX bytes) y ES:DI protocolo de búsqueda (normalmente
POP
DI
;
1492h:1992h). A la salida, si el TSR ya está instalado,
RET
;
CF=0 y ES:DI apunta a la cadena de identificación del
ENDP
;
mismo. Si no, CF=1 y ningún registro alterado.
mx_find_tsr
PROC
; ------------ Desviar vectores de interrupción a las nuevas rutinas.
; ------------ Buscar un TSR por la interrupción Multiplex. A la
MOV
AH,0C0h
PUSH
AX
han sido instaladas está en DI. Por ello, CS ha de
PUSH
CX
;
desplazarse (100h-DI)/16 unidades atrás (DI se supone
PUSH
SI
;
múltiplo de 16). El segmento inicial es ES.
PUSH
DS
PUSH
ES
PUSH
DI
MOV
AL,0
PUSH
CX
INT
2Fh
POP
CX
CMP
AL,0FFh
JNE
mx_skip_hndl
;
Se tendrá en cuenta que está ensambladas para correr en
;
un offset inicial (100h) y que el offset real en que
;
activar_INTs
desvia_otro:
; desviar INT xx a DS:DX
mx_rep_find:
PROC PUSH
CX
PUSH
DS
MOV
AX,100h
SUB
AX,DI
MOV
CL,4
SHR
AX,CL
MOV
CX,ES
CLD
SUB
CX,AX
PUSH
DI
MOV
DS,CX
REP
CMPSB
LEA
SI,offsets_ints
POP
DI
MOV
CX,CS:[SI]
JE
mx_tsr_found
ADD
SI,2
POP
DI
MOV
AL,CS:[SI]
; número del vector en curso
POP
ES
MOV
DX,CS:[SI+1]
; obtener offset
POP
DS
MOV
AH,25h
POP
SI
; preservar DS para el retorno
; AX = 100h-DI
; AX = (100h-DI)/16
; CX vectores a desviar mx_skip_hndl:
; no hay TSR ahí
; comparar identificación
; programa buscado hallado
161
PROGRAMAS RESIDENTES
mx_tsr_found:
POP
CX
MOV
CS:mx_ul_tsroff,AX
POP
AX
MOV
CS:mx_ul_tsrseg,0 ; apuntar a tabla vectores
INC
AH
POP
AX
JNZ
mx_rep_find
PUSH
AX
STC
MOV
AH,35h
RET
INT
21h
POP
AX
ADD
SP,4
; «sacar» ES y DI de la pila
POP
DS
MOV
CL,4
POP
SI
SHR
BX,CL
POP
CX
MOV
DX,ES
POP
AX
ADD
DX,BX
MOV
AH,0C0h
CALL
mx_ul_tsrcv?
JNC
mx_ul_tsrcv
JMP
mx_ul_otro
PUSH
ES:[DI-16]
CLC mx_ul_masmx:
RET mx_find_tsr
ENDP
; ------------ Eliminar TSR del convenio si es posible. A la entrada,
mx_ul_tsrcv:
;
en AH se indica la entrada Multiplex; a la salida, CF=1
PUSH
ES:[DI-12]
;
si fue imposible y CF=0 si se pudo. Se corrompen todos
MOV
DI,ES:[DI-8]
;
los registros salvo los de segmento. En caso de fallo
MOV
CL,ES:[DI-1]
;
al desinstalar, AL devuelve el vector «culpable».
MOV
CH,0
CMP
AL,ES:[DI]
mx_ul_buscav: mx_unload
JE
mx_ul_usavect
PUSH
ES
ADD
DI,5
CALL
mx_ul_tsrcv?
LOOP
mx_ul_buscav
JNC
mx_ul_able
ADD
SP,4
POP
ES
JMP
mx_ul_otro
PROC
mx_ul_pasada:
mx_ul_pasok:
; offset a la tabla de vectores
; número de vectores en CX
; este TSR usa vector analizado
; no lo usa
mx_ul_usavect: POP
CX
; tamaño del TSR
POP
BX
; segmento del TSR
XCHG
AH,AL
CMP
DX,BX
MOV
BP,AX
JB
mx_ul_otro
MOV
CX,2
ADD
BX,CX
PUSH
CX
CMP
DX,BX
LEA
SI,tabla_vectores
JA
mx_ul_otro
MOV
CL,ES:[SI-1]
PUSH
AX
MOV
CH,0
XOR
AL,AL
XCHG
AH,AL
CMP
AX,BP
POP
AX
JNE
mx_ul_chain
; no
POP
ES
; sí: ¡posible reponer vector!
POP
CX
; BP=entrada Multiplex del TSR
; siguiente pasada
; CX = nº vectores
AX
PUSH
AX
DEC
AL
PUSH
CX
MOV
AL,ES:[SI]
JNZ
mx_ul_pasok
CMP
CX,1
POP
BX
JNE
mx_ul_noult
PUSH
BX
MOV
AL,2Fh
PUSH
CX
LEA
SI,tabla_vectores
PUSH
ES
DEC
BX
mx_ul_busca2f: CMP
mx_ul_noult:
; ...TSR del convenio en ES:DI
AL,AL
mx_ul_masvect: POP
mx_ul_2f:
; INT xx en DX (aprox.)
XOR
RET mx_ul_able:
; vector en ES:BX
ES:[SI],AL
; pasada en curso
; vector en curso
; ¿último vector?
; ¿INT 2Fh?
; la INT xx no le apunta
; la INT xx le apunta
; ¿es el propio TSR?
JE
mx_ul_pasok
JNZ
mx_ul_norest
; no es la segunda pasada
ADD
SI,5
POP
ES
; segunda pasada...
JMP
mx_ul_busca2f
PUSH
ES
CMP
AL,2Fh
PUSH
DS
JNE
mx_ul_pasok
MOV
BX,CS:mx_ul_tsroff ; restaurar INT's
ADD
SI,5
MOV
DS,CS:mx_ul_tsrseg
JMP
mx_ul_2f
CLI
PUSH
ES
MOV
CX,ES:[SI+1]
PUSH
AX
MOV
[BX+1],CX
MOV
AH,0
MOV
CX,ES:[SI+3]
SHL
AX,1
MOV
[BX+3],CX
SHL
AX,1
STI
DEC
AX
POP
; ¿restaurar INT 2Fh?
DS
161
mx_ul_norest:
mx_ul_chain:
mx_ul_otro:
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
POP
ES
POP
POP
CX
STC
ADD
SI,5
DEC
CX
JZ
mx_unloadable
JMP
mx_ul_masvect
MOV
CS:mx_ul_tsroff,DI ; ES:DI almacena la dirección
MOV
CS:mx_ul_tsrseg,ES ; de la variable vector
MOV
DX,ES:[DI+1]
MOV
CL,4
SHR
DX,CL
PUSH
AX
MOV
CX,ES:[DI+3]
MOV
AH,9
ADD
DX,CX
INT
21h
MOV
AH,0BFh
POP
AX
INC
AH
; a por otro TSR
JZ
mx_ul_exitnok
; ¡se acabaron!
JMP
mx_ul_masmx
mx_ul_exitnok: ADD POP
SP,6
; CF=1
RET
; siguiente vector
; no más, ¡desinstal-ar/ado!
AX
mx_ul_tsroff
DW
0
mx_ul_tsrseg
DW
0
mx_unload
ENDP
; ------------ imprimir cadena en DS:DX delimitada por un '$'
print
PROC
; INT xx en DX (aprox.)
RET print
ENDP
; equilibrar pila ; ***********************************************
ES
; *
STC RET
; imposible desinstalar
; *
* D A T O S
N O
R E S I D E N T E S
*
mx_unloadable: POP
CX
; *
DEC
CX
; ***********************************************
JZ
mx_ul_exitok
; desinstalado
JMP
mx_ul_pasada
; 1ª pasada exitosa: por la 2ª
TEST
ES:info_extra,111b
MOV
mx_ul_exitok:
*
xms_ins
DB
gestor_XMS
LABEL DWORD
ES,ES:segmento_real ; segmento real del bloque
XMS_off
DW
0
JZ
mx_ul_freeml
XMS_seg
DW
0
CMP
xms_ins,1
JNE
mx_ul_freeml
alloc_strat
DW
0
; estrategia asignación (DOS 5)
MOV
DX,ES
umb_state
DB
0
; estado de bloques UMB (DOS 5)
MOV
AH,11h
CALL
gestor_XMS
tsr_dir
LABEL DWORD
POP
ES
tsr_off
DW
0
tsr_seg
DW
0
offsets_ints
DW
4
; número de vectores interceptados
DB
8
; tabla de offsets de los vectores
DW
ges_int08
; de interrupción interceptados
; ¿tipo de instalación?
; cargado en RAM convencional
; no hay controlador XMS (¿?)
; liberar memoria superior
CLC
0
; a 1 si presente controlador XMS ; dirección del controlador XMS
; dirección de la copia residente
RET mx_ul_freeml:
MOV
AH,49h
INT
21h
POP
ES
; liberar bloque de memoria ES:
CLC RET mx_ul_tsrcv?:
PUSH
AX
PUSH
ES
; ¿es TSR del convenio?...
PUSH
DI
MOV
DI,1492h
MOV
ES,DI
MOV
DI,1992h
INT
2Fh
CMP
AX,0FFFFh
JNE
mx_ul_ncvexit
CMP
WORD PTR ES:[DI-4],"#*"
JNE
mx_ul_ncvexit
CMP
WORD PTR ES:[DI-2],"*#"
JNE
mx_ul_ncvexit
ADD
SP,4
POP
AX
; CF=0
RET mx_ul_ncvexit: POP
DI
POP
ES
; ...no es TSR del convenio
161
PROGRAMAS RESIDENTES
DB
9
DB "
DW
ges_int09
DB "Valladolid.",13,10,10
(c) 1992 CiriSOFT,
DB
10h
DB " RCLOCK [/A=hh:mm:ss|OFF|ON] [ON|OFF] [/T=] [/X=] [/Y=] [/C=] "
DW
ges_int10
DB "[/U] [/ML] [/?|H]",13,10,10
DB
2Fh
DB "
DW
ges_int2F
DB "/A=OFF se puede",13,10
param_ml
DB
0
; a 1 si se indicó /ML
DB "sonar, quedará",13,10
param_u
DB
0
; a 1 si se indicó /U
DB "
param_onoff
DB
0
; a 1 si se indicó ON u OFF
DB "Se puede can-",13,10
param_a
DB
0
; a 1 si se indicó /A
DB "
param_a_onoff
DB
0
; a 1 si se indicó /A=ON o /A=OFF
DB "durante el mismo.",13,10
param_t
DB
0
; a 1 si se indicó /T
DB "
param_x
DB
0
; a 1 si se indicó /X
DB "Equivalente a pulsar",13,10
param_y
DB
0
; a 1 si se indicó /Y
DB "
param_c
DB
0
; a 1 si se indicó /C
DB "sonido en curso.",13,10
rclock_txt
DB
13,10,"
instalado_txt
DB
" instalado.",13,10,"$"
/A
DB "
DB " RCLOCK v2.3$"
desactivada (hasta un posterior /A=ON o bien /A=hh:mm:ss). "
celar siempre el sonido pulsando Ctrl-Alt-R o AltGr-R "
ON
y OFF Controlan la aparición del reloj en pantalla. "
AltGr-R ó Ctrl-Alt-R con el reloj ya instalado y sin "
/T
Indica el nivel de avisos sonoros del reloj: 0 ninguno; 1 "
DB "señal horaria;",13,10 2, a las medias; 4 a los cuartos y 5 cada cinco minutos. "
DB "Cada uno de los",13,10 DB "
niveles incluye a su vez a los anteriores. Por defecto, "
" ya instalado.",13,10
DB "/T=1.",13,10
DB
"
DB "
DB
13,10,"$"
DW
err0_txt, err1_txt, err2_txt, err3_txt
DB "siempre refe-",13,10
- Parámetros indicados actualizados."
/X e /Y
Indican las coordenadas de pantalla donde se "
DB "imprimirá el reloj; su",13,10 DB "
tabla_err
Indica una hora de alarma y activa la misma; con /A=ON o "
controlar a posteriori la habilitación de la alarma. Tras "
DB "
ya_install_txt DB
(c) Grupo Universitario de Informática - "
valor varía según el modo de pantalla. Las coordenadas son "
DW
err4_txt,err5_txt, err6_txt, err7_txt
DB "
ini_err_txt
DB
13,10,"
DB "gráfico. Para /X=72",13,10
err0_txt
DB
"sintaxis incorrecta$"
DB "
err1_txt
DB
"hora de alarma incorrecta$"
DB "la columna 72,",13,10
err2_txt
DB
"parámetro no admitido: /$"
DB "
err3_txt
DB
"parámetro distinto de 0, 1, 2, 4 ó 5: /$"
DB "activo.",13,10
err4_txt
DB
"parámetro fuera del rango 0..124: /$"
DB "
err5_txt
DB
"parámetro fuera del rango 0..59: /$"
DB 13,10
err6_txt
DB
"parámetro fuera del rango 0..255: /$"
DB "
err7_txt
DB
"necesario numéro en el parámetro /$"
DB "posible.",13,10
fin_err_txt
DB
13,10
DB "
DB
"
DB
13,10,7,"$"
- Error: $"
Ejecute RCLOCK /? para obtener ayuda."
ridas al modo texto, aunque la pantalla esté en modo "
(valor por defecto) el reloj no se imprimirá realmente en "
sino lo más a la derecha posible según el modo de vídeo "
/C
/U
Indica los atributos de color en que aparece el reloj."
Permite desinstalar el programa de la memoria si ello es "
/ML Fuerza la instalación en memoria convencional -por defecto "
DB "se cargará en",13,10 DB "
memoria superior XMS o en su ausencia en la administrada "
DB "por el DOS 5.0-",13,10,"$" mal_ver_txt1
DB
"
mal_ver_txt2
DB
" de este programa.",13,10,7,"$"
- Error: ya está instalada la versión $"
des_ok_txt
DB
" desinstalado.",13,10,"$"
des_no_ok_txt
DB
13,10,"
DB
"instalado después un programa"
DB
13,10,"
DB
"alguna interrupción común).",13,10,7,"$"
rclock
ENDS END
imp_desins_txt DB
nocabe_txt
ayuda_txt
13,10,"
- Desinstalación imposible (se ha "
que no respeta el convenio y tiene "
- Programa aún no instalado: "
DB
"imposible desinstalarlo.",13,10,"$"
DB
": Instalación imposible.",13,10
DB
"
DB
"misma técnica.",13,10,"$"
Ya hay 64 programas residentes con la "
LABEL BYTE
DB 13,9,9,"RCLOCK v2.3 - Utilidad de reloj-alarma residente.",13,10
inicio
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
10.10. - USO SIN LIMITES DE SERVICIOS DEL DOS EN PROGRAMAS RESIDENTES. Como se dijo al principio del capítulo, desde un programa residente no se pueden emplear directamente los servicios del DOS. Si se salta esta norma se pueden crear programas que funcionen bajo determinadas circunstancias, pero nada robustos. Por ejemplo, una utilidad para volcar la pantalla a un fichero en disco al pulsar una cierta combinación de teclas, podría funcionar correctamente si es ejecutada desde la línea de comandos, o desde dentro de un editor de texto. Sin embargo, si es invocada mientras se ejecuta un comando DIR o mientras el programa principal está accediendo al disco o, simplemente, ejecutando cualquier función del DOS tal como consultar la fecha, nuestra utilidad dejaría de funcionar correctamente. Y el fallo no consiste en que la pantalla no se vuelque en disco, o se vuelque mal: el problema es que el ordenador se cuelga, siendo preciso reinicializarlo. Aunque es fácil y, en ocasiones más cómodo y recomendable acceder directamente a la pantalla y al teclado, el DOS es la herramienta más potente para acceder al disco y su utilidad en este campo es prácticamente insustituíble. Para la BIOS o el hardware no existen los discos virtuales ni las unidades de disco en red; por otra parte, el DOS constituye un soporte básico que permite a los programas ignorar la evolución futura de las unidades de almacenamiento. Por consiguiente, poder utilizar el DOS desde los programas residentes es algo más que interesante. Con este objetivo, la propia Microsoft tuvo que enfrentarse a las limitaciones del sistema para desarrollar el comando PRINT desde la versión 2.0; en la actualidad es casi universalmente conocido lo que hay que hacer para emplear el DOS desde un programa residente, aunque una gran mayoría de los libros aún no expliquen estas técnicas. Algunos de ellos, incluso muestran programas residentes que llaman descaradamente al DOS, sin tomar precauciones de ninguna clase ¡por algo no los he incluido en la bibliografía!. El término no reentrante que se aplica al DOS significa que no puede ser empleado simultáneamente por dos procesos, sin embargo se trata de un código serialmente reusable como veremos. El DOS posee tres pilas internas: la pila de E/S (I/O Stack), la pila de disco (Disk Stack) y la pila auxiliar (Auxiliary Stack). Las funciones 0 a la 0Ch utilizan la pila de E/S; las restantes utilizan la pila de disco. Si se llama al DOS durante un error crítico (por ejemplo, DIR B: cuando no hay disquete en la unidad) se utiliza la pila auxiliar. La existencia de estas pilas locales significa que si el DOS es llamado cuando ya estaba ejecutando una función (y ya había conmutado a la pila interna correspondiente) volverá a inicializar el puntero de pila y en la nueva reentrada se cargará el contenido previo de la pila. Si estaba ejecutando una función 0-0Ch y se le llama solicitando una 0Dh o superior, no habrá problemas, ya que hay dos pilas separadas para cada caso; sin embargo no suele haber tanta suerte. Algunas funciones del DOS son tan simples que éste no conmuta a ninguna pila interna: la 33h, 50h, 51h, 62h y 64h: con ellas sí es reentrante; con las demás (que además son la mayoría y las más interesantes) por desgracia no lo es. Para solucionar este problema hay dos métodos: interrumpir al DOS sólo cuando no esté ejecutando alguna función; esto es, cuando no está dentro de una INT 21h. Alternativamente, el programa residente puede salvar todo el contexto del DOS, incluyendo las tres pilas internas, para restaurarlas después de haber realizado su tarea. En este libro trataremos especialmente el primer método, tradicionalmente el más empleado y el más probado. 10.10.1. - UNA PRIMERA APROXIMACION. Para detectar si el ordenador está ejecutando código del DOS (si está dentro de una INT 21h) se podría desviar esta interrupción y colocar una nueva rutina que incrementara una variable indicativa al principio, llamara a la INT 21h original y después volviera a decrementar la variable antes de retornar. Así, por ejemplo, desde una interrupción de teclado o periódica, se podría comprobar si el DOS ya está trabajando antes de llamarle (variable distinta de cero). Sin embargo, más que una variable habría que tener dos (una para indicar que la pila E/S está en uso y otra para la pila de disco). Por otro lado, la rutina debería ser algo más sofisticada todavía, ya que hay funciones del DOS que no retornan (las de terminar programa: la 0, 31h y 4Ch) y esto, si no
PROGRAMAS RESIDENTES
161
se tiene cuidado, significaría no decrementar como es debido la variable que indica que se ha abandonado la INT 21h. Además, para liar aún más el asunto, ¿qué hacer con los errores críticos?. Y, para colmo, todavía hay más: si el DOS está dentro de la INT 21h, función 0Ah (entrada en buffer por teclado), nuestra variable diría que no es posible usar el DOS en ese momento, ya que está ya en uso, cuando está científicamente demostrado que en este caso sí es reentrante si se utiliza una función 0Dh o superior (en la línea de comandos, el DOS está ejecutando precisamente esa función de entrada por teclado). Por fortuna, el DOS viene aquí en nuestro socorro: no será preciso diseñar la compleja rutina propuesta, ya que el propio sistema posee una variable interna que indica si en ese momento puede ser interrumpido. Se trata de la variable no documentada InDOS. Existe una función secreta del DOS para obtener la dirección de esta variable, de un byte, que valdrá 0 en el caso de que el DOS esté libre y pueda ser llamado desde un programa residente. Esa variable se incrementa automática y adecuadamente con las llamadas a la INT 21h, y se decrementa al salir. No hay mejor manera de aprender a construir programas residentes fiables y eficientes que espiar cómo lo hace el fabricante del sistema operativo con los suyos propios. El comando PRINT del DOS, cuando se queda residente, desvía un montón de interrupciones, entre ellas la 1Ch (equivalente a la 8) y la 28h. La interrupción 28h (Idle) es invocada por el DOS en las operaciones de entrada por teclado, cuando se encuentra libre de otras tareas, para permitir a los programas residentes aprovechar ese tiempo muerto de CPU. Desde dentro de una INT 28h se puede usar el DOS incluso aunque InDOS sea igual a 1. El comando PRINT, cuando entra en acción, realiza además una serie de tareas adicionales: preserva el DTA activo (área de transferencia a disco), el PSP del programa interrumpido, los vectores de INT 1Bh (Ctrl-Break), INT 23h (Ctrl-C), INT 24h (manipulador de errores críticos); desvía esos vectores hacia unas rutinas propias; a continuación establece un DTA y un PSP propios. Tras enviar los caracteres a la impresora, leyéndolos del disco (con las funciones del DOS, por supuesto) vuelve a restaurar todo lo salvado. Pero vayamos más despacio. 10.10.2. - PASOS A REALIZAR PARA USAR EL DOS. Para obtener la dirección de InDOS se puede emplear la función 34h del DOS, que devuelve un puntero en ES:BX a dicha variable. La dirección de InDOS es constante, por lo que se puede inicializar al instalar el programa residente (no cambiará de lugar en toda la sesión de trabajo). Como luego nos será de utilidad, conviene decir aquí ahora que el Banderín de Errores Críticos del DOS está situado justo después de InDOS en las versiones 2.x y justo antes en la 3.0 (en la 3.1 y siguientes, la función 5D06h permite obtener su dirección en DS:SI). Por tanto, desde los programas residentes bastará, en principio, comprobar que InDOS es igual a cero antes de llamar al DOS (y, de paso, que el Banderín de Errores Críticos es también cero). En caso contrario, se puede inicializar una variable que indique que el programa residente tiene aún pendiente su ejecución: desde la interrupción periódica se puede comprobar si está pendiente la activación del programa residente y se puede verificar el estado del DOS hasta que éste esté listo para ser llamado, lo que sucederá tarde o temprano. Además de la interrupción periódica, también se puede desviar la INT 28h: desde esta interrupción se puede llamar al DOS, como dije antes, incluso aunque InDOS sea igual a 1 (pero no mayor) siempre que la función del DOS a ejecutar sea superior a la 0Ch (lo más normal). Sin embargo, cuando sea seguro llamar al DOS, habrá que hacer algunas cosas más antes de empezar a realizar la labor propia del programa residente. En el PSP se almacena mucha información vital para la ejecución de los programas. Una de las áreas más importantes es el JFT (Job File Table) que contiene información referida a los ficheros del programa que se ejecuta. No es conveniente, desde un programa residente, modificar el PSP del programa principal. Por tanto, habrá que anotar la dirección del PSP actual y conmutar al del programa residente; al final del trabajo se procederá a restaurar el PSP del programa principal. Si no se toma esta precaución, podría suceder de todo. Por ejemplo: si el programa residente abre un fichero usando el PSP del programa principal, cuando éste termine (el programa principal) ese fichero será probablemente cerrado sin que el programa residente se entere. Para obtener la dirección del PSP activo se puede utilizar la función Get PSP (50h; ó la 62h, totalmente equivalente) que devuelve en BX su segmento; la función Set PSP (51h) permite establecer un nuevo PSP indicando en BX el segmento. Si se desea mantener la compatibilidad con el DOS 2.x, hay que tener en cuenta además un error de este sistema operativo. La errata consiste en que las funciones 50h y 51h no operan bien en el DOS 2.x a menos que el sistema use la pila de errores críticos. Por tanto, con esta versión del sistema se puede forzar el
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Banderín de Errores Críticos a un valor 0FFh antes de llamar a las funciones 50h y 51h, para volverlo a poner a cero después: así, el DOS cree que el sistema está en medio de un error y usa la pila que queremos. Además del PSP se debe cambiar el DTA (Disk Transfer Area) que utiliza el DOS para acceder al disco: este área está normalmente en el offset 80h del PSP (sobrescribe el campo de parámetros de la línea de comandos cuando el programa accede a disco) y ocupa 128 bytes. Basta con preservar el DTA del programa principal, cuya dirección se obtiene en ES:BX con la función Get DTA (2Fh), y activar un nuevo DTA (por ejemplo, en el offset 80h del PSP de programa residente) utilizando la función Set DTA (1Ah), pasando su dirección en DS:DX. La información extendida de errores es otro punto a tener en consideración. Supongamos que el programa principal comete un error y el DOS genera la correspondiente información extendida de errores (a partir de la versión 3.0). Si en ese momento se activa el programa residente, puede que realice alguna función del DOS con éxito y el DOS sobrescribirá la condición de error previa. Por tanto, es deber del programa residente preservar y restaurar la información extendida de errores antes de actuar. La función Get Extended Error Information (59h) devuelve en AX, BX y CX la información extendida de errores. Con la función Set Extended Error Information (5D0Ah), en DS:DX se suministra al DOS la dirección de una tabla que contiene el AX, BX y CX con la información extendida de errores a establecer. Como complemento, si se van a emplear las funciones de acceso a disco del DOS, también es conveniente monitorizar la INT 13h para evitar un acceso a disco cuando no ha finalizado el anterior (aunque el DOS esté en posición correcta). Si se van a emplear las INT 25h/26h, convendría monitorizarlas; así como la INT 10h si se utilizan servicios de vídeo (aunque sean del DOS). Por monitorizar se entiende interceptar esa interrupción e instalar una rutina de control que incremente y decremente una variable cada vez que empieza o termina una de esas interrupciones, con objeto de saber cuándo se está dentro de ellas. En general, los programas residentes que accedan demasiado intensivamente al disco (en una especie de multitarea) deberían monitorizar no sólo INT 13h sino también INT 25h e INT 26h. 10.10.3. - RESUMIENDO, ¡NO ES TAN DIFICIL!. El procedimiento a seguir, por tanto, para activar un programa residente respondiendo por ejemplo a la pulsación de una combinación de teclas, es el siguiente: - Desde la interrupción del teclado, y una vez detectada la combinación de teclas, intentar activar el programa residente. Será posible activarlo si: no estaba ya activo, no hay una INT 13h en curso, InDOS=0 y el Banderín de Errores Críticos también es igual a 0. - Por si falla, desde la interrupción del temporizador se puede comprobar si está pendiente aún la activación del programa residente (por si no se pudo cuando se pulsaron las teclas); en ese caso, volverlo a intentar de nuevo, con los mismos pasos que en el caso anterior. - Desde la interrupción 28h comprobar si está pendiente aún la activación del programa residente: en ese caso, si no estaba ya activo e InDOS<=1 y el Banderín de Errores Críticos es igual a 0 se puede proceder a activar el programa residente. - Como mínimo habrán de existir dos variables de control: Una que indica si el programa residente ya está activo (y se deben rechazar o posponer nuevas activaciones, ya que éste se supone no reentrante). Otra, que indique si el programa residente va a ser activado en breve (en cuanto el DOS nos deje). Ambas variables son semáforos que conviene tratar con cuidado, para evitar reentradas en el programa residente: cuando desde una interrupción son comprobadas (ej., desde una INT 28h) podría producirse otra interrupción (como INT 8) lo que complica ligeramente la programación. Aunque no lo he dicho antes, todos los programas residentes que usan el DOS deben definir una pila propia, ya que la del programa interrumpido puede no ser suficientemente grande. Por el hecho de definir una pila propia, los programas residentes que usan funciones del DOS no son reentrantes; lo cual no es, por lo general, una limitación muy importante.
PROGRAMAS RESIDENTES
161
- Por supuesto, antes de ejecutar su código propiamente dicho, el programa residente deberá preservar el DTA, el PSP y la información extendida de errores, así como los vectores de INT 1Bh/23h/24h. Después deberá desviar las INT 1Bh e INT 23h hacia un IRET (para evitar un Ctrl-Break ó Ctrl-C) y la INT 24h, para implementar una gestión propia de los errores críticos. Al final, deberá restaurar todo de nuevo. Toda la información vertida hasta ahora procede de la versión original del libro Undocumented DOS, citado en la bibliografía. Sin embargo, en mi experiencia personal con los programas residentes he sacado la conclusión de que es conveniente también desviar la INT 21h e intentar desde la misma activar el programa residente, tal como si se tratara de una interrupción periódica más. El motivo es que desde la INT 8 ó la INT 1Ch hay que tener bastante suerte para que el DOS esté desocupado cuando se producen, ya que estas interrupciones sólo suceden 18 veces cada segundo. Esto significa que, por ejemplo, mientras se formatea un disco y se intenta activar el programa residente, puede que éste no responda hasta haberse formateado medio disco o, incluso, hasta finalizar el formateo. Sin embargo, mientras se formatea el disco, se producen miles de llamadas a la INT 21h: cuando InDOS sea cero tras acabar una sola de estas llamadas, podremos darnos cuenta; sin embargo, utilizando sólo la interrupción periódica estaremos a merced de la suerte. Desviar la INT 21h e intentar activar el programa residente desde ella permite por ejemplo que éste actúe, en medio de un formateo de disco, de manera casi instantánea cuando se le requiere. Otro ejemplo: con el método normal, sin controlar la INT 21h, mientras se saca un directorio por pantalla y se intenta activar el programa residente, cada cierto número de líneas éste responde; controlando la INT 21h, responde cada dos o tres caracteres impresos. Es evidente que la INT 21h pone a nuestra disposición un método mucho más efectivo a menudo que la interrupción periódica; sin embargo, tampoco es conveniente prescindir de esta última ya que la INT 21h sólo funciona cuando alguien llama al DOS (y no siempre alguien lo está llamando). En general, conviene utilizar las dos interrupciones a la vez: si bien interceptar la INT 21h no está recomendado en ningún sitio excepto en este libro, puedo asegurar que he tenido bastantes ocasiones de comprobar que es completamente fiable. 10.10.4.- UN METODO ALTERNATIVO: EL SDA. Hasta ahora hemos visto el método más común para poder emplear el DOS desde un programa residente. Sin embargo, este método depende de la molesta variable InDOS. Esto limita la efectividad de los programas residentes, que no pueden ser activados por ejemplo cuando se ejecuta un comando TYPE. La solución alternativa que se apuntaba al principio de este apartado consiste en salvar el contexto del DOS y restaurarlo después, algo factible desde el DOS 3.0. Esto supone bastantes diferencias respecto al método estudiado hasta ahora. En lugar de chequear InDOS se debe verificar que el DOS no está en una sección crítica (que por fortuna es lo más normal) como luego veremos; y esto tanto desde la interrupción del teclado como desde la periódica o desde la INT 28h. Al comienzo del código del programa residente, se debe salvar el estado del DOS: esto significa que hay que pedir memoria al sistema (o tenerla reservada de antemano en cantidad suficiente) para contener esa información. También hay que instalar las nuevas rutinas de control de INT 1Bh, 23h y 24h; no es necesario preservar el PSP activo (ya incluido en el área salvada): lo que sí es preciso es activar el PSP propio. Tampoco es preciso preservar el DTA ni la información extendida de errores: aunque se debe establecer un nuevo DTA, al restaurar el estado del DOS más tarde éste será también automáticamente restablecido. Y bien, ¿en qué consiste el estado o contexto del DOS?: se basa en un área de datos, el SDA (Swappable Data Area), cuyo tamaño oscila entre 24 bytes y 2 Kbytes. Este área almacena el PSP activo y las tres pilas del DOS, así como la dirección del DTA... Para manipular el SDA se puede emplear la función del sistema Get Address of DOS Swappable Data Area (5D06h), que devuelve en DS:SI un puntero al SDA, en DX el número mínimo de bytes a preservar cuando el DOS está libre y en CX el número de bytes a preservar cuando el DOS está ocupado (InDOS distinto de cero). Desde la versión 4.0 del DOS se debe utilizar en su lugar la función Get DOS Swappable Data Areas (5D0Bh), ya que este sistema no posee un único área de datos sino múltiples. El procedimiento general consistirá, simplemente, en salvar el SDA al principio y restaurarlo al final. Como se dijo antes, el SDA sólo puede ser accedido cuando el DOS no está en un momento crítico. Cuando el DOS entra y sale de los momentos críticos, llama a la INT 2Ah con AX=8000h (inicio de momento crítico) o bien AX=8100h o AX=8200h (fin de momento crítico). Se debe interceptar la INT 2Ah e incrementar/decrementar una variable que indique las entradas/salidas del DOS en fase crítica.
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Este método para gestionar los programas residentes requiere algo más de memoria: en especial, si se quiere asegurar la compatibilidad con futuras versiones del sistema, habrá que reservar mucho más de 2Kb para almacenar el SDA (intentar utilizar memoria convencional puede fallar, ya que el programa principal puede tenerla toda asignada) aunque este problema es menor en máquinas con memoria expandida o extendida. No hay que olvidar que el SDA no se puede grabar en disco (para eso hay que usar el DOS, y el DOS no se puede emplear hasta no haber salvado el SDA). También es quizá algo más complejo. Sin embargo, añade algo más de potencia a los programas residentes, ya que pueden ser activados casi en cualquier momento y prácticamente en cualquier circunstancia. El autor de este libro nunca ha empleado este método. 10.10.5.- METODOS MENOS ORTODOXOS. Hay programadores que utilizan métodos muy curiosos para emplear los servicios del DOS desde los programas residentes. Un ejemplo, expuesto por Douglas Boling en su artículo de la revista RMP (Ed. Anaya, Marzo-Abril de 1992) consiste en activar el Banderín de Errores Críticos antes de llamar a las funciones ordinarias del DOS: de esta manera, se utiliza la pila de errores críticos en lugar de la de disco, con lo que no hay conflictos. Esto, por supuesto, sin que el DOS estuviera antes en estado crítico (en caso de estarlo hay que esperar). El inconveniente de este método es que sólo un programa residente de este tipo puede estar activo en un momento dado en el ordenador. Evidentemente, también hay que desviar la INT 24h para controlar un posible error crítico de verdad. 10.11. - EJEMPLO DE PROGRAMA RESIDENTE QUE UTILIZA EL DOS. El programa propuesto de ejemplo (SCRCAP) es el tradicional capturador de pantallas, en este caso de texto. El método que emplea es el clásico de comprobar la variable InDOS. Al pulsar Alt-SysReq (combinación por defecto) comienza a actuar. Emite un sonido ascendente que precede la grabación y otro descendente que la sucede, para confirmar que ha grabado. Los ficheros que genera tienen por nombre SCRxx-nn.SCR, donde xx es la anchura de la pantalla en columnas (en hexadecimal) y nn el número de fichero, entre 00 y 99. Los ficheros se crean a partir de 00 cuando se instala el programa, sobrescribiendo otros existentes con anterioridad. Al almacenar en el nombre del fichero la anchura del modo de vídeo, es fácil después procesar la imagen al conocer sus dimensiones. El programa no comprueba el modo de vídeo, por lo que en pantallas gráficas se obtienen resultados desconcertantes. Sin embargo, la ventaja de ello es que de esta manera puede salvar pantallas extrañas no estándar (como 132x60, etc.) que pueden poseer ciertas tarjetas. El fichero es creado en el directorio activo por defecto; si se invoca la utilidad mientras se ejecuta un DIR, el fichero podría crearse en el directorio visualizado (algunas versiones del COMMAND cambian el directorio activo momentáneamente). Como cabía esperar, el programa se autoinstala automáticamente en memoria superior y tiene opción de desinstalación, siendo también configurables las teclas de activación. Entre los aspectos técnicos, decir que se desvía la INT 21h como se comentó con anterioridad. En ese sentido, SCRCAP puede ser invocado con éxito mientras se formatea un disquete (bueno, pero tampoco para grabar precisamente sobre ese disquete). Se define una pila interna de 0,75 Kbytes, suficiente para el programa que graba la pantalla y para dar cabida a todas las interrupciones hardware que puedan anidarse durante el proceso (examinando la memoria con DEBUG se puede observar qué cantidad máxima de pila es consumida tras un rato de trabajo, ya que los caracteres 'PILA' permanecen en la zona de la misma aún no empleada). Desde la rutina de control de INT 8 e INT 9 se llama a una subrutina, proceso_tsr, que toma la decisión de activar el programa residente si el DOS está preparado, o lo pospone en caso contrario. Desde la INT 28h se hace la comprobación más relajada de InDOS (basta con que sea no mayor de 1) y se toma también la decisión de activar el programa residente o seguir esperando: en el primer caso se llama a proceso_tsr con una variable (in28) que indica que ya no hay que hacer más comprobaciones. En proceso_tsr se comprueba la variable activo para evitar una reentrada al programa residente: como es un semáforo, es preciso inhibir las interrupciones con objeto de que entre su consulta y ulterior hipotética modificación no pueda ser modificado por nadie (por otro proceso lanzado por interrupciones). Al final, la rutina tarea_TSR es el auténtico programa residente. Simplemente modificando esta rutina se pueden crear programas residentes que realicen cualquier función, pudiendo llamar para ella al DOS.
161
PROGRAMAS RESIDENTES
SCRCAP termina residente dejando en memoria todo el PSP, a diferencia de programas anteriores. Los últimos 128 bytes del PSP se dejan residentes porque serán empleados como área de transferencia a disco (DTA). Conviene ahora hacer un pequeño apunte importante: cuando el programa es relocalizado a la memoria superior, hay que actualizar un campo en el PSP relocalizado (rutina reubicar_prog): se trata del campo que apunta a la JFT (offset 36h del PSP), con objeto de que apunte correctamente al nuevo segmento en que reside el PSP. Si no se tomara esta precaución, no se accedería al disco correctamente. Si se compara el listado de SCRCAP con el de RCLOCK, el lector comprobará que tienen común cerca del 50% de las líneas. Sólo cambia la ayuda, algún parámetro, alguna subrutina de la instalación y, por supuesto, el código residente. En general, las subrutinas que componen ambos programas son lo suficientemente generales como para acomodar múltiples soluciones informáticas: se puede considerar que ambos programas son una especie de plantillas para crear utilidades residentes. Para hacer nuevos programas residentes que hagan otras tareas, basta con cambiar sólo la parte residente y poco más. Esto permite trabajar con comodidad, pese a tratarse del lenguaje ensamblador, y producir múltiples programas en tiempo récord. ; ********************************************************************
;
; *
; bit 7 a 1: «extension_id» definida
; *
* SCRCAP 1.0
; * ; *
Utilidad residente de captura de pantallas de texto.
; *
*
multiplex_id
DB
0
*
vectores_id
DW
tabla_vectores
*
extension_id
DW
tabla_extra
DB
"*##*"
DB
"CiriSOFT:SCRCAP:1.0",0
DB
6
*
; ********************************************************************
autor_nom_ver
; ------------ Macros de propósito general
tabla_vectores EQU XPUSH
DB
MACRO RM IRP reg, PUSH reg ENDM
MACRO RM IRP reg,
ENDM
scrcap
DW
0
ant_int08_seg
DW
0
DB
9
ant_int09
LABEL DWORD
ant_int09_off
DW
0
ant_int09_seg
DW
0
DB
13h
ant_int13
LABEL DWORD
ant_int13_off
DW
0
ant_int13_seg
DW
0
DB
21h
ant_int21
LABEL DWORD
SEGMENT
ant_int21_off
DW
0
ASSUME CS:scrcap, DS:scrcap
ant_int21_seg
DW
0
DB
28h
ORG
ini_residente
8
ant_int08_off
; ------------ Programa
EQU
100h
$
; vectores de interrupción interceptados
$
LABEL DWORD
POP reg ENDM
; número Multiplex de este TSR
ant_int08
ENDM
XPOP
011: *.SYS formato EXE
ant_int28
LABEL DWORD
ant_int28_off
DW
0
ant_int28_seg
DW
0
DB
2Fh
; INT 8 ; dirección original
; INT 9 ; dirección original
; INT 13h ; dirección original
; INT 21h ; dirección original
; INT 28h ; dirección original
; INT 2Fh
ant_int2F
LABEL DWORD
ant_int2F_off
DW
0
; ------------ Identificación estandarizada del programa
ant_int2F_seg
DW
0
program_id
LABEL BYTE
tabla_extra
LABEL BYTE
segmento_real
DW
0
; segmento real donde será cargado
DW
ctrl_exterior ; permitido control exterior
offset_real
DW
0
; offset real
DW
0
longitud_total DW
0
; zona de memoria ocupada (párrafos)
info_extra
80h ; bits 0, 1 y 2-> 000: normal, con PSP
inicio:
JMP
DB
main
"
"
"
ctrl_exterior
; dirección original
; campo reservado
LABEL BYTE
;
001: bloque UMB XMS
reubicabilidad DB
1
;
010: *.SYS
activacion
act
DW
; programa 100% reubicable
161
act
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
DW
STI
1
; ------------ Variables internas
preguntan:
CMP
AH,CS:multiplex_id
JE
preguntan
JMP
CS:ant_int2F
CMP
DI,1992h
JNE
ret_no_info
MOV
AX,ES
; saltar al gestor de INT 2Fh
dosver
DW
?
; versión del DOS
ega
DB
ON
; a ON si EGA o superior
activo
DB
OFF
inminente
DB
OFF
CMP
AX,1492h
marcas
DB
8
; Por defecto, Alt...
JNE
ret_no_info
cod_rastreo
DB
54h
; ...SysReq (PetSys)
PUSH
CS
in13
DW
0
POP
ES
in28
DW
0
LEA
DI,autor_nom_ver
indos
LABEL DWORD
MOV
AX,0FFFFh
indos_off
DW
?
indos_seg
DW
?
crit_err
LABEL DWORD
crit_err_off
DW
?
crit_err_seg
DW
?
ant_pila_off
DW
?
ant_pila_seg
DW
?
mainpsp
DW
?
maindta
LABEL DWORD
maindta_off
DW
?
CMP
CS:inminente,ON
maindta_seg
DW
?
JNE
exit_08
; no hay ejecución pendiente
errinfo
LABEL DWORD
; Extended error information
CALL
proceso_tsr
; ejecutar TSR si es posible
errinfo_ax
DW
?
; del programa principal
errinfo_bx
DW
?
errinfo_cx
DW
?
ret_no_info:
; no llama alguien del convenio
; no llama alguien del convenio
; sí llama: darle información
; "entrada multiplex en uso"
IRET ges_int2F
ENDP
; ------------ Rutina de gestión de INT 8
ges_int08
PROC PUSHF
; PSP del programa principal
CALL
; DTA del programa principal
STI
IRET
ges_int08
ENDP
; ------------ Rutina de gestión de INT 9
DW
8 DUP (0)
ret_off
DW
?
ret_seg
DW
?
ret_flags
DW
?
DB
192 DUP ("PILA")
pila_ini
EQU
$
CALL
CS:ant_int09
fich_nom
DB
"SCRxx-00.SCR",0
CMP
AL,CS:cod_rastreo ; ¿tecla de activación?
fich_handle
DW
?
JNE
fin_09
MOV
AX,40h
PUSH
DS
local_ints
; DX, SI, DI, DS, ES, etc.
exit_08:
CS:ant_int08
ges_int09
PROC STI
; 0,75 Kb de pila
PUSH
AX
IN
AL,60h
PUSHF
DW
3
DB
1Bh
; INT 1Bh
MOV
DS,AX
DW
ges_int1B
; nueva dirección
MOV
AL,DS:[17h]
; dirección original
POP
DS
ant_int1B
LABEL DWORD
ant_int1B_off
DW
0
AND
AL,15
ant_int1B_seg
DW
0
CMP
AL,CS:marcas
DB
23h
; INT 23h
JNE
fin_09
DW
ges_int23
; nueva dirección
CALL
proceso_tsr
POP
AX
ant_int23
LABEL DWORD
ant_int23_off
DW
0
ant_int23_seg
DW
0
DB
24h
; INT 24h
DW
ges_int24
; nueva dirección
ant_int24
LABEL DWORD
ant_int24_off
DW
0
ant_int24_seg
DW
0
; dirección original
fin_09:
; ¿marcas de activación?
; ejecutar TSR si es posible
IRET ges_int09
ENDP
; ------------ Rutina de gestión de INT 13h
; dirección original ges_int13
PROC
FAR
; gestionar INT 13h
INC
CS:in13
; indicar entrada en INT 13h
CALL
CS:ant_int13
STI PUSHF
; ------------ Rutina de gestión de INT 2Fh
ges_int2F
PROC
FAR
PUSHF
; mucho cuidado con los flags
161
PROGRAMAS RESIDENTES
DEC
CS:in13
INC
CS:in28
; dentro de INT 28h
CALL
proceso_tsr
; ejecutar código del TSR
DEC
CS:in28
; fuera de INT 28h
exit_28:
JMP
CS:ant_int28
ges_int28
ENDP
; salida de INT 13h
POPF RET ges_int13
2
; retornar sin tocar flags
ENDP
; ------------ Rutinas de gestión de INT 1Bh, 23h y 24h. ; ------------ Rutina de control de ejecución del TSR ges_int1B
EQU
ges_int23
PROC
THIS BYTE
proceso_tsr
IRET ges_int23
ges_int24
; gestionar INTs 1Bh/23h
; ignorar Ctrl-C y Ctrl-Break
ENDP
PROC
; gestionar INT 24h
PROC
; ejecutar TSR si se puede
CMP
CS:in28,0
JNE
proceder
CMP
CS:in13,0
JA
no_proceder
; dentro de INT 28h
; INT 13h en curso
XPUSH
STI MOV
AX,3
LDS
BX,CS:crit_err
CMP
CS:dosver,300h
MOV
AL,[BX]
JAE
ret_int24
LDS
BX,CS:indos
XOR
AX,AX
OR
AL,[BX]
; función de fallo
; 0 en DOS 2.x
; crit_err OR indos
ret_int24:
IRET
AND
AL,AL
ges_int24
ENDP
XPOP
JZ
proceder
; se cumple que ambos a 0
MOV
CS:inminente,ON
; esperar próxima INT 8/28h
; ------------ Rutina de gestión de INT 21h
no_proceder:
RET ges_int21
proceder:
CS:ret_off
; offset de retorno
CMP
CS:activo,ON
; ¿ya estaba activo?
POP
CS:ret_seg
; segmento de retorno
JE
exit_proceso
; evitar reentrada
POP
CS:ret_flags
; flags de retorno
MOV
CS:activo,ON
; ahora sí, activo
PUSH
CS:ret_seg
PUSH
CS:ret_off
PUSH CALL
STI CS:inminente,OFF
CS:ret_flags
MOV
CS:ant_pila_off,SP
CS:ant_int21
MOV
CS:ant_pila_seg,SS
; dejar sólo segmento:offset
; ya atendida la petición
; preservar pila
CLI
CMP
CS:inminente,ON
MOV
SP,CS
JNE
exit_21
; no hay ejecución pendiente
MOV
SS,SP
CALL
proceso_tsr
; ejecutar TSR si es posible
LEA
SP,pila_ini
; nueva pila habilitada
STI
POPF
XPUSH
; retornar sin alterar flags
XPUSH
ENDP
XPOP
CALL
pushset_ints
CALL
pushset_psp
CALL
pushset_dta
CALL
push_crit_err
CALL
kbuff_limp
CALL
tarea_TSR
CALL
pop_crit_err
CALL
pop_dta
CALL
pop_psp
CALL
pop_ints
XPUSH
XPOP
LDS
BX,CS:crit_err
CLI
CMP
BYTE PTR [BX],0
MOV
SP,CS:ant_pila_seg
XPOP
MOV
SS,SP
JNE
exit_28
MOV
SP,CS:ant_pila_off
MOV
CS:activo,OFF
; ------------ Rutina de gestión de INT 28h
ges_int28
; ...semáforo comprobado
MOV
RET ges_int21
; a comprobar semáforo...
FAR
POP
PUSHF
exit_21:
CLI
PROC
PROC
; gestionar INT 28h
STI CMP
CS:activo,ON
JE
exit_28
CMP
CS:inminente,ON
JNE
exit_28
CMP
CS:in13,0
JA
exit_28
; TSR ya activo
; no hay que activarlo
; INT 13h en curso
; ¿error crítico?
XPUSH LDS
BX,CS:indos
CMP
BYTE PTR [BX],1
XPOP
JA
exit_28
exit_proceso:
STI RET
; ¿Indos>1? proceso_tsr
ENDP
; DS y ES apuntan al TSR
; ejecutar proceso residente
; pila restaurada
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
; ------------ Subrutinas de apoyo
pushset_ints
phst_otro:
pushset_ints
pop_ints
pop_otro:
pop_ints
pushset_psp
getpsp3:
PROC
; interceptar INT 1Bh/23h/24h
PUSH
BX
MOV
AH,50h
MOV
BX,segmento_real
PUSH
ES
INT
21h
LEA
SI,local_ints
POP
BX
MOV
CX,[SI]
MOV
mainpsp,BX
PUSH
CX
MOV
AL,[SI+2]
MOV
AH,35h
INT
21h
MOV
[SI+5],BX
MOV
[SI+7],ES
MOV
DX,[SI+3]
MOV
psp_ok:
; activar nuevo PSP
RET pushset_psp
ENDP
pop_psp
PROC
; restaurar PSP programa principal
PUSH
DS
MOV
AX,dosver
CMP
AH,2
AL,[SI+2]
JA
setpsp3
MOV
AH,25h
LDS
BX,crit_err
; en DOS 2.x ...
INT
21h
MOV
BYTE PTR [BX],0FFh
; forzar error crítico
ADD
SI,7
PUSH
BX
POP
CX
MOV
AH,50h
LOOP
phst_otro
MOV
BX,CS:mainpsp
POP
ES
INT
21h
RET
POP
BX
ENDP
MOV
BYTE PTR [BX],0
JMP
psp_poped
MOV
AH,50h
MOV
BX,mainpsp
INT
21h
POP
DS
PROC
; INT xx preservada
; INT xx desviada
; restaurar vectores INT 1Bh/23h/24h
setpsp3:
PUSH
DS
LEA
SI,local_ints
MOV
CX,[SI]
PUSH
CX
MOV
AL,CS:[SI+2]
MOV
AH,25h
MOV
DX,CS:[SI+5]
MOV
DS,CS:[SI+7]
INT
21h
MOV
AH,2Fh
ADD
SI,7
INT
21h
POP
CX
MOV
maindta_off,BX
LOOP
pop_otro
MOV
maindta_seg,ES
POP
DS
MOV
AH,1Ah
RET
MOV
DX,80h
ENDP
MOV
DS,segmento_real
INT
21h
XPOP
PROC
psp_poped:
; restaurar PSP
; anular error crítico
; DOS 3+
; restaurar PSP
RET pop_psp
ENDP
pushset_dta
PROC XPUSH
; INT xx restaurada
; preservar PSP y activar el nuevo
MOV
AX,dosver
CMP
AH,2
JA
getpsp3
PUSH
DS
LDS
DI,crit_err
MOV
BYTE PTR [DI],0FFh
MOV
AH,51h
INT
21h
PUSH
; almacenar DTA activo
; establecer nuevo DTA
RET
; en DOS 2.x ...
pushset_dta
ENDP
pop_dta
PROC PUSH
DS
MOV
AH,1Ah
MOV
DX,maindta_off
MOV
DS,maindta_seg
BX
INT
21h
MOV
AH,50h
POP
DS
MOV
BX,CS:segmento_real
INT
21h
; activar nuevo PSP
MOV
BYTE PTR [DI],0
; anular error crítico
POP
BX
POP
DS
CMP
dosver,300h
JMP
psp_ok
JB
push_crit_fin
MOV
AH,62h
MOV
AH,59h
INT
21h
MOV
BX,0
; forzar error crítico
; BX = PSP activo (DOS 2.x)
; restaurar DTA
RET
; BX = PSP activo (DOS 3+)
pop_dta
ENDP
push_crit_err
PROC
; necesario DOS 3.0+
161
PROGRAMAS RESIDENTES
INT
21h
MOV
errinfo_ax,AX
; preservar información de
MOV
errinfo_bx,BX
; errores críticos
MOV
errinfo_cx,CX
init_nomfich
PROC PUSH
DS
MOV
AX,40h
push_crit_fin: RET
MOV
DS,AX
push_crit_err
MOV
AX,DS:[4Ah]
POP
DS
MOV
AH,AL
SHR
AH,1
SHR
AH,1
pop_crit_err
ENDP
PROC CMP
dosver,300h
JB
pop_crit_fin
MOV
AX,5D0Ah
SHR
AH,1
MOV
BX,0
SHR
AH,1
LEA
DX,errinfo
AND
AL,15
INT
21h
; restaurar información de
ADD
AX,'00'
; errores críticos
CMP
AL,'9'
JBE
al_es_hex
ADD
AL,'A'-'9'-1
CMP
AH,'9'
pop_crit_fin:
RET
pop_crit_err
ENDP
kbuff_limp
PROC
; necesario DOS 3.0+
; limpiar buffer del teclado
; binario -> hex
MOV
AH,1
JBE
ah_es_hex
INT
16h
ADD
AH,'A'-'9'-1
JZ
kbuff_limpio
XCHG
AH,AL
MOV
AH,0
MOV
WORD PTR fich_nom+3,AX
INT
16h
RET
JMP
kbuff_limp
kbuff_limpio:
RET
kbuff_limp
ENDP
ah_es_hex:
init_nomfich
; anchura de pantalla
ENDP
; ------------ Obtener segmento de vídeo y tamaño de la pantalla
; ------------ Proceso residente que puede emplear el DOS
tarea_TSR
al_es_hex:
; anchura de pantalla
dscx_eq_video
PROC
PROC
; devolver CX = tamaño pantalla
MOV
AX,40h
MOV
DS,AX
; y apuntar DS a la misma
CALL
sonidoUp
MOV
AL,DS:[49h]
; modo de pantalla
CALL
init_nomfich
MOV
BX,0B000h
; supuesto adaptador monocromo
LEA
DX,fich_nom
MOV
CX,4000
; número de bytes
MOV
CX,0
CMP
AL,7
MOV
AH,3Ch
JE
video_ok
INT
21h
MOV
BX,0B800h
; adaptador de color
JC
tarea_err
MOV
AX,DS:[4Eh]
; offset de la página activa
MOV
fich_handle,AX
MOV
CL,4
CALL
dscx_eq_video
SHR
AX,CL
; bytes -> párrafos
MOV
BX,CS:fich_handle
ADD
BX,AX
; segmento de vídeo efectivo
XOR
DX,DX
MOV
AX,25
; 25 líneas
MOV
AH,40h
CMP
CS:ega,ON
INT
21h
JNE
modo_ok
JC
tarea_err
XOR
AH,AH
PUSH
CS
MOV
AL,DS:[84h]
POP
DS
INC
AL
MOV
BX,fich_handle
MUL
WORD PTR DS:[4Ah] ; líneas*columnas = caracteres
MOV
AH,3Eh
SHL
AX,1
INT
21h
MOV
CX,AX
JC
tarea_err
MOV
DS,BX
CALL
inc_nombre
CALL
sonidoDown
dscx_eq_video
PUSH
CS
; ------------ Incrementar número de fichero para siguiente vez
POP
DS
; abrir fichero
; grabar pantalla
modo_ok:
; cerrar fichero video_ok:
; tarjeta modesta
; AX = líneas EGA/VGA
; AX = tamaño buffer de vídeo
RET
; preparar futuro nombre
ENDP
RET tarea_err:
RET tarea_TSR
ENDP
; ------------ Inicializar nombre de fichero con anchura de pantalla.
inc_nombre
PROC LEA
BX,fich_nom
MOV
AX,[BX+6]
INC
AH
161
inc_ok:
inc_nombre
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
CMP
AH,'9'
IN
AL,61h
JBE
inc_ok
OR
AL,3
MOV
AH,'0'
JMP
SHORT $+2
INC
AL
JMP
SHORT $+2
CMP
AL,'9'
OUT
61h,AL
JBE
inc_ok
MOV
AL,182
MOV
AL,'9'
JMP
SHORT $+2
MOV
[BX+6],AX
JMP
SHORT $+2
RET
OUT
43h,AL
ENDP
POP
AX
; activar sonido
; preparar canal 2
RET ; ------------ Sonido ascendente
sonidoON
sonidoUp
; ------------ Inhibir sonido
sonar_arriba:
PROC CALL
espera55ms
CALL
sonidoON
MOV
AX,2400
PUSH
AX
MOV
CX,18
IN
AL,61h
CALL
sonidoAX
AND
AL,255-3
CALL
espera55ms
JMP
SHORT $+2
SUB
AX,30
JMP
SHORT $+2
LOOP
sonar_arriba
OUT
61h,AL
CALL
sonidoOFF
POP
AX
sonidoOFF
PROC
; desactivar sonido
RET
RET sonidoUp
ENDP
sonidoOFF
ENDP
ENDP
; ------------ Sonido descendente
; ------------ Programar la nota AX en el temporizador
sonidoDown
sonidoAX
sonar_abajo:
PROC
PROC
CALL
espera55ms
PUSH
AX
CALL
sonidoON
OUT
42h,AL
MOV
AX,3000
MOV
AL,AH
MOV
CX,18
JMP
SHORT $+2
CALL
sonidoAX
JMP
SHORT $+2
CALL
espera55ms
OUT
42h,AL
ADD
AX,30
POP
AX
LOOP
sonar_abajo
CALL
sonidoOFF
; canal 2 del 8253 programado
RET sonidoAX
ENDP
RET sonidoDown
; ------------ Fin del área residente
ENDP
; ------------ Pausa de 55 milisegundos
fin_residente
EQU
$
espera55ms
bytes_resid
EQU
fin_residente-ini_residente
PROC XPUSH MOV
AX,40h
MOV
DS,AX
STI
espera_tic:
; por si acaso AL,DS:[6Ch]
; *****************************
CMP
AL,DS:[6Ch]
; *
JE
espera_tic
; *
XPOP
; *
* I N S T A L A C I O N
* *
; *****************************
ENDP main
; ------------ Activar sonido
sonidoON
(bytes_resid+15)/16
MOV
RET espera55ms
parrafos_resid EQU
PROC PUSH
AX
PROC LEA
DX,scrcap_txt
CALL
print
CALL
inic_general
CALL
detectarEGA
; mensaje inicial
; inicializar ciertas variables
161
PROGRAMAS RESIDENTES
params_ok:
desinst:
no_pesame:
no_residente:
instalable:
instalar:
handle_ok:
CALL
obtener_param
; analizar posibles parámetros
MOV
DI,256
; instalación mem. convencional
JNC
params_ok
; son correctos
CALL
inicializa_id
; inicializar identificación
CALL
info_err_param
; no: informar del error/ayuda
CALL
reubicar_prog
; reubicar programa a ES:DI
JMP
fin_noresid
CALL
activar_ints
; interceptar vectores
CALL
residente?
; ¿programa ya residente?
CALL
free_environ
; liberar espacio de entorno
JC
no_residente
; aún no
MOV
DX,memoria
; tamaño zona residente
CMP
param_u,1
; ¿se solicita desinstalarlo?
MOV
AX,3100h
JE
desinst
; así es
INT
21h
CALL
adaptar_param
; parámetros en copia residente
MOV
AX,4C00h
LEA
DX,ya_install_txt
INT
21h
CALL
print
CALL
info_ya_ins
JMP
fin_noresid
MOV
ES,tsr_seg
MOV
AH,ES:multiplex_id
; *
CALL
mx_unload
; *
LEA
DX,des_ok_txt
JNC
no_pesame
; ha sido posible
LEA
DX,des_no_ok_txt
; no es posible
CALL
print
JMP
fin_noresid
CMP
AX,0
; ¿reside una versión distinta?
JE
instalable
; no: se admite instalación
CALL
error_version
; error de versión incompatible
JMP
fin_noresid
CMP
param_u,1
; no residente: ¿desinstalar?
JNE
instalar
; no lo piden
LEA CALL
instalar_ml:
main
; terminar residente
; terminar no residente
ENDP
; informar de teclas activación
; *************************************
; desinstalarlo:
* SUBRUTINAS PARA LA INSTALACION
; *
* *
; *************************************
; ------------ Extraer posibles parámetros de la línea de comandos
obtener_param
PROC MOV
BX,81h
; apuntar a zona de parámetros
CALL
saltar_esp
; saltar delimitadores
JNC
otro_pmt
; quedan más parámetros
JMP
fin_proc_pmt
; no más parámetros
CMP
AL,'/'
DX,imp_desins_txt ; lo piden, ¡serán despistados!
JE
pmt_barrado
print
CMP
AL,'?'
JMP
fin_noresid
JE
pmt_hlp
MOV
AX,parrafos_resid ; área residente
JMP
mal_proc_pmt
ADD
AX,16
INC
BX
MOV
memoria,AX
MOV
AL,[BX]
; letra del parámetro
CALL
mx_get_handle
CMP
AL,13
; ¿fin de mandatos?
JNC
handle_ok
JE
mal_proc_pmt
; falta parámetro
LEA
DX,nocabe_txt
CMP
AL,'?'
CALL
print
JE
pmt_hlp
JMP
fin_noresid
OR
AL,' '
MOV
multiplex_id,AH
; entrada multiplex para SCRCAP
CMP
AL,'h'
LEA
DX,instalado_txt
; mensaje de instalación
JE
pmt_hlp
CALL
print
CMP
AL,'s'
CALL
info_ya_ins
; informar teclas activación
JE
pmt_S
CALL
preservar_ints
; tomar nota de vectores
CMP
AL,'t'
CMP
param_ml,0
; ¿se indicó parámetro /ML?
JE
pmt_T
JNE
instalar_ml
; en efecto
CMP
AL,'u'
MOV
AX,memoria
; párrafos de memoria precisos
JE
pmt_U
CALL
UMB_alloc
; pedir memoria superior XMS
MOV
SI,[BX]
; ¿parámetro de dos caracteres?
JNC
instalar_umb
; hay la suficiente
OR
SI,"
; mayusculizar
MOV
AX,memoria
CMP
SI,"lm"
CALL
UPPER_alloc
; pedir memoria superior DOS 5
JE
pmt_ML
JC
instalar_ml
; no hay la suficiente
STC instalar_umb:
fin_noresid:
; 256 bytes de PSP (completo)
otro_pmt:
pmt_barrado:
; obtener entrada Multiplex
; no quedan entradas
mal_proc_pmt:
ES,AX
; segmento del bloque UMB
MOV
DI,256
; ES:256 zona a donde reubicar
CALL
inicializa_id
; inicializar identificación
CALL
reubicar_prog
; reubicar el programa a ES:DI
CALL
activar_ints
; interceptar vectores
JMP
fin_noresid
; programa instalado «arriba»
; parámetro precedido por '/'
; poner en minúsculas
; parámetro /S=
; parámetro /T=
"
STC
; ¿parámetro /ML?
; error en parámetro(s)
RET
; indicar que usa memoria DOS
MOV
STC
otro_pmt_mas:
fin_proc_pmt:
CLC
; parámetros procesados ok.
RET pmt_hlp:
pmt_S:
MOV
param_ayuda,1
JMP
mal_proc_pmt
MOV
param_s,1
CALL
get_num
JC
mal_proc_pmt
; «error» de ayuda
161
fuera_rango:
pmt_T:
pmt_U:
pmt_ML:
obtener_param
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
MOV
marcas,AL
JE
fin_num
CMP
AX,15
CMP
AL,32
JA
fuera_rango
JE
fin_num
AND
AL,AL
CMP
AL,9
JZ
fuera_rango
JE
fin_num
JMP
otro_pmt_mas
CMP
AL,'/'
MOV
marcas,255
JE
fin_num
JMP
mal_proc_pmt
CMP
AL,':'
MOV
param_t,1
JE
fin_num
CALL
get_num
INC
BX
MOV
cod_rastreo,AL
MOV
AL,[BX]
JMP
otro_pmt_mas
JMP
obtener_num
MOV
param_u,1
MOV
SI,BX
INC
BX
DEC
SI
JMP
otro_pmt_mas
XOR
DX,DX
MOV
param_ml,1
MOV
AX,1
; AX = 10 elevado a la 0 = 1
ADD
BX,2
DEC
BX
; próximo carácter a procesar
JMP
otro_pmt_mas
MOV
CL,[BX]
CMP
CL,'='
JE
ok_num
CMP
CL,':'
JE
ok_num
CMP
CL,'.'
JNE
no_millar
CMP
AX,1000
JE
otro_car
JMP
mal_num
CMP
CL,'0'
JB
mal_num
CMP
CL,'9'
; puntero al primer carácter
JA
mal_num
; hay parámetro
SUB
CL,'0'
; pasar ASCII a binario
MOV
CH,0
; CX = 0 .. 9
PUSH
AX
; AX = 10 elevado a la N
AND
AX,AX
JNZ
multiplica
AND
CL,CL
JNZ
mal_num_pop
; a la izda sólo permitir ceros
PUSH
DX
; tras completar 5º dígito
fin_num:
; en efecto otro_car:
ENDP
; ------------ Saltar espacios, tabuladores, ... buscando un parámetro
saltar_esp:
MOV
AL,[BX]
INC
BX
CMP
AL,9
JE
saltar_esp
CMP
AL,32
JE
saltar_esp
CMP
AL,0Dh
JE
fin_param
DEC
BX
CLC
; carácter tabulador
; espacio en blanco no_millar: ; fin de zona de parámetros
RET fin_param:
STC
; no hay parámetro
RET
; ------------ Obtener número chequeando delimitadores /= y /:
get_num:
err_sintax:
multiplica:
INC
BX
MOV
AL,[BX]
MUL
CX
INC
BX
POP
DX
CMP
AL,'='
JC
mal_num_pop
JE
delimit_ok
ADD
DX,AX
CMP
AL,':'
JC
mal_num_pop
JE
delimit_ok
POP
AX
CMP
AX,10000
JNE
STC
; sintaxis incorrecta
; fin número
; fin número (otro parámetro)
; fin número (otro dato)
; delimitador: fin de número
; delimitador: fin de número
; saltar los puntos de millar
; separador millar descolocado
; DX = DX + digito (CX) * 10 ^ N (AX)
potencia
; AX*10 no se desbordará
MOV
AL,[BX]
MOV
AX,0
; como próximo dígito<>0 a
CALL
obtener_num
JMP
otro_car
; la izda ... pobre usuario
JC
err_sintax
MOV
DI,10
INC
BX
PUSH
DX
; no manchar DX al multiplicar
MUL
DI
; AX = AX elevado a la (N+1)
POP
DX
RET delimit_ok:
; fin número
potencia:
RET
JMP
otro_car
;
puntero (BX) apuntará al final del número y CF=1 si el
mal_num_pop:
POP
AX
; reequilibrar pila
;
número era incorrecto.
mal_num:
MOV
BX,SI
; número mayor de 65535
; ------------ Extraer nº de 16 bits y depositarlo en AX; al final, el
STC obtener_num
CMP
; condición de error
RET
PROC AL,0Dh
; fin zona parámetros y número
ok_num:
MOV
BX,SI
; número correcto
161
PROGRAMAS RESIDENTES
; resultado
INT
2Fh
; sí: obtener su dirección
; condición de Ok.
MOV
XMS_off,BX
; y preservarla
RET
MOV
XMS_seg,ES
ENDP
MOV
xms_ins,1
POP
ES
MOV
AX,DX
CLC
obtener_num
RET
; ------------ Mensajes de error / ayuda XMS_ausente:
err_ok:
inic_XMS
otro_error
LEA
DX,ayuda_txt
; ------------ Comprobar si el programa ya reside en memoria. A la
CALL
print
;
salida, CF=0 si programa ya reside, con «tsr_seg» y
;
«tsr_off» inicializadas apuntando a la cadena de
LEA
DX,err_sintax_txt
;
identificación de la copia residente.
CMP
marcas,255
;
programa no reside aún (AX=0) o reside pero en otra
JNE
err_ok
;
versión distinta (AX=1).
LEA
DX,err_tec_txt
CALL
print
residente?
PROC
LEA
DX,err_sintax_fin
PUSH
CX
CALL
print
PUSH
SI
PUSH
DI
PUSH
ES
PUSH
AX
LEA
DI,autor_nom_ver
MOV
SI,DI
info_err_param ENDP
; ------------ Ya está instalada otra versión distinta del programa
error_version
Si CF=1, el
; identificación del programa
MOV
AL,0
PUSH
ES
MOV
CL,255
LEA
DX,mal_ver_txt1
CLD
CALL
print
REPNE SCASB
LES
DI,tsr_dir
SUB
DI,SI
MOV
AL,':'
MOV
CX,DI
MOV
CL,255
MOV
AX,1492h
CLD
MOV
ES,AX
REPNE SCASB
MOV
DI,1992h
; ES:DI protocolo de búsqueda
REPNE SCASB
CALL
mx_find_tsr
; buscar si está en memoria
MOV
tsr_off,DI
; anotar la dirección programa ; por si estaba instalado
PROC
MOV
DL,ES:[DI]
; número de versión
MOV
AH,2
MOV
tsr_seg,ES
INT
21h
POP
AX
MOV
DL,'.'
JNC
resid_ok
MOV
AH,2
POP
ES
INT
21h
PUSH
ES
MOV
DL,ES:[DI+2]
LEA
DI,autor_nom_ver
MOV
AH,2
MOV
SI,DI
INT
21h
MOV
AL,':'
LEA
DX,mal_ver_txt2
MOV
CL,255
CALL
print
REPNE SCASB
POP
ES
REPNE SCASB
; revisión
; tamaño autor+programa+versión
; CF=0 -> programa ya residente
RET
SUB
DI,SI
ENDP
MOV
CX,DI
MOV
AX,1492h
MOV
ES,AX
MOV
DI,1992h
; ES:DI protocolo de búsqueda
CALL
mx_find_tsr
; buscar si está en memoria
MOV
tsr_off,DI
; anotar dirección del programa
MOV
tsr_seg,ES
; por si instalada otra versión
MOV
AX,0
; ------------ Considerar presencia de controlador XMS
inic_XMS
ENDP
param_ayuda,1
JNE
RET
error_version
xms_ins,0
CMP
RET otro_error:
MOV RET
info_err_param PROC
PROC MOV
AX,4300h
INT
2Fh
CMP
AL,80h
JNE
XMS_ausente
JC
resid_ok
PUSH
ES
MOV
AX,1
MOV
AX,4310h
STC
; chequear presencia XMS
; no instalado
; tamaño autor+programa
; CF=1, AX=0 -> no residente
; CF=1, AX=1 -> sí: otra vers.
161
resid_ok:
residente?
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
POP
ES
MOV
AH,cod_rastreo
POP
DI
POP
DS
POP
SI
LEA
DX,act_teclas_txt
POP
CX
CALL
print
RET
TEST
AL,4
ENDP
JZ
alt?
LEA
DX,act_ctrl
CALL
print_
TEST
AL,8
JZ
shift_izq?
LEA
DX,act_alt
CALL
print_
TEST
AL,2
JZ
shift_der?
; ------------ Inicializar ciertas variables alt?: inic_general
PROC XPUSH
crit_ok:
inic_general
; **
MOV
AH,30h
INT
21h
XCHG
AH,AL
MOV
dosver,AX
; versión del DOS
LEA
DX,act_shift_izq
CALL
inic_XMS
; detectar controlador XMS
CALL
print_
MOV
AH,34h
TEST
AL,1
INT
21h
JZ
fin
MOV
indos_off,BX
LEA
DX,act_shift_der
MOV
indos_seg,ES
CALL
print_
INC
BX
CMP
cod_rastreo,0
CMP
dosver,300h
JE
no_mas_teclas
JB
crit_ok
LEA
DX,act_c_txt
SUB
BX,2
CMP
AH,54h
CMP
dosver,300h
JE
act_ok
JE
crit_ok
LEA
DX,act_otra_txt
MOV
AX,5D06h
act_ok:
CALL
print_
INT
21h
no_mas_teclas: LEA
shift_izq?:
shift_der?:
; dirección de InDOS fin:
; Critical Error detrás en 2.x
; Critical Error antes en 3.0
XPUSH
CALL
XPOP
RET
POP
DS
MOV
crit_err_off,BX
MOV
crit_err_seg,ES
POP
ES
print
CALL
print
PUSH
AX
; dirección de ese flag
MOV
DL,'-'
; *
MOV
AH,2
RET
INT
21h
ENDP
POP
AX
; *
print_:
DX,act_fin_txt
RET ; ------------ Detectar EGA o tarjeta superior
info_ya_ins
detectarEGA
; ------------ Adaptar parámetros de un SCRCAP ya instalado en memoria
ega_ini:
PROC MOV
BL,10h
MOV
AH,12h
INT
10h
PUSH
ES
CMP
BL,10h
MOV
ES,tsr_seg
MOV
AL,OFF
CMP
param_s,1
JE
ega_ini
JNE
s_ok
MOV
AL,ON
MOV
AL,marcas
MOV
ega,AL
MOV
ES:marcas,AL
CMP
param_t,1
JNE
c_ok
MOV
AL,cod_rastreo
MOV
ES:cod_rastreo,AL
POP
ES
adaptar_param ; pedir información EGA al BIOS
; no es EGA
s_ok:
RET detectarEGA
ENDP
; ------------ Informar de las teclas que activan SCRCAP c_ok: info_ya_ins
tec_no_res:
ENDP
PROC
RET
PROC adaptar_param
ENDP
PUSH
DS
CALL
residente?
JC
tec_no_res
; ------------ Inicializar área «program_id» del programa residente.
MOV
DS,tsr_seg
;
A la entrada, ES:DI = seg:off a donde será reubicado
MOV
AL,marcas
;
y CF=1 si se utiliza memoria superior XMS.
161
PROGRAMAS RESIDENTES
inicializa_id
CMP
xms_ins,1
PROC
JNE
no_umb_disp
; no hay controlador XMS
PUSHF
MOV
DX,AX
; número de párrafos ; solicitar memoria superior
MOV
segmento_real,ES
; anotar segmento del bloque
MOV
AH,10h
MOV
offset_real,DI
; ídem con el offset
CALL
gestor_XMS
MOV
AX,memoria
CMP
AX,1
; ¿ha ido todo bien?
MOV
longitud_total,AX
MOV
AX,BX
; segmento UMB/código de error
MOV
AL,1
JNE
XMS_fallo
; fallo
POP
DX
; ok
POP
CX
POP
BX
POPF
info_ok:
; CF=0: usar memoria UMB XMS
JNC
info_ok
DEC
AL
OR
info_extra,AL
; usar memoria convencional
CLC RET
RET inicializa_id
ENDP
no_umb_disp:
MOV
AX,0
XMS_fallo:
POP
DX
POP
CX
POP
BX
; ------------ Preservar vectores de interrupción previos
STC
preservar_INTs PROC
otro_vector:
PUSH
ES
PUSH
DI
LEA
DI,tabla_vectores
MOV
CL,[DI-1]
MOV
CH,0
PUSH
CX
PUSH
DI
MOV
AH,35h
MOV
AL,[DI]
INT
21h
POP
RET UMB_alloc
; ------------ Reservar memoria superior, con DOS 5.0, del tamaño ; CX vectores interceptados
solicitado (AX párrafos). Si no hay bastante CF=1,
;
en caso contrario devuelve el segmento en AX.
UPPER_alloc
PROC PUSH
AX
MOV
AH,30h
DI
INT
21h
POP
CX
CMP
AL,5
MOV
[DI+1],BX
POP
AX
MOV
[DI+3],ES
JAE
UPPER_existe
ADD
DI,5
STC
LOOP
otro_vector
POP
DI
POP
ES
; anotar donde apunta
JMP
UPPER_fin
; necesario DOS 5.0 mínimo
PUSH
AX
; preservar párrafos...
MOV
AX,5800h
INT
21h
MOV
alloc_strat,AX
MOV
AX,5802h
INT
21h
MOV
umb_state,AL
MOV
AX,5803h
MOV
BX,1
INT
21h
MOV
AX,5801h
MOV
BX,41h
INT
21h
; High Memory best fit
RET
POP
BX
; ...párrafos requeridos
ENDP
MOV
AH,48h
INT
21h
; asignar memoria
; guardado el resultado
; repetir con los restantes UPPER_existe:
RET
; ------------ Liberar espacio de entorno
free_environ
;
; obtener vector de INT xx
preservar_INTs ENDP
free_environ
ENDP
PROC PUSH
ES
MOV
ES,DS:[2Ch]
MOV
AH,49h
INT
21h
POP
ES
; dirección del entorno
; liberar espacio de entorno
; ------------ Reservar bloque de memoria superior del nº párrafos AX,
PUSHF
;
devolviendo en AX el segmento donde está. CF=1 si no
PUSH
AX
;
está instalado el gestor XMS (AX=0) o hay un error (AL
MOV
AX,5801h
;
devuelve el código de error del controlador XMS).
MOV
BX,alloc_strat
INT
21h
MOV
AX,5803h
UMB_alloc
PROC PUSH
BX
MOV
BL,umb_state
PUSH
CX
XOR
BH,BH
PUSH
DX
INT
21h
; preservar estrategia
; preservar estado UMB
; conectar cadena UMB's
; restaurar estrategia
; restaurar estado cadena UMB
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
POP
AX
POPF ; hubo fallo
SUB
CX,AX
MOV
DS,CX
LEA
SI,offsets_ints
JC
UPPER_fin
PUSH
DS
MOV
CX,CS:[SI]
DEC
AX
ADD
SI,2
MOV
DS,AX
MOV
AL,CS:[SI]
; número del vector en curso
INC
AX
MOV
DX,CS:[SI+1]
; obtener offset
MOV
WORD PTR DS:[1],AX
; manipular PID
MOV
AH,25h
MOV
WORD PTR DS:[16],20CDh
; simular PSP
INT
21h
PUSH
ES
ADD
SI,3
MOV
CX,DS
LOOP
desvia_otro
MOV
ES,CX
POP
DS
MOV
CX,CS
POP
CX
DEC
CX
RET
MOV
DS,CX
MOV
CX,8
MOV
SI,CX
; ------------ Buscar entrada no usada en la interrupción Multiplex.
MOV
DI,CX
;
A la salida, CF=1 si no hay hueco (ya hay 64 programas
;
residentes instalados con esta técnica). Si CF=0, se
;
devuelve en AH un valor de entrada libre en la INT 2Fh.
mx_get_handle
PROC
desvia_otro:
activar_INTs
CLD REP
MOVSB
POP
ES
POP
DS
; copiar nombre de programa
UPPER_fin:
RET
UPPER_alloc
ENDP
mx_busca_hndl: PUSH
AH,0C0h AX
MOV
AL,0
INT
2Fh
; ------------ Reubicar programa residente a su dirección definitiva.
CMP
AL,0FFh
;
POP
AX
JNE
mx_si_hueco
INC
AH
JNZ
mx_busca_hndl
reubicar_prog
Se copia también el PSP.
PROC PUSH
DI
LEA
SI,ini_residente
MOV
CX,bytes_resid
mx_no_hueco:
STC RET
mx_si_hueco:
CLD
reubicar_prog
CLC RET
REP
MOVSB
XOR
SI,SI
XOR
DI,DI
MOV
CX,256
; ------------ Buscar un TSR por la interrupción Multiplex. A la
REP
MOVSB
;
entrada, DS:SI cadena de identificación del programa
POP
DI
;
(CX bytes) y ES:DI protocolo de búsqueda (normalmente
MOV
ES:[36h],ES
;
1492h:1992h). A la salida, si el TSR ya está instalado,
RET
;
CF=0 y ES:DI apunta a la cadena de identificación del
ENDP
;
mismo. Si no, CF=1 y ningún registro alterado.
mx_find_tsr
PROC
mx_get_handle
; nuevo segmento de la JFT
; ------------ Desviar vectores de interrupción a las nuevas rutinas.
ENDP
MOV
AH,0C0h
PUSH
AX
han sido instaladas está en DI. Por ello, CS ha de
PUSH
CX
;
desplazarse (100h-DI)/16 unidades atrás (DI se supone
PUSH
SI
;
múltiplo de 16). El segmento inicial es ES.
PUSH
DS
PUSH
ES
PUSH
DI
MOV
AL,0
PUSH
CX
INT
2Fh
POP
CX
CMP
AL,0FFh
JNE
mx_skip_hndl
;
Se tendrá en cuenta que está ensambladas para correr en
;
un offset inicial (100h) y que el offset real en que
;
activar_INTs
; desviar INT xx a DS:DX
ENDP
MOV
CLC
; CX vectores a desviar
PROC PUSH
CX
PUSH
DS
MOV
AX,100h
SUB
AX,DI
MOV
CL,4
SHR
AX,CL
MOV
CX,ES
; preservar DS para el retorno
; AX = 100h-DI
; AX = (100h-DI)/16
mx_rep_find:
CLD
; no hay TSR ahí
161
PROGRAMAS RESIDENTES
mx_skip_hndl:
mx_tsr_found:
ADD
SI,5
JMP
mx_ul_2f
PUSH
ES
PUSH
AX
DI
MOV
AH,0
POP
ES
SHL
AX,1
POP
DS
SHL
AX,1
POP
SI
DEC
AX
POP
CX
MOV
CS:mx_ul_tsroff,AX
POP
AX
MOV
CS:mx_ul_tsrseg,0 ; apuntar a tabla vectores
INC
AH
POP
AX
JNZ
mx_rep_find
PUSH
AX
STC
MOV
AH,35h
RET
INT
21h
POP
AX
PUSH
DI
REP
CMPSB
POP
DI
JE
mx_tsr_found
POP
; comparar identificación mx_ul_pasok: ; programa buscado hallado
ADD
SP,4
; «sacar» ES y DI de la pila
POP
DS
MOV
CL,4
POP
SI
SHR
BX,CL
POP
CX
MOV
DX,ES
POP
AX
ADD
DX,BX
MOV
AH,0C0h
CALL
mx_ul_tsrcv?
JNC
mx_ul_tsrcv
JMP
mx_ul_otro
PUSH
ES:[DI-16]
CLC mx_ul_masmx:
RET mx_find_tsr
ENDP
; ------------ Eliminar TSR del convenio si es posible. A la entrada,
mx_ul_tsrcv:
;
en AH se indica la entrada Multiplex; a la salida, CF=1
PUSH
ES:[DI-12]
;
si fue imposible y CF=0 si se pudo. Se corrompen todos
MOV
DI,ES:[DI-8]
;
los registros salvo los de segmento. En caso de fallo
MOV
CL,ES:[DI-1]
;
al desinstalar, AL devuelve el vector «culpable».
MOV
CH,0
CMP
AL,ES:[DI]
mx_ul_buscav: mx_unload
JE
mx_ul_usavect
PUSH
ES
ADD
DI,5
CALL
mx_ul_tsrcv?
LOOP
mx_ul_buscav
JNC
mx_ul_able
ADD
SP,4
POP
ES
JMP
mx_ul_otro
PROC
mx_ul_pasada:
; offset a la tabla de vectores
; número de vectores en CX
; este TSR usa vector analizado
; no lo usa
mx_ul_usavect: POP
CX
; tamaño del TSR
POP
BX
; segmento del TSR
XCHG
AH,AL
CMP
DX,BX
MOV
BP,AX
JB
mx_ul_otro
MOV
CX,2
ADD
BX,CX
PUSH
CX
CMP
DX,BX
LEA
SI,tabla_vectores
JA
mx_ul_otro
MOV
CL,ES:[SI-1]
PUSH
AX
MOV
CH,0
XOR
AL,AL
XCHG
AH,AL
CMP
AX,BP
POP
AX
JNE
mx_ul_chain
; no
POP
ES
; sí: ¡posible reponer vector!
POP
CX
; BP=entrada Multiplex del TSR
; siguiente pasada
; CX = nº vectores
AX
PUSH
AX
DEC
AL
PUSH
CX
MOV
AL,ES:[SI]
JNZ
mx_ul_pasok
CMP
CX,1
POP
BX
JNE
mx_ul_noult
PUSH
BX
MOV
AL,2Fh
PUSH
CX
LEA
SI,tabla_vectores
PUSH
ES
DEC
BX
mx_ul_busca2f: CMP
mx_ul_noult:
; ...TSR del convenio en ES:DI
AL,AL
mx_ul_masvect: POP
mx_ul_2f:
; INT xx en DX (aprox.)
XOR
RET mx_ul_able:
; vector en ES:BX
ES:[SI],AL
; pasada en curso
; vector en curso
; ¿último vector?
; ¿INT 2Fh?
; la INT xx no le apunta
; la INT xx le apunta
; ¿es el propio TSR?
JE
mx_ul_pasok
JNZ
mx_ul_norest
; no es la segunda pasada
ADD
SI,5
POP
ES
; segunda pasada...
JMP
mx_ul_busca2f
PUSH
ES
CMP
AL,2Fh
PUSH
DS
JNE
mx_ul_pasok
MOV
BX,CS:mx_ul_tsroff ; restaurar INT's
; ¿restaurar INT 2Fh?
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
MOV
DS,CS:mx_ul_tsrseg
CLI
mx_ul_chain:
mx_ul_otro:
JNE
mx_ul_ncvexit
MOV
[BX+1],CX
ADD
SP,4
MOV
CX,ES:[SI+3]
POP
AX
MOV
[BX+3],CX
DI
DS
POP
ES
POP
ES
POP
AX
POP
CX
STC
ADD
SI,5
DEC
CX
JZ
mx_unloadable
JMP
mx_ul_masvect
MOV
CS:mx_ul_tsroff,DI ; ES:DI almacena la dirección
MOV
CS:mx_ul_tsrseg,ES ; de la variable vector
MOV
DX,ES:[DI+1]
MOV
CL,4
SHR
DX,CL
MOV
CX,ES:[DI+3]
ADD
DX,CX
MOV
AH,0BFh
INC
AH
JZ
mx_ul_exitnok
JMP
mx_ul_masmx SP,6
; no más, ¡desinstal-ar/ado!
DW
0
mx_ul_tsrseg
DW
0
mx_unload
ENDP
; ------------ Imprimir cadena en DS:DX delimitada por un 0
MOV
BX,DX
MOV
AL,[BX]
AND
AL,AL
; a por otro TSR
JZ
fin_print
; ¡se acabaron!
MOV
DL,AL
MOV
AH,2
PUSH
BX
INT
21h
POP
BX
INC
BX
JMP
print_mas
XPOP
print_mas:
; equilibrar pila
; imposible desinstalar
mx_unloadable: POP
CX
DEC
CX
JZ
mx_ul_exitok
; desinstalado
JMP
mx_ul_pasada
; 1ª pasada exitosa: por la 2ª
TEST
ES:info_extra,111b
MOV
ES,ES:segmento_real ; segmento real del bloque
JZ
mx_ul_freeml
CMP
xms_ins,1
JNE
mx_ul_freeml
MOV
DX,ES
MOV
AH,11h
CALL
gestor_XMS
POP
ES
fin_print:
; ¿tipo de instalación?
; cargado en RAM convencional
; no hay controlador XMS (¿?)
; liberar memoria superior
CLC RET AH,49h
INT
21h
POP
ES
; liberar bloque de memoria ES:
CLC RET mx_ul_tsrcv?:
PROC XPUSH
STC
MOV
; CF=1
mx_ul_tsroff
print
; INT xx en DX (aprox.)
PUSH
AX
PUSH
ES
; ¿es TSR del convenio?...
PUSH
DI
MOV
DI,1492h
MOV
ES,DI
MOV
DI,1992h
INT
2Fh
CMP
AX,0FFFFh
JNE
mx_ul_ncvexit
CMP
WORD PTR ES:[DI-4],"#*"
; ...no es TSR del convenio
RET
; siguiente vector
ES
RET
; CF=0
RET mx_ul_ncvexit: POP
POP
POP
mx_ul_freeml:
WORD PTR ES:[DI-2],"*#"
CX,ES:[SI+1]
mx_ul_exitnok: ADD
mx_ul_exitok:
mx_ul_ncvexit
CMP
MOV
STI
mx_ul_norest:
JNE
RET print
ENDP
161
PROGRAMAS RESIDENTES
DB
": Instalación imposible.",13,10
*
DB
"
*
DB
"misma técnica.",13,10,0
; ********************************** ; * ; *
DATOS PARA LA INSTALACION
; *
nocabe_txt
Ya hay 64 programas residentes con la "
*
; **********************************
ON
EQU
1
; constantes booleanas
OFF
EQU
0
xms_ins
DB
0
gestor_XMS
LABEL DWORD
XMS_off
DW
0
XMS_seg
DW
0
alloc_strat
DW
0
; estrategia asignación (DOS 5)
umb_state
DB
0
; estado de bloques UMB (DOS 5)
; a 1 si presente controlador XMS
err_sintax_txt DB
13,10,"
- Parámetro(s) incorrecto(s).",0
err_tec_txt
DB
13,10,"
- Parámetro /S fuera de rango.",0
err_sintax_fin DB
13,10,"
"ayuda.",13,10,7,0
DB
13,10
DB
"
mal_ver_txt2
DB
" de este programa.",13,10,7,0
des_ok_txt
DB
" desinstalado.",13,10,0
des_no_ok_txt
DB
13,10,"
DB
"instalado después un programa"
DB
13,10,"
DB
"alguna interrupción común).",13,10,7,0
mal_ver_txt1
; dirección del controlador XMS
; dirección de la copia residente
- Error: ya está instalada la versión ",0
tsr_dir
LABEL DWORD
tsr_off
DW
0
tsr_seg
DW
0
memoria
DW
0
; párrafos que ocupará SCRCAP
offsets_ints
DW
6
; número de vectores interceptados
ayuda_txt
DB
8
; tabla de offsets de los vectores
DB 13,9,"
DW
ges_int08
; de interrupción interceptados
DB 13,10
DB
9
DB "
DW
ges_int09
DB "Valladolid.",13,10,10
DB
13h
DB 9,"
DW
ges_int13
DB 13,10,10
DB
21h
DB "
DW
ges_int21
DB "pantalla actual se",13,10
DB
28h
DB "
DW
ges_int28
DB "anchura hexadecimal",13,10
DB
2Fh
DB "
DW
ges_int2F
DB "partiendo de 00",13,10
param_ml
DB
0
; a 1 si se indicó parámetro /ML
DB "
param_s
DB
0
; a 1 si se indicó parámetro /S
DB "que se invoca la",13,10
param_t
DB
0
; a 1 si se indicó parámetro /T
DB "
param_u
DB
0
; a 1 si se indicó parámetro /U
DB "(más de 25 líneas",13,10
param_ayuda
DB
0
; a 1 si se indicaron parámetros /? /H ó ?
DB "
imp_desins_txt DB
13,10,"
DB
DB "
- Desinstalación imposible (se ha "
que no respeta el convenio y tiene "
- Programa aún no instalado: "
"imposible desinstalarlo.",13,10,0
LABEL BYTE SCRCAP 1.0 - Utilidad de captura de pantallas de texto."
(c) 1992 CiriSOFT,
(c) Grupo Universitario de Informática - "
SCRCAP [/ML] [/S=marcas] [/T=codigo de rastreo] [/U] [/?|H]"
Una vez instalado, al pulsar Alt-SysReq (Alt-PetSis) la "
salvará en disco con nombre SCRxx-nn.SCR, donde xx es la "
de la misma (en columnas) y nn el número de fichero; ya que, "
tras instalar el programa,
se crean sucesivamente cada vez "
utilidad. Se salvan también pantallas de texto no estándar "
u 80 columnas); las pantallas gráficas generan ficheros "
DB "inservibles. ; ------------ Texto
Ejecute SCRCAP /? para obtener "
DB
Lo que",13,10
se almacena en los ficheros es exactamente el contenido del "
DB "buffer de vídeo;",13,10 scrcap_txt
DB
13,10,"
SCRCAP 1.0",0
DB "
la captura va precedida y sucedida de un sonido de aviso "
DB "durante 1 segundo.",13,10,10 instalado_txt
DB
" instalado.",0
DB "
Por defecto se instala residente en memoria superior (si la "
DB "hay) de manera",13,10 ya_install_txt DB
" ya instalado.",0
DB "
automática,
sea cual sea la versión del sistema o el "
DB "controlador de memoria",13,10 - Pulse ",0
DB "
(incluso sin indicar DOS=UMB en el CONFIG del DOS 5.0): con "
act_teclas_txt DB
13,10,"
act_ctrl
DB
"Ctrl",0
DB "/ML se fuerza la",13,10
act_alt
DB
"Alt",0
DB "
act_shift_der
DB
"ShiftDer",0
DB "Kb).",13,10,10
act_shift_izq
DB
"ShiftIzq",0
DB "
act_c_txt
DB
"SysReq",0
DB "activación
act_otra_txt
DB
8," y la tecla elegida",0
DB "
act_fin_txt
DB
8," para activarlo.",13,10,0
DB "8-Alt); con /T puede",13,10 DB "
instalación en memoria convencional. Consumo: 2208 bytes (2,16 "
El parámetro /S permite elegir la combinación de teclas de " (se",13,10
obtiene sumando: 1-shift derecho, 2-shift izdo, 4-Ctrl, "
cambiarse opcionalmente la tecla de activación. Se puede "
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
DB "desinstalar con /U,",13,10 DB "
siendo a menudo posible incluso aunque no sea el último TSR "
DB "instalado.",13,10,0
fin_prog
EQU
$
scrcap
ENDS END
inicio
Para visualizar las pantallas capturadas puede utilizarse la utilidad SCRVER.C, que admite comodines para poder ver cualquier conjunto de ficheros. Con SCR2TXT.C se convierten las pantallas capturadas (de 40/80/94/100/120/132 ó 160 columnas) a modo texto: se suprimen los colores, se eliminan la mayoría de los códigos de control, se quitan los espacios en blanco al final de las líneas y se añaden retornos de carro para separarlas. Esto último provoca, en pantallas que ocupan justo las 80 columnas, que al emplear el TYPE del DOS las líneas queden separadas por una línea extra en blanco (si tuvieran 79 columnas o si se carga desde un editor de texto, no habrá problemas).
exit (1); }
/********************************************************************/ /*
*/
/* SCRVER 1.0
-
Utilidad para visualizar pantallas 80x25 y 40x25
buffer=MK_FP((peekb(0x40,0x49)==7 ? 0xB000: 0xB800), 0);
*/
/*
capturadas por SCRCAP. Borland C en modo "Large". */
/*
*/
fnsplit (argv[1], disco, direct, fich, ext); if (!*ext) strcpy (ext, ".*");
/********************************************************************/
fnmerge (ruta, disco, direct, fich, ext); ultimo=findfirst (ruta, &fichero, FA_ARCH|FA_HIDDEN|FA_RDONLY); if (ultimo) {
#include
printf("\nNombre de fichero incorrecto.\n"); exit(1); }
#include #include
while (!ultimo) {
#include
fnmerge (ruta, disco, direct, fichero.ff_name, "");
#include
if (fichero.ff_name[3]=='2') { _AX=1; __emit__(0xcd, 0x10); }
/* modo de 40x25 */
else { _AX=3; __emit__(0xcd, 0x10); }
void main(int argc, char **argv)
/* modo 80x25 */
if ((handle=open(ruta, O_RDONLY | O_BINARY, 0)) == -1) {
{ int
handle, ultimo;
void
far *buffer;
printf("Error al abrir fichero de entrada.\n"); exit(1); } read(handle, buffer, 30000); close(handle); ultimo=(getch()==27) || findnext (&fichero);
struct ffblk fichero; char
}
disco[MAXDRIVE], direct[MAXDIR], fich[MAXFILE], ext[MAXEXT], ruta[MAXPATH];
_AX=3; __emit__(0xcd, 0x10);
/* modo 80x25 */
}
if (argc<2) { printf("\nIndique el(los) fichero(s) a visualizar.\n");
/********************************************************************/
#include
/*
#include
/* SCR2TXT 1.0
*/ -
/* /*
Utilidad para convertir pantallas capturadas por */ SCRCAP a modo texto. Borland C en modo "Large".
#include
*/ */
/********************************************************************/
void main(int argc, char **argv) { int
handler, handlew, ultimo, ancho, ih, il;
#include
struct ffblk fichero;
#include
char
buffer[512], *p,
161
PROGRAMAS RESIDENTES
disco[MAXDRIVE], direct[MAXDIR],
fnmerge (rutar, disco, direct, fichero.ff_name, "");
fich[MAXFILE], ext[MAXEXT], rutar[MAXPATH], rutaw[MAXPATH];
strcpy (rutaw, rutar); p=rutaw; while ((*p) && (*p!='.')) p++; *(p-5)=*(p-4)=*(p-3)='0'; *(p+1)=*(p+3)='T'; *(p+2)='X'; *(p+4)=0;
printf("\n"); ih=fichero.ff_name[3]-'0'; if (ih>9) ih-='A'-'9'-1;
if (argc<2) {
il=fichero.ff_name[4]-'0'; if (il>9) il-='A'-'9'-1;
printf("Indique el(los) fichero(s) a convertir.\n"); exit (1); }
ancho=(ih<<4)+il; if ((ancho!=40) && (ancho!=80) && (ancho!=94) && (ancho!=100) &&
fnsplit (argv[1], disco, direct, fich, ext);
(ancho!=114) && (ancho!=120) && (ancho!=132) && (ancho!=160)) {
if (!*ext) strcpy (ext, ".*"); fnmerge (rutar, disco, direct, fich, ext);
printf("
ultimo=findfirst (rutar, &fichero, FA_ARCH|FA_HIDDEN|FA_RDONLY);
rutar); exit(1); }
- Error: el fichero %s no es del tipo SCRxx-nn.SCR\n",
if (ultimo) { if ((handler=open(rutar, O_RDONLY | O_BINARY, 0)) == -1) {
printf("Nombre de fichero incorrecto.\n"); exit(1); }
printf("Error al abrir fichero de entrada.\n"); exit(1); } if ((handlew=_creat(rutaw, 0)) == -1) {
while (!ultimo) {
printf("Error al abrir fichero de salida.\n"); exit(1); }
printf("Procesando %s\n", rutar);
while (read(handler, buffer, ancho<<1)==ancho<<1) { for (il = (ancho<<1)-2; (il>=0) && buffer[il]==' '; il-=2); p=buffer; for (ih=0; ih<=il; ih+=2) { if (((*p>6) && (*p<32)) || !*p) *p=' ';
/* carácter control */
write (handlew, p, 1); p+=2; } p=buffer; *p++=0x0D; *p++=0x0A; *p=0; write (handlew, buffer, 2); }
close(handler); close (handlew); ultimo=findnext (&fichero); } }
10.12. - PROGRAMAS RESIDENTES INVOCABLES EN MODOS GRÁFICOS. La mayoría de los programas residentes prefieren operar con pantallas de texto: ocupan menos memoria, son totalmente estándar y más rápidas. En la práctica, la dificultad asociada al proceso de preservar el contenido de una pantalla gráfica y después restaurarla lleva a muchos programas residentes a no dejarse activar cuando la pantalla está en modo gráfico. Sin embargo, existe una técnica sencilla que permite simplificar este proceso, siendo operativa en todos los modos de la EGA y VGA estándar, aunque presenta alguna dificultad en ciertos modos de la VGA. 10.12.1 - CASO GENERAL. En los modos estándar de IBM (y en general también en los no estándar) cuando se solicita a la BIOS que establezca el modo de vídeo (véanse las funciones de la BIOS en los apéndices) si el bit más significativo del modo se pone a 1, al cambiar de modo no se limpia la pantalla. Esta característica está disponible sólo en máquinas con tarjeta EGA o VGA (tanto XT como AT). Se trata de una posibilidad muy interesante, que permite a los programas residentes activar momentáneamente una pantalla de texto, preservar el fragmento de la misma que van a emplear y, al final, restaurarlo y volver al modo gráfico como si no hubiera sucedido nada, sin necesidad de preservar ni restaurar zonas gráficas. También habrán de preservar la posición inicial del cursor y la página de vídeo activa inicialmente (que habrán de restaurar junto con el modo de vídeo), así como las paletas de la EGA y VGA, tareas éstas que puede simplificar la BIOS.
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Por ejemplo: si la pantalla estaba en modo 12h (VGA 640x480 con 16 colores) se puede activar el modo 83h (el 3 con el bit 7 activo) de texto de 80x25 y, cuando halla que restaurarla, activar el modo 92h (el 12h con el bit 7 activo). Evidentemente, después habrá que engañar de alguna manera a la BIOS para que crea que la pantalla está en modo 12h y no 92h (sutil diferencia, ¿no?) y ello se consigue borrando el bit más significativo de la posición 40h:87h (la variable de la BIOS 40h:49h indica siempre el número de modo de pantalla con el bit más significativo borrado: este bit se almacena separadamente en 40h:87h). Esta operación es segura, ya que la diferencia entre el modo 12h y el 92h es sólo a nivel de software y no de hardware. Un programa residente elegante, además, se tomará la molestia de dejar activo el bit de 40h:87h si así lo estaba al principio, antes de restaurar el modo gráfico (poco probable, pero posible -sobre todo cuando el usuario activa más de un programa residente de manera simultánea-). 10.12.2 - CASO DEL MODO 13H DE LA VGA Y MODOS SUPERVGA. Esta técnica presenta, sin embargo, una ligera complicación al trabajar en el modo 13h de la VGA (320x200 con 256 colores) o en la mayoría de los modos SuperVGA. El problema consiste en que, al pasar a modo texto, la BIOS define el juego de caracteres -que en la EGA/VGA es totalmente programable- utilizando una cierta porción de la memoria de vídeo de la tarjeta. Por desgracia, esa porción de la memoria de la tarjeta gráfica es parte de la pantalla en el modo 13h y en los modos SuperVGA. La solución no es muy complicada, aunque sí un poco engorrosa. Ante todo, recordar que esto sólo es necesario en modos de pantalla avanzados o en el 13h. Una posible solución consiste en preservar la zona que va a ser manchada (8 Kb) en un buffer, pasar a modo texto y, antes de volver al modo gráfico, redefinir el juego de caracteres de texto de tal manera que al volver a modo gráfico ya esté restaurada la zona manchada. Este orden de operaciones no es caprichoso y lo he elegido para reducir los accesos al hardware, como se verá. El problema principal radica en el hecho de que la arquitectura de la pantalla en los modos gráficos y de texto varía de manera espectacular. Por ello, no hay un algoritmo sencillo para acceder a la zona de memoria de gráficos que hay que preservar. Para no desarrollar complicadas rutinas -por si fuera poco, una para cada modo gráfico- es más cómodo programar el controlador de gráficos para configurar de manera cómoda la memoria de vídeo y preservar sin problemas los 8 Kb deseados. Después, no hace falta restaurar el estado de ningún controlador de vídeo, ya que la BIOS lo reprogramará correctamente al pasar a modo texto. Por último, y estando aún en modo texto, se redefinirá el juego de caracteres con los 8 Kb preservados. Como inmediatamente después se vuelve al modo gráfico, el usuario no notará la basura que aparezca en la pantalla durante breves instantes y, de nuevo, la BIOS reprogramará adecuadamente el controlador de gráficos. El siguiente ejemplo práctico parte de la suposición de que nos encontramos en el modo 13h: CALL
def_car_on
; habilitar acceso a tabla de caracteres
CALL
preservar8k
; guardar 8 Kb de A000:0000 en un buffer
MOV
AX,83h
INT
10h
; pasar a modo texto 80x25 ; ... operar en modo texto ...
CALL
def_car_on
CALL
restaurar8k
; habilitar acceso a tabla de caracteres ; copiar el buffer de 8 Kb en A000:0000
MOV
AX,93h
; 13h + 80h
INT
10h
; restaurar de nuevo el modo gráfico
Las rutinas preservar8k y restaurar8k son tan obvias que, evidentemente, no las comentaré. Sin embargo, la rutina que prepara el sistema de vídeo de tal manera que se pueda redefinir el juego de caracteres de texto, requiere conocimientos acerca de la arquitectura de las tarjetas gráficas EGA y VGA a bajo nivel. Esta información puede obtenerse en libros especializados sobre gráficos (consúltese la bibliografía) aunque a continuación expongo el listado de def_car_on; eso sí, sin entrar en detalles técnicos acerca de su funcionamiento: def_car_on
PROC MOV
DX,3C4h ; puerto del secuenciador
LEA
SI,car_on
MOV
CX,4
; códigos a enviarle
161
PROGRAMAS RESIDENTES
CLD CLI def_on_1:
; precauciones
LODSW OUT
DX,AX
LOOP
def_on_1
STI
def_on_2:
; programar registro ; no más precauciones
MOV
DL,0CEh ; 3CEh = puerto del controlador de gráficos
MOV
CX,3
LODSW OUT
DX,AX
LOOP
def_on_2
; programarlo
RET car_on
DW
def_car_on
ENDP
100h, 402h, 704h, 300h, 204h, 5, 6
; datos
10.12.3 - ALGUNOS PROBLEMAS. En la aplicación práctica de las rutinas expuestas se han detectado algunos problemas de compatibilidad con algunas tarjetas. El más grave se produjo con una OAK SuperVGA: en algunos modos de 800 y 1024 puntos, se colgaba el ordenador al ejecutar def_car_on. La solución adoptada consistió en dar un paso intermedio: antes de llamar a def_car_on se puede poner la pantalla en un modo no conflictivo y que sea gráfico para evitar que la BIOS defina el juego de caracteres (como el 13h+80h=93h); en este modo sí se puede ejecutar def_car_on, antes de pasar al modo texto. 10.12.4 - CONSIDERACIONES FINALES. El método propuesto es ciertamente sencillo, aunque se complique un poco más en algunos modos de la VGA. Tiene requerimientos (como el buffer de 8 Kb) que no están quizá al alcance de los programas residentes menos avanzados. Los más avanzados pueden grabar los 8 Kb en disco duro, si la máquina está dotada del mismo, así como toda la memoria de pantalla CGA (unos modestos 16 Kb) en las máquinas que no están dotadas de EGA o VGA y no pueden conmutar el modo de pantalla sin borrar la misma. Las máquinas que no tengan disco duro aumentarán el consumo de memoria del programa residente en 8/16 Kb, aunque ¡peor sería tener que preservar hasta 1 Mb de memoria de vídeo!. El problema está en las tarjetas no compatibles VGA: mucho cuidado al utilizar la rutina def_car_on (hay que detectar antes la presencia de una auténtica EGA/VGA, ¡no vale la MCGA!). En MCGA no se puede aplicar def_car_on en el modo 13h, aunque afortunadamente esta tarjeta está poco extendida (sólo acompaña al PS/2-30, en sus primeros modelos un compatible XT); los más perfeccionistas siempre pueden consultar bibliografía especializada en gráficos para tratar de manera especial este adaptador de vídeo, aunque sería incluso más recomendable ocuparse antes de la Hércules. Otro premio reservado para estos perfeccionistas será la posibilidad de conmutar los modos de pantalla accediendo al hardware y sin apoyo de la BIOS, para que no borre la pantalla en las CGA. Téngase en cuenta que esta operación sería mucho más delicada en las EGA y VGA (es más difícil restaurar todos los parámetros hardware del modo gráfico activo inicialmente) en las que además habría que definir un juego de caracteres de texto. Por cierto, el estándar VESA posee también funciones para preservar y restaurar el estado del adaptador de vídeo; el lector podría encontrar interesante documentarse acerca de ello. 10.13. - PROGRAMAS RESIDENTES EN ENTORNO WINDOWS 3. El tema de los programas residentes de DOS funcionando bajo Windows no es demasiado importante ya que, en teoría, desde dentro de Windows no es necesario tener instalados programas residentes, al tratarse de un entorno multitarea que permite tener varios programas activos en pantalla a la vez. Sin embargo, puede ser interesante en ocasiones crear programas residentes que también operen bajo Windows, de cara a no tener que desarrollar una versión específica no residente para este entorno. Un problema importante de los programas residentes consiste en la dificultad para leer el teclado. La
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
razón es que Windows reemplaza totalmente al controlador del DOS, anulando los TSR que se activan por teclado. En los AT se puede leer el puerto del teclado en cualquier momento (fuera de la INT 9) aunque no es recomendable porque la práctica reiterada de este método provoca anomalías en el mismo (tales como aparición de números en los cursores, estado de Shift que se engancha, etc.) debido a las limitaciones del hardware. Un método más recomendable, aunque menos potente, consiste en comprobar las variables de la BIOS que indican el estado de mayúsculas, bloque numérico, shift, ... ya que estas variables son correctamente actualizadas desde dentro de Windows. El único problema es la limitación de combinaciones posibles que se pueden realizar con estas teclas, de cara a permitir la convivencia de varios programas residentes (problema que se puede solventar permitiendo al usuario elegir las teclas de activación). El otro problema está relacionado con la multitarea de Windows. Si se abren varios procesos DOS desde este entorno y se activa el programa residente en más de uno de ellos, pueden aparecer problemas de reentrada (la segunda ejecución estropeará los datos de la primera). La solución más sencilla consiste en no permitir la invocación del programa residente desde más de una tarea; sin embargo, en algunos TSR (tales como utilidades de macros de teclado, etc.) esto supone una grave e intolerable restricción. Otra solución sencilla consiste en obligar al usuario a instalar el TSR en cada sesión de DOS abierta, con lo que todo el entorno de operación será local a dicha sesión. Para los casos en que no sea recomendable esto último, se puede quemar el último y más efectivo cartucho: comunicar el TSR con el conmutador de tareas de Windows para emplear memoria instantánea. El único inconveniente es que Windows sólo facilita memoria instantánea en el modo extendido 386, no en el modo estándar ni -en el caso de la versión 3.0- en el real. Sin embargo, con la versión 3.1 de Windows, en el modo estándar se puede emplear el conmutador de tareas del DOS 5.0, que es el que utiliza dicho modo. No deja de ser una pena tener que utilizar un método diferente para el modo estándar que para el extendido, aunque la recompensa para quien implemente soporte en sus TSR para los dos métodos es que les hará compatibles también con el conmutador de tareas del MS-DOS 5.0. Se puede interceptar el arranque de Windows y comprobar si lo hace en modo real, en cuyo caso se puede abortar su ejecución y emitir un mensaje de error para solicitar al usuario que no desinstale el TSR antes de entrar en ese modo de Windows. Cuando Windows arranca, llama a la INT 2Fh con AX=1605h: un TSR puede interceptar esta llamada (como en cualquier otra interrupción, llamando primero al controlador previo) y comprobar si el bit 0 de DX está a cero (en ese caso se estará ejecutando en modo extendido): si se desea abortar la ejecución de Windows bastará cargar un valor distinto de 0 en CX antes de retornar. Si el TSR necesita áreas de datos locales a cada sesión en el modo extendido, puede indicárselo a Windows con un puntero a un área de datos denominado SWSTARTUPINFO en ES:BX. Para ello, y teniendo en cuenta que puede haber varios TSR que intercepten las llamadas a la INT 2Fh con AX=1605h, este área ha sido diseñada para almacenar una cadena de referencias entre todos ellos; por ello es preciso almacenar primero el ES:BX inicial de la rutina en dicha estructura y cargar ES:BX apuntándola antes de retornar. El formato de SWSTARTUPINFO es el siguiente: DW
3
; versión de la estructura
DD
?
; puntero a la próxima estructura SWSTARTUPINFO (ES:BX inicial)
DD
0
; puntero al nombre ASCIIZ del dispositivo virtual (ó 0)
DD
0
; datos de referencia del dispositivo virtual (si tiene nombre)
DD
?
; puntero a la tabla de registros de datos locales (ó 0)
El formato de la tabla de registros de datos locales, que define las estructuras de datos que serán locales a cada sesión, es el siguiente: DD
?
; dirección de memoria de la estructura
DW
?
; tamaño de la estructura
.
.
.
.
.
.
DD
0
; estructura NULL
DW
0
; (fin de lista)
En los momentos críticos en que el TSR deba evitar una conmutación de tareas, puede emplear las
161
PROGRAMAS RESIDENTES
funciones BeginCriticalSection (llamar a INT 2Fh con AX=1681h) y EndCriticalSection (llamar a INT 2Fh con AX=1682h); el TSR debe estar poco tiempo en fase crítica para no ralentizar Windows. Para detectar la presencia del conmutador de tareas del MS-DOS 5.0 se debe llamar a la INT 2Fh con AX=4B02h: si a la vuelta AX es 0, significa que está cargado y ES:DI apunta a la rutina de servicio del mismo, que pone varias funciones a disposición de los TSR: los TSR deberán ejecutar la función AX=4 (Conectar a la cadena de Notificación) al instalarse en memoria y la función AX=5 (Desconectar de la Cadena de Notificación) al ser desinstalados, para informar al conmutador. Una vez enganchado, el TSR será llamado por el conmutador de tareas para ser informado de todo lo interesante que suceda (de cosas tales como la creación y destrucción de sesiones, suspensión del conmutador, etc.) por medio de la ejecución de la rutina de notificación del mismo, pudiendo el TSR permitir o no, por ejemplo, la suspensión de la sesión... el aviso de inicio de sesión es fundamental para los TSR que tienen áreas de datos temporales que inicializar al comienzo de cada sesión. El procedimiento general lo inicia el conmutador de tareas llamando a la INT 2Fh con AX=4B01h: los TSR serán invocados unos tras otros (pasándose mutuamente el control). Para gestionar esto existe una estructura de datos denominada SWCALLBACKINFO (apuntada por ES:BX al llamar a INT 2Fh con AX=4B01h): DD
?
; puntero a la estructura SWCALLBACKINFO anterior
DD
?
; puntero a la rutina de notificación del TSR
DD
?
; área reservada
DD
?
; puntero a la lista de estructuras SWAPINFO
La lista de estructuras SWAPINFO tiene a su vez el siguiente formato: DW
10
DW
?; identificador del API (1-NETBIOS, 2-802.2, 3-TCP/IP, 4-Tuberías LanManager,
; longitud de la estructura
DW
?
; número de la mayor versión del API soportada
DW
?
; número de la menor versión del API soportada
DW
?; nivel de soporte: 1-mínimo (el TSR impide la conmutación de la tarea incluso
5-NetWare IPX)
tras finalizar sus funciones), 2-soporte a nivel API (el TSR impide la conmutación
de
tareas
si
las
peticiones
son
importantes),
3-
Compatibilidad de conmutación (se permite conmutar de tarea incluso con peticiones
importantes,
aunque
algunas
podrían
fallar),
4-Sin
compatibilidad (se permite siempre la conmutación).
Cuando el conmutador de tareas arranca, ejecuta una INT 2Fh con AX=4D05h para tomar nota de los bloques de datos locales a cada sesión, llamada que los TSR deberán detectar del mismo modo que cuando comprobaban la ejecución de Windows en modo extendido: la estructura de datos es además, por fortuna, la misma en ambos casos. Las funciones que debe soportar la rutina de notificación, apuntada por la estructura SWCALLBACKINFO, son las siguientes: 0000h inicialización del conmutador Devuelve: AX = 0000h si permitido = no cero si no permitir iniciar el conmutador 0001h pregunta de suspensión del conmutador BX = Identificación de sesión Devuelve: AX = 0000h si permitir conmutación (el TSR no está en región crítica) = 0001h si no 0002h suspensión del conmutador BX = Identificación de sesión interrupciones inhibidas Devuelve: AX = 0000h si permitido conmutar de sesión = 0001h si no 0003h activando conmutador BX = Identificación de sesión
161
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
CX = banderines de estado de la sesión bit 0: activo si primera activación de la sesión bits 1-15: reservado (0) interrupciones inhibidas Devuelve: AX = 0000h 0004h sesión activa del conmutador BX = Identificación de sesión CX = banderines de estado de la sesión bit 0: activo si primera activación de la sesión bits 1-15: reservado (0) Devuelve: AX = 0000h 0005h crear sesión del conmutador BX = Identificación de sesión DEVUELVE: AX = 0000h si permitido = 0001h si no
161
PROGRAMAS RESIDENTES
0006h destruir sesión BX = Identificación de sesión Devuelve: AX = 0000h 0007h salida del conmutador BX = banderines bit 0: activo si el conmutador que llama es el único cargado bits 1-15: reservados (0) Devuelve: AX = 0000h
203
CONTROLADORES DE DISPOSITIVOS
Capítulo XI: CONTROLADORES DE DISPOSITIVO
11.1. - INTRODUCCIÓN. Los controladores de dispositivo (device drivers en inglés) son programas añadidos al núcleo del sistema operativo, concebidos inicialmente para gestionar periféricos y dispositivos especiales. Los controladores de dispositivo pueden ser de dos tipos: orientados a caracteres (tales como los dispositivos NUL, AUX, PRN, etc. del sistema) o bien orientados a bloques, constituyendo las conocidas unidades de disco. La diferencia fundamental entre ambos tipos de controladores es que los primeros reciben o envían la información carácter a carácter; en cambio, los controladores de dispositivo de bloques procesan, como su propio nombre indica, bloques de cierta longitud en bytes (sectores). Los controladores de dispositivo, aparecidos con el DOS 2.0, permiten añadir nuevos componentes al ordenador sin necesidad de rediseñar el sistema operativo. Los controladores de dispositivo han sido tradicionalmente programas binarios puros, similares a los COM aunque ensamblados con un ORG 0, a los que se les colocaba una extensión SYS. Sin embargo, no hay razón para que ello sea así ya que un controlador de dispositivo puede estar incluido dentro de un programa EXE, con la condición de que el código del controlador sea el primer segmento de dicho programa. El EMM386.EXE del MS-DOS 5.0 sorprendió a más de uno en su día, ya que llamaba la atención observar cómo se podía cargar con DEVICE: lo cierto es que esto es factible incluso desde el DOS 2.0 (pese a lo que pueda indicar algún libro), pero ha sido mantenido casi en secreto. Actualmente es relativamente frecuente encontrar programas de este tipo. La ventaja de un controlador de dispositivo de tipo EXE es que puede ser ejecutado desde el DOS para modificar sus condiciones de operación, sin complicar su uso por parte del usuario con otro programa adicional. Además, un controlador de dispositivo EXE puede superar el límite de los 64 Kb, ya que el DOS se encarga de relocalizar las referencias absolutas a segmentos como en cualquier programa EXE ordinario. Por cierto, el RAMDRIVE.SYS de WINDOWS 3.1 (no el de MS-DOS 5.0) y el VDISK.SYS de DRDOS 6.0 son realmente programas EXE, aunque renombrados a SYS (aviso: no recomiendo a nadie ponerles extensión EXE y ejecutarlos después). 11.2.- ENCABEZAMIENTO Y PALABRA DE ATRIBUTOS. Todo controlador de dispositivo de bloques comienza con una cabecera estándar, mostrada a continuación: ┌──────────────────────────────────────────────────────────────────────────────────────┐ │
CABECERA DEL CONTROLADOR DE DISPOSITIVO DE BLOQUES
│
├──────────────────────────────────────────────────────────────────────────────────────┤ │ offset
0
DD 0FFFFFFFFh
; doble palabra de valor -1
│
│ offset
4
DW 0
; palabra de atributos (ejemplo arbitrario)
│
│ offset
6
DW estrategia
; desplazamiento de la rutina de estrategia
│
│ offset
8
DW interrupcion
; desplazamiento de la rutina de interrupción
│
│ offset 10
DB 1
; número de discos definidos: 1 por ejemplo
│
│ offset 11
DB 7 DUP (0)
; 7 bytes no usados
│
└──────────────────────────────────────────────────────────────────────────────────────┘
Al principio, una doble palabra con el valor 0FFFFFFFFh (-1 en complemento a 2) será modificada posteriormente por el DOS para enlazar el controlador de dispositivo con los demás que haya en el sistema, formando una cadena. No fue una ocurrencia muy feliz elegir precisamente ese valor inicial como obligatorio
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
para la copia en disco, dado que la instrucción de código de operación 0FFFFh es ilegal y bloquea la CPU si es ejecutada. Esto significa que un controlador de dispositivo binario puro no puede ser renombrado a COM y ejecutado también desde el DOS (habrá de ser necesariamente de tipo EXE). A continuación, tras esta doble palabra viene una palabra de atributos, cuyo bit más significativo está borrado en los dispositivos de bloques para diferenciarlos de los dispositivos de caracteres. Tras ello, aparecen los offsets a las rutinas de estrategia e interrupción, únicas de las que consta el controlador. Por último, un byte indica cuántas nuevas unidades de disco se definen y detrás hay 7 bytes reservados -más bien no utilizados-. ┌──────────────────────────────────────────────────────────────────────────────────────┐ │
PALABRA DE ATRIBUTOS DEL CONTROLADOR DE DISPOSITIVO DE BLOQUES
│
├──────────────────────────────────────────────────────────────────────────────────────┤ │ bit 15:
borrado para indicar dispositivo de bloques
│
│ bit 14:
activo si se soporta IOCTL
│
│ bit 13:
activo para indicar disco de formato no-IBM
│
│ bit 12:
reservado
│
│ bit 11:
en DOS 3+ activo si soportadas órdenes OPEN/CLOSE y REMOVE
│
│ bit 10:
reservados
│
│ bit 9:
no documentado. Al parecer, el DRIVER.SYS del DOS 3.3 lo emplea para
│
│
indicar que no está permitida una E/S directa en las unidades «nuevas»
│
│ bit 8:
no documentado. El DRIVER.SYS del DOS 3.3 lo pone activo para las
│
│
unidades «nuevas»
│
│ bit 7:
en DOS 5+ activo si soportada orden 19h (CHECK GENERIC IOCTL SUPPORT)
│
│ bit 6:
en DOS 3.2+ activo si soportada orden 13h (GENERIC IOCTL)
│
│ bits 5-2: reservados
│
│ bit 1:
activo si el driver soporta direccionamientos de sector de 32 bits
│
│
(unidades de más de 65536 sectores y, por ende, más de 32 Mb).
│
│ bit 0:
reservado
│
└──────────────────────────────────────────────────────────────────────────────────────┘
En la palabra de atributos, el bit 15 indicaba si el dispositivo es de bloques o caracteres: en este último caso, la cabecera del controlador de dispositivo cambia ligeramente para indicar cuál es el nombre del dispositivo: ┌──────────────────────────────────────────────────────────────────────────────────────┐ │
CABECERA DEL CONTROLADOR DE DISPOSITIVO DE CARACTERES
│
├──────────────────────────────────────────────────────────────────────────────────────┤ │ offset
0
DD 0FFFFFFFFh
; doble palabra de valor -1
│
│ offset
4
DW 8000h
; palabra de atributos (ejemplo arbitrario)
│
│ offset
6
DW estrategia
; desplazamiento de la rutina de estrategia
│
│ offset
8
DW interrupcion
; desplazamiento de la rutina de interrupción
│
DB "AUX
; nombre del dispositivo (8 caracteres)
│
│ offset 10
"
└──────────────────────────────────────────────────────────────────────────────────────┘
Aunque en el ejemplo aparece AUX, ello es un ejemplo de lo que no se debe hacer, a no ser que sea lo que realmente se desea hacer (se está creando un dispositivo AUX que ya existe, con lo que se sobrescribe y anula el puerto serie original). En general, además de los nombres de los dispositivos del sistema, no deberían utilizarse los que crean ciertos programas (como el EMMXXXX0 del controlador EMS, etc.). Conviene decir aquí que muchos de los controladores de dispositivo de caracteres instalados en el ordenador no lo son tal realmente, sino que se trata de simples programas residentes que se limitan a dar error a quien intenta acceder a ellos (pruebe el lector a ejecutar la orden COPY *.* EMMXXXX0: con el controlador de memoria expandida instalado) aunque algunos implementan ciertas funciones vía IOCTL. La palabra de atributos del controlador de dispositivo de caracteres también cambia respecto al de bloques, pero sustancialmente: ┌──────────────────────────────────────────────────────────────────────────────────────┐ │
PALABRA DE ATRIBUTOS DEL CONTROLADOR DE DISPOSITIVO DE CARACTERES
│
├──────────────────────────────────────────────────────────────────────────────────────┤
203
CONTROLADORES DE DISPOSITIVOS
│
bit 15:
activo para indicar dispositivo de caracteres
│
│
bit 14:
activo si se soporta IOCTL
│
│
bit 13:
en DOS 3+ activo si se soporta orden 10h (OUTPUT UNTIL BUSY)
│
│
bit 12:
reservado
│
│
bit 11:
en DOS 3+ activo si soportadas órdenes OPEN/CLOSE y REMOVE)
│
│
bits 10-8: reservados
│
│
bit 7:
en DOS 5+ activo si soportada orden 19h (CHECK GENERIC IOCTL SUPPORT)
│
│
bit 6:
en DOS 3.2+ activo si soportada orden 13h (GENERIC IOCTL)
│
│
bit 5:
reservado
│
│
bit 4:
activo si el dispositivo es «especial» y utiliza la INT 29h (llamada
│
por el DOS para imprimir e carácter ubicado en AL).
│
activo si es el dispositivo CLOCK$ (CLOCK en MS-DOS 2.X y anteriores)
│
│
Este dispositivo poco conocido es útil para consultar o establecer en
│
│
cualquier momento la hora del sistema con la siguiente secuencia de 6
│
│
bytes:
DW dias_transcurridos_desde_1980
│
│
DB minutos
│
│
DB horas
│
│
DB centésimas de segundo
│
│
DB segundos
│
│ │
bit 3:
│
bit 2:
activo si es el dispositivo NUL
│
│
bit 1:
activo si es el dispositivo de salida estándar
│
│
bit 1:
activo si es el dispositivo de entrada estándar
│
└──────────────────────────────────────────────────────────────────────────────────────┘
11.3. - RUTINAS DE ESTRATEGIA E INTERRUPCIÓN. Cuando el DOS va a acceder a un dispositivo (debido a una petición de un programa de usuario) ejecuta, de manera secuencial, las rutinas de estrategia e interrupción, que son de tipo FAR. Hay que recordar que el paso del MS-DOS 1.0 al 2.0 supuso una emigración de la filosofía del CP/M a la del UNIX. La razón de la existencia separada de las rutinas de estrategia e interrupción se inspira en la filosofía de diseño del UNIX y su arquitectura multitarea, aunque para el DOS hubiera sido suficiente una sola rutina. De hecho, la rutina de estrategia tiene como única misión recoger la dirección de la cabecera de petición de solicitud que el DOS envía al driver, en ES:BX. Las 3 líneas de código siguientes constituyen una rutina de estrategia, ya que son prácticamente idénticas en todos los controladores de dispositivo: ┌──────────────────────────────────────────────────────────────────────────────────────┐ │
RUTINA DE ESTRATEGIA
│
├──────────────────────────────────────────────────────────────────────────────────────┤ │
PROC
FAR
│
MOV
CS:pcab_pet_desp,BX
│
MOV
CS:pcab_pet_segm,ES
│
RET
│
estrategia
estrategia
; de tipo FAR
│ │ │ │ │
ENDP
│
│
│
pcab_peticion
LABEL DWORD
│
│
pcab_pet_desp
DW
0
│
│
pcab_pet_segm
DW
0
│
└──────────────────────────────────────────────────────────────────────────────────────┘
¿Para qué sirve la cabecera de petición de solicitud?: sencillamente, es un área de datos que el DOS utiliza para comunicarse con el controlador de dispositivo. Por medio de este área se envían las órdenes y los parámetros que el dispositivo soporta, y se recogen ciertos resultados. La rutina de interrupción del dispositivo, además de preservar todos los registros que va a alterar para restaurarlos al final, se encarga de consultar la dirección de la cabecera de petición de solicitud que almacenó la rutina de estrategia y comprobar qué le está pidiendo el DOS. No es realmente una rutina de interrupción ya que retorna con RETF, en vez de con IRET, por lo que nunca podrá ser invocada por una interrupción hardware. Aunque según la orden a procesar el tamaño de la cabecera de petición de solicitud puede variar, los primeros 13 bytes son:
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
┌───────────────────────────────────────────────────────────────────────────────────────┐ │
CABECERA DE PETICIÓN DE SOLICITUD (13 PRIMEROS BYTES) COMÚN A TODAS LAS ÓRDENES
│
├───────────────────────────────────────────────────────────────────────────────────────┤ │
offset
0
DB longitud_bloque
; longitud total de la cabecera
│
│
offset
1
DB num_disco
; disco implicado (sólo en disp. bloques)
│
│
offset
2
DB orden
; orden solicitada por el sistema
│
│
offset
3
DW palabra_estado
; donde devolver la palabra de estado
│
│
offset
5
DD pun_dos
; apuntador usado por el DOS
│
│
offset
9
DD encadenamiento
; usado por el DOS para encadenar
│
└───────────────────────────────────────────────────────────────────────────────────────┘
En general, la rutina de interrupción suele multiplicar por dos el número de la orden (almacenada en el offset 2 de la cabecera de petición), para así acceder indexadamente a una tabla de palabras que contiene los desplazamientos a las rutinas que procesan las diversas órdenes: aunque esto no ha de ser necesariamente así, casi todos los controladores de dispositivo se comportan de esta manera. 11.4. - ORDENES A SOPORTAR POR EL CONTROLADOR DE DISPOSITIVO. ┌──────────────────────────────────────────────────────────────────────┐ │
00h
INIT
│
│
01h
MEDIA CHECK (dispositivos de bloque)
│
│
02h
BUILD BPB (dispositivos de bloque)
│
│
03h
IOCTL INPUT
│
│
04h
INPUT
│
│
05h
NONDESTRUCTIVE INPUT, NO WAIT (dispositivos de caracteres)
│
│
06h
INPUT STATUS (dispositivos de caracteres)
│
│
07h
INPUT FLUSH (dispositivos de caracteres)
│
│
08h
OUTPUT
│
│
09h
OUTPUT WITH VERIFY
│
│
0Ah
OUTPUT STATUS (dispositivos de caracteres)
│
│
0Bh
OUTPUT FLUSH (dispositivos de caracteres)
│
│
0Ch
IOCTL OUTPUT
│
│
0Dh
(DOS 3+) DEVICE OPEN
│
│
0Eh
(DOS 3+) DEVICE CLOSE
│
│
0Fh
(DOS 3+) REMOVABLE MEDIA (dispositivos de bloques)
│
│
10h
(DOS 3+) OUTPUT UNTIL BUSY (dispositivos de caracteres)
│
│ 11h-12h │
13h
│
no usada
(DOS 3.2+) GENERIC IOCTL
│ 14h-16h
no usadas
│ │
│
17h
(DOS 3.2+) GET LOGICAL DEVICE
│
│
18h
(DOS 3.2+) SET LOGICAL DEVICE
│
│
19h
(DOS 5.0+) CHECK GENERIC IOCTL SUPPORT
│
└──────────────────────────────────────────────────────────────────────┘
La tabla anterior resume las órdenes que puede soportar un controlador de dispositivo; en general no será preciso implementar todas: de hecho, incluso para un disco virtual basta con algunas de las primeras 16. Todas las órdenes devuelven una palabra de estado al sistema operativo, cuyo formato puede consultarse a continuación. En general, las ordenes no soportadas pueden originar un error o bien ser sencillamente ignoradas (en ese sentido, crear un dispositivo NUL es tarea realmente sencilla). ┌───────────────────────────────────────────────────────────────────────────────────────┐ │
FORMATO DE LA PALABRA DE ESTADO
│
├───────────────────────────────────────────────────────────────────────────────────────┤ │ bit 15:
Activo si hay error, en ese caso los bits 0-7 indican el tipo de error
│ bits 14-10: Reservados
│ │
│ bit 9:
Activo si el controlador de dispositivo no está listo. En las operaciones │
│
de entrada está listo si hay un carácter en el buffer de entrada o si tal │
│
buffer no existe; en las de salida cuando el buffer aún no está lleno.
│
203
CONTROLADORES DE DISPOSITIVOS
│ bit 8:
Activo si el controlador de dispositivo ha acabado de ejecutar la orden.
│
│
Hasta el DOS 5.0 al menos, esto es siempre así (en un hipotético sistema
│
│
multitarea, una orden podría ejecutarse en varias ráfagas de CPU).
│
│ bits 7-0:
Código de error, si el bit 15 está activo:
│
│
00h disco protegido contra escritura
│
│
01h unidad desconocida
│
│
02h unidad no preparada
│
│
03h orden desconocida
│
│
04h error de CRC
│
│
05h longitud inválida de la cabecera de petición
│
│
06h fallo en el posicionamiento del cabezal
│
│
07h medio físico desconocido
│
│
08h sector no encontrado
│
│
09h impresora sin papel
│
│
0Ah error de escritura
│
│
0Bh error de lectura
│
│
0Ch anomalía general
│
│
0Dh reservado
│
│
0Eh (CD-ROM) medio físico no disponible
│
│
0Fh cambio de disco no permitido
│
└───────────────────────────────────────────────────────────────────────────────────────┘
La construcción de rutinas de gestión para las diversas órdenes que han de soportarse no es un proceso muy complicado, pese a que está envuelto en una leyenda negra. Sin embargo, puede que parte de la explicación que viene a continuación sobre dichas órdenes sea difícil de entender al lector poco iniciado. No hay que olvidar que los controladores de dispositivo respetan unas normas de comportamiento definidas por el fabricante del DOS, y más que de intentar comprender por qué una cosa es de una manera determinada, de lo que se trata es de obedecer. En general, lo que no se entienda puede ser pasado por alto ya que probablemente no es estrictamente necesario conocerlo. Además, casi ningún controlador necesita soportar todas las órdenes, como se verá al final en los programas de ejemplo. 11.4.0. - Orden 0 o INIT. ┌──────────────────────────────────────────────────────────────────────────────────────┐ │
CABECERA DE PETICIÓN DE SOLICITUD PARA LA ORDEN 0 (INIT)
│
├──────────────────────────────────────────────────────────────────────────────────────┤ │
offset
│
offset 0Dh
0
13 BYTES:
Ya vistos con anterioridad.
│
BYTE:
A la vuelta, indicar al DOS el nº de unidades de disco
│
definidas (solo en dispositivos de bloque).
│
A la vuelta, indica el último byte residente con un
│
│ │
offset 0Eh:
DWORD:
│
puntero largo de 32 bits. Si el dispositivo no se instala │
│
ante algún fallo, para no quedar residente basta indicar
│
│
un offset 0 (el segmento es vital inicializarlo con CS).
│
A la entrada, el DOS indica dónde comienza la línea de
│
│
parámetros del CONFIG.SYS. A la salida se indica al DOS
│
│
la dirección de la tabla de apuntadores a estructuras BPB │
│
offset 12h:
DWORD:
│
(esto último sólo en los dispositivos de bloques).
│
Desde el DOS 3.0, número de discos lógicos existentes
│
│
hasta ese momento ej. 3 para A: B: y C: (solo en los
│
│
dispositivos de bloque).
│
│
offset 16h:
BYTE:
└──────────────────────────────────────────────────────────────────────────────────────┘
Esta es la primera de todas las órdenes y se ejecuta siempre una vez cuando el dispositivo es cargado en memoria, con objeto de que éste se inicialice. Aquí sí se pueden emplear libremente las funciones del DOS (en el resto de las órdenes no: el driver es un programa residente más). En su inicialización el driver decide qué cantidad de memoria se queda residente y puede analizar la línea de comandos del CONFIG.SYS para comprobar los parámetros del usuario. En los dispositivos de bloque se indica también al sistema el número de unidades definidas por el controlador y la dirección de una tabla de punteros a estructuras BPB, ya que existe
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
una de estas estructuras para cada unidad lógica. El BPB (BIOS Parameter Block) es una estructura que contiene información sobre las unidades; puede consultarse en el capítulo 7. Aunque el BPB ha sido ampliado en las últimas versiones del DOS, para construir discos de menos de 65536 sectores solo hace falta completar los primeros campos (solo hasta los relacionados con el DOS 2.0 o, como mucho, el 3.0). Los parámetros en la línea de comandos del CONFIG.SYS son similares a los de un programa ordinario, aunque como se observa en el cuadro anterior su dirección se obtiene en el puntero de 32 bits ubicado en el offset 12h de la cabecera de petición de solicitud. Por ello, si ES:BX apunta a dicha cabecera, la instrucción LES BX,ES:[BX+12h] tiene como resultado alterar el valor de ES:BX para que ahora apunte a la zona de parámetros. En ella, aparece todo lo que había después del '=' o el ' ' que seguía al DEVICE. Por ejemplo, para una línea de config.sys como la siguiente: DEVICE \DOS\VDISK.SYS 128 el contenido de la zona de parámetros sería '\DOS\VDISK.SYS 128' -sin incluir las comillas, lógicamente-. Como se puede observar, el nombre y ruta del programa están separados de sus parámetros por uno o más delimitadores (espacios en blanco o tabuladores -ASCII 9-); al final se encuentra el código de retorno de carro -ASCII 13- aunque quizá en algunas versiones del DOS podría estar indicado el final de la cadena por un salto de línea -ASCII 10- en lugar del retorno de carro. Aviso: tras el nombre/ruta del fichero, las versiones más antiguas del DOS colocan un byte a cero. No se debe modificar la línea de parámetros: además de improcedente puede ser peligroso, al tratarse de un área de datos del sistema. En los dispositivos de bloque, el mismo campo donde se obtiene la dirección de los parámetros ha de ser empleado para devolver al DOS la dirección de los punteros a los BPB: el sentido común indica que primero debe leerse la dirección de los parámetros y después puede modificarse dicho campo. 11.4.1. - Orden 1 o MEDIA CHECK. Esta orden sólo es preciso implementarla en los dispositivos de bloques, sirve para que el sistema pregunte al controlador si se ha producido un cambio en el soporte: por ejemplo, si se ha cambiado el disquete de la disquetera. En general, los discos fijos y virtuales suelen responder que no, ya que es seguro que nadie puede haberlos cambiado; en los disquetes suele responderse que sí (ante la duda). En caso de que el soporte haya cambiado, el DOS invalida y libera todos los buffers en memoria relacionados con el mismo. Si no ha cambiado, el DOS sacará la información de sus buffers internos evitando en lo posible un acceso al disco. ┌──────────────────────────────────────────────────────────────────────────────────────┐ │
CABECERA DE PETICIÓN DE SOLICITUD PARA LA ORDEN 1 (MEDIA CHECK)
│
├──────────────────────────────────────────────────────────────────────────────────────┤ │
offset
│
offset 13
0
13 BYTES:
Ya vistos con anterioridad.
│
BYTE:
A la entrada, el DOS indica el descriptor del soporte
│
(solo en dispositivos de bloque)
│
│ │
offset 14
BYTE:
A la vuelta, el driver indica el resultado: 0FFh si se ha │
│
producido un cambio, 0 si se desconoce (lo que equivale
│
│
al primer caso) y 1 si no ha habido cambio.
│
└──────────────────────────────────────────────────────────────────────────────────────┘
11.4.2. - Orden 2 o BUILD BPB. Es ejecutada por el sistema si la respuesta a la orden MEDIA CHECK es afirmativa (cambio de soporte). El DOS necesita entonces averiguar las características del nuevo soporte, para lo que pide al driver que le suministre un BPB con información. De nuevo, esta orden solo ha de implementarse en los dispositivos de bloques. Desde el DOS 3.0 se recomienda anotar la etiqueta de volumen del disco cuando se ejecuta esta orden para detectar un posible cambio ilegal del mismo, aunque lo cierto es que este método es bastante ineficiente (discos sin etiquetar, con la misma etiqueta...); desde el DOS 4.0 se mejora este asunto con los números de serie, pero pocos drivers se molestan en comprobarlos. Las versiones más antiguas del DOS (2.x) necesitan que cambie el byte descriptor de soporte para detectar el cambio de disco. Las versiones actuales, habida cuenta del caos de bytes de identificación comunes para disquetes diferentes, no requieren que el byte descriptor cambie
203
CONTROLADORES DE DISPOSITIVOS
para aceptar el cambio y confían en la información que suministra MEDIA CHECK. En los discos de tipo IBM, los más comunes, el DOS intenta cooperar con el controlador de dispositivo en los cambios de disco. Por ello, se las apaña para leer el primer sector de la FAT y se lo pasa al driver, que así tiene más fácil la tarea de detectar el tipo de disco y suministrar al DOS el BPB adecuado, ya que el primer byte de la FAT contiene el tipo de disco (byte descriptor de medio). En los discos que no son de tipo IBM es el driver quien, por sus propios medios, ha de apañárselas para detectar el tipo de disco introducido en la unidad correspondiente: por ejemplo, leyendo el sector de arranque. En algunos casos puede resultar útil indicar que el disco es de tipo no IBM; por ejemplo en un controlador para un soporte físico que necesite detectar el medio introducido para poder acceder al mismo. Por ejemplo en una disquetera: al introducir un nuevo disco de densidad diferente al anterior, el intento por parte del DOS de leer la FAT en los discos tipo IBM provocaría un fallo (si esto no sucede con el controlador del propio sistema para las disqueteras es porque la BIOS suplanta al DOS, realizando quizá algunas tareas más de las que debería tener estrictamente encomendadas al detectar un cambio de disco). ┌──────────────────────────────────────────────────────────────────────────────────────┐ │
CABECERA DE PETICIÓN DE SOLICITUD PARA LA ORDEN 2 (BUILD BPB)
│
├──────────────────────────────────────────────────────────────────────────────────────┤ │
offset
│
offset 13
0
13 BYTES:
Ya vistos con anterioridad.
│
BYTE:
A la entrada, el DOS indica el descriptor del soporte.
│
(solo en dispositivos de bloque)
│
A la entrada, el DOS apunta a un buffer que contiene el
│
│ │
offset 14
DWORD:
│
primer sector de la FAT (cuyo 1º byte es el descriptor de │
│
soporte) si el disco es de tipo IBM; de lo contrario el
│
│
buffer está vacío y puede emplearse para otro propósito.
│
│
offset 18
A la vuelta, el driver devuelve aquí la dirección del BPB │
DWORD:
│
del nuevo disco (no la de ninguna tabla de punteros).
│
└──────────────────────────────────────────────────────────────────────────────────────┘
11.4.3. - Orden 3 o IOCTL INPUT. Puede ser soportada tanto por los dispositivos de caracteres como por los de bloque, el sistema solo la utiliza si así se le indicó en la palabra de atributos del dispositivo (bit 14). El IOCTL es un mecanismo genérico de comunicación de las aplicaciones con el controlador de dispositivo; por medio de esta función, los programas de usuario solicitan información al controlador (subfunciones 2 y 4 de la función 44h del DOS) sin tener que emplear el canal normal por el que se envían los datos. Es frecuente que no esté soportada en los dispositivos más simples. La cabecera de petición de solicitud de esta orden y de varias de las que veremos a continuación es la siguiente: ┌──────────────────────────────────────────────────────────────────────────────────────┐ │
CABECERA DE PETICIÓN DE SOLICITUD PARA LAS ÓRDENES:
│
│
3 (IOCTL INPUT)
│
│
4 (INPUT)
│
│
8 (OUTPUT)
│
│
9 (OUTPUT VERIFY)
│
│
10h (OUTPUT UNTIL BUSY)
│
├──────────────────────────────────────────────────────────────────────────────────────┤ │
offset
│
offset 13
0
13 BYTES:
Ya vistos con anterioridad.
│
BYTE:
A la entrada, el DOS indica el descriptor del soporte.
│
(solo en dispositivos de bloque)
│
│ │
offset 14
DWORD:
En entrada, dirección del área de transferencia a memoria │
│
offset 18
WORD:
En entrada, número de sectores (dispositivos de bloques)
│
│
o bytes (dispositivos de caracteres) a transferir.
│
│
A la salida, sectores/bytes realmente transferidos.
│
│
offset 20
WORD:
offset 22
DWORD:
│ │
Número de sector de comienzo (solo en los dispositivos de │ bloques y de menos de 32 Mb)
│
En las órdenes 4 y 8 y desde el DOS 3.0 se devuelve al
│
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
│
DOS un puntero a la etiqueta de volumen del disco en el
│
│
caso de un error 0Fh.
│
Número de sector de comienzo en discos de más de 32Mb
│
│
(ver bit 1 de palabra de atributos). En cualquier caso,
│
│
solo debe considerarse este campo si la longitud de la
│
│
cabecera de petición (byte 0) es mayor de 1Ah.
│
│
offset 26
DWORD:
└──────────────────────────────────────────────────────────────────────────────────────┘
11.4.4. - Orden 4 o INPUT. Esta orden es una de las más importantes. Sirve para que el sistema lea los datos almacenados en el dispositivo. Si el dispositivo es de caracteres, los almacenará en un buffer de entrada a medida que le van llegando del periférico y los enviará en respuesta a esta orden (si no los tiene, espera un tiempo razonable a que le lleguen antes de "fallar"). Si el dispositivo es de bloque, no se envían bytes sino sectores completos. En los dispositivos de caracteres, lo más normal es que el DOS solicite transferir sólo 1 en cada vez, aunque en teoría podría solicitar cualquier cantidad. En el caso de los dispositivos de bloque esta orden es ejecutada por el DOS cuando se accede a disco vía INT 25h/26h. 11.4.5. - Orden 5 o NONDESTRUCTIVE INPUT. Solo debe ser soportada por los dispositivos de caracteres. Es análoga a INPUT, con la diferencia de que no se avanza el puntero interno al buffer de entrada de datos tras leer el carácter. Por ello, tras utilizar esta orden será preciso emplear después la 4 para leer realmente el carácter. La principal utilidad de esto es que el sistema puede saber si el dispositivo tiene ya un nuevo carácter disponible antes de llamarle, para evitar que éste se quede parado hasta que le llegue. El bit 9 de la palabra de estado devuelta indica, si está activo, que el dispositivo está ocupado (sin caracteres). 11.4.6. - Orden 6 o INPUT STATUS. Es totalmente análoga a NONDESTRUCTIVE INPUT, con la salvedad de que ni siquiera se envía el siguiente carácter del buffer de entrada. Sólo sirve para determinar el estado del controlador, indagando si tiene caracteres disponibles o no. 11.4.7. - Orden 7 o INPUT FLUSH. Solo disponible en dispositivos de caracteres, vacía el buffer del dispositivo. Lo que éste suele hacer es sencillamente igualar los punteros al buffer de entrada interno (el puntero al último dato recibido del periférico y el puntero al próximo carácter a enviar al sistema cuando se lo pida). 11.4.8. - Orden 8 u OUTPUT. Es otra de las órdenes más importantes, análoga a INPUT pero actuando al revés. Permite al sistema enviar datos al dispositivo, bien sean caracteres o sectores completos, según el tipo de dispositivo. 11.4.9. - Orden 9 u OUTPUT VERIFY. Es análoga a OUTPUT, con la salvedad de que el dispositivo efectúa, tras escribir, una lectura inmediata hacia un buffer auxiliar, con la correspondiente comprobación de que lo escrito es correcto al comparar ambos buffers. Resulta totalmente absurdo implementarla en un disco virtual (el 11% de la memoria del sistema podría estar ya destinada a detectar un fallo en cualquier byte de la misma, y además es igual de probable el error durante la escritura que durante la verificación) por lo que en este caso debe comportarse igual que la orden anterior. En los discos físicos de verdad, sin embargo, conviene tomarla en serio. 11.4.10. - Orden 0Ah u OUTPUT STATUS.
CONTROLADORES DE DISPOSITIVOS
203
Es similar a INPUT STATUS y, como ésta, propia de los dispositivos de caracteres. Su misión es análoga, pero relacionada con el buffer de salida en vez del buffer de entrada. 11.4.11. - Orden 0Bh u OUTPUT FLUSH. También exclusiva de dispositivos de caracteres, es equivalente a INPUT FLUSH, vaciándose el buffer de salida en lugar de el de entrada. 11.4.12. - Orden 0Ch o IOCTL OUTPUT. Es complementaria de la orden IOCTL INPUT: se pueden enviar cadenas de información a través de la función 44h del DOS (subfunciones 3 y 5). Es útil para lograr una comunicación de ciertas informaciones con el controlador a través de otro canal, sin tener que mezclarla con los datos que se le envían. Algunos programas residentes, instalados como falsos controladores de dispositivo de caracteres soportan ciertos comandos vía IOCTL, evitando a las aplicaciones acceder directamente a la zona de memoria donde está instalado el controlador para modificar sus variables. 11.4.13. - Orden 0Dh o DEVICE OPEN. Solo implementada desde el DOS 3.0 y superior, indica que el dispositivo o un fichero almacenado en él ha sido abierto. El controlador se limita a incrementar un contador. Esta orden y las dos siguientes no han de estar necesariamente soportadas. 11.4.14. - Orden 0Eh o DEVICE CLOSE. Solo implementada desde el DOS 3.0 y superior, indica que el dispositivo o un fichero almacenado en él ha sido cerrado. El controlador se limita a decrementar un contador: si éste llega a cero, se reinicializan los buffers internos, si los hay, para permitir por ejemplo un posible cambio de disco. 11.4.15. - Orden 0Fh o REMOVABLE MEDIA. Solo implementada también desde el DOS 3.0 y superior, indica al sistema si el dispositivo es removible o no, apoyándose en los resultados de las dos órdenes anteriores. 11.4.16. - Orden 10h u OUTPUT UNTIL BUSY. Solo es admitida en dispositivos de caracteres y a partir del DOS 3.0; sirve para enviar más de un carácter al periférico. En concreto, se envían todos los que sean posibles (de la cantidad solicitada) hasta que el periférico esté ocupado: entonces se retorna. Aquí no se considera un error no haber podido transferir todo. Esta función es útil para acelerar el proceso de salida. 11.4.17. - Otras órdenes. Las órdenes 11h, 12h, 14h, 15h y 16h no han sido aún definidas, ni siquiera en el DOS 5.0. La orden 13h o GENERIC IOCTL, disponible desde el DOS 3.2 permite un mecanismo más sofisticado de comunicación IOCTL. También en el DOS 3.2 han sido definidas las órdenes 17h (GET LOGICAL DEVICE) y 18h (SET LOGICAL DEVICE). El DOS 5.0 añade una nueva: la 19h (CHECK GENERIC IOCTL SUPPORT). Por cierto, las ordenes 80h y superiores están destinadas a la comunicación con los dispositivos CD-ROM... 11.5. - LA CADENA DE CONTROLADORES DE DISPOSITIVO INSTALADOS. Los controladores de dispositivo forman una cadena en la memoria, una lista conectada por los 4 primeros bytes de la cabecera utilizados a modo de puntero. A medida que se van instalando en memoria,
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
quedan de tal manera que los últimos cargados apuntan a los predecesores. Al final, el sistema operativo apunta el dispositivo NUL al último dispositivo instalado, colocándose NUL al final de la cadena. Por tanto, averiguando la dirección del dispositivo NUL y siguiendo la cadena de apuntadores obtenida en los primeros 4 bytes de cada uno (en la forma segmento:offset) se puede recorrer la lista de dispositivos (ya sean de caracteres o de bloque) en orden inverso al que fueron instalados en memoria. El último de ellos estará apuntando a XXXX:FFFF. La lista de controladores de dispositivo puede pasar por la memoria convencional o por la superior, saltando de una a la otra múltiples veces. Algunos gestores de memoria, como QEMM cuando se utiliza LOADHI.SYS (en lugar del DEVICEHIGH del DOS) colocan la cadena de dispositivos en memoria convencional, aunque luego instalen el mismo en memoria superior. Esto quiere decir que para acceder al código o datos internos del dispositivo conviene tomar precauciones, de cara a averiguar la dirección donde realmente reside. El programa TURBODSK que veremos más adelante utiliza la cadena de controladores de dispositivo para buscarse a sí mismo en memoria e identificar todas las posibles unidades que controla. Por desgracia, la manera de obtener la dirección del dispositivo NUL varía de unas versiones del DOS a otras, aunque solo ligeramente. Hay que utilizar la función indocumentada Get List of Lists (servicio 52h del DOS) e interpretar la información que devuelve: En ES:BX más un cierto offset comienza la cabecera del dispositivo NUL (el propio dispositivo, no un puntero al mismo). Ese offset es 17h para las versiones 2.X del DOS, 28h para la 3.0X y 22h para todas las demás, habidas y por haber. La utilidad DRV.C listada más abajo recorre los dispositivos instalados, informando de ellos. Adicionalmente, excepto en las versiones más antiguas del DOS, DRV.C accede a los bloques de control de memoria que preceden a los dispositivos que están ubicados en un offset 0 respecto al segmento, con objeto de indicar el consumo de memoria de los mismos y el nombre del fichero ejecutable. Con DR-DOS 5.0 no se informa correctamente del nombre, ni tampoco del tamaño (excepto si el dispositivo está instalado en memoria superior); no hay problemas sin embargo con DR-DOS 6.0 ni, por supuesto, con MS-DOS 4.0 ó posterior. A continuación, antes del listado del programa, se muestra un ejemplo de salida del mismo bajo MS-DOS 5.0 (por supuesto, no recomiendo a nadie instalar tantos discos virtuales). ╔════ DRV 1.0 ═══ LISTA DE DISPOSITIVOS DEL SISTEMA ═══ (c) 1992 CiriSOFT ════╗ ║ Dirección Tipo Nombre Estrat. Interr. Atributo Programa Tamaño ║ ║ ───────── ──────── ───────────── ──────── ──────── ──────── ──────── ────── ║ ║ 0116:0048 Carácter NUL 0DC6 0DCC 8004 ║ ║ E279:0000 Bloque Unidad I: 00CB 00D6 0800 RAMDRIVE 1184 ║ ║ E22B:0000 Bloque Unidad H: 00CB 00D6 0800 RAMDRIVE 1232 ║ ║ E1A7:0000 Bloque Unidad G: 0086 0091 0800 VDISK 2096 ║ ║ E103:0000 Bloque Unidad F: 0086 0091 0800 VDISK 2608 ║ ║ E0E6:0000 Bloque Unidad E: 005A 0065 0800 TDSK 448 ║ ║ E0BE:0000 Bloque Unidad D: 005A 0065 0800 TDSK 624 ║ ║ E013:0000 Carácter CON 0078 0083 8013 ZANSI 2720 ║ ║ E003:0000 Carácter ALTDUP$ 00C2 00CD 8000 ALTDUP 240 ║ ║ DFD8:0000 Carácter KEYBSP50 0012 0018 8000 KEYBSP 672 ║ ║ DD90:0000 Carácter gmouse 0012 0021 8000 GMOUSE 9328 ║ ║ DD85:0000 Carácter ACCESOS$ 0013 001A 8000 ACCESOS 160 ║ ║ DD7C:0000 Carácter &FDREAD2 0012 0012 8000 FDREAD 128 ║ ║ 0316:0000 Carácter KEYBUF21 0012 0018 8000 KEYBUFF 160 ║ ║ D803:0000 Carácter SMARTAAR 00A2 00AD C800 SMARTDRV 22400 ║ ║ 0255:003F Carácter QEMM386$ 0051 007D C000 ║ ║ 0255:0000 Carácter EMMXXXX0 0051 0064 C000 QEMM386 3072 ║ ║ 0070:0023 Carácter CON 06F5 0700 8013 ║ ║ 0070:0035 Carácter AUX 06F5 0721 8000 ║ ║ 0070:0047 Carácter PRN 06F5 0705 A0C0 ║ ║ 0070:0059 Carácter CLOCK$ 06F5 0739 8008 ║ ║ 0070:006B Bloque Unidades A:-C: 06F5 073E 08C2 ║ ║ 0070:007B Carácter COM1 06F5 0721 8000 ║ ║ 0070:008D Carácter LPT1 06F5 070C A0C0 ║ ║ 0070:009F Carácter LPT2 06F5 0713 A0C0 ║ ║ 0070:00B8 Carácter LPT3 06F5 071A A0C0 ║ ║ 0070:00CA Carácter COM2 06F5 0727 8000 ║ ║ 0070:00DC Carácter COM3 06F5 072D 8000 ║
203
CONTROLADORES DE DISPOSITIVOS
║ 0070:00EE Carácter COM4 06F5 0733 8000 ║ ╚═════════════════════════════════════════════════════════════════════════════╝ while (FP_OFF(siguiente)!=0xffff) {
//
DRV 1.0
//
Utilidad para listar los controladores de dispositivo instalados.
disp = (unsigned char huge *) siguiente; printf("║\n║ %04X:%04X ", FP_SEG(disp), FP_OFF(disp)); if (disp[5] & 0x80) {
#include
printf("Carácter ");
#include
for
(i=10;
i<18;
i++)
printf("%c",disp[i]);
printf("
");
struct REGPACK r; unsigned long huge *siguiente;
}
unsigned char huge *disp;
else { printf("Bloque
int i, disco, dosver;
");
if (disp[10]==1) printf("Unidad %c:
void main()
", disco--);
else {
{ r.r_ax=0x3000; intr (0x21, &r);
printf("Unidades %c:-%c:",disco-disp[10]+1, disco);
/* obtener versión del DOS */
disco-=disp[10];
dosver=(r.r_ax << 8) | (r.r_ax >> 8); if ((dosver & 0xFF00)==0x200) i=0x17;
}
/* DOS 2.XX }
*/
printf("
else if ((dosver>0x2FF) && (dosver<0x30A)) i=0x28; /* DOS 3.0X
%04X
%04X
%04X
", disp[6] | (disp[7]<<8),
disp[8] | (disp[9]<<8), disp[4] | (disp[5]<<8));
*/ else i=0x22;
/* otra versión if ((!FP_OFF(disp)) && (dosver>0x31E)) {
*/
for (i=-8; i<0; i++) r.r_ax=0x5200; intr (0x21, &r);
if (disp[i]>=' ') printf("%c",disp[i]); else printf(" ");
/* "Get List of Lists" */
printf(" %6u ",(disp[-13] | (disp [-12] << 8)) << 4); }
siguiente=MK_FP(r.r_es, r.r_bx+i); disco='A'-1;
else
while (FP_OFF(siguiente)!=0xffff) {
printf("
disp = (unsigned char huge *) siguiente; if (!(disp[5] & 0x80)) disco+=disp[10];
");
siguiente = (unsigned long huge *) *siguiente;
/* contar discos */ }
siguiente = (unsigned long huge *) *siguiente; }
printf("║\n╚"); for (i=1; i<78; i++) printf("═"); printf("╝\n"); }
siguiente=MK_FP(r.r_es, r.r_bx+i); printf("\n╔════ DRV 1.0 ═══ LISTA DE DISPOSITIVOS DEL SISTEMA ═══ (c) 1992 CiriSOFT ════╗\n"); printf("║ Dirección
Tipo
Nombre
Estrat.
Interr.
────────
────────
Atributo Programa Tamaño ║\n"); printf("║
─────────
────────
──────── ──────── ────── ");
─────────────
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
11.6. - EJEMPLO DE CONTROLADOR DE DISPOSITIVO DE CARACTERES. El controlador propuesto de ejemplo crea un dispositivo HEX$ que imprime en pantalla y en hexadecimal todo lo que recibe. Por supuesto, el programa se instala en el CONFIG.SYS con una orden del tipo DEVICE=HEX.SYS. En principio, sería un programa mucho más simple si se limitara a imprimir los caracteres que recibe, aunque ello no tendría utilidad alguna. De hecho, la mayor parte de la complejidad del listado no se debe al controlador de dispositivo, sino al resto. Para empezar, las órdenes Open, Close o Remove, en un hipotético dispositivo que simplemente sacara por pantalla lo que recibe están de más. Además, la rutina que procesa los caracteres (procesa_AL) se limitaría a imprimirles; también se eliminarían todas las demás subrutinas de apoyo. Sin embargo, el hecho de realizar un volcado hexadecimal complica bastante el asunto. El listado hexadecimal que se obtiene es similar al siguiente: C:\WP51\TEXTOS>type prueba.bin > hex$ 00000000 45 73 74 65 20 65 73 20 - 75 6E 20 66 69 63 68 65 00000010 72 6F 20 64 65 20 70 72 - 75 65 62 61 73 2E 20 53 00000020 A2 6C 6F 20 73 69 72 76 - 65 20 70 61 72 61 20 70 00000030 72 6F 62 61 72 2E 0A 0D
Este es un fiche ro de pruebas. S ólo sirve para p robar...
Es preciso implementar la orden Open para detectar el inicio de la transferencia, inicializando a cero el contador de offset relativo de la izquierda. Los caracteres se imprimen unos tras otros en hexadecimal (con un guión separador tras el octavo) y se van almacenando en un buffer hasta completar 16: entonces, se imprimen de nuevo pero en ASCII (sustituyendo por puntos los códigos de control). La orden Close sirve para detectar el final de la operación: ante ella se escriben los espacios necesarios y se vuelcan los códigos ASCII acumulados hasta el momento (entre 0 y 15) que restasen por ser imprimidos. Por emplear Open y Close este controlador de dispositivo necesita DOS 3.0 o superior. Utilizando COPY en vez de TYPE, al enviar varios ficheros con los comodines el COMMAND suele encadenarles en uno solo y el offset es relativo al primero enviado (esto depende de la versión del intérprete de comandos). Aunque se supone que el DOS va a enviar los caracteres de uno en uno, el dispositivo se toma la molestia de prever que esto pueda no ser así, procesando en un bucle todos los que se le indiquen. Para imprimir se utiliza la INT 29h del DOS (fast console OUTPUT), más recomendable que llamar a un servicio del sistema operativo (que a fin de cuentas va a parar a esta interrupción). No hay que olvidar que los controladores de dispositivo son también programas residentes a todos los efectos, con las mismas limitaciones. Sin embargo, desde los programas normales no es recomendable utilizar la INT 29h, entre otras razones porque esos programas, además de imprimir a poca velocidad, no soportarían redireccionamiento en la salida (la INT 29h no es precisamente rápida, aunque sí algo más que llamar al DOS). El dispositivo HEX$ sólo actúa en salida, imprimiendo en pantalla lo que recibe. Si se intenta leer desde él devuelve una condición de error (por ejemplo, al realizar COPY HEX$ FICH.TXT). Para visualizar ficheros binarios que puedan contener la marca de fin de fichero (^Z) no basta hacer TYPE o COPY a secas: en estos casos se debe emplear COPY /B FICHERO.EXT HEX$, la opción /B sirve para que la salida no se detenga ante el ^Z. La operación de impresión en pantalla se supone siempre exitosa; por ello el dispositivo no modifica la variable que indica el número de caracteres a procesar: al devolverla precisamente como estaba al principio indica que se han procesado sin problemas todos los solicitados. En la instalación se comprueba la versión del DOS, para cerciorarse de la presencia de un 3.0 o superior. Este driver de ejemplo sólo consume 464 bytes de memoria bajo MS-DOS 5.0. Tras ensamblarlo y linkarlo hay que aplicar EXE2BIN para pasarlo de EXE a SYS (TLINK /t sólo opera cuando hay un ORG 100h). Como se puede verificar observando el listado, las únicas órdenes realmente soportadas por el dispositivo son, aparte de OPEN, CLOSE y REMOVE, las órdenes WRITE y WRITE VERIFY. Todas las demás, en este controlador que no depende del hardware típico de entrada/salida, son innecesarias. Como el proceso de escritura en pantalla se supone siempre con éxito, WRITE VERIFY es idéntica a WRITE, sin realizar verificación alguna. Las órdenes no soportadas pueden ser ignoradas o bien desembocar en un error, según sea el caso.
203
CONTROLADORES DE DISPOSITIVOS
; ********************************************************************
DW
ioctl_output
; *
*
DW
open
*
DW
close
; *
*
DW
remove
; *
Controlador de dispositivo para volcado hexadecimal en salida. *
; *
*
EQU
$
DB
8 DUP (0)
; buffer para contener los caracteres
EQU
$
; recibidos de 16 en 16.
DB
8 DUP (0)
; *
HEX$ 1.0
-
(c) 1992
Ciriaco García de Celis.
ini_buffer
; ******************************************************************** med_buffer
; ------------ Macros de propósito general
fin_buffer
EQU
$
XPUSH
puntero
DW
ini_buffer
; puntero al buffer
dirl
DW
0
; offset relativo del carácter del
dirh
DW
0
; fichero o canal en proceso
MACRO RM
; apilar lista de registros
IRP reg, PUSH reg ENDM ENDM ; ------------ Rutina de estrategia. XPOP
MACRO RM
; desapilar lista de registros estrategia
IRP reg, POP reg ENDM
PROC
FAR
MOV
CS:pcab_pet_desp,BX
MOV
CS:pcab_pet_segm,ES
RET
ENDM estrategia
ENDP
; ************ Inicio del área residente.
; ------------ Rutina de interrupción.
HEXSEG
interrupción
SEGMENT
tipo_drive
PROC
FAR
XPUSH
ASSUME CS:HEXSEG, DS:HEXSEG
LDS
BX,CS:pcab_peticion
DD
-1
; encadenamiento con otros drivers
MOV
AL,[BX+2]
DW
8800h
; palabra de atributo:
CBW
; bit 15 a 1: dispositivo caracteres
CMP
AL,0Fh
; bit 14 a 0: sin control IOCTL
JBE
orden_ok
; bit 11 a 1: soportados Open/Close
MOV
AX,8102h
JMP
exit_interr
SHL
AX,1
;
y Remove (DOS 3.0+) orden_ok:
; AX = orden (AH = 0)
DW
estrategia
DW
interrupción ; rutina de interrupción
LEA
SI,p_rutinas
DB
"HEX$
ADD
SI,AX
"
; rutina de estrategia
; nombre del dispositivo
; AL = orden
; orden correcta
; orden = orden * 2
XPUSH ; ------------ Variables y tablas de datos globales fijas.
pcab_peticion
LABEL DWORD
pcab_pet_desp
DW
0
pcab_pet_segm
DW
0
; puntero a la cabecera de petición
exit_interr:
LABEL WORD
CS:[SI]
XPOP
; ejecutar orden
MOV
[BX+3],AX
XPOP
; devolver palabra de estado
RET interrupción
p_rutinas
CALL
ENDP
; tabla de rutinas del controlador
DW
init
; ------------ Las rutinas que controlan el dispositivo devuelven AX
DW
media_check
;
con la palabra de estado. Pueden cambiar todos los
DW
build_bpb
;
registros (de 16 bits), incluídos los de segmento.
DW
ioctl_input
DW
read
input_status:
; conjunto de órdenes con
DW
read_nowait
output_status:
; tratamiento idéntico
DW
input_status
input_flush:
DW
input_flush
output_flush:
DW
write
ioctl_output:
DW
write_verify
retorno_ok:
DW
output_status
DW
output_flush
MOV RET
AX,100h
; no hay error, ignorar orden
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
media_check:
write
ENDP
procesa_AL
PROC
build_bpb: read:
; sólo soportada la salida
read_nowait: ioctl_input:
MOV
AX,8103h
; órdenes no soportadas
RET
open
open
close
tam_ok:
escr_esp:
esp_escr:
PROC MOV
CS:puntero,OFFSET ini_buffer ; inicializa puntero
MOV
CS:dirl,0
MOV
CS:dirh,0
JMP
retorno_ok
no_direcc: ; offset relativo a cero
no_sep:
ENDP
PROC
; fin de transferencia:
BX,puntero
MOV
[BX],AL
INC
puntero
CMP
BX,OFFSET ini_buffer
JNE
no_direcc
; no es inicio de «párrafo»
CALL
imprimir_desp
; imprimir desplazamiento
CMP
BX,OFFSET med_buffer
JNE
no_sep
CALL
imprimir_sep
ADD
dirl,1
; INC no afecta al acarreo
ADC
dirh,0
; incrementada dirección
CALL
print_8hex
; imprimir byte en hexadecimal
; aún no alzanzada la mitad
AX,CS
MOV
AL,' '
MOV
DS,AX
CALL
print_AL
LEA
CX,fin_buffer
CMP
puntero,OFFSET fin_buffer
MOV
BX,puntero
JB
AL_procesado
SUB
CX,BX
; CX caracteres faltan
LEA
BX,ini_buffer
MOV
AX,CX
; para un párrafo
MOV
puntero,BX
ADD
CX,CX
CALL
imprimir_asc
ADD
CX,AX
CMP
BX,OFFSET med_buffer
JA
tam_ok
ADD
CX,2
MOV
AL,' '
PUSH
AX
JCXZ
esp_escr
MOV
AL,' '
CALL
print_AL
CALL
print_AL
LOOP
escr_esp
CALL
print_AL
MOV
BX,puntero
MOV
AX,dirh
; CX = CX * 3
; dos espacios de separación
AL_procesado:
RET
procesa_AL
ENDP
imprimir_desp
PROC
BX,OFFSET fin_buffer
XCHG
AH,AL
fin_buff
CALL
print_8hex
MOV
BYTE PTR [BX],' '
XCHG
AH,AL
INC
BX
CALL
print_8hex
JMP
limpia_buffer
MOV
AX,dirl
LEA
BX,ini_buffer
XCHG
AH,AL
MOV
puntero,BX
CALL
print_8hex
CALL
imprimir_asc
XCHG
AH,AL
JMP
retorno_ok
CALL
print_8hex
MOV
AL,' '
CALL
print_AL
CALL
print_AL
POP
AX
ENDP
remove
PROC MOV
AX,300h
RET
; acabado el buffer:
; imprimirlo en ASCII
; indicar
imprimir_desp
ENDP
imprimir_sep
PROC
ENDP
write
PROC
write_verify:
MOV
CX,[BX+12h]
; bytes a transferir
PUSH
AX
LES
DI,[BX+0Eh]
; dirección inicial
MOV
AL,'-'
MOV
AX,CS
CALL
print_AL
MOV
DS,AX
MOV
AL,' '
MOV
AL,ES:[DI]
CALL
print_AL
POP
AX
; DS: -> HEX$
XPUSH procesa_AL
XPOP
INC
DI
LOOP
otro_car
JMP
retorno_ok
; acabado el buffer:
; imprimirlo en ASCII
; dos espacios al principio
; byte alto palabra alta
; byte bajo palabra alta
; byte alto palabra baja
; byte bajo palabra baja
; dos espacios separadores
RET
; «controlador ocupado»
remove
CALL
; espacio separador
; imprimir desplazamiento
JAE
close
otro_car:
; guardar carácter
MOV
limpia_buffer: CMP
fin_buff:
; inicio de transferencia:
; permitido corromper registros
MOV
; imprimir guión separador
RET
; procesar carácter imprimir_sep
ENDP
imprimir_asc
PROC
; otro carácter
; siempre Ok.
MOV
; imprimir en ASCII 16 bytes AL,' '
; a partir de DS:BX
203
CONTROLADORES DE DISPOSITIVOS
asc_dump:
asc_ok:
CALL
print_AL
MOV
[BX+10h],CS
MOV
CX,16
LEA
BX,mal_dos_txt
MOV
AL,[BX]
CALL
print
CMP
AL,' '
MOV
AX,100h
JAE
asc_ok
RET
MOV
AL,'.'
LEA
AX,retorno_ok
CALL
print_AL
MOV
CS:p_rutinas,AX
INC
BX
MOV
WORD PTR [BX+0Eh],OFFSET init
LOOP
asc_dump
MOV
[BX+10h],CS
MOV
AL,0Dh
LEA
BX,instalado_txt
CALL
print_AL
CALL
print
MOV
AL,0Ah
MOV
AX,100h
CALL
print_AL
; espacio separador
; no imprimir los de control
; retorno de carro
ENDP
print_8hex
PROC
PROC DL,CS:[BX]
AND
DL,DL
AH,AL
JZ
fin_print
MOV
CL,4
MOV
AH,2
SHR
AL,CL
PUSH
BX
CALL
print_4hex
INT
21h
MOV
AL,AH
POP
BX
AND
AL,00001111b
INC
BX
CALL
print_4hex
JMP
print
POP
AX
PROC
; imprimir nibble hexad. en AL
PUSH
AX
ADD
AL,'0'
CMP
AL,'9'
JBE
hex_AL
ADD
AL,'A'-'9'-1
CALL
print_AL
POP
AX
print_4hex
ENDP
print_AL
PROC INT
RET
print
ENDP
instalado_txt
DB
13,10,"Dispositivo HEX$ instalado.",13,10,0
mal_dos_txt
DB
13,10,"Error: HEX$ necesita DOS 3.0 o superior."
DB
13,10,0
ENDS END
; imprimir ASCII en AL 29h
RET ENDP
; ************ Instalación invocada desde el CONFIG.SYS
PROC PUSH
BX
MOV
AH,30h
INT
21h
POP
BX
CMP
AL,3
JAE
dos_ok
MOV
WORD PTR [BX+0Eh],0
; obtener versión del DOS
; OFFSET 0: terminar
; instalación siempre Ok.
fin_print:
HEXSEG
RET
; indicado área residente
; imprimir cadena en CS:BX
MOV
AX
print_4hex
init
print
MOV
ENDP
print_AL
ENDP
; imprimir byte hexad. en AL
RET
hex_AL:
init
PUSH
print_8hex
; anular rutina INIT
RET
; salto de línea
RET imprimir_asc
dos_ok:
; sin quedar residente
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
11.7. - EJEMPLO DE CONTROLADOR DE DISPOSITIVO DE BLOQUES. 11.7.1. - DISCO VIRTUAL TURBODSK: CARACTERÍSTICAS. El disco virtual propuesto no es el clásico minidisco de ejemplo, de un segmento de 64 Kb. Por el contrario, se ha preferido crear un disco completo que pueda competir al mismo nivel que los del sistema, con objeto de recoger todas las circunstancias posibles que implica su desarrollo. Al final, este disco ha sido dotado de varias comodidades adicionales no disponibles en los discos del DOS. Por un lado, es posible modificar su tamaño una vez que ha sido instalado, sin necesidad de arrancar de nuevo el ordenador. Esta asignación dinámica de la memoria significa que, en la práctica, es factible tener instalado el controlador sin reservar memoria: cuando es preciso utilizar el disco, se le formatea; después de ser usado, se puede desasignar la memoria extendida, expandida o convencional que ocupaba. Esto último es más que recomendable si, por ejemplo, se va a ejecutar WINDOWS a continuación y ya no se necesita el disco virtual. Otra ventaja es que es mucho más flexible que los discos virtuales que acompañan al sistema operativo, permitiendo definir con mayor libertad los parámetros e incluyendo uno nuevo (el tamaño de cluster). Los usuarios avanzados nunca estuvieron contentos con los discos del sistema que abusaban demasiado del ajuste de parámetros. Aunque una elección torpe de parámetros de TURBODSK puede crear un disco prácticamente inútil, e incluso incompatible con algunas versiones del DOS, también es cierto que los usuarios con menos conocimientos pueden dejar a éste que elija los parámetros por ellos, con excepción del tamaño del disco. Los usuarios más informados, en cambio, no tendrán ahora trabas. Sin embargo, la pretensión inicial de hacer TURBODSK más rápido que los discos del sistema, de la que hereda su peculiar nombre, ha tenido que enfrentarse a la elevada eficiencia de RAMDRIVE. Las últimas versiones de este disco ya apuran bastante el rendimiento del sistema, por lo que superarle sólo ha sido posible con un truco en la memoria expandida/convencional y en máquinas 386DX y superiores: TURBODSK detecta estas CPU y aprovechar su bus de 32 bits para realizar las transferencias de bloques de memoria. La velocidad es sin duda el factor más importante de un disco virtual, con mucho, por lo que no se deben ahorrar esfuerzos para conseguirla. A continuación se resumen las características de TURBODSK, comparándolo con los discos virtuales del sistema: RAMDRIVE en representación del MS-DOS 5.0 (aunque se incluye una versión más reciente que viene con WINDOWS 3.1) y el VDISK de DR-DOS 6.0. Como puede observarse, la única característica que TURBODSK no presenta es el soporte de memoria extendida vía INT 15h de VDISK, tampoco implementado ya en RAMDRIVE. El motivo es simplificar el programa, ya que en la actualidad es difícil encontrar máquinas con memoria extendida que no tengan instalada la especificación XMS que implementa HIMEM.SYS o algunas versiones del EMM386. ┌─────────────────┐ │ CARACTERÍSTICAS │ ├─────────────────┴───────────────────────────────────────────────────────────────┐ │ RAMDRIVE VDISK TURBODSK │ │ (WINDOWS 3.1) (DR-DOS 6.0) v2.3 │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ Capacidad máxima: 32 Mb 32 Mb 64 Mb │ │ Soporte de memoria convencional: Sí Sí Sí │ │ Soporte de memoria EMS: Sí Sí Sí │ │ Soporte de memoria extendida INT 15h: No Sí No │ │ Soporte de memoria extendida XMS: Sí No Sí │ │ Tamaño de sector soportado: 128-1024 128-512 32-2048 │ │ Ficheros en directorio raíz: 4-1024 4-512 1-65534 │ │ Asignación dinámica de la memoria: No No Sí │ │ Tamaño de cluster definible: No No Sí │ │ Memoria convencional consumida (MS-DOS 5.0): 1184-1232 2096-2608 448-624 │ └─────────────────────────────────────────────────────────────────────────────────┘
203
CONTROLADORES DE DISPOSITIVOS
Para calcular la velocidad de los discos virtuales se ha utilizado el programa KBSEC.C listado más abajo. Los resultados de KBSEC pueden variar espectacularmente en función del fabricante del controlador de memoria o del sistema operativo. Este programa de test es útil para analizar el rendimiento de un disco virtual en fase de desarrollo o para que el usuario elija la memoria más rápida según la configuración de su equipo. Dicho programa bloquea todas las interrupciones excepto IRQ 0 (INT 8), la cual a su vez desvía con objeto de aumentar la precisión del cálculo; por ello es exclusivo para la comprobación de discos virtuales y no flexibles. Debe ser ejecutado sin tener instalado ningún caché. KBSEC fuerza el buffer de transferencia a una dirección de memoria determinada, con objeto de no depender aleatoriamente de la velocidad dispar de la memoria y los controladores XMS/EMS en función del segmento que sea utilizado. La fiabilidad de KBSEC está avalada por el hecho de que siempre da exactamente el mismo resultado al ser ejecutado en las mismas condiciones. Para hacerse una idea de la potencia de los discos virtuales, conviene tener en cuenta que un disco fijo con 19 ms de tiempo de acceso e interface IDE, en un 386-25 puede alcanzar una velocidad de transferencia de casi un megabyte, 17 veces menos que la mejor configuración de disco virtual -que además posee un tiempo de acceso prácticamente nulo- en esa misma máquina. ┌──────────────────────────────────────────────────────────────────────────────────────┐ │
Velocidad del disco bajo MS-DOS 5.0, calculada por KBSEC, con los buffers que
│
│
establece el DOS por defecto
y con sólo KEYB y
│
│
DOSKEY instalados. Para evaluar la memoria convencional no estaba instalado ningún
│
│
controlador de memoria; para la memoria XMS estaba instalado sólo HIMEM.SYS y para
│
│
la EMS,
│
│
en función de la gestión de memoria del sistema). Datos en Kb/segundo.
(aunque esto no influye en KBSEC)
tanto HIMEM.SYS como EMM386.EXE a la vez
(los resultados varían bastante
│
├──────────────────────────────────────────────────────────────────────────────────────┤ │ │
VDISK
RAMDRIVE
TURBODSK
563
573
573
│
- Memoria convencional:
│
│ │
8088-8 MHz:
│ │
286-12 Mhz (sin estados de espera):
│
- Memoria extendida/XMS:
1980
4253
4253
│
│
- Memoria convencional:
4169
4368
4368
│
│
│
386-25 MHz (sin caché):
│
- Memoria extendida/XMS:
6838
17105
17095
│
│
- Memoria expandida EMS:
1261
8308
14937
│
│
- Memoria convencional:
7297
6525
14843
│
│
│
486-25 MHz sin caché externa:
│
- Memoria extendida/XMS:
7370
10278
10278
│
│
- Memoria expandida EMS:
2533
7484
9631
│
│
- Memoria convencional:
8256
8454
11664
│
└──────────────────────────────────────────────────────────────────────────────────────┘
/********************************************************************* *
*
*
KBSEC 1.2 - Utility to calc with high precision the data transfer *
*
rate (the read data transfer read) in a ramdisk.
* *
(C) 1992-1995 Ciriaco García de Celis
- Do not run this program with a cache program loaded; compile
*
it in LARGE memory model with
*
disabled. Use Borland C. This program has english messages.
*
«Test stack overflow»
/* 63 Kb
#define TIEMPO 110L
/* 6 segundos * 18,2 ≈ 110 tics (error < 1%) */
(no sobrepasar 64 Kb en un acceso) */
*
#define TM 18.2
*
#define HORA_BIOS MK_FP(0x40, 0x6c)
/* cadencia de interrupciones del temporizador */ /* variable de hora del BIOS */
*
* *
#define MAXBUF 64512L
option
*
unsigned long ti, vueltas, far *cbios;
*
unsigned segmento, tamsect, far *pantalla;
*
unsigned char far *sbuffer;
*
static unsigned tiempo;
*
int unidad;
*********************************************************************/
void interrupt (*viejaIRQ0)();
#include #include
void interrupt nuevaIRQ0 () /* rutina ejecutada cada 55 ms */
#include
{
#include
tiempo++;
/* incrementar nuestro contador de hora */
203
outportb (0x20,0x20);
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
printf ("\nNeeds a disk from %2.0f Kb to 32 Mb\n", MAXBUF/1024.0);
/* EOI al controlador de interrupciones */
exit (3); }
}
textmode (C80); clrscr();
void prep_hw (void)
printf ("\nComputing speed (wait %2.0f sec.)...", TIEMPO/TM);
{ viejaIRQ0=getvect(8);
/* preservar vector de int. periódica */
setvect (8, nuevaIRQ0);
/* instalar nueva rutina de control
outportb (0x21, 0xfe);
/* inhibir todas las int. salvo timer */
pantalla=MK_FP((peekb(0x40,0x49)==7 ? 0xB000:0xB800), 0x140);
*/
prep_hw(); ti=tiempo=vueltas=0;
}
while (ti==tiempo); /* esperar pulso del reloj */ ti+=TIEMPO; void rest_hw (unsigned long tiempo_transcurrido_con_reloj_parado) while (ti >= tiempo)
{ outportb (0x21, 0);
/* autorizar todas las interrupciones */
setvect (8, viejaIRQ0);
/* restaurar vector de int. periódica */
if (absread (unidad, MAXBUF / tamsect, 0L, sbuffer)!=0) { rest_hw(ti-tiempo); printf ("\nError reading the disk.\n"); exit(254); }
cbios=HORA_BIOS; *cbios+=tiempo_transcurrido_con_reloj_parado;
else if (!(vueltas++ & 7)) *pantalla++=0xf07;
}
/* "imprimir" */
rest_hw(TIEMPO); clrscr(); void main(int argc, char **argv) printf("\nKBSEC 1.2: Effective data transfer rate on drive %c:\
{
%6.0f Kb/sec.\n", unidad+'A',MAXBUF/1024.0*vueltas/(TIEMPO/TM));
if (allocmem ((unsigned) ((MAXBUF+0x1800) >> 4), &segmento)!=-1) { printf("\nInsufficient memory.\n"); exit(255); }
}
sbuffer=MK_FP((segmento+0x100) & 0xff00 | 0x80, 0); /* 2Kb+n*4Kb */
if (argc<2) { printf("\nChoose the drive to test.\n"); exit(1); } unidad=(argv[1][0] | 0x20) - 'a'; if ((unidad<2) || (absread (unidad, 1, 0L, sbuffer)!=0)) { printf ("\nChoose drive C or above with less than 32 Mb.\n"); exit (2); }
tamsect = sbuffer[11] | (sbuffer[12]<<8); ti = (long) tamsect * ((sbuffer[0x14] << 8) | sbuffer[0x13]); if ((ti < MAXBUF) || (ti > 33554431L)) {
11.7.2. - ENSAMBLANDO TURBODSK. El listado fuente de TURBODSK consta de un único fichero que ha de ser ensamblado sin demasiados parámetros especiales. Este programa puede ser perfectamente ensamblado de manera indistinta por MASM 6.X (con el parámetro de compatibilidad con versiones anteriores) o por TASM, aunque preferiblemente por el segundo. Versiones de MASM anteriores a la citada no tienen potencia suficiente, básicamente porque no permiten emplear la directiva .386 dentro de los segmentos. Con TASM conviene emplear la opción /m5 para que el ensamblador ejecute todas las pasadas necesarias para optimizar el código al máximo (como mínimo habría que solicitar 2, en cualquier caso, para que no emita errores). 11.7.3. - ANÁLISIS DETALLADO DEL LISTADO DE TURBODSK. El listado completo de TURBODSK puede consultarse al final de este apartado. Se describirán paso a paso todas las peculiaridades del programa, por lo que el listado debería ser comprensible prácticamente al 100%. A lo largo de la explicación aparecen numerosas alusiones al comportamiento de RAMDRIVE y VDISK. Por supuesto, los detalles referidos a RAMDRIVE o VDISK se refieren exclusivamente a la versión de los mismos que acompaña a Windows 3.1 y a DR-DOS 6.0, respectivamente, no siendo necesariamente aplicable a otras anteriores o futuras de dichos programas. Evidentemente, la información sobre ambos no ha sido obtenida escribiendo al fabricante para solicitarle el listado fuente, por lo que es un tanto difusa e incompleta, aunque sí suficiente para complementar la explicación de TURBODSK y dar una perspectiva más amplia.
CONTROLADORES DE DISPOSITIVOS
203
LA CABECERA DE TURBODSK El inicio de TURBODSK es el clásico de todos los controladores de dispositivo de bloques. La palabra de atributos es idéntica a la de VDISK o RAMDRIVE. Hay que hacer aquí una breve mención al bit 13 que indica si el dispositivo es de tipo IBM o no: la verdad es que en nuestro caso daría igual elegir un tipo que otro (la diferencia es que en los de tipo IBM el DOS accede a la FAT antes que al propio sector de arranque para verificar el tipo de disco). Finalmente se optó por seguir la corriente de los discos del DOS, aunque existen por ahí discos virtuales de tipo «no-IBM». En principio, hoy por hoy da lo mismo cómo esté este bit de la palabra de atributos, tan sólo existe una sutil diferencia en la orden BUILD BPB. A continuación vienen las variables de TURBODSK, la mayoría de las cuales son intuitivas. Sin embargo, las dos primeras son algo especiales. La primera (cs_tdsk) está destinada a almacenar el valor del registro CS, que indica dónde reside el disco virtual. Aunque en principio puede parecer redundante, esta operación es necesaria para lograr la compatibilidad con algunos gestores de memoria, como QEMM, que pueden cargar la cabecera del dispositivo en memoria convencional y el resto del mismo en la superior: a nosotros nos interesa conocer la dirección donde reside todo el dispositivo, con objeto de acceder a él para ulteriores modificaciones de sus condiciones de operación. Cuando se utiliza el LOADHI de QEMM, el dispositivo es cargado en memoria superior, pero después QEMM se encarga de copiar la cabecera en memoria convencional, pasando la cadena de controladores de dispositivo del DOS por dicha memoria. Como nosotros buscaremos a un posible TURBODSK residente siguiendo esa cadena, gracias a la variable cs_tdsk podemos saber la dirección real del disco virtual. QEMM crea además unas falsas rutinas de estrategia e interrupción en memoria convencional que luego llaman a las de la memoria superior. Sin embargo, esto no es relevante para nosotros. Por fortuna, QEMM 6.0 también soporta el DEVICEHIGH del DOS, en cuyo caso la totalidad del dispositivo es cargado en memoria superior; sin embargo, no está de más tomar precauciones para los casos en que no sea así. La segunda variable es id_tdsk y su utilidad es fundamental: sirve para certificar que el controlador de dispositivo es TURBODSK, indicando además la versión. Esta variable está ubicada en los primeros 18 bytes de la cabecera, que son los que QEMM copia en memoria convencional. Si algún gestor de memoria extraño realizara la misma maniobra de QEMM y copiase menos de 18 bytes en memoria convencional, no pasaría nada: TURBODSK sería incapaz de hallarse a sí mismo residente en la memoria superior, por lo que no habría riesgo alguno de provocar un desastre. Por fortuna, estas complicadas argucias de los controladores de memoria tienden a desaparecer desde la aparición del DOS 5.0 que, de alguna manera, ha normalizado el uso de la memoria superior. Existe otra variable importante, tipo_soporte, que indica en todo momento el estado del disco. En general, las variables más importantes de TURBODSK han sido agrupadas al principio y el autor del programa se ha comprometido a no moverlas en futuras versiones. Esto significa que otros programas podrán detectar la presencia de TURBODSK e influir en sus condiciones de operación. Más adelante hay otras variables internas al programa: por un lado, la tabla de saltos para las rutinas que controlan el dispositivo; por otro, un BPB con información válida (si no fuera correcto, el DOS se podría estrellar al cargar el dispositivo desde el CONFIG). Este BPB será modificado cuando se defina el disco, se defina éste desde el CONFIG o no (esto último es lo más normal y recomendable). En el BPB solo se han completado los campos correspondientes al DOS 2.x; la razón es que los demás no son necesarios ni siquiera para el DOS 5.0: la información adicional de las últimas versiones de los BPB es empleada por las rutinas de más bajo nivel del sistema operativo, aquellas que se relacionan con la BIOS y el hardware; sin embargo, estas nuevas variables no son relevantes para la interfaz del DOS con el controlador de dispositivo. LAS RUTINAS QUE CONTROLAN EL DISPOSITIVO. Veremos ahora las principales rutinas de TURBODSK. Para empezar, la rutina de estrategia de TURBODSK no merece ningún comentario, pero sí la de interrupción. Es bastante parecida a la de los discos del sistema, pero con una diferencia: si el disco no está aún preparado y no se ha reservado memoria para él
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
(esto sucede con la variable tipo_soporte igual a cero) hay que rechazar todos los accesos al disco devolviendo un código de unidad no preparada, algo así como decir que no hay disquete dentro de la disquetera virtual. En cualquier otro caso, y valiéndose de la tabla de saltos, llamamos a la subrutina adecuada que gestiona cada orden. Estas subrutinas devuelven en AX la palabra de estado que hay que devolver al sistema, por lo que al final se realiza esta operación. En el caso de un error de transferencia (debido al fallo de algún controlador de memoria o a un intento de acceso fuera de los límites del disco), se indica al DOS que se han transferido 0 sectores; de lo contrario, esta variable de la cabecera de petición queda como estaba al principio, indicando que se han transferido tantos sectores como fueron solicitados. Las órdenes READ NOWAIT, INPUT STATUS, INPUT FLUSH, OUTPUT STATUS, OUTPUT FLUSH, IOCTL OUTPUT, OPEN y CLOSE no están realmente soportadas. Sin embargo, si el DOS las invoca, TURBODSK se limita a terminar como si nada hubiera sucedido, devolviendo una palabra de estado 100h que indica función terminada. A la orden IOCTL INPUT, en cambio, se responde con un error (orden no soportada) ya que TURBODSK no está preparado para enviar cadenas IOCTL a nadie (una cosa es no hacer caso de las que envían, ¡pero cuando además las solicitan!); en general, el comportamiento hasta el momento es 100% idéntico al de RAMDRIVE. Sin embargo, la orden MEDIA CHECK es totalmente diferente de la de los discos virtuales del DOS. A la pregunta de ¿ha habido cambio de disco?, tanto VDISK como RAMDRIVE responden siempre que no. En cambio, TURBODSK puede haber sido modificado por el usuario, debido a la asignación dinámica de memoria que soporta. En estos casos, el programa que formatea el disco virtual (el propio TURBODSK cuando el usuario define un disco) colocará la variable cambiado a un valor 0FFh. Este valor es el que se devolverá la primera vez al DOS, indicando que se ha producido un cambio de disco. Las siguientes veces, TURBODSK no volverá a cambiar (no hasta otro formateo), motivo por el cual la variable se redefine a 1. En el momento en que el disco es cambiado, el DOS ejecuta la orden BUILD BPB, con la que se le suministra la dirección del nuevo BPB (la misma de siempre, pero con un BPB actualizado). La orden REMOVE se limita a devolver una condición de controlador ocupado. No estaba muy claro qué había que hacer con ella, por lo que se optó por imitar el funcionamiento de RAMDRIVE. Lo cierto es que hay órdenes que casi nunca serán empleadas, o que no tiene sentido que sean utilizadas, pero conviene considerarlas en todo caso. Las últimas órdenes que implementa TURBODSK son las de lectura y escritura o escritura con verificación. En estas órdenes simplemente se inicializa un flag (el registro BP) que indica si se trata de leer o escribir: si BP es 0 es una escritura, si es 1 una lectura. Finalmente, se salta a la rutina Init_io que se encarga de preparar los registros para la lectura o escritura, consultando el encabezamiento de petición de solicitud para estas órdenes. Más o menos mezclada con estas órdenes está la rutina que gestiona la interrupción 19h. Esta interrupción es necesario desviarla para mejorar la convivencia con algunos entornos multitarea basados en el modo virtual del 386. En principio, cuando una tarea virtual es cancelada (debido a un CTRL-ALT-DEL o a un cuelgue de la misma) el sistema operativo debería desasignar todos los recursos ligados a ella, incluida la memoria expandida o extendida que tuviera a su disposición. Sin embargo, parece que existen entornos no muy eficientes en los que al anular una tarea no se recupera la memoria que ocupaba. Por tanto, es deber de la propia tarea, antes de morir, el devolver la memoria a los correspondientes controladores. La interrupción 19h se ejecuta en estos momentos críticos, por lo que TURBODSK aprovecha para liberar la memoria EMS/XMS ocupada y, tras restaurar el vector previo de INT 19h (para mejorar la compatibilidad) continúa el flujo normal de la INT 19h. La mayoría de los discos virtuales no desvían la INT 19h; sin embargo, RAMDRIVE sí y TURBODSK no quería ser menos... aunque, en el caso de utilizar memoria convencional no se realiza ninguna tarea (RAMDRIVE ejecuta una misteriosa y complicada rutina). La rutina Init_io se ejecuta inmediatamente antes de una lectura o escritura en el disco, preparando los registros. Se controla aquí que el primer y último sector a ser accedido estén dentro del disco: en caso contrario se devuelve un error de sector no encontrado. En realidad, TURBODSK no comprueba si el primer sector está
CONTROLADORES DE DISPOSITIVOS
203
en el disco, para ahorrar memoria; al contrario que la mayoría de los discos virtuales. La razón es que si el último sector está dentro del disco ¡como no lo va a estar también el primero!. También hay que tener en cuenta la histórica leyenda de los 64 Kb. En concreto, el problema reside en la dirección donde depositar o leer los datos. Pongamos por ejemplo que un programa pretende leer del disco virtual 48 Kb de datos en la dirección DS:A000h. En principio, el manual de referencia para programadores de Microsoft dice que el dispositivo solo está obligado a transferir cuanto pueda sin cambiar de segmento. Sin embargo, el RAMDRIVE de Microsoft no considera esta circunstancia, por lo que si un programa intenta hacer un acceso ilegal de este tipo se corromperá también una parte indeseada del segmento de datos, ya que al llegar al final de un segmento se comienza por el principio del mismo otra vez (esto no es así en el caso de emplear memoria extendida, pero sí en la convencional y expandida). En TURBODSK se prefirió limitar la transferencia al máximo posible antes de que se desborde el segmento: hay que tener en cuenta que un desbordamiento en el segmento de datos puede llegar a afectar al de código, con todo lo que ello implica. Cierto es que un acceso incorrecto a disco es una circunstancia crítica de la que no se puede responsabilizar al mismo, pero a mi juicio es mejor no poner las cosas todavía peor. Otro asunto es controlar el tamaño absoluto del área a transferir: en ningún caso debe rebasar los 64 Kb, aunque no está muy claro si los puede alcanzar o no. RAMDRIVE opera con palabras de 16 bits, permitiendo un máximo de 8000h (exactamente 64 Kb), excepto en el caso de trabajar con memoria extendida: al pasar el nº de palabras a bytes, unidad de medida del controlador XMS, el 8000h se convierte en 0 (se desborda el registro de 16 bits al multiplicar por 2): con este tipo de memoria RAMDRIVE no soporta transferencias de 64 Kb exactos (por ello, KBSEC.C emplea un buffer de 63 y no de 64 Kb). En TURBODSK se decidió transferir 64 Kb inclusive como límite máximo, en todos los casos. En memoria expandida y convencional, por otro lado, existe el riesgo de que el offset del buffer sea impar y, debido al tamaño del mismo, se produzca un acceso de 16 bits en la dirección 0FFFFh, ilegal en 286 y superiores. Esto provoca un mensaje fatal del controlador de memoria, preguntando si se desea seguir adelante o reinicializar el sistema (QEMM386), o simplemente se cuelga el ordenador (con el EMM386 del MS-DOS 5.0 o en máquinas 286). Por ejemplo, pruebe el lector a leer justo 32 Kb en un buffer que comience en 8001h con RAMDRIVE en memoria EMS: RAMDRIVE no pierde el tiempo comprobando estas circunstancias críticas, aunque VDISK parece que sí. En TURBODSK se optó también por ser tolerante a los fallos del programa que accede al disco: además de limitar el acceso máximo a 64 Kbytes, y de transferir sólo lo que se pueda antes del desbordamiento del segmento, puede que todavía se transfiera entre uno y tres bytes menos, ya que se redondea por truncamiento la cuenta de palabras que faltan para el final del segmento para evitar un direccionamiento ilegal en el offset 0FFFFh (estas circunstancias críticas deben evaluarse utilizando las interrupciones 25h/26h, ya que al abrir ficheros ordinarios el DOS es siempre suficientemente cauto para no poner a prueba la tolerancia a fallos de las unidades de disco). Inmediatamente después de la rutina Init_io de TURBODSK está colocada la que gestiona el disco en memoria expandida. No existe ningún nexo de unión y ambas se ejecutan secuencialmente. Al final de Init_io hay una instrucción para borrar el acarreo. Esto es así porque la rutina que gestiona el disco puede ser accedida, además de desde Init_io, desde el gestor de la interrupción 19h. El acarreo sirve aquí para discernir si estamos ante una operación normal de disco o ante una inicialización del sistema. En el caso de una operación de disco, BP indica además si es lectura o escritura. TURBODSK soporta también memoria extendida XMS y convencional: cuando se utilizan estas memorias, la rutina correspondiente sustituye a la de memoria EMS por el simple y efectivo procedimiento de copiarla encima. Esta técnica, que horrorizará a más de un programador, es frecuente en la programación de sistemas bajo MS-DOS. De esta manera, TURBODSK y RAMDRIVE (que también comete esta inmoralidad) economizan memoria, ya que solo queda residente el código necesario. El hecho de que por defecto esté colocada la rutina de memoria expandida es debido a que es, con diferencia, la más larga de todas y así siempre queda hueco para copiar encima las otras. A la hora de terminar residente, si la máquina tiene memoria extendida y no se indica /A, no se dejará espacio más que para las rutinas de memoria extendida y convencional, para economizar más memoria. ANÁLISIS DE LAS RUTINAS DE GESTIÓN DE MEMORIA. Las rutinas que gestionan los diversos tipos de memoria tienen los mismos parámetros de entrada (obtenidos de Init_io) y sirven para leer/escribir en el disco según lo que indique BP, así como para liberar la memoria asignada en respuesta a una interrupción 19h. Retornan devolviendo en AX el resultado de la
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
operación, que será normalmente exitoso. En caso de fallo de algún controlador de memoria, devolverían un código de error de anomalía general. Trabajando con memoria EMS. La rutina más compleja es la que gestiona la memoria expandida EMS. Además, un disco virtual que se precie debe soportar transferencias incluso en el caso de que el buffer donde leer/escribir los datos esté también en la memoria expandida y se solape con el propio disco. Este aspecto no es tenido en cuenta por ningún disco virtual de dominio público con soporte de memoria EMS que yo conozca, aunque sí por los del DOS; a esto se debe que algunas aplicaciones que trabajan con memoria expandida adviertan que pueden operar mal con ciertos discos virtuales. En el caso de VDISK, el algoritmo es muy poco eficiente: este disco virtual realiza un bucle, con una vuelta para cada sector, donde hace todas estas tareas: preservar el contexto del mapa de páginas, calcular las direcciones, transferir a un buffer auxiliar, recuperar el contexto del mapa de páginas y transferir del buffer auxiliar hacia donde solicita el DOS. Ello significa que, para transferir 32 Kb en sectores de 0,5 Kb, se salva y restaura ¡64 veces! el contexto del mapa de páginas. No digamos si los sectores son más pequeños, además del hecho (mucho más grave) de que transfiere dos veces y de la cantidad de veces que calcula las direcciones. Cierto es que salvar el contexto del mapa de páginas y volverlo a restaurar es necesario, de cara a que el disco virtual (un programa residente a todos los efectos) no afecte al programa de usuario que se está ejecutando, por si éste utiliza también memoria expandida. La pregunta es, ¿por qué no sacaron los autores de VDISK esas operaciones fuera del bucle?, y ¿por qué utilizar un buffer auxiliar?. Lógicamente hay una respuesta. Piense el lector qué sucederá si el buffer donde leer o escribir que suministra el programa principal, está en memoria expandida: ¡se solapa con el disco virtual!. Para solucionar este posible solapamiento, VDISK se ve obligado a realizar esas operaciones con objeto de permitir una transferencia de la memoria expandida a la propia memoria expandida, a través de un buffer auxiliar. Este algoritmo provoca que VDISK sea prácticamente tan lento como un buen disco duro cuando trabaja con memoria expandida y sectores de 512 bytes, ¡y bastante más lento si se utilizan los sectores de 128 bytes que suele establecer por defecto!. Además, el buffer del tamaño de un sector incrementa el consumo de memoria en 512 bytes. ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ │
ESQUEMA DE FUNCIONAMIENTO DE LA RUTINA DE GESTIÓN DE MEMORIA EMS DE TURBODSK
│
├──────────────────────────────────────────────────────────────────────────────────────────────────┤ │ │
│
Analizaremos el caso más conflictivo:
│
Cuando el área a transferir ocupa los 16 Kbytes máximos.
│
│
│ │ │
│ │
│
│
│
│
- - - - -├───────────────┤- - - - - - - - - - - - - -├───────────────┤- - - - -
│
│
│
│
│
│
M
│
│
E
│
│
├───────────────┤
M
├───────────────┤-- ¿
│
│
│
O
│
│
│
│
R
│
│
├───────────────┤
I
├───────────────┤-- À
│
│
A
│
│
│
│
├───────────────┤
E
├───────────────┤ ½───── caso B
│
│
│
│
M
│
│
│
│
S
│
│
│
│ │
│
Página 3
Página 2
│ Página 1
Página 0
│
│
Página 3
Página 2
Página 1
Página 0
│
│
│
│ │
│
16
│
│
Kb
│ │
│
│
│
│
- - - - -├───────────────┤- - - - - - - - - - - - - -├───────────────┤- - - - -
│
│
│
│
│
│
│
│
│
│
│
│
│
│
├───────────────┤
│
½───── caso A
├───────────────┤
│ │
│
│
│
Resulta evidente, en el caso A, que si el buffer donde leer/escribir los datos comienza por │
203
CONTROLADORES DE DISPOSITIVOS
│
debajo de la dirección marcada por la flecha (o justo en esa dirección) no colisionará
│
página
│
párrafos se pierde precisión,
│
más 1 párrafo) por debajo del inicio de la página 0.
0,
ya que no excede de 16 Kb de longitud. TURBODSK
con
la │
Como al convertir la dirección segmentada a │
se asegura que la dirección esté 401h párrafos
(16 Kb │ │
│
│
│
En el caso B, el buffer está en memoria expandida pero comienza justo detrás de la página 0 │
│
y, por lo que no hay colisión con esta página.
│
comprueba que el buffer comience al menos
│
En
│
convertir la dirección segmentada se hace truncando.
realidad,
Una vez más,
bastaría con comprobar si dista
por razones de redondeo, TURBODSK │
párrafos por encima del inicio de la página 0. │
401h
al
menos
400h
bytes,
ya
que el redondeo al │ │
│ │
│ Conclusión:
para que no haya colisión,
el buffer ha de estar a 401h párrafos de distancia │
│
(expresada en valor absoluto) del inicio de la página 0. ¿Qué sucede si hay colisión?. Pues que │
│
no se puede emplear la página 0, que se solapa con el buffer.
│
la página 2 ya que si el buffer empieza justo donde apunta la flecha del caso B, como su tamaño │
│
es de no más de 16 Kb,
│
párrafo! (no olvidar que si empieza por encima de la flecha no colisiona con la página 0).
│
tanto, tenemos que utilizar la página 3.
│
las páginas pueden ser definidas por el usuario en la dirección que desee
│
EMM386 del MS-DOS 5.0),
│
en que hay colisión,
│
páginas hay una distancia absoluta de 32 Kb).
no puede invadir...
sí,
En
En ese caso, bastaría con elegir │
¡sí puede invadir la página 2, aunque sólo un │
general,
Por │
en un sistema con memoria EMS 4.0 donde │ (parámetros /Pn= del │
basta con asegurarse que la página alternativa a la 0,
está alejada al menos 48 Kb de la página 0
(esto
es,
para los casos │
que
entre
ambas │ │
│
│
│
Se comprende ahora la necesidad de restaurar el contexto del mapa de páginas antes de pasar │
│
utilizar una nueva página para las transferencias: el hecho de necesitar una nueva página viene │
│
determinado porque la hasta entonces utilizada se solapa con el buffer
│
el contenido del buffer!.
│
que quede salvado para
│
restaurado).
¡y es preciso restaurar │
Además, hay que volver a salvar el contexto de manera inmediata para │
otra
ocasión
(o para cuando se acabe el acceso al disco y haya de ser │ │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
En principio, no se recomienda a nadie intentar comprender la rutina de TURBODSK para la memoria EMS (Procesa_ems): dada su complejidad, es más fácil para un programador desarrollar la suya propia que intentar entender la actual: fundamentalmente, porque los escasos 247 bytes que ocupa evidencian en qué medida el autor se ha decantado por la eficiencia en detrimento de la claridad al diseñarla. Sin embargo, las pautas que se darán pueden ser útiles. TURBODSK utiliza una técnica totalmente diferente a la de VDISK, para evitar el buffer auxiliar. En principio, debido a que TURBODSK transfiere bloques de hasta 16 Kb en cada iteración, el bucle no dará nunca más de 5 vueltas (un bloque de disco de 64 Kb puede estar comprendido en 5 páginas EMS). Al principio se salva una sola vez el contexto de la memoria expandida, antes de entrar en el bucle, volviéndose a restaurar al final del todo, también una sola vez. No se realizará esto más veces si no hay solapamientos. Por otra parte, como sólo se utiliza una página de memoria expandida a un tiempo, TURBODSK elige inteligentemente una que no colisione con la del buffer del programa principal a donde enviar/recibir los datos. En el caso en que haya colisión con la página 0, TURBODSK restaura el contexto y lo vuelve a salvar, con objeto de devolver la memoria expandida a la situación inicial y mantener la primera copia que se hizo del contexto; además, elige otra página que diste al menos 32 Kb de la página 0 (bastaría con 16 Kb, pero se hace así para evitar problemas en los redondeos si los buffers no empiezan en posiciones alineadas a párrafo). El esquema gráfico lo explica con mayor claridad. Tras la transferencia, si había habido colisión se vuelve de nuevo a restaurar y preservar el contexto, para volver al estado previo a la entrada en el bucle. Estas operaciones hacen que TURBODSK sea ligeramente más lento cuando el buffer de lectura/escritura está en memoria expandida, pero probablemente la diferencia no llegue al 1% al caso en que no hay solapamientos. El funcionamiento general consiste en ir mapeando las páginas de memoria expandida una a una, considerando las tres posibilidades: al principio, puede ser necesario transferir un fragmento del final de la primera página mapeada; después, puede ser preciso transferir algunas páginas enteras y, por último, una parte inicial de la última página. Esto significa que TURBODSK sólo mapea (y una sola vez) las páginas estrictamente necesarias para la transferencia; además, no transfiere sector a sector sino el mayor número posible que pueda ser transferido de una sola vez y se evita la necesidad de hacer doble
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
transferencia (con el consiguiente ahorro, además, del buffer de 512 bytes). Este algoritmo permite que TURBODSK sea tan rápido como cabría esperar de un disco virtual, incluso al trabajar con memoria EMS. De hecho, al transferir 32 bits en los 386 y superiores, la velocidad que desarrolla en memoria EMS no se queda muy por detrás de la que consigue el controlador de memoria XMS en estas máquinas. El inconveniente de la rutina de gestión de memoria EMS en TURBODSK es, como se dijo antes, la complejidad: está optimizada para reducir en lo posible el tamaño, por lo que puede resultar de difícil comprensión. Por ejemplo, posee una subrutina encargada de acceder al controlador de memoria que, en caso de fallo, altera la pila para retornar directamente al programa principal y no al procedimiento que la llamó. Estas maniobras que aumentan la complejidad y dificultan posteriores modificaciones del código, están bastante documentadas en el listado, por lo que no habrá más referencias a ellas. Hay que reconocer que por 30 ó 40 bytes más la rutina podría haber sido todo un ejemplo de programación estructurada, pero cuando se escribió TURBODSK, entre los principales objetivos estaba reducir el consumo de memoria. Esta rutina es además la misma para leer que para escribir: en el caso de la escritura, se limita simplemente a intercambiar la pareja DS:SI con la ES:DI antes y después de realizar la transferencia. RAMDRIVE, por su parte, cuenta con un algoritmo con un rendimiento similar al de TURBODSK, pero totalmente distinto. La principal diferencia es que RAMDRIVE mapea varias páginas consecutivas, lo que le permitiría en ocasiones ser levemente más rápido que TURBODSK; sin embargo, como no transfiere con 32 bits, en los 386 y superiores es notablemente más lento que TURBODSK. RAMDRIVE necesita que las páginas de memoria expandida sean contiguas (podrían no serlo en EMS 4.0), emitiendo un error de instalación en caso contrario; el método de TURBODSK es algo más tolerante: no necesita que sean estrictamente contiguas, basta solo con que entre las 4 primeras haya alguna que diste de la primera al menos 32 Kb, la cual asigna dinámicamente. Para terminar con el análisis de la gestión de este tipo de memoria, hablaremos algo acerca de la manera de comunicarse con el controlador de memoria. En principio, lo más normal es cargar los registros e invocar la INT 67h, analizando el valor en AH para determinar si ha habido error. Sin embargo, se ha constatado que RAMDRIVE, ante un código de error 82h (EMM ocupado) vuelve a reintentar de manera indefinida la operación, excepto en el caso de la función 40h (obtener el estado del gestor) utilizada en la instalación, en la que hay sólo 32768 intentos. Este comportamiento parece estar destinado a mejorar la convivencia con entornos multitarea, en los que en un momento dado el controlador de memoria puede estar ocupado pero algo más tarde puede responder. Por tanto, también se incorporó esta técnica a TURBODSK. Un último aspecto a considerar está relacionado con el uso de instrucciones de 32 bits en las rutinas de TURBODSK: en principio han sido cuidadosamente elegidas con el objetivo de economizar memoria. Por ello, la instrucción PUSHAD (equivalente a PUSHA, pero con los registros de 32 bits) venía muy bien para apilar de una sola vez todos los registros de propósito general. Sin embargo, la correspondiente instrucción POPAD no opera correctamente, por desgracia, en la mayoría de los 386, aunque el fallo fue corregido en las últimas versiones de este procesador (los 386 de AMD también lo tienen, ¡qué curioso!). Se trata de un fallo conocido por los fabricantes de software de sistemas, pero poco divulgado, aunque tampoco es muy grave: básicamente, el problema reside en que EAX no se restaura correctamente. El fallo de esta instrucción, al parecer descubierto por Jeff Prothero está ligado a las instrucciones que vienen inmediatamente a continuación, y está demostrado que poniendo un NOP detrás -entre otros- nunca falla. En las rutinas de TURBODSK se observa también que los registros de 32 bits empleados en la transferencia son enmascarados para que no excedan de 0FFFFh, ya que podrían tener la parte alta distinta de 0 y ello provocaría una trágica excepción del controlador de memoria al intentar un acceso -por otra parte, de manera incorrecta- fuera de los segmentos de 64Kb. Trabajando con memoria XMS. La memoria extendida vía XMS, implementada por HIMEM.SYS y algún controlador de memoria expandida, es notablemente más sencilla de manejar que la expandida. En el caso de VDISK, se emplea el tradicional método de la INT 15h de la BIOS para transferir bloques en memoria extendida. Pese a ello, el VDISK de DR-DOS 6.0 es una versión moderna del legendario controlador, y puede convivir satisfactoriamente con WINDOWS y con los programas que soportan la especificación XMS debido a que toma las precauciones necesarias. En TURBODSK se prefirió emigrar a los servicios del controlador XMS (rutina
CONTROLADORES DE DISPOSITIVOS
203
Procesa_xms, al final del listado), al igual que RAMDRIVE, ya que casi todas las máquinas que poseen memoria extendida en la actualidad tienen instalado el controlador XMS. Las que no lo tienen instalado, se les puede añadir fácilmente (solo requiere al menos DOS 3.0). Las ventajas del controlador XMS son múltiples. Por un lado, la velocidad es bastante elevada, ya que en los 386 y superiores utiliza automáticamente instrucciones de transferencia de 32 bits. Por otro, es extraordinariamente sencillo el proceso: basta crear una estructura con la información del bloque a mover de la memoria convencional hacia/desde la extendida e invocar la función 0Bh. La diferencia entre TURBODSK y RAMDRIVE es que el primero crea la estructura sobre la pila (solo son 8 palabras). La ventaja de ello es que las instrucciones PUSH consumen mucha menos memoria que las MOV; por otro lado así no hace falta reservar el buffer para la estructura. Hablando de pila: todos los programas residentes que utilizan servicios XMS suelen definir una pila interna, ya que la llamada al controlador XMS puede crear una trama de pila de hasta ¡256 bytes!. Sin embargo, RAMDRIVE no define una pila propia, y no es difícil deducir por qué: el DOS, antes de acceder a los controladores de dispositivo, conmuta a una de sus pilas internas, que se supone suficientemente grande para estos eventos. Por el mismo motivo, se decidió no incorporar una pila a TURBODSK, aunque hay discos virtuales de dominio público que sí lo hacen. Es fácil comprobar la pila que el DOS pone a disposición de los drivers: basta hacer un pequeño programa en DEBUG que acceda al disco virtual (por ejemplo, vía INT 25h) y, sabiendo dónde reside éste, poner un punto de ruptura en algún lugar del mismo con una INT 3. Al ejecutar el programa en DEBUG, el control volverá al DEBUG al llegar al punto de ruptura del disco virtual, mostrando los registros. En MS-DOS 5.0, donde se hizo la prueba, todavía quedaban más de 2 Kb de pila en el momento del acceso al disco virtual (el tamaño de la pila es el valor de SP). Finalmente, decir que debido a que utilizan la misma memoria de la misma manera, TURBODSK y RAMDRIVE desarrollan velocidades prácticamente idénticas al operar en memoria extendida. Hay sin embargo un detalle curioso que comentar: RAMDRIVE instala una rutina que intercepta las llamadas al controlador XMS. Hacer esto es realmente complicado, teniendo en cuenta que el controlador XMS no se invoca por medio de una interrupción, como los demás controladores, sino con un CALL inter-segmento. Por ello, es preciso modificar parte del código ejecutable del propio controlador de memoria. Esto es posible porque el controlador XMS siempre empieza también por una instrucción de salto lejana de cinco bytes (o una corta de dos o tres, seguida de NOP's, considerando RAMDRIVE todas estas diferentes posibilidades). RAMDRIVE intercepta la función 1 (asignar el HMA), pero comprobando también si AL vale 40h: esto significa que está intentando detectar la llamada de algún programa en concreto, ya que el valor de AL es irrelevante para el controlador XMS. En ese caso, en lugar de continuar el flujo normal, determina la memoria extendida libre y hace unas comprobaciones, pudiendo a consecuencia de ello retornar con un error 91h (el HMA ya está asignado). Todo parece destinado a mejorar la compatibilidad con algún programa, probablemente también de Microsoft, aunque ningún otro disco virtual -TURBODSK entre ellos- realiza estas extrañas maniobras. Esta forma de trabajar es lo que podríamos denominar programación a nivel de cloacas, usando código basura para tapar la suciedad de otros programas previos. Trabajando con memoria convencional. En memoria convencional hay pocas diferencias entre todos los discos virtuales. Como no hay controladores de memoria por el medio, la operación del disco siempre resultará exitosa. La diferencia de TURBODSK frente a RAMDRIVE y VDISK es que en los 386 y superiores utiliza de nuevo transferencias de 32 bits. Sin embargo, esto no es demasiado importante, ya que estas máquinas suelen tener la memoria convencional destinada a cosas más útiles que un disco. En los PC/XT el rendimiento de todos los discos virtuales suele ser muy similar, excepto algún despistado de dominio público que mueve palabras de 8 bits. La rutina Procesa_con ubicada al final de TURBODSK se encarga de gestionar esta memoria. LA SINTAXIS DE TURBODSK. TURBODSK puede ser ejecutado desde el DOS o el CONFIG.SYS indistintamente, y además en el primer caso de manera repetida, para cambiar las características de un disco ya definido. En cualquier caso, el programa habrá de ser instalado obligatoriamente en el CONFIG.SYS. Repasaremos la sintaxis que admite antes de proceder a estudiar la instalación del programa: DEVICE=TDSK.EXE [tamaño [tsect [nfich [tclus]]]] [/E] [/A|X] [/M] [/F]
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Alternativamente, desde el DOS: TDSK [U:] [tamaño [tsect [nfich [tclus]]]] [/E] [/A|X] [/C] [/M] [/F] El tamaño del disco ha de estar entre 8 y 65534 Kb (para exceder de 32 Mb hacen falta sectores de al menos 1024 bytes). Se puede omitir en el CONFIG si no se desea definir el disco en ese momento, y desde el DOS si solo se quiere obtener información del disco definido. Tsect es el tamaño de sector, entre 32 y 2048 bytes en potencias de dos. Sin embargo, DR-DOS no opera correctamente con sectores de menos de 128 bytes, aunque sí el MS-DOS 5.0, que por otro lado no soporta sectores de más de 512 bytes (DR-DOS sí). El número de ficheros del directorio raíz viene a continuación (nfich) y ha de estar comprendido entre 1 y 65534: TURBODSK lo ajusta para aprovechar totalmente los sectores empleados en el directorio. Aviso: con sectores de 32 bytes, el MS-DOS 5.0 toma el nº de entradas del directorio raíz como módulo 256. El tamaño de cluster (sectores/cluster) es el último parámetro numérico, debiendo estar comprendido entre 1 y 255. Sin embargo, el MS-DOS no soporta tamaños de cluster que no sean potencia de 2 (DR-DOS sí). Los parámetros numéricos intermedios que se desee omitir se pueden poner a cero, para que TURBODSK tome valores por defecto. TURBODSK sólo necesita que se indique el tamaño del disco, ajustando los demás parámetros de la manera más aconsejable. De lo expuesto anteriormente se deduce que es sencillo crear discos que no operen correctamente, si no se tienen en cuenta las limitaciones de los diversos sistemas operativos, aunque esto es responsabilidad del usuario y el programa no limita su libertad. Con /E se fuerza la utilización de memoria extendida, aunque es un parámetro un tanto redundante (TURBODSK utiliza por defecto esta memoria). /A y /X sirven, indistintamente, para utilizar memoria expandida. Hasta ahora, la sintaxis de TURBODSK es idéntica a la de RAMDRIVE y VDISK, si se exceptúa el parámetro adicional del tamaño de cluster. Sin embargo, TURBODSK soporta la presencia de varias unidades instaladas simultáneamente: desde el DOS puede ser preciso indicar también la letra de la unidad a tratar, aunque por defecto se actúa siempre sobre la primera. También se puede indicar /C desde el DOS para forzar el empleo de memoria convencional en máquinas con memoria expandida y/o extendida. /M genera una salida menos espectacular, en monocromo y redireccionable (desde el CONFIG se imprime en monocromo por discreción y este conmutador actúa al revés, forzando una salida en color). La opción /F, no documentada en la ayuda del programa, permite elegir el número de FATS (1 ó 2). Lo normal es trabajar con una FAT, pero TURBODSK soporta la definición de 2 con objeto de permitir la creación de discos idénticos a los estándar del DOS. Así, con un pequeño programa de utilidad es fácil montar ficheros imagen de disquetes (creados con el DISKCOPY de DR-DOS 6.0, con DCOPY o con otras utilidades) en un disco virtual de tamaño suficiente. Dicho volcado debe hacerse justo tras redefinir el disco y antes de realizar ningún acceso al mismo, para aprovechar el hecho de que el DOS va a ser informado de un cambio de soporte. Ejemplo de lo que puede aparecer en pantalla al definir un disco: ┌──────────────────────────┬──────────────────────────┐ │ TURBODSK 2.3 - Unidad D: │ Tamaño de sector: 512 │ ├──────────────────────────┤ Nº entradas raiz: 128 │ │ Tamaño: 512 Kbytes │ Sectores/cluster: 1 │ │ Memoria: Extendida XMS │ 1012 clusters (FAT12) │ └──────────────────────────┴──────────────────────────┘
EL PROCESO DE INSTALACIÓN DE TURBODSK. Casi el 80% del listado de TURBODSK está destinado a instalar y mantener el disco virtual en memoria. TURBODSK puede ser ejecutado desde la línea de comandos y desde el CONFIG.SYS; los procedimientos Main e Init, respectivamente, constituyen el programa principal en ambos casos. El funcionamiento del programa es muy similar en los dos casos, aunque hay ciertas diferencias lógicas. Al principio de ambas rutinas se inicializa una variable que indica si estamos en el CONFIG o en el AUTOEXEC (más en general, en la línea de comandos). Algunas subrutinas concretas actuarán de manera diferente según desde donde sea ejecutado el programa.
CONTROLADORES DE DISPOSITIVOS
203
El procedimiento Init se corresponde exactamente con la orden INIT del controlador de dispositivo, realizando todas las tareas que cabría esperar de la misma: inicializar el puntero a la tabla de BPB's (solo uno, ya que cada TURBODSK instalado controla un solo disco), el número de unidades (una), así como la memoria que ocupa el programa: al final de Init, si no se va utilizar memoria expandida se reserva espacio sólo para las rutinas de memoria convencional y extendida. Se puede definir el disco desde el CONFIG o, sin indicar capacidad o indicando un tamaño 0, instalar el driver sin reservar memoria: para definir el disco se puede ejecutar TURBODSK después desde el DOS. En cualquier caso, desde el CONFIG no se permite definir el disco en memoria convencional, ya que si así fuera no se podría desasignar en el futuro. Tampoco es muy recomendable reservar memoria extendida o expandida, para evitar una posible fragmentación de la misma (esto depende de la eficacia de los controladores de memoria) aunque sí se permite definir un disco de estos desde el CONFIG. También es vital considerar el parámetro de tamaño de sector que el usuario pueda definir, incluso aunque no se cree el disco al indicar un tamaño 0. La razón es que el DOS asigna el tamaño de sus buffers de disco para poder soportar el sector más grande que defina algún controlador de dispositivo de bloques. El MSDOS 5.0 no soporta sectores de más de 512 bytes, pero DR-DOS opera satisfactoriamente con sectores de uno o dos Kbytes, e incluso más. Sin embargo, no es recomendable utilizar sectores de más de 512 bytes, ya que el tamaño de los buffers aumenta y se consume más memoria. Empero, TURBODSK, gracias a los sectores de más de 512 bytes permitiría operar con discos de más de 32 Mb sin rebasar el límite máximo de 65535 sectores. Otro pequeño detalle: si la versión del DOS es anterior a la 3.0, se ajusta la palabra de atributos, para indicar que no se soportan las órdenes Open/Close/Remove, con objeto de parecerse lo más posible a un controlador del DOS 2.X (RAMDRIVE también se toma esta molestia). También desde el CONFIG se desvía la INT 19h. El procedimiento Main es muy similar al Init, la principal diferencia radica en que en el caso de utilizar memoria convencional hay que terminar residente, para que el DOS respete el bloque de memoria creado para contener el disco. Sin embargo, se dejan residentes sólo los primeros 96 bytes del PSP. También desde Main puede ser necesario desalojar la memoria de un disco previo, si se indica uno nuevo. Es preciso, así mismo, considerar ciertas circunstancias nuevas que no podían darse desde el CONFIG: una versión del DOS anterior a la 2.0, que el driver no haya sido instalado antes desde el CONFIG, que se indique una letra de unidad que no se corresponda con un driver TURBODSK, que el tamaño de sector exceda el máximo que permite la configuración del DOS, que se solicite memoria expandida y no se halla reservado espacio para la rutina que la soporta o que se intente redefinir el disco desde WINDOWS. Este último aspecto se consideró a raiz de los riesgos que conlleva. Supongamos, por ejemplo, que el usuario abre una sesión DOS desde WINDOWS y define un disco de media mega en memoria convencional, volviendo después a WINDOWS: WINDOWS recupera toda la memoria convencional que había asignado para su propio uso, pero TURBODSK no puede darse cuenta de esta circunstancia y, si el usuario intenta grabar algo en el disco virtual, el sistema se estrellará. La memoria virtual de WINDOWS también da problemas al crear discos en memoria expandida o extendida. Por tanto, las definiciones del disco han de hacerse antes de entrar en WINDOWS. Tampoco conviene definir el disco desde DESQVIEW, aunque si se anula de nuevo antes de abandonar DESQVIEW no habrá problemas, por lo que TURBODSK sí permite modificar el disco desde el interior de este entorno. Tanto Init como Main leen la línea de parámetros indicados por el usuario y ejecutan ordenadamente los procedimientos necesarios para definir el disco, si ésto es preciso. LAS PRINCIPALES SUBRUTINAS PARA LA INSTALACIÓN. Veremos ahora con detalle algunas rutinas importantes ejecutadas durante la instalación del disco virtual. La rutina Gestionar_ram, ejecutada sólo desde la línea de comandos del DOS, rebaja la memoria asignada al TDSK.EXE en ejecución a 96 bytes. Esto se hace así para poder utilizar después las funciones estándar del sistema para asignar memoria. Esta acrobacia provoca la creación de un bloque de control de memoria (MCB) en el offset 96 del PSP, lo cual es inocuo; también se libera el espacio de entorno por si acaso se fuera a terminar residente. Los procedimientos Errores_Dos y Errores_config comprueban algunos errores que pueden
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
producirse al ejecutar el programa desde la línea de comandos del DOS o desde el CONFIG. En el procedimiento Max_sector invocado desde Errores_Dos se comprueba si el tamaño de sector indicado excede el máximo que soporta el DOS, para lo que se utiliza la función 52h (Get List of Lists); si es así se indica al usuario que ese tamaño de sector debe definirse previamente desde el CONFIG. En la rutina TestWin se comprueba si Windows está activo, para evitar en ese caso una modificación del disco por parte del usuario. Por desgracia, hay que chequear en dos interrupciones distintas las presencia de Windows. Antes de llamar a la INT 2Fh se comprueba que esta interrupción esté apuntando a algún sitio: en el sistema DOS 2.11 en que se probó TURBODSK esa interrupción estaba apuntando a 0000:0000 y el ordenador se colgaba si no se tomaba esta precaución. También desde el DOS, el procedimiento Reside_tdsk? busca la primera unidad TURBODSK residente de todas las que puede haber en la memoria. Para ello crea una tabla con todos los dispositivos de bloque del sistema (rutina Lista_discos) y empieza a buscar desde el final hacia atrás (se trata de encontrar la primera unidad TURBODSK y no la última). Alternativamente, si se había indicado una letra de unidad, el procedimiento Obtener_segm recorre la tabla de discos para asegurarse de que esa letra de unidad es un dispositivo TURBODSK, así como para anotar la dirección donde reside. La rutina Inic_letra, ejecutada desde el CONFIG, calcula la letra que el sistema asignará a la unidad, con objeto de informar en el futuro al usuario. Desde el DOS 3.0, el encabezamiento de petición de solicitud de la orden INIT almacena este dato. Dado que DR-DOS 6.0 no inicializa correctamente el tamaño del encabezamiento de solicitud de esta orden, es más seguro verificar la versión del DOS que comprobar si este dato está definido o no, en función de las longitudes, que sería lo normal. En el caso del DOS 2.X, no hay más remedio que crear una tabla con los dispositivos de bloque del sistema y contarlos (¿a que ya sabe por qué RAMDRIVE y VDISK no informan o informan incorrectamente de la letra de unidad al instalarse en estas versiones del DOS?). El procedimiento Lista_discos, como dije con anterioridad, crea una tabla con todos los dispositivos de bloque del sistema. Para ello utiliza la valiosa función indocumentada 52h (Get List of Lists) del DOS. Por desgracia, la manera de acceder a la cadena de controladores de dispositivo varía según la versión del DOS, por lo que TURBODSK tiene en cuenta los tres casos posibles (DOS 2.X, 3.0 y versiones posteriores). En la tabla creada, con cuatro bytes por dispositivo: los dos primeros indican el segmento donde reside, el segundo el número de unidades que controla y el tercero puede valer 1 ó 0 para indicar si se trata de una unidad TURBODSK o no. El final de la tabla se delimita con un valor de segmento igual a cero. En el caso de un dispositivo TURBODSK no se anota el segmento donde reside sino la variable cs_tdsk del mismo, que indica la dirección real incluso en el caso de que el dispositivo haya sido relocalizado por QEMM a la memoria superior. La rutina Desinstala libera la memoria que ocupa un disco residente con anterioridad, inhabilitando el driver. En el caso de la memoria convencional hay que liberar tanto el segmento que ocupaba el disco como el del PSP previamente residente. El procedimiento Mem_info evalúa la memoria disponible en el sistema y toma la decisión de qué tipo y cantidad de la misma va a ser empleada. En principio se procura utilizar la memoria que el usuario indica. De lo contrario, por defecto se intenta emplear, en este orden, memoria extendida, expandida o convencional. En el caso de que no haya suficiente memoria se rebaja la cantidad solicitada, generándose un mensaje de advertencia. Si no se indica el tipo de memoria, en el caso de no haber la suficiente extendida (aunque haya algo) se utiliza la expandida, pero el recurso a la memoria convencional se evita siempre. A la memoria expandida se le asigna menos prioridad que a la extendida debido a que, en equipos 386 y superiores, normalmente es memoria extendida que emula por software la expandida: suele ser más rápido dejar directamente al controlador XMS la tarea de realizar las transferencias de bloques de memoria. El procedimiento Mem_info se apoya en tres subrutinas que calculan la cantidad disponible de cada tipo de memoria, despreciando longitudes inferiores a 8 Kb que es el tamaño mínimo del disco. La subrutina Eval_xms chequea la presencia de un controlador de memoria extendida; sin embargo, antes de llamar a INT 2Fh se toma una vez más la precaución de comprobar que esta interrupción está apuntado a algo. La subrutina Eval_ems detecta la presencia del controlador de memoria expandida buscando un dispositivo "EMMXXXX0". El método ordinario suele ser intentar abrir ese
CONTROLADORES DE DISPOSITIVOS
203
dispositivo y después comprobar por IOCTL que no se trata de un fichero con ese nombre; sin embargo, los controladores de dispositivo invocados desde el CONFIG.SYS no deben acceder a las funciones IOCTL, por lo que se utiliza el algoritmo alternativo de comprobar si esa cadena está en el offset 10 del vector 67h. En esta subrutina se comprueba además la versión del controlador: en la 4.0 y posterior hay que buscar, recuérdese, dos páginas de memoria expandida (una de ellas la 0) que disten entre sí 32 Kb. Finalmente, la subrutina Eval_con determina la memoria convencional disponible. Al principio le solicita casi 1 Mb al DOS, con objeto de que éste falle e indique cual es la cantidad máxima de memoria disponible. Seguidamente se procede a pedir justo esa memoria, para que el DOS devuelva el segmento en que está disponible, volviéndose a liberarla inmediatamente a continuación. Al final, al tamaño de ese bloque de memoria se le restan 128 Kb ya que, con memoria convencional, hay que tener la precaución de no ocuparla toda y dejar algo libre. Además, en esos 128 Kb que se perdonan será preciso que TDSK.EXE se autoreubique antes de formatear el disco, como veremos después. Con MS-DOS 5.0 se puede crear un disco virtual en memoria superior, cargando TDSK.EXE con el comando LOADHIGH: sin embargo, hay que pedir sólo exactamente la cantidad de memoria superior disponible en la máquina (o algo menos); de lo contrario el DOS asignará memoria convencional para satisfacer la demanda: dado que normalmente hay más memoria convencional libre que superior, no será preciso solicitar en estos casos, afortunadamente, 128 Kb de menos para lograr que sea asignada memoria superior (TDSK.EXE se autorelocalizará hacia la memoria convencional y permitirá emplear toda la memoria superior libre que quede). El procedimiento Mem_reserva procede a la efectiva asignación de memoria al disco, en el caso de que finalmente éste se instale, y una vez que ya se había decidido el tipo de memoria a emplear. Si se utiliza memoria expandida, desde la versión 4.0 del controlador se asigna un nombre al handle con objeto de que los programas de diagnóstico muestren una información más detallada al usuario. El afán de información no se detiene aquí: en el caso de emplear memoria extendida, TURBODSK comprueba si la creación de un handle XMS implica la aparición de otro handle EMS, lo busca y le renombra. Esto sucede con QEMM y otros controladores de memoria que no distinguen la expandida de la extendida. La subrutina Adaptar_param es una pieza clave dentro del programa: aquí se decide qué parte del disco va a ocupar el directorio, la FAT, el tipo de FAT, etc. Se toman valores por defecto o, en caso contrario, los que el usuario haya indicado, considerando todas las posibilidades de error. TURBODSK permite un elevado grado de libertad. Por ejemplo, es factible definir un directorio raíz que consuma la mitad de la capacidad del disco, clusters de hasta 31 Kbytes... evidentemente, los valores que TURBODSK asigna por defecto suelen ser bastante más operativos; pero en principio hay, como se dijo, libertad total para las decisiones del usuario. En el caso de versiones 2.X del sistema se establece un tamaño de cluster por defecto tal que nunca sea necesaria una FAT de 16 bits (no soportada por estas versiones). El algoritmo para determinar el tipo de FAT del disco consiste en considerar el número de sectores libres que quedan después de descontar el sector de arranque y el directorio raíz. Teniendo en cuenta el tamaño de cluster en bytes y que la FAT de 12 bits añade 1,5 bytes adicionales para cada cluster, se aplica esta fórmula: número de sectores libres * tamaño de sector ──────────────────────────────────────────── + 1 tamaño de cluster + 1,5
que devuelve el número de cluster más alto del disco (se añade uno ya que los clusters se numeran desde dos; por ejemplo, 100 clusters se numerarían entre 2 y 101 inclusive). Si el resultado es mayor o igual que 4086, la FAT no puede ser de 12 bits, por lo que se debe recalcular la fórmula sustituyendo el 1,5 por 2 y definiendo una FAT de 16 bits. Hay casos críticos en que una FAT de 12 bits no alcanza, pero al definirla de 16 el tamaño adicional que ella misma ocupa hace que el número de cluster más alto baje de 4086: en estos casos se reserva espacio para una FAT de 16 bits que luego será realmente de 12; sin embargo, se trata de una circunstancia muy puntual y poco probable. En principio, con los tamaños de cluster y sector que TURBODSK asigna por defecto, la FAT será de 12 bits a menos que el disco exceda los 8 Mb. Conviene hacer hincapié en que los discos con 4085 clusters o más (con número de cluster más alto 4086 o superior) tienen una FAT de 16 bits. Por desgracia, casi todos los libros consultados (y ya es mala suerte) tienen esta información incorrecta: para unos, la FAT16 empieza a partir de 4078 clusters; para otros, a
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
partir de 4086; otros, no distinguen entre nº de clusters y nº más alto de cluster... hay un auténtico caos ya que las fuentes de información se contradicen. Al final, lo más sencillo es crear discos virtuales con 4084/4085 clusters y espiar qué hace el DOS. Es muy fácil: se graban algunos ficheros y se mira la FAT con algún programa de utilidad (PCTOOLS, DISKEDIT). A simple vista se deduce si el DOS asigna una FAT de 12 o de 16 bits. Tanto el MS-DOS 3.1 como el 3.3, 4.0 y 5.0; así como el DR-DOS 3.41, 5.0 y 6.0 asignan FAT's de 16 bits a partir de 4085 clusters inclusive. Por fortuna, todas las versiones del DOS parecen comportarse igual. Asignar el tipo de FAT correcto es vital por muchos motivos; entre otros por que si fuera excesivamente pequeña el disco funcionaría mal. Sin embargo, los CHKDSK de casi todas las versiones del DOS (excepto el del MS-DOS 3.30 y el de DR-DOS 6.0), incluido el de MS-DOS 5.0, poseen una errata por la que suponen que los discos de 4085 a 4087 clusters tienen una FAT de 12 bits, con lo que pueden estropear el disco si el usuario ejecuta un CHKDSK/F. Esto es un fallo exclusivo de CHKDSK que debería ser corregido en el futuro, por lo que no se ha evitado estos tamaños de disco (casi nadie ejecuta CHKDSK sobre un disco virtual, y en ese caso no va a tener tan mala suerte). Resulta curioso este fallo de CHKDSK, teniendo en cuenta que es un programa que accede a la FAT y que 4087 (0FF7h) es precisamente la marca de cluster defectuoso en una FAT de 12 bits, ¡nunca un número de cluster cualquiera!. Por ejemplo, con un comando del tipo TDSK 527 128 0 1 /E (no vale la memoria expandida, ya que redondearía a 528 Kb), se puede crear un disco de 4087 clusters en el que los CHKDSK de las versiones del DOS señaladas informen incorrectamente de la presencia de errores (si decide hacer pruebas, retoque el número de entradas del directorio para variar ligeramente el número de clusters). Una vez definidos los parámetros básicos de la estructura del disco, el procedimiento Preparar_bpb inicializa el BPB, actualizándolo al nuevo disco; también se indica que ha habido cambio de disco. El procedimiento Prep_driver se encarga de copiar el BPB recién creado sobre el del driver residente en memoria, así como de actualizar las variables de la copia residente en memoria, copiando simplemente las del TDSK.EXE en ejecución. También se instala la rutina necesaria para gestionar el disco, según el tipo de memoria a emplear por el mismo: esta rutina se instala por partida doble, tanto en la copia residente como en el propio código del TDSK.EXE que se ejecuta (la rutina de gestión de memoria será accedida directamente al formatear el disco virtual). En el caso de emplear memoria convencional, antes de formatear el disco hay que tomar precauciones. El motivo radica en el hecho de que el disco probablemente comience en el offset 96 del PSP. Por tanto, si se inicializa sin más el sector de arranque, la FAT y el directorio raíz (en eso consiste simplemente el formateo) el propio TDSK.EXE se autodestruirá. Para evitarlo, TDSK.EXE se copia a sí mismo en esos 128 Kb libres que siempre hay, incluso en el peor de los casos, pasando a ejecutarse en ese nuevo destino por medio de una instrucción RETF que carga CS al retornar (procedimiento Relocalizar). Se copia todo, pila incluida (se actualiza también SS). No habrá problemas, ya que TDSK.EXE es realmente un programa COM disfrazado de EXE, que carece de referencias absolutas a segmentos. Se toma la precaución de relocalizar TDSK.EXE (que no ocupa más de 12 Kb) justo a la mitad de ese área de 128 Kb, para evitar solapamientos consigo mismo en casos críticos. Se puede llegar a sobreescribir parte de la zona transitoria del COMMAND.COM, lo cual provoca simplemente su recarga desde disco. Ciertamente, no es muy ortodoxo que un programa en ejecución vaya dando paseos por la memoria del PC, pero estas cosas se pueden hacer en MS-DOS y nadie puede cuestionar la efectividad del método. Los programadores más conservadores han tenido suerte de que el adaptador de vídeo monocromo cuente con sólo 4 Kb. ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ │
│
ESQUEMA DE LA AUTORELOCALIZACIÓN DE TDSK.EXE (UN CASO CONCRETO)
├──────────────────────────────────────────────────────────────────────────────────────────────────┤ │ │
│ │
Casi todas las cifras son arbitrarias, a modo de ejemplo práctico.
│
│
│ │
│ 1 Mb
┌─────────────────────────┐
1 Mb
┌─────────────────────────┐
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
640 Kb
├─────────────────────────┤
640 Kb
├─────────────────────────┤½─┐
│
203
CONTROLADORES DE DISPOSITIVOS
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
aprox. 588 Kb ┌─¾├─────────────────────────┤
│
│
│
│
│
│
│
│
│
│ │
│
│
nueva pila de TDSK.EXE │
│
│
│
│
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│
│
│
┌────────¾ │
│
│
│ 128 Kb │
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
├─────────────────────────┤
│
│
│
│
│
│
│
│ PSP TDSK.EXE (256 bytes)│
│
│
│
│
│
│
└─¾├─────────────────────────┤
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
.
.
.
│
.
.
.
│
.
.
.
│
│
│
512 Kb
│
64 Kb libres (área de
│
│
│
│
seguridad)
│
│
│
├─────────────────────────┤½─┘
│
│
│
.
.
.
│
.
.
.
│
.
.
.
│
│ │
│
│
├─────────────────────────┤½─┐
│
│
│
│
│
│
│
│
│
├─────────────────────────┤
│
│
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
│
│
│
│
│
│
│
│
│ ────┘
│
Área de almacenamiento │
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
├─────────────────────────┤
│
├─────────────────────────┤
│
│
│ PSP TDSK.EXE (256 bytes)│
│
│ PSP TDSK.EXE (96 bytes) │
│
│
├─────────────────────────┤½─┘
├─────────────────────────┤
│
│
│
│
│
│
└─────────────────────────┘
│
Después
│
│ │
0 Kb
│
576 Kb
TDSK.EXE
pila de TDSK.EXE
TDSK.EXE
DOS/BIOS
│
└─────────────────────────┘
0 Kb
│ Futuros programas
del disco virtual
DOS/BIOS
Antes
│
│
│
│ │
│ En este esquema se muestra la autorelocalización
de
TDSK.EXE
en
memoria
en el caso de │
los
bloques
de control de │
│
definirse el disco en memoria convencional.
│
memoria ni otros detalles. Si la memoria está suficientemente fragmentada
│
programas residentes tras definir algún disco)
│
respetar 128 Kb al final del bloque que nos asigna el DOS ni tampoco quizá relocalizar TDSK.EXE;│
│
sin embargo, el programa no está optimizado hasta ese extremo.
│
hacia la frontera de los 576 Kb en lugar de los 512 se debe a evitar problemas de colisiones en │
│
casos críticos de cantidad de memoria libre y tamaño de disco solicitado por el usuario.
No
están puede
reflejados que
no
fuera El
(por haber instalado │
estrictamente
hecho
de
necesario │
relocalizar TDSK │ │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
El procedimiento Formatear_tdsk es extraordinariamente sencillo: se encarga de realizar lo que desde hace algún tiempo ha dado en llamarse formateo rápido. Evidentemente, en un disco virtual no es preciso verificar la memoria buscando posibles sectores defectuosos. Basta copiar un sector de arranque y poner a 0 la FAT y el directorio raíz, con la excepción de los primeros 3 bytes de la FAT (4 si es de 16 bits) y los 32 primeros bytes del directorio raíz, que contienen una entrada con la etiqueta de volumen. TURBODSK se toma la molestia de consultar la fecha y hora actuales para inicializar la etiqueta de volumen. Para grabar los sectores en el disco no se puede emplear el elegante método de llamar a la INT 26h: aunque el driver residente ya está totalmente preparado para operar, si se reserva memoria desde el CONFIG.SYS el DOS no está aún listo para ejecutar la INT 26h ya que el driver aún no está encadenado a la lista de dispositivos; por ello es preciso acceder directamente al mismo (sin embargo, una vez terminado el arranque del ordenador no hubiera habido problema alguno). Hablando de acceso directo al disco, otra ventaja de no utilizar INT 25h/INT 26h es que Windows 95 no permite un uso directo de estas funciones. Los programas que acceden a estas interrupciones son considerados inadecuados. TURBODSK puede funcionar bajo Windows 95, sin obligar al usuario a reconfigurar nada, gracias entre otros motivos a que no utiliza INT 26h.
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Con MS-DOS 2.11 y 3.1 hubo bastantes problemas, ya que estos sistemas no detectan muy bien el cambio de disco aunque la rutina MEDIA CHECK del controlador de dispositivo se lo indique: son versiones del DOS muy desconfiadas que además comprueban el byte descriptor de medio. Es de suponer que cuando el disco informa que ha habido cambio, estas versiones invalidarán los buffers asociados a él; sin embargo, si creen que se trata de un disco del mismo tipo no se molestan en actualizar el BPB. Por ello, con estas versiones, tras el formateo TURBODSK hace dos cambios de disco consecutivos, con modificación del byte descriptor de medio entre ambos. El hecho de hacer un segundo cambio se debe al interés de restaurar el byte descriptor de medio inicial. Además, el DOS 2.11 probado necesitaba dos cambios en cualquier caso: si no, no se tomaba en serio el cambio de disco. Entre cambio y cambio, se pregunta al sistema el espacio libre en disco para forzar un acceso al mismo. El procedimiento renombrar_mcb cambia el nombre del bloque de memoria de TDSK.EXE: en el caso de que el disco ocupe memoria convencional/superior, el comando MEM del sistema operativo indicará claramente que se trata de TDSK y además qué unidad controla. Es una tontería, pero mola. AMPLIACIONES DE TURBODSK Después de esta completa exposición sobre las rutinas que componen TURBODSK, espero que el lector esté suficientemente preparado para entender en conjunto el funcionamiento del programa y para crear unidades de disco por su cuenta. Una posible mejora de TURBODSK sería evitar la pérdida de datos al redefinir el disco, tratándose por ejemplo de aumentar su capacidad. Es complejo añadir esta optimización, ya que la arquitectura del nuevo disco puede cambiar demasiado (nuevo tamaño de FAT e incluso tipo de la misma). Además, el usuario iba a tener muchos problemas siempre, ya que sería muy frecuente que cuando tratase de reducir el tamaño del disco éste estuviera demasiado lleno. En general, los discos virtuales redimensionables que soportan una redefinición sin pérdida de datos, suelen permitir esto de manera limitada y bajo circunstancias concretas. Lo que sí sería más interesante es crear un disco virtual con asignación de memoria en tiempo real: cuando el usuario pretende crear un fichero, habilitar el espacio suficiente. Sin embargo, esto significa unir las complicaciones anteriores a otras nuevas, complicaciones que restarían velocidad al disco virtual, además de la dificultad de implementarlas que desanima al programador más audaz. Por otra parte, no está muy claro que el MS-DOS sea un sistema adecuado para soportar tal disco: al final, el proyecto podría quedar descartado en la fase de análisis (si es que alguien acepta el reto).
203
CONTROLADORES DE DISPOSITIVOS
;┌───────────────────────────────────────────────────────────────────┐
;
documentado,
│
;
en el fichero DOC quién lo ha realizado.
;│
indicando claramente en el listado y
;│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▄ │ ;│
▀▀▒▒█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │
;│
▒▒█
▒▒▄ ▒▒▄
▒▒▒▒▄
;│
▒▒█
▒▒█ ▒▒█
▒▒█▀▒▒▄ ▒▒█▀▒▒▄ ▒▒▄▀▒▒▄ ▒▒█▀▒▒▄ ▒▒█▀▀▀▀ ▒▒█ ▒▒█▀ │
;│
▒▒█
▒▒█ ▒▒█
▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█
;│
▒▒█
▒▒█ ▒▒█
▒▒▒▒▄▀▀ ▒▒▒▒▄▀▀ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒▒▒▒▒▄ ▒▒▒▒█▀
│
;│
▒▒█
▒▒█ ▒▒█
▒▒█▀▒▒▄ ▒▒█▀▒▒▄ ▒▒█ ▒▒█ ▒▒█ ▒▒█
│
;│
▒▒█
▒▒█ ▒▒█
▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒▄ ▒▒█ ▒▒█ ▒▒▄
;│
▒▒█
▒▒▒▒▒▒▒▄ ▒▒█ ▒▒█ ▒▒▒▒▄▀▀
;│
▀▀
▀▀▀▀▀▀▀
▀▀
▒▒▒▒▄
▀▀
▒▒▄
▀▀▀▀
▒▒▒▒▄
▒▒▒▒▒▒▄ ▒▒▄
▒▒█▒▒█▀
▀▀▀▒▒█ ▒▒█▒▒▄
▀▒▒▄▀▀ ▒▒▒▒▒█▀ ▒▒▒▒▒▒█ ▒▒█ ▀▀
▀▀▀▀▀
▒▒▄ │
▀▀▀▀▀▀
│
Versión 2.3
; apilar lista de registros
PUSH rm ENDM ENDM
▀▀ │
░░▒▒▓▓██▓▓▒▒░░
XPOP
│
;│
│
;│
│
;│
MACRO regmem IRP rm,
│
│ ░░▒▒▓▓██▓▓▒▒░░
XPUSH
▒▒▄ │
▀▀
;│ ;│
; ------------ Macros de propósito general
CONTROLADOR DE DISCO VIRTUAL PARA SISTEMAS DOS Y WINDOWS 3
MACRO regmem
; desapilar lista de registros
IRP rm, POP rm ENDM
│
ENDM
│
;│ ;│
│
* * * Programa de Dominio Público * * *
; ------------ Estructuras de datos
│
;│
│
cab_PETICION
STRUC
;│
Grupo Universitario de Informática. Facultad de Ciencias.
│
tamano
DB
?
; los comandos de la cabecera
;│
Apartado 6062 - Valladolid (España)
│
unidad
DB
?
; de petición
│
orden
DB
?
;│
(C) 1992-1995
Ciriaco García de Celis.
;│
; parte inicial común a todos
;│
Internet Email:
[email protected]
│
estado
DW
?
;│
FidoNet:
2:341/21.8
│
dos_info
DB
8 DUP (?)
│
cab_PETICION
ENDS
cab_INIT_BBPB
STRUC
;│ ;│
Mensajes en alemán cortesía de Axel Christoph Frinke
│
;│
Internet Email:
│
[email protected]
│
;│
;└───────────────────────────────────────────────────────────────────┘
;
Aviso:
Este programa contiene instrucciones exclusivas de los
;
procesadores 386 y superiores.
;
fichero
;
compatibilidad con los procesadores 8086 y 286:
EXE,
Debe ser ensamblado como
de la siguiente manera,
para asegurar la
; ;
(TYPE cab_PETICION) DUP (?)
DB
?
; número de unidades definidas
fin_resid_desp DW
?
; área que quedará residente
fin_resid_segm DW
?
bpb_cmd_desp
DW
?
; línea de órdenes del CONFIG
bpb_cmd_segm
DW
?
; y puntero al BPB
nuevo_disco
DB
?
; (DOS 3+) (0-A:, 1-B:,...)
cab_INIT_BBPB
ENDS
- Con TASM 2.0:
;
TASM tdsk /m3
;
TLINK tdsk
cab_MEDIACHECK STRUC
; ;
num_discos
; para comandos INIT/BUILD_BPB
DB
- Con MASM 6.0
(versiones anteriores de MASM generarían
;
un disco virtual que requeriría
un
386
o
superior,
;
además habría que mover las directivas
;
el tipo de procesador y colocarlas con «peligro»):
que
controlan
(TYPE cab_PETICION) DUP (?)
media_descrip
DB
?
; descriptor de medio
cambio
DB
?
; 1: no cambiado, 0FFh:sí, 0:?
cab_MEDIACHECK ENDS
cab_READ_WRITE STRUC DB
(TYPE cab_PETICION) DUP (?)
DB
?
; descriptor de medio
transfer_desp
DW
?
; dirección de transferencia
transfer_segm
DW
?
; ;
ML /Zm tdsk.asm
; ;
o alternativamente:
; estructura para MEDIA CHECK
DB
transfer_sect
DW
?
; nº de sectores a transferir
;
ML /c /Zm tdsk.asm
transfer_sini
DW
?
; primer sector a transferir
;
TLINK tdsk
cab_READ_WRITE ENDS
;
; ;
La ventaja de TLINK frente a LINK es que el fichero
;
ejecutable ocupa
;
ubicada al final del programa
2 Kbytes
menos en disco (a la tabla
;
en la cabecera del fichero EXE y no ocupando disco).
se
le
asigna
_PRINCIPAL
SEGMENT ASSUME CS:_PRINCIPAL, DS:_PRINCIPAL
; ;
; ************ Disco virtual: inicio del área residente.
memoria
IMPORTANTE:
Cualquier cambio realizado en el programa debe ser
203
tipo_drive
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
ems_pagni
DB
; bit 15 a 0: dispositivo de bloques
xms_driver
LABEL DWORD
; dirección del controlador XMS, en el
; bit 14 a 0: sin control IOCTL
xms_desp
DW
?
; caso de emplear memoria XMS.
; bit 13 a 0: formato IBM
xms_segm
DW
?
cpu386
DB
OFF
; a ON si 386 ó superior
f_tdsk_ctrl
EQU
$
; final del área a actualizar
letra_unidad
DB
?
; letra ASCII del disco ('C', 'D',...)
bpb_ptr
DW
bpb
; puntero al BPB del disco
rutina_larga
DB
OFF
; a ON si reservado espacio en
DD
-1
; encadenamiento con otros drivers
DW
0800h
; palabra de atributo:
?
; nº de página física alternativa
; bit 11 a 1: soportados Open/Close ;
y Remove (DOS 3.0+)
DW
estrategia
; rutina de estrategia
DW
interrupcion ; rutina de interrupción
DB
1
; número de unidades
; ------------ Variables y tablas de datos globales fijas. Estas ;
variables no serán movidas de sitio en otras versiones
;
de TURBODSK, con objeto de facilitar un control externo
;
del disco virtual por parte de otros programas. Todo lo
;
que está dentro del «área a actualizar» será copiado
; memoria para la larga rutina de
;
sobre el TURBODSK residente al redefinir el disco, para
; gestión de memoria EMS.
;
inicializar todas las variables precisas.
cs_tdsk
DW
; ------------ Variables internas de TURBODSK; su ubicación podría ;
cambiar en futuras versiones del programa.
; de tal manera que parte de la cabecera queda
pcab_peticion
LABEL DWORD
; en memoria convencional, con el dispositivo
pcab_pet_desp
DW
0
; completo en la memoria superior, en la que es
pcab_pet_segm
DW
0
p_rutinas
LABEL WORD
? ; Segmento de TDSK. Con QEMM-386, los drivers ; pueden ser relocalizados en memoria superior
; puntero a la cabecera de petición
; ejecutado. Tras la instalación, QEMM copia en ; memoria convencional los primeros 18 bytes de
id_tdsk
DB
DW
init
; actualizándola. Pese a que la cadena de
DW
media_check
; dispositivos del sistema pasa por la memoria
DW
build_bpb
; convencional en este caso, esta variable nos
DW
ioctl_input
; permite conocer la dirección REAL en memoria
DW
read
; superior (o en cualquier otra) de TURBODSK,
DW
read_nowait
; que así es compatible con el LOADHI de QEMM.
DW
input_status
DW
input_flush
DW
write
DW
write_verify
DW
output_status
DW
output_flush
DW
ioctl_output
DW
open
; DOS 3.0+
DW
close
; DOS 3.0+
DW
remove
; DOS 3.0+
EQU
0FAh
"TDS23" ; esto es TURBODSK 2.3 y no otro ; controlador de dispositivo
num_ordenes
i_tdsk_ctrl
tipo_soporte
DB
EQU
DB
; tabla de rutinas del controlador
; la cabecera, entre los que está esta palabra,
10h
$
0FFh
; nº de órdenes soportadas
; inicio del área a actualizar
; 0: disco no formateado ; 1: se emplea memoria XMS 2.0+ ; 2:
"
"
"
; 3:
"
"
" convencional
EMS 3.2+
media
; byte descriptor de medio utilizado por ; TURBODSK. No es 0F8h como en los discos ; virtuales del sistema ya que TURBODSK
; 0FFh: aún no ejecutada INIT
; no es un dispositivo fijo. Este byte no cambiado
DB
?
; al formatear el disco virtual se pone
; es empleado por los discos estándar del
; a 0FFh (para indicar cambio de disco)
; dos y al ser mayor de 0F7h no provoca ; mensajes extraños con antiguos CHKDSKs.
mem_handle
tdsk_psp
DW
DW
?
?
; para memoria EMS/XMS; si se utiliza ; memoria convencional, apunta al
bpb
LABEL BYTE
; Estos valores del BPB son arbitrarios:
; segmento donde empieza el disco
bytes_sector
DW
512
; se inicializarán si se define el disco
sect_cluster
DB
1
; al instalar desde el CONFIG; en caso
; segmento del PSP residente si se
sect_reserv
DW
1
; contrario, como son correctos, el DOS
; utiliza memoria convencional
num_fats
DB
1
; no tendrá problemas para realizar sus
entradas_raiz
DW
128
; cálculos internos iniciales al instalar
ems_pagina0
DW
?
; segmento de página EMS (si se emplea)
num_sect
DW
128
; el driver. En concreto, el tamaño de
ems_paginai
DW
?
; segmento alternativo
media_byte
DB
media
; sector influye de manera directa en el
203
CONTROLADORES DE DISPOSITIVOS
sectores_fat
DW
4
fin_bpb
EQU
$
; tamaño de los buffers de disco del DOS.
input_status:
; tratamiento idéntico
input_flush: output_status:
; ------------ Rutina de estrategia del disco virtual.
output_flush: ioctl_output:
estrategia
PROC
FAR
open:
MOV
CS:pcab_pet_desp,BX
close:
MOV
CS:pcab_pet_segm,ES
retorno_ok:
RET
build_bpb:
MOV
[BX].bpb_cmd_desp,OFFSET bpb
MOV
[BX].bpb_cmd_segm,CS
JMP
retorno_ok
MOV
AX,8103h
; orden no soportada
AH,3
; fin de función, indicar
; no hay error, ignorar orden
RET estrategia
ENDP
; ------------ Rutina de interrupción del disco virtual. TURBODSK, ;
al igual que RAMDRIVE o VDISK, no define una pila
;
interna. Es responsabilidad del DOS que ésta tenga el
;
tamaño adecuado (con el disco en memoria XMS, el
;
controlador XMS puede requerir hasta 256 bytes de
;
pila). TURBODSK no consume más de 64 bytes de pila en
;
ningún momento, y sólo alrededor de 48 antes de llamar
;
al controlador XMS cuando se emplea esta memoria.
interrupcion
PROC
ioctl_input:
RET
remove:
nueva_int19
orden_ok:
no_test_fmt:
exit_interr:
RET
; «controlador ocupado»
PROC
; Interceptar reinicialización
.286
FAR
XPUSH
PUSHA
LDS
BX,CS:pcab_peticion
XPUSH
MOV
AL,[BX].orden
; AL = orden
XOR
AX,AX
MOV
AH,0
; AX = orden
MOV
ES,AX
CMP
AL,CS:num_ordenes
CMP
AL,CS:tipo_soporte ; ¿Disco formateado?
JB
orden_ok
; orden soportada
JE
no_lib
MOV
AL,3
;
MOV
CS:tipo_soporte,AL ; sí: anularlo
CMP
CS:tipo_soporte,AH
CALL
procesa_io
JNE
no_test_fmt
; tipo_soporte distinto de 0
LEA
SI,ant19off
MOV
AX,8102h
; disco no formateado: error
MOV
DI,64h
JMP
exit_interr
PUSH
CS
SHL
AX,1
POP
DS
MOV
SI,AX
" desconocida (IOCTL INPUT)
no_lib:
; orden = orden * 2
; Esto es una interrupción
; no
; CF=1: liberar memoria EMS/XMS
; desplazamiento de INT 19h
CLI
XPUSH
MOVSW
XOR
BP,BP
MOVSW
MOV
AX,100h
CALL
CS:[SI+OFFSET p_rutinas]
XPOP
AND
AH,AH
JNS
exit_interr
CMP
AL,3
JE
exit_interr
MOV
[BX].transfer_sect,0 ; otro: movidos 0 sectores
MOV XPOP
XPOP
; no hubo error (bit 15 = 0)
POPA
; ejecutar orden
DB
0EAh
ant19off
DW
?
ant19seg
DW
?
; código de JMP FAR SEG:OFF
.8086 nueva_int19
ENDP
[BX].estado,AX
read:
INC
write:
; error de orden desconocida
BP
; indicar lectura (BP=1) ; escritura (BP=0)
write_verify:
RET interrupcion
MOV
ENDP init_io
PROC
; preparar registros E/S
; ------------ Las rutinas que controlan el dispositivo devuelven AX
LES
DI,DWORD PTR [BX].transfer_desp
; * direc. ES:DI
;
con la palabra de estado. Pueden cambiar todos los
LDS
AX,DWORD PTR [BX].transfer_sect
; nº sectores AX
;
registros (de 16 bits), incluídos los de segmento. A la
MOV
BX,DS
;
entrada, BP=0 y AX=100h.
MOV
SI,CS:bytes_sector
ADD
AX,BX
JNC
io_ok?
; último sector < 65536
MOV
AX,8108h
; «sector no encontrado»
media_check:
MOV
AL,CS:cambiado
; condición de «disco cambiado»
MOV
CS:cambiado,AH
; de momento ya no cambiará más
MOV
[BX].cambio,AL
io_proc:
io_no_ok:
RET io_ok?:
read_nowait:
; 1º sector ¡DS indefinido!
; conjunto de órdenes con
CMP
AX,CS:num_sect
JA
io_no_ok
; sector final ¡fuera!
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
SUB
AX,BX
ADD
BX,CX
MUL
SI
; DX(CF):AX = tamaño bloque
MOV
CX,CS:ems_pagina0
RCR
AX,1
; CF:AX/2 -> AX = palabras
MOV
DS,CX
MOV
CX,DI
XOR
DL,DL
NEG
CX
; 10000h-CX: CF=1 si CX<>0
SUB
BX,CX
; CF:CX bytes hasta fin de
JNC
rpos
; segmento = (10000h-DI)/2
NEG
BX
; valor absoluto
CMP
BX,401h
; distancia respecto página EMS
JAE
no_conflicto
; más de 16 Kb: no solapamiento ; está CX apilado
CMC
io_cx_ok:
; intentar emplear página 0
RCR
CX,1
CMP
AX,CX
JAE
io_cx_ok
MOV
CX,AX
; * tamaño: CX palabras
CALL
copia_contexto
JCXZ
io_no_ok
; CX=0 si DI=0FFFFh (fatal)
MOV
DS,CS:ems_paginai
MOV
AX,BX
; sector inicial
MOV
DL,CS:ems_pagni
; usar página alternativa
MUL
SI
; * desplazamiento en DX:AX
OR
BP,8000h
; indicar su uso
POP
CX
; * pila totalmente equilibrada
MOV
BX,AX
rpos:
CLC init_io
; AX = segmento de datos
; ¡no reinicializando!
no_conflicto:
ENDP
MOV
DH,44h
; DL = 0 ó 2 (página física)
; ------------ Area residente dependiente del tipo de memoria empleada
CALL
llama_EMM
; DH = 44h -> mapear página EMS
;
por el disco. La rutina instalada por defecto es la más
XPUSH
;
larga de todas, para «dejar hueco» donde copiar encima
SUB
SI,4000h
;
las otras si se va a utilizar otro tipo de memoria. Si
NEG
SI
; SI = 4000h - SI: «resto»
;
se modifican las rutinas, convendría medirlas por si
SHR
SI,1
; bytes -> palabras
;
acaso la de memoria EMS deja de ser la más larga...
CMP
CX,SI
JB
cx_ok
procesa_io
EQU
MOV
CX,SI
POP
SI
; + SI=desplazamiento relativo
$ cx_ok:
procesa_ems
procesa_pag:
; no ocupada toda la página
; ---- La rutina de gestión de memoria EMS transfiere
CLD
;
bloques de hasta 16Kb de una vez. Intenta mapear
POP
BX
; + palabras restantes
;
en la página física 0: si no puede, debido a un
SUB
BX,CX
; descontar las que se moverán
;
solapamiento con el buffer de transferencia del
PUSH
BX
; * volver a apilar el viejo CX
;
programa principal (si está también en memoria
CALL
coloca_regs
;
EMS), utiliza otra página alternativa que dista
CMP
CS:cpu386,ON
;
al menos 32 Kb absolutos de la 0. Para dilucidar
JNE
trans_16bit
;
si hay solapamiento, se compara la distancia
.386
;
entre direcciones origen y destino antes de la
PUSHAD
;
transferencia: si es mayor de 401h párrafos
SHR
CX,1
; nº palabras de 32 bit a mover
;
(16400 bytes, 16 para redondeo) no hay problema.
JCXZ
transferido
; evitar desgracia
;
Ante un solapamiento se procede a restaurar el
XOR
EAX,EAX
; asegurar no violación
;
contexto de las páginas mapeadas, antes y
DEC
AX
; de segmento-64K
;
después de la transferencia, para poder acceder
AND
ECX,EAX
; EAX = 0FFFFh
;
a la memoria expandida donde está el buffer del
AND
ESI,EAX
;
programa principal.
AND
EDI,EAX
REP
MOVSD
transferido:
PROC
POPAD
; ¿386 o superior?
; transferencia ultrarrápida ; POPAD falla en muchos 386
.8086
JNC
no_emslib
MOV
DH,45h
; sistema reinicializando:
NOP
CALL
llama_EMM
; liberar memoria EMS
ADD
CX,CX
ADD
DI,CX
; simular cambio normal de DI
MOV
SI,DX
; preservar DX
ADD
SI,CX
; y de SI
MOV
DH,47h
JMP
fin_trans
CALL
llama_EMM
; DH=47h -> salvar contexto EMS
trans_16bit:
REP
MOVSW
MOV
DX,SI
; recuperar DX
fin_trans:
CALL
coloca_regs
MOV
BX,4000h
; tamaño de página (16 Kb)
AND
BP,BP
DIV
BX
; AX = 1ª página EMS a mapear
JNS
ahorra_ms
MOV
SI,DX
; offset relativo en 1ª página
CALL
copia_contexto
; está CX apilado
PUSH
CX
; **
AND
BP,1
; de momento, no se usará más
MOV
BX,DI
POP
CX
; **
MOV
CL,4
JCXZ
fin_leer
; no quedan más palabras
SHR
BX,CL
INC
AX
; próxima página EMS
MOV
CX,ES
XOR
SI,SI
; ahora desde inicio página EMS
RET no_emslib:
; ++
ahorra_ms:
; bytes del offset -> párrafos
; arreglar fallo de POPAD
; mover palabras de 16 bit
; ¿se usó página alternativa?
203
CONTROLADORES DE DISPOSITIVOS
fin_leer:
JMP
procesa_pag
MOV
DH,48h
CALL
llama_EMM
; DH=47h restaurar contexto EMS
MOV
AX,100h
; no hubo problemas
; <<< Fin del código residente del disco virtual >>>
; ************ Instalación (invocada desde CONFIG.SYS).
RET procesa_ems
ENDP init
llama_EMM
MOV
CS:modo,CONFIG
; ejecutando desde CONFIG
;
con la pila (SP) tal y como estaba al principio
CALL
obtDosVer
; obtener versión del DOS
;
del procedimiento «procesa_ems», y utilizando
LEA
AX,retorno_ok
;
siempre CALL, para que en el caso de que haya
MOV
CS:p_rutinas,AX
; anular rutina INIT
;
errores retorne correctamente al nivel anterior
INC
CS:tipo_soporte
; 0: disco no formateado
;
(nivel previo a «procesa_ems»). Se corrompe DX
MOV
CS:cs_tdsk,CS
; inicializar esa variable
;
y, si hay error, AX también (devuelve 810Ch).
CMP
CS:dosver,300h
; ¿DOS inferior al 3.0?
JAE
dos_ok
; DOS 3.0+
PROC
AND
CS:tipo_drive,0F7FFh
; ajustar atributos
XPUSH
MOV
CS:num_ordenes,0Dh
; y número de órdenes
MOV
SI,[BX].bpb_cmd_desp
MOV
ES,[BX].bpb_cmd_segm
; ES:SI -> parámetros
MOV
[BX].num_discos,1
; una unidad de disco
LEA
AX,bpb_ptr
MOV llama_denuevo: MOV
AX,DX
; función en AX
DX,CS:mem_handle
; handle EMS
dos_ok:
XPUSH
llama_ok:
INT
67h
; llamar al EMM
MOV
CL,AH
MOV
[BX].bpb_cmd_desp,AX
XPOP
MOV
[BX].bpb_cmd_segm,CS
; inicializado puntero BPB
AND
CL,CL
CALL
desvia_int19
; controlar INT 19h
JZ
llama_ok
CALL
inic_letra
; obtener letra de unidad
CMP
CL,82h
MOV
BX,CS
JE
llama_denuevo
MOV
DS,BX
; DS: -> _PRINCIPAL
XPOP
MOV
BX,SI
; ES:BX -> parámetros
JNE
ret_atras
CALL
salta_nombre
; buscar inicio parámetros
CALL
procesar_param
; procesar parámetros
; además, ZF = 1
; intentarlo hasta que funcione
RET ret_atras:
POP
AX
; sacar dirección de retorno
PUSH
DS
;
MOV
AX,810Ch
; error de «anomalía general»
POP
ES
; ES: -> _PRINCIPAL
; retornar dos niveles atrás
CMP
param_b,ON
JNE
pet_ayuda?
MOV
AH,8
; ---- ¡Cuidado!: esta rutina debe ser invocada siempre
MOV
DL,80h
;
con CX (y sólo CX) apilado: recarga CX desde la
PUSH
ES
;
pila y corrompe BX dejando aún en la pila CX.
INT
13h
POP
ES
AND
DL,3
JZ
pet_ayuda?
LEA
AX,procesa_io
NEG
AX
JMP
bytes_res_ok
CMP
param_h,ON
JE
fin_instalar
; piden ayuda
CALL
max_sector
; obtener mayor sector
MOV
BX,param_tsect
CMP
BX,AX
; ¿el nuestro es mayor?
JBE
sect_def_ok
; no
MOV
bytes_sector,BX
; sí: ajustar BPB
CALL
errores_config
RET llama_EMM
ENDP
copia_contexto PROC XPOP
MOV
DH,48h
CALL
llama_EMM
MOV
DH,47h
CALL
llama_EMM
PUSH
CX
JMP
BX
; equilibrar pila a llama_EMM
; restaurar contexto EMS
; preservarlo de nuevo
pet_ayuda?:
; más rápido que PUSH BX/RET
copia_contexto ENDP
coloca_regs
PROC
; ---- ¡Cuidado!: esta rutina debe ser invocada siempre
PROC
; ¿invertir sentido?
TEST
BP,1
JNZ
colocados
XCHG
SI,DI
sect_def_ok:
; opción /B
; ¿nº de discos duros?
; no existe disco duro
; no quedará residente
TEST
lista_err,ERROR0+ERROR1
XPUSH
JNZ
fin_instalar
; algún error importante
XPOP
CMP
param_tdisco,0
; ¿se define disco ahora?
; escritura: invertir sentido
colocados:
RET
JE
fin_instalar
; no: no hay más que hacer
coloca_regs
ENDP
CALL
mem_info
; evaluar memoria del PC
CMP
tdisco,0
; ¿se reservará memoria?
JE
fin_instalar
; no: no hay más que hacer
tam_proc_ems
EQU
$-OFFSET procesa_ems
; tamaño de esta rutina
203
fin_instalar:
res_largo:
res_corto:
bytes_res_ok:
init
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
CALL
mem_reserva
; reservar memoria
CMP
param_tdiscof,ON
JC
fin_instalar
; fallo al reservarla
JNE
exit_instalar
CALL
test_CPU
; detectar 386 ó superior
CMP
ES:tipo_soporte,0
CALL
adaptar_param
; adaptar parámetros disco
JE
cont_instalar
; no estaba formateado aún
CALL
preparar_BPB
; BPB del nuevo disco
CALL
desinstala
; liberar memoria ocupada
CALL
prep_driver
; preparar el driver
mem_info
; evaluar memoria del PC
CALL
formatear_tdsk
; inic. BOOT, FAT y ROOT
CMP
tdisco,0
; ¿se reservará memoria?
CALL
info_disco
; informar sobre el disco
JE
exit_instalar
; no: no hay más que hacer
CMP
tipo_soporte,2
CALL
mem_reserva
; reservar memoria
JE
res_largo
JC
exit_instalar
; fallo reservando memoria
CMP
param_a,ON
CALL
test_CPU
; detectar 386 ó superior
JE
res_largo
CALL
adaptar_param
; adaptar parámetros disco
CALL
eval_xms
CALL
preparar_BPB
; BPB del nuevo disco
CALL
eval_ems
CALL
relocalizar
; autoreubicación de TDSK
CMP
ems_kb,0
CALL
prep_driver
; preparar el driver
JE
res_corto
CALL
formatear_tdsk
; BOOT, FAT y ROOT
CMP
xms_kb,0
info_disco
; informar sobre el disco
JNE
res_corto
CMP
tipo_soporte,3
; ¿memoria convencional?
MOV
AX,tam_proc_ems
JNE
fin_no_res
; no usada
MOV
rutina_larga,ON
CALL
renombrar_mcb
; cambiar nombre del MCB
JMP
bytes_res_ok
MOV
DX,6
; usada: 96 bytes de PSP
MOV
AX,tam_proc_xms
MOV
AX,3100h
MOV
BX,tam_proc_con
INT
21h
; terminar residente
CMP
AX,BX
CALL
set_errorlevel
; preparar ERRORLEVEL
JAE
bytes_res_ok
MOV
AH,4Ch
XCHG
AX,BX
INT
21h
LDS
BX,CS:pcab_peticion
ADD
AX,OFFSET procesa_io
MOV
[BX].fin_resid_desp,AX ; reservar memoria para
MOV
[BX].fin_resid_segm,CS ; las rutinas a usar
MOV
AX,100h
cont_instalar: CALL
; se utiliza memoria EMS
; se indicó /A
; no hay memoria EMS
exit_instalar: CALL ; la hay, pero también XMS
; dejar sitio a rutina EMS
; dejar sitio a XMS/conv.
fin_no_res:
main
; instalación siempre Ok.
cabria_ems:
; final normal
ENDP
; ------------ Inicializar la variable con la versión del DOS
obtDosVer
PROC
RET
XPUSH
ENDP
MOV
AH,30h
INT
21h
XCHG
AH,AL
MOV
CS:dosver,AX
XPOP
; ------------ Redefinición (invocada desde el AUTOEXEC.BAT o el DOS).
main
; no indicado nuevo tamaño
PROC
FAR
MOV
CS:modo,AUTOEXEC
; ejecutando desde el DOS
CALL
obtDosVer
; obtener versión del DOS
CALL
gestionar_ram
; gestión de memoria
MOV
AX,_PRINCIPAL
; programa de un segmento
; ------------ Determinar segmento del PSP, último segmento de memoria
MOV
DS,AX
; DS: -> _PRINCIPAL
;
y liberar espacio de entorno. Se modifica también el
MOV
BX,81h
; ES:BX línea de órdenes
;
bloque de memoria de TDSK reduciéndolo a 96 bytes: esto
CALL
procesar_param
; procesar parámetros
;
provoca la creación de un bloque de control de memoria
CMP
param_h,ON
;
en el offset 96 del PSP, lo cual no es peligroso. El
JE
exit_instalar
;
objetivo de esta maniobra es poder asignar memoria al
PUSH
DS
;
disco después (sólo si hace falta memoria convencional)
POP
ES
;
usando los servicios estándar del DOS.
CALL
errores_Dos
TEST
err_grave,0FFFFh
gestionar_ram
PROC
JNZ
exit_instalar
; algún error grave
MOV
CS:segm_psp,DS
; indicar segmento del PSP
MOV
ES,segm_tdsk
; ES: --> disco residente
MOV
AX,DS:[2]
; segmento más alto
CMP
param_a,ON
MOV
CS:top_ram,AX
; indicar tope de memoria
JNE
cabria_ems
PUSH
ES
CMP
ES:rutina_larga,ON
MOV
ES,DS:[2Ch]
JE
cabria_ems
MOV
AH,49h
OR
lista_err,ERROR2
INT
21h
; liberar área de entorno
TEST
lista_err,ERROR0+ERROR2
POP
ES
; ES: -> PSP
JNZ
exit_instalar
MOV
BX,6
; piden ayuda
; ES: --> _PRINCIPAL
; cabe la rutina EMS
; ¿error sintaxis ó EMS?
; sí: no modificar disco
RET obtDosVer
ENDP
; segmento del entorno
203
CONTROLADORES DE DISPOSITIVOS
gestionar_ram
CMP
AX,"?/"
JE
p_ayuda
RET
CMP
AX,"m/"
ENDP
JNE
param_id?
MOV
param_m,ON
JMP
p_barra_exit
CMP
AX,"i/"
MOV
AH,4Ah
; hacer creer al DOS que
INT
21h
; TDSK ocupa sólo 96 bytes
p_exit?:
; ------------ Leer los parámetros de la línea de comandos (ES:BX). param_id?:
; /H y /? son equivalentes
; ¿indicado /M?
; ¿indicado /I= o /I:?
;
Se inicializan las correspondientes variables. En caso
;
de error, se dejan a cero las variables y se acumula en
JNE
param_fats?
;
«lista_err» un ERROR0 (error de sintaxis).
ADD
BX,3
CMP
BYTE PTR ES:[BX-1],'='
JE
p_id_ok
procesar_param PROC
p_param2:
p_param3:
p_param4:
p_param5:
fin_param:
CALL
busca_param
; saltar delimitadores
CMP
BYTE PTR ES:[BX-1],':'
JC
fin_param
; no hay más parámetros
JNE
param_b_mal
CALL
param_barra
; gestionar parámetro tipo "/A"
CALL
obt_num
JC
procesar_param
; era parámetro tipo "/A"
MOV
param_i,ON
MOV
param_tdisco,AX
; es numérico: tamaño del disco
MOV
codigo_tfno,AX
MOV
param_tdiscof,ON
; parámetro de tamaño indicado
SUB
BX,2
CALL
busca_param
JMP
p_barra_exit
JC
fin_param
CMP
AX,"f/"
CALL
param_barra
JNE
param_b?
JC
p_param2
ADD
BX,3
MOV
param_tsect,AX
CMP
BYTE PTR ES:[BX-1],'='
CALL
busca_param
JE
p_f_ok
JC
fin_param
CMP
BYTE PTR ES:[BX-1],':'
CALL
param_barra
JNE
param_b_mal
JC
p_param3
CALL
obt_num
MOV
param_tdir,AX
MOV
param_f,AX
CALL
busca_param
SUB
BX,2
JC
fin_param
JMP
p_barra_exit
CALL
param_barra
CMP
AX,"b/"
JC
p_param4
JNE
param_unidad?
MOV
param_tcluster,AX ; tamaño de cluster
MOV
param_b,ON
CALL
busca_param
JMP
p_barra_exit
JC
fin_param
CALL
param_barra
JC
p_param5
CALL
validacion
p_id_ok:
param_fats?:
; tamaño de sector
p_f_ok: ; entradas al directorio
param_b?:
param_unidad?: CMP
param_num?
AND
AL,255-32
MOV
param_unidad,AL
JMP
p_barra_exit
CMP
AL,'/'
JNE
param_num
param_b_mal:
OR
lista_err,ERROR0
param_num:
CALL
obt_num
; validación de parámetros
RET param_num?:
procesar_param ENDP
param_barra
p_exp1?:
p_exp:
p_exp2?:
p_ayuda?:
p_ayuda:
PROC ; ¿indicado /E?
AH,':'
JNE
; últimas opciones posibles
CMP
AX,"e/"
JNE
p_exp1?
CLC
MOV
param_e,ON
RET
JMP
p_barra_exit
CMP
AX,"a/"
JNE
p_exp2?
MOV
param_a,ON
JMP
p_barra_exit
CMP
AX,"x/"
JE
p_exp
CMP
AX,"c/"
JNE
p_barra_exit:
ADD
; ¿indicado /F= o /F:?
; leer número de FATs
; ¿indicado /B?
; ¿parámetro de unidad?
; poner en mayúsculas
; puede ser número
; es parámetro numérico: leerlo ; no es parámetro barrado
BX,2
STC
; ¿indicado /A?
; leer código telefónico
; saltar este parámetro ; es parámetro barrado
RET
; /A y /X son equivalentes
param_barra
ENDP
validacion
PROC MOV
AX,0FFFFh
CMP
AX,param_tdisco
p_ayuda?
JE
sintax_err
MOV
param_c,ON
CMP
AX,param_tsect
JMP
p_barra_exit
JE
sintax_err
CMP
AX,"h/"
CMP
AX,param_tdir
JNE
p_exit?
JE
sintax_err
MOV
param_h,ON
CMP
AX,param_tcluster
JMP
p_barra_exit
JE
sintax_err
; ¿indicado /C?
; ¿indicado /H?
; ¿números correctos?
203
valida_tsect:
valida_tclus:
pf_a1:
sintax_err:
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
CMP
param_tdisco,0
JE
valida_tsect
CMP
param_tdisco,8
JB
sintax_err
CMP
param_tdisco,65534
JA
sintax_err
MOV
busca_param
PROC
; saltar delimitadores BX
INC
BX
MOV
AX,ES:[BX]
AX,param_tsect
CMP
AL,' '
CMP
AX,0
JE
p_delimit
JE
valida_tclus
CMP
AL,9
CMP
AX,32
JE
p_delimit
JE
valida_tclus
CMP
AL,13
CMP
AX,64
JE
p_final
JE
valida_tclus
CMP
AL,10
CMP
AX,128
JE
p_final
JE
valida_tclus
OR
AX,"
CMP
AX,256
CLC
JE
valida_tclus
CMP
AX,512
JE
valida_tclus
CMP
AX,1024
JE
valida_tclus
CMP
AX,2048
JNE
sintax_err
XPUSH
; si hay error
CMP
param_tcluster,256
XOR
AX,AX
; número en proceso de creación
JAE
sintax_err
MOV
CL,ES:[BX]
CMP
param_f,1
CMP
CL,'0'
JB
pf_a1
; /F=1 ó /F=2 exclusivamente
JB
no_digito
CMP
param_f,2
; si no, forzarlo y perdonar
CMP
CL,'9'
JBE
fin_validar
JBE
digito_ok
MOV
param_f,2
CMP
CL,' '
JMP
fin_validar
JE
fin_num
MOV
param_f,1
CMP
CL,9
JMP
fin_validar
JE
fin_num
MOV
param_tdiscof,OFF ; no definir disco ahora
CMP
CL,13
XOR
AX,AX
JE
fin_num
MOV
param_tdisco,AX
CMP
CL,10
MOV
param_tsect,AX
JE
fin_num
MOV
param_tdir,AX
CMP
CL,'/'
MOV
param_tcluster,AX
JE
fin_num
OR
lista_err,ERROR0
JMP
num_incorr
XOR
DX,DX
MOV
SI,10
MUL
SI
; saltar nombre del driver en
JC
num_incorr
; línea de órdenes del CONFIG
RET
validacion
ENDP
fin_nombre:
ENDP
DEC
fin_validar:
salta_nombre
salta_nombre ; no indicado tamaño (o 0)
p_delimit:
; no indicado tamaño de sector
STC
; CR ó LF indican el final
; poner en minúsculas
; se acabaron los parámetros
RET
; debe estar entre 0..255
busca_param
ENDP
obt_num
PROC
otro_digito:
no_digito:
; aviso de error de sintaxis
; leer número: devolver 65535
MOV
AL,ES:[BX]
XOR
CH,CH
INC
BX
SUB
CL,'0'
CMP
AL,' '
ADD
AX,CX
JE
fin_nombre
JC
num_incorr
CMP
AL,9
INC
BX
JE
fin_nombre
JMP
otro_digito
CMP
AL,0Dh
num_incorr:
MOV
AX,65535
JE
fin_nombre
fin_num:
XPOP
CMP
AL,0Ah
JE
fin_nombre
AND
AL,AL
JZ
fin_nombre
JMP
salta_nombre
DEC
BX
RET
; tabulador
RET p_final:
digito_ok:
PROC
"
; espacio en blanco
; posibles delimitadores...
; AX = AX * 10
; AX = AX + dato
; indicar valor incorrecto
RET obt_num
; necesario para DOS 2.x
ENDP
; ------------ Detectar errores que se pueden producir sólo en la ;
línea de comandos.
errores_Dos
PROC
203
CONTROLADORES DE DISPOSITIVOS
existe_tdsk?:
busca_unidad:
PUSH
ES
CMP
dosver,200h
JAE
existe_tdsk?
OR
err_grave,ERROR0
JMP
fin_err_Dos
CALL
reside_tdsk?
CMP
segm_tdsk,0
JNE
busca_unidad
; ya instalado
OR
err_grave,ERROR1
; error: TURBODSK no instalado
JMP
fin_err_Dos
MOV
ES,segm_tdsk
CMP
param_unidad,0
JE
disco_defecto
CALL JC disco_defecto: CALL
fin_err_Dos:
; necesario DOS 2.x+
; error de DOS incorrecto
fin_cod_ok:
CMP
ES:tipo_soporte,0
JNE
fin_cod_ok
MOV
AL,0
; disco no formateado
RET
set_errorlevel ENDP ; ¿instalado TURBODSK? ; ------------ Obtener mayor tamaño de sector definido en el sistema.
max_sector
PROC XPUSH MOV
AH,52h
INT
21h
; no se indicó letra de unidad
ADD
BX,10h
obtener_segm
; segmento del TDSK indicado
CMP
CS:dosver,30Ah
fin_err_Dos
; fallo (no es unidad TDSK)
JAE
psect_ok
max_sector
; obtener mayor sector
INC
BX
; DOS anterior al 3.1
MOV
AX,ES:[BX]
; mayor tamaño de sector
XPOP
; definido por cualquier disp.
; ES: -> disco virtual
psect_ok:
; Get List of Lists
MOV
BX,param_tsect
CMP
BX,AX
JBE
fin_err_Dos
; tamaño de sector correcto
OR
lista_err,ERROR3
; el tamaño no definible ahora
MOV
param_tsect,0
; ignorar tamaño indicado
CALL
test32Mb
; ------------ Si el disco es de más de 32 Mb, comprobar si el sector
CALL
testWin
;
es de al menos 1024 bytes.
POP
ES test32Mb
PROC
RET
RET max_sector
ENDP
CMP
param_tdisco,32768
JBE
fin32mb
; ------------ Detectar errores que se pueden producir sólo desde
CMP
param_tsect,1024
;
JAE
fin32mb
OR
lista_err,ERROR15
; sector de menos de 1024
MOV
param_tdisco,32768
; evitar fallo posterior
errores_Dos
ENDP
el CONFIG.SYS
errores_config PROC
no_unidad:
fin_err_con:
CMP
param_unidad,0
fin32mb:
RET
JE
no_unidad
test32Mb
ENDP
OR
lista_err,ERROR1
CMP
param_c,ON
JNE
fin_err_con
OR
lista_err,ERROR1
CALL
test32Mb
; ------------ Desde Windows, no se permite redefinir el disco.
testWin
RET errores_config ENDP
; ------------ Preparar valor de ERRORLEVEL para el retorno.
set_errorlevel PROC MOV
AL,255
TEST
err_grave,ERROR1
JNZ
fin_cod_ok
DEC
AL
TEST
err_grave,ERROR2
JNZ
fin_cod_ok
DEC
AL
TEST
err_grave,ERROR3
JNZ
fin_cod_ok
DEC
AL
TEST
lista_err,ERROR0
JNZ
fin_cod_ok
CMP
param_h,ON
JE
fin_cod_ok
MOV
AL,BYTE PTR ES:mem_handle
; ¿TDSK no instalado?
siWin: ; ¿unidad incorrecta? noWinEnh:
; ¿dentro de Windows?
PROC CMP
param_tdiscof,ON
JNE
fin_testWin
CMP
dosver,300h
JB
fin_testWin
MOV
AX,1600h
INT
2Fh
AND
AL,AL
JZ
noWinEnh
CMP
AL,80h
JE
noWinEnh
OR
err_grave,ERROR3
JMP
fin_testWin
MOV
AX,4680h
INT
2Fh
AND
AX,AX
JZ
siWin
; no redefinido el disco
; no buscar Windows en DOS 2.x
; ¿Windows en modo extendido?
; ¿Windows en modo extendido?
; estamos dentro de Windows
; Windows en modo real/estándar
fin_testWin:
RET
; error de sintaxis
testWin
ENDP
; ayuda: handle desconocido
; ------------ Verificar la presencia en memoria de TURBODSK. Se
; handle XMS/EMS
;
inicializa «segm_tdsk» y «letra_unidad» indicando dónde
;
reside el primer dispositivo TURBODSK de todos los que
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
;
puede haber instalados. La letra de la unidad se halla
;
del propio TDSK residente, para evitar conflictos con
; ------------ Colocar nuevo gestor de INT 19h al instalar TDSK desde
;
programas que manipulan ilegalmente la lista de
;
el CONFIG.SYS. En algunos entornos multitarea basados
;
unidades, del tipo de Stacker o Smartdrive.
;
en el modo virtual-86 del 386 y superiores, si no se
;
libera la memoria EMS/XMS tras una cancelación de la
PROC
;
tarea virtual, ésta queda permanentemente ocupada hasta
XPUSH
;
un reset «frío» del sistema, sin poder ser aprovechada
CALL
lista_discos
;
por los demás procesos. La INT 19h se ejecuta cuando la
LEA
SI,area_trabajo-4
;
tarea en curso va a ser inminentemente cancelada por el
ADD
SI,4
;
sistema, y TURBODSK la intercepta para poder liberar la
CMP
WORD PTR [SI],0
;
memoria EMS/XMS en el último instante. La rutina que
JNE
busca_final
;
controla INT 19h contiene código de 286, por lo que se
SUB
SI,4
;
chequea la presencia de este procesador.
CMP
SI,OFFSET area_trabajo
JB
fin_busca
desvia_int19
PROC
CMP
BYTE PTR [SI+3],1
JNE
busca_tdsk
MOV
AX,[SI]
MOV
reside_tdsk?
busca_final:
busca_tdsk:
fin_busca:
reside_tdsk?
; ir al final de la tabla
; no reside (segm_tdsk = 0)
XPUSH MOV
BX,CS
MOV
DS,BX
segm_tdsk,AX
CALL
test_CPU
PUSH
DS
CMP
cpu286,ON
MOV
DS,AX
JNE
fin_desvia19
MOV
AL,letra_unidad
MOV
AX,3519h
POP
DS
INT
21h
MOV
letra_unidad,AL
MOV
ant19off,BX
XPOP
; encontrada unidad TURBODSK
; con esta letra de unidad
MOV
ant19seg,ES
RET
LEA
DX,nueva_int19
ENDP
MOV
AX,2519h
INT
21h
XPOP
; ------------ Obtener el segmento de la unidad TURBODSK indicada, si
fin_desvia19:
; no es 286 ó superior
; ES:BX anterior INT 19h
; nueva rutina de control
RET
;
existe, accediendo a una tabla de dispositivos que se
;
crea. A la salida, CF=1 si esa unidad no es TURBODSK.
desvia_int19
obtener_segm
PROC
; ------------ Obtener la letra de la unidad de disco definida. Esta
busca_ultimo:
recorre_dsks:
CALL
lista_discos
;
rutina se invoca sólo desde CONFIG.SYS con DS:BX
LEA
SI,area_trabajo-4
;
apuntando a la cabecera de petición de la orden INIT.
ADD
SI,4
CMP
WORD PTR [SI],0
inic_letra
PROC
JNE
busca_ultimo
SUB
SI,4
MOV
AL,[BX].nuevo_disco
CMP
SI,OFFSET area_trabajo
ADD
AL,'A'
JB
tdsk_no_hay
PUSH
CS
CMP
BYTE PTR [SI+3],1
POP
DS
JNE
recorre_dsks
CMP
dosver,300h
PUSH
DS
JAE
letra_ok
MOV
DS,[SI]
CALL
lista_discos
MOV
AL,letra_unidad
LEA
SI,area_trabajo
POP
DS
XOR
AL,AL
CMP
AL,param_unidad
JNE
recorre_dsks
MOV
letra_unidad,AL
MOV
AX,[SI]
MOV
segm_tdsk,AX
MOV
ES,AX
XPUSH
; realmente, el primero
; unidad del TDSK residente
; disco TDSK: ¿es el buscado?
cuenta_discos: ADD
; inicializar letra de unidad
; inicializar segmento letra_ok:
CLC
OR
; unidad en DOS 3.0+
; DS -> _PRINCIPAL
; hallar unidad en DOS 2.x
; cuenta de discos
AL,[SI+2]
ADD
SI,4
CMP
WORD PTR [SI],0
JNE
cuenta_discos
ADD
AL,'A'
MOV
letra_unidad,AL
XPOP
; guardar letra de unidad
RET
RET tdsk_no_hay:
ENDP
err_grave,ERROR2
; unidad indicada no es TDSK
inic_letra
ENDP
STC
obtener_segm
RET
; ------------ Crear una lista de todos los dispositivos de bloque
ENDP
;
del sistema. La lista tiene una entrada de 4 bytes
203
CONTROLADORES DE DISPOSITIVOS
;
para cada dispositivo: los dos primeros indican el
POP
ES
;
segmento en que reside, el siguiente el número de
PUSH
ES
;
unidades que controla y el último vale 1 ó 0 para
PUSHF
;
indicar si es una unidad TDSK o no. El final de la
MOV
ES,ES:tdsk_psp
;
lista lo señaliza un segmento igual a 0.
MOV
AH,49h
INT
21h
lista_discos
PROC
pdisp_ok:
XPUSH
CMP
dosver,31Eh
MOV
AH,52h
; "Get list of lists"
JA
mcb_ok
INT
21h
; obtener puntero en ES:BX
MOV
AX,ES
MOV
CX,17h
; supuesto DOS 2.x
DEC
AX
CMP
dosver,300h
MOV
ES,AX
JB
pdisp_ok
MOV
DI,8
MOV
CX,28h
MOV
CX,DI
CMP
dosver,30Ah
JB
pdisp_ok
MOV
CX,22h
ADD
BX,CX
; supuesto DOS 3.0x
; versiones del DOS superiores mcb_ok:
MOV
AL,' '
REP
STOSB
; hasta DOS 3.30 borrar nombre
lib_con_ok?
; liberado correctamente
POPF
DI,area_trabajo-4 ; tabla de dispositivos-4
JNC
ADD
DI,4
POPF
disp_skip:
LES
BX,ES:[BX]
CMP
BX,-1
STC
JE
disp_fin
JMP
TEST
BYTE PTR ES:[BX+5],80h
JNZ
disp_skip
; es dispositivo de caracteres
MOV
CL,ES:[BX+10]
; es de bloques
MOV
[DI],ES
; anotar dirección
MOV
POP
; siguiente dispositivo
lib_con_ok?:
ES ; ha habido fallo desinstalado
POPF
; recuperar condición de error
POP
ES
JMP
desinstalado
MOV
AH,0Ah
[DI+2],CL
CALL
ES:xms_driver
MOV
BYTE PTR [DI+3],0 ; de momento, no es TDSK
CMP
AX,1
PUSH
DI
JE
desinstalado
; éxito al liberar memoria XMS
LEA
SI,id_tdsk
MOV
DI,SI
JMP
desinstalado
; fallo
MOV
CX,5
MOV
AH,45h
INT
67h
CMP
AH,0
JE
desinstalado
libera_ext:
STC
; identificación de TURBODSK
libera_exp:
CLD REP
CMPSB
; ¿es TURBODSK?
POP
DI
JNE
disp_otro
; es de bloques, pero no TDSK
CMP
AH,82h
MOV
AX,ES:cs_tdsk
; segmento real de TDSK
JE
libera_exp
MOV
[DI],AX
; corregir dirección en tabla
INC
BYTE PTR [DI+3]
; indicar dispositivo TDSK
JMP
disp_otro
MOV
WORD PTR [DI],0
XPOP
STC desinstalado:
; ¿EMM ocupado?
; fallo al liberar memoria EMS
MOV
ES:tipo_soporte,0 ; disco «no formateado»
; buscar hasta completar tabla
JNC
desins_ok
; final de la lista
OR
lista_err,ERROR14 ; fallo al liberar memoria
STC
RET
desins_ok:
RET
ENDP
desinstala
ENDP
; ------------ Liberar la memoria ocupada por un TURBODSK residente.
desinstala
; DOS 3.31+: el MCB es correcto
CLD
LEA
lista_discos
; liberar PSP residente
PUSHF
disp_otro:
disp_fin:
; condición de error
PROC
; ------------ Determinar la configuración del sistema: tipos de ;
memoria y cantidad de la misma. Se indica en «tdisco»
;
un valor 0 si no se define ahora el disco, sea cual sea
MOV
DX,ES:mem_handle
;
el motivo del fallo, y se actualiza la variable que
MOV
AL,ES:tipo_soporte
;
indica los mensajes de error y advertencia a imprimir.
DEC
AL
JZ
libera_ext
mem_info
PROC
DEC
AL
JZ
libera_exp
PUSH
; liberar memoria extendida
MOV
tdisco,0
; ley de Murphy
CALL
eval_xms
; inicializar «xms_kb»
ES
CALL
eval_ems
; inicializar «ems_kb»
MOV
ES,DX
CALL
eval_con
; inicializar «con_kb»
MOV
AH,49h
MOV
AX,param_tdisco
; cantidad de memoria necesaria
INT
21h
CMP
param_a,ON
; liberar memoria expandida
; liberar memoria convencional:
203
usara_ems:
usar_ems:
no_ems:
usara_xms:
usar_xms:
no_xms:
forzar_con:
usara_con:
usar_con:
no_con:
valdria_ems:
nv_ems:
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
JNE
no_ems
; no solicitan memoria EMS
JAE
usar_xms
MOV
BX,ems_kb
; solicitan memoria EMS...
MOV
AX,ems_kb
AND
BX,BX
JMP
usar_ems
JNZ
usara_ems
CMP
modo,AUTOEXEC
OR
lista_err,ERROR7
JE
forzar_con
; sólo se puede usar mem. conv.
JMP
mem_infoado
OR
lista_err,ERROR5
; ho hay memoria EMS ni XMS
CMP
AX,BX
JBE
usar_ems
MOV
AX,BX
OR
lista_err,ERROR4
MOV
tdisco,AX
MOV
tipo_soporte,2
JMP
mem_infoado
PUSH
ES
CMP
param_e,ON
MOV
AX,352Fh
JNE
no_xms
; no solicitan memoria XMS
INT
21h
MOV
BX,xms_kb
; solicitan memoria XMS...
MOV
AX,ES
AND
BX,BX
AND
AX,AX
JNZ
usara_xms
JZ
xms_ok
OR
lista_err,ERROR6
MOV
AX,4300h
JMP
mem_infoado
INT
2Fh
CMP
AX,BX
CMP
AL,80h
JBE
usar_xms
JNE
xms_ok
MOV
AX,BX
MOV
AX,4310h
OR
lista_err,ERROR4
INT
2Fh
MOV
tdisco,AX
MOV
xms_segm,ES
MOV
tipo_soporte,1
MOV
xms_desp,BX
JMP
mem_infoado
MOV
AH,8
CMP
param_c,ON
CALL
xms_driver
JNE
no_con
; no solicitan memoria conv.
AND
AX,AX
MOV
BX,con_kb
; solicitan memoria conv. ...
JNZ
xms_kb_ok
AND
BX,BX
CMP
BL,0A0h
JNZ
usara_con
JE
xms_kb_ok
OR
lista_err,ERROR10 ; no hay memoria conv. libre
TEST
BL,80h
JMP
mem_infoado
JZ
xms_kb_ok
; no hay memoria XMS disponible
CMP
AX,BX
OR
lista_err,ERROR8
; fallo real del controlador
JBE
usar_con
CMP
AX,8
; mayor bloque XMS disponible
MOV
AX,BX
JB
xms_ok
OR
lista_err,ERROR4
MOV
xms_kb,AX
MOV
tdisco,AX
POP
ES
MOV
tipo_soporte,3
JMP
mem_infoado
CMP
AX,xms_kb
; no indicado tipo de memoria
JBE
usar_xms
; intentar emplear memoria XMS
CMP
ES:rutina_larga,ON
;
versión del EMM es 4.0 o superior, las páginas
JE
valdria_ems
;
de memoria expandida pueden no ser contiguas:
MOV
BX,xms_kb
;
buscar una que diste 32 Kb de la página 0.
CMP
BX,0
; imposible usar EMS
JNE
usara_xms
; queda algo de XMS
JMP
usar_con?
PUSH
ES
MOV
BX,ems_kb
MOV
AX,3567h
CMP
AX,BX
INT
21h
JA
nv_ems
MOV
DI,10
JMP
usar_ems
LEA
SI,emm_id
MOV
BX,ems_kb
MOV
CX,8
OR
BX,xms_kb
CLD
JZ
usar_con?
; no hay un ápice de XMS ni EMS
REP
CMPSB
OR
lista_err,ERROR4
; rebajado el tamaño solicitado
JE
ems_existe
MOV
AX,xms_kb
JMP
ems_ok
CMP
AX,ems_kb
MOV
CX,8000h
usar_con?: ; no hay memoria EMS disponible
; piden algo razonable
mem_infoado:
RET
mem_info
ENDP
eval_xms
; no hay memoria XMS disponible
; piden algo razonable
; rebajado el tamaño
; indicar memoria extendida
; piden algo razonable
; hay algo de EMS (más que XMS)
; ---- Calcular memoria extendida disponible
; rebajado el tamaño
; indicar memoria expandida
; hay más o igual XMS que EMS
xms_kb_ok:
; rebajado el tamaño xms_ok:
PROC
; dirección de INT 2Fh en ES:BX
; apunta a 0000:XXXX (DOS 2.x)
; ¿hay controlador XMS?
; obtener su dirección
; preguntar memoria libre
; no hubo fallo
; asignada ya toda la memoria
; mínimo necesario: 8 Kb
RET
; indicar memoria convencional eval_xms
ENDP
; ---- Calcular memoria expandida disponible. Si la
eval_ems
; emplear memoria EMS
ems_existe:
PROC
; vector de INT 67h en ES:BX
; ¿instalado controlador EMS?
; nº de intentos prudente
203
CONTROLADORES DE DISPOSITIVOS
emm_llama:
MOV
AH,40h
SHL
BX,CL
; páginas EMS disponibles
INT
67h
MOV
ems_kb,BX
; Kb EMS disponibles (0,16,...)
AND
AH,AH
POP
ES
JZ
emm_responde
CMP
AH,82h
ems_ok:
RET eval_ems
ENDP
emm_busca_pag
PROC
LOOPE emm_llama emm_fatal:
emm_responde:
emm_pag_ok:
emm_obt_pag:
emm_pags_ok:
ems_busca_i:
bxpositivo:
emm_obt_kb:
emm_kb_ok:
; fallo del EMM
; buscar página nº DX (EMS 4.0)
OR
lista_err,ERROR9
JMP
ems_ok
MOV
AH,41h
INT
67h
AND
AH,AH
MOV
JZ
emm_pag_ok
LODSW
CMP
AH,82h
CMP
AX,DX
JE
emm_responde
JE
hallada_pag
JMP
emm_fatal
LOOP
emm_otra_pag
MOV
ems_pagina0,BX
ADD
BX,0C00h
MOV
ems_paginai,BX
MOV
ems_pagni,3
MOV
AH,46h
INT
67h
CMP
AL,40h
JB
emm_obt_kb
MOV
emm_otra_pag:
; reintentar (EMM ocupado)
LEA
SI,area_trabajo
PUSH
CX
LODSW BX,AX
; BX = segmento de la página ; AX = nº de la página
STC
; inicializar página EMS hallada_pag:
POP
CX
RET ; página alternativa: la 3
emm_busca_pag
ENDP
; ---- Calcular el tamaño del mayor bloque de memoria
; obtener versión del EMM
;
convencional disponible. Como mínimo se dejarán
;
unos 128 Kb libres en él, para que el usuario
ems4,ON
;
pueda volver a ejecutar TDSK y el DOS tenga algo
XPUSH
;
de memoria libre. A la mitad de esos 128Kb (para
POP
ES
;
evitar solapamientos) es donde TURBODSK se
MOV
AX,5800h
;
autorelocalizará antes de formatear el disco.
LEA
DI,area_trabajo
INT
67h
POP
ES
CMP
modo,AUTOEXEC
; ¿se ejecuta desde el DOS?
AND
AH,AH
JNE
conv_ok
; no, desde el config
JZ
emm_pags_ok
MOV
AH,48h
CMP
AH,82h
MOV
BX,0FFFFh
JE
emm_obt_pag
INT
21h
JMP
emm_fatal
MOV
DX,BX
XOR
DX,DX
MOV
CL,6
CALL
emm_busca_pag
SHR
BX,CL
; BX = Kb del mayor bloque
JC
emm_fatal
SUB
BX,128
; restar 128 Kb
MOV
ems_pagina0,BX
JC
conv_ok
; no quedan ni 128 Kb
INC
DX
; buscar la siguiente
CMP
BX,8
CMP
DX,5
; la 5ª y siguientes no valen
JE
emm_fatal
;
CALL
emm_busca_pag
;
JC
emm_fatal
;
MOV
ems_paginai,BX
;0C00h│ 32 │ │
MOV
ems_pagni,DL
; pá
SUB
BX,ems_pagina0
; rra │
│ │
JNC
bxpositivo
; fos │
└>├──────┤
NEG
BX
;
│
│
CMP
BX,0C00h
;
└>
JB
ems_busca_i
MOV
; versión anterior a la 4.0
; obtener dirección de páginas
eval_con
; buscar página 0
┌>
PROC
JB
conv_ok
├──────┤
MOV
con_kb,BX
│
│
MOV
BX,DX
┌>├──────┤<-- pág i
MOV
AH,48h
PUSH
BX
INT
21h
POP
BX
│
│ Kb │ ├──────┤ │
; pedir 1 Mb al DOS (fallará)
; tamaño del mayor bloque
; no quedan siquiera 8 Kb
; tamaño del mayor bloque
; localizarlo (AX=segmento)
XPUSH
; preservar ES y segmento (AX)
ADD
AX,BX
; añadir longitud
├──────┤<-- pág 0
SUB
AX,1024/16*64
; restar 64 Kb
; no distan 32 Kb: buscar otra
MOV
segm_reubicar,AX
; segmento de autoreubicación
AH,42h
POP
ES
; recuperar segmento del bloque
INT
67h
MOV
AH,49h
AND
AH,AH
INT
21h
; liberarlo
JZ
emm_kb_ok
POP
ES
; recuperar ES
CMP
AH,82h
conv_ok:
RET
JE
emm_obt_kb
eval_con
ENDP
JMP
emm_fatal
MOV
CL,4
│
; ------------ Reservar la memoria llamando al gestor que la controla.
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
;
Con memoria XMS y existiendo un controlador EMS 4.0+ se
;
comprueba si el handle XMS provoca la creacción de otro
;
en EMS (caso de QEMM386 y otros emuladores de EMS) y en
XPUSH
;
ese caso se le renombra, para mejorar la información de
POP
ES
;
los programas de diagnóstico.
LEA
BX,area_trabajo[512]
CALL
lista_handles
LEA
SI,area_trabajo
LEA
DI,area_trabajo[512]
MOV
CX,256
mem_reserva
ren_handle
PROC MOV
AL,tipo_soporte
DEC
AL
JZ
mem_r_xms
DEC
AL
JZ
mem_r_ems
MOV
CL,6
MOV
BX,tdisco
SHL
BX,CL
MOV
AH,48h
INT
21h
MOV
mem_handle,AX
MOV
BX,segm_psp
MOV
tdsk_psp,BX
; tipo de memoria empleada
; 2: memoria expandida EMS
; 3: memoria convencional ren_hnld_fin:
; crear nueva lista de handles
MOV
DX,[DI-2]
CALL
nombrar_hndl
POP
ES
; comparar con vieja lista
; handle nuevo
ren_handle
ENDP
lista_handles
PROC
; segmento del disco virtual
; inicializar esta variable
listar_h:
; crear en DS:BX una lista con
MOV
CX,256
; los 256 posibles handles
XOR
DX,DX
; activos indicando los usados
MOV
AX,5300h DI,area_trabajo[tam_a_trabajo-8]
skip_lst_hndl
LEA
LEA
BX,area_trabajo
XPUSH
CALL
lista_handles
INT
67h
AH,9
XPOP
MOV
DX,tdisco
CMP
AH,0
CALL
xms_driver
JE
handle_usado
AND
AX,AX
MOV
WORD PTR [BX],0
JNZ
mem_rda_xms
JMP
lista_h
OR
lista_err,ERROR8
MOV
; EMS 4.0+: listado de handles
; pedir memoria XMS
; fallo del controlador XMS
handle_usado:
MOV
[BX],DX
; indicar error
lista_h:
ADD
BX,2
INC
DX
LOOP
listar_h
mem_handle,DX ; preservar condición de error
CMP
ems4,ON
JNE
skip_ren_hndl
CALL
ren_handle
; error (handle no usado)
; anotar número de handle
lista_handles
ENDP
nombrar_hndl
PROC
; en EMS 4.0+ renombrar handle ; nombrar handle (EMS 4.0+)
MOV
AX,5301h
MOV
BX,tdisco
LEA
SI,nombre_tdsk
ADD
BX,15
MOV
BL,letra_unidad
AND
BL,11110000b
MOV
[SI+5],BL
MOV
tdisco,BX
INT
67h
MOV
CL,4
RET
SHR
BX,CL
MOV
AH,43h
INT
67h
AND
AH,AH
JZ
mem_rda_ems
OR
lista_err,ERROR9
STC
; zona no usada
RET
RET
; redondear para arriba
; dar nombre al handle
; Kb -> nº páginas de 16 Kb
nombrar_hndl
; pedir memoria EMS
; ------------ Detectar 286 y 386 o superior.
test_CPU
ENDP
PROC
; fallo del controlador EMS
PUSHF
; indicar error
POP
AX
OR
AH,70h
; intentar activar bit 12, 13 ó 14
AX
; del registro de estado
RET
mem_reserva
; al handle XMS y renombrarlo
RET
skip_ren_hndl: POPF
nhandle_ok:
ren_hnld_fin
JNE
PUSHF
mem_rda_ems:
CMPSW
JE
ems4,ON
STC
mem_r_ems:
REP
CMP
skip_lst_hndl: MOV
mem_rda_xms:
; detectar el handle EMS ligado
CLD
; 1: memoria extendida XMS
RET mem_r_xms:
PROC
MOV
mem_handle,DX
PUSH
CMP
ems4,ON
POPF
JNE
nhandle_ok
CALL
nombrar_hndl
PUSHF POP
AX
CLC
AND
AH,0F0h
RET
CMP
AH,0F0h
ENDP
JE
fin_test_CPU
; en EMS 4.0+ nombrar handle
; es 8086 o similar
203
CONTROLADORES DE DISPOSITIVOS
MOV
cpu286,ON
; es 286 o superior
JB
prop_valido
AND
AH,70h
; 286 pone bits 12, 13 y 14 a cero
MOV
CL,8
JZ
fin_test_CPU
; es 286
CMP
AX,4084*4
MOV
cpu386,ON
; 386 o superior
JB
prop_valido
fin_test_CPU:
RET
MOV
CL,16
test_CPU
ENDP
CMP
AX,4084*8
JB
prop_valido
MOV
CL,32
MOV
tdir,BX
; ------------ Definir valores por defecto y adaptar los parámetros prop_valido:
;
indicados por el usuario a la realidad. Esta rutina
;
inicializa el futuro sector 0 del disco. No se permite
MOV
tcluster,CL
; inicializar valores recomendados
;
que el usuario indique un directorio que ocupe más de
MOV
DX,1024
; AX = tamaño del disco en Kb
;
medio disco. Para determinar el tipo de FAT se halla el
MUL
DX
; DX:AX = bytes totales del disco
;
nº de sectores libres del disco (llamémoslo nsect),
MOV
CX,param_tsect
;
descontanto el sector de arranque y el directorio raiz;
AND
CX,CX
;
y se aplica la siguiente fórmula, que devuelve el nº de
JNZ
tsect_def
; se ha definido tamaño de sector
;
cluster más alto del disco al considerar también la
tsect_rec:
MOV
CX,tsect
; tamaño por defecto
;
ocupación de la futura FAT (12 bits = 1,5 bytes):
tsect_def:
CALL
divCX
JNC
nsect_ok
OR
lista_err,ERROR11
JMP
tsect_rec
MOV
tsect,CX
; ; ;
nsect * tamsect
2 * nsect * tamsect
------------------ + 1 = --------------------- + 1
;
tamcluster + 1,5
2 * tamcluster + 3
nsect_ok:
; menos de 65536 sectores: correcto
; asumir por defecto y recalcular
MOV
numsect,AX
;
Al resultado se le suma 1, ya que los clusters se
MOV
BX,AX
;
numeran a partir de 2, para calcular el cluster de nº
SHR
BX,1
;
más alto del disco. Si ese número es 4086 o más habrá
MOV
CX,param_tdir
;
de utilizarse una FAT de 16 bits, recalculándose la
AND
CX,CX
;
fórmula anterior sustituyendo 1,5 por 2 y 3 por 4. Al
JNZ
tdir_def
; se ha definido nº entradas
;
final, una vez determinado el tipo de FAT habrá de
tdir_rec:
MOV
CX,tdir
; nº por defecto
;
calcularse con exactitud el número de cluster más alto,
tdir_def:
MOV
AX,tsect
;
ya que hay casos críticos en que una FAT12 no sirve
XOR
DX,DX
;
pero al aplicar una FAT16 el número de clusters baja de
MOV
SI,32
; 32 bytes = tamaño entrada direct.
;
nuevo de 4085 (debido al mayor consumo de disco de la
DIV
SI
; AX nº entradas direct. por sector
;
FAT16) resultado de ello la asignación de una FAT12,
XCHG
AX,CX
;
pese a que se reserva espacio para la de 16. Hay que
XOR
DX,DX
; DX:AX = nº de entradas
;
considerar además el caso de que el disco tenga 2 FAT.
DIV
CX
; CX = entradas en cada sector
AND
DX,DX
; AX = nº sectores del ROOT
JZ
dir_ok?
INC
AX
; redondear tamaño de ROOT
CMP
AX,BX
; BX = 1/2 nº sectores del disco
;
adaptar_param
prop_ok:
PROC
; BX = 1/2 del nº total de sectores
MOV
AX,tdisco
; en Kb
MOV
BX,AX
; entradas de directorio propuestas
MOV
CL,1
; sectores por cluster propuestos
JB
dir_ok
CMP
AX,128
; ¿disco de 128 Kb o menos?
OR
lista_err,ERROR12
JBE
prop_ok
JMP
tdir_rec
MOV
BX,128
MOV
sdir,AX
CMP
AX,512
MUL
tsect
JBE
prop_ok
MOV
CX,32
MOV
BX,256
CALL
divCX
CMP
AX,2042
MOV
tdir,AX
JBE
prop_ok
MOV
AX,512
MOV
CL,2
; evitar FAT16
XOR
DX,DX
CMP
AX,4084
; ¿disco de casi 4 Mb o menos?
DIV
tsect
JBE
prop_ok
MOV
BL,tcluster
MOV
CL,4
XOR
BH,BH
MOV
BX,384
MUL
BX
CMP
AX,16384
AND
AL,AL
JB
prop_ok
JZ
propclus_ok
MOV
BX,512
MOV
tcluster,AL
CMP
dosver,300h
MOV
BX,param_tcluster
JAE
prop_valido
AND
BX,BX
CMP
AX,4084*2
JNZ
tcluster_def
dir_ok?:
dir_ok: ; ¿disco de 512 Kb o menos?
; ¿disco de casi 2 Mb o menos?
; evitar FAT16 hasta 8 Mb
; ¿disco de menos de 16 Mb?
propclus_ok:
; en DOS 2.xx evitar FAT16
; directorio excesivo
; directorio por defecto
; optimizar tamaño de directorio
; 512 / tamaño de sector
; ajustar tamaño de cluster
; se ha definido tamaño de cluster
203
tcluster_rec:
tcluster_def:
tcluster_mal:
tcluster_ok:
fat16:
calc_sfat:
fat_ok:
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
MOV
BL,tcluster
SHL
AX,1
XOR
BH,BH
RCL
DX,1
SHL
BX,1
MOV
CX,tamcluster
CMP
BX,numsect
SHL
CX,1
JB
tcluster_ok
ADD
CX,SI
OR
lista_err,ERROR13 ; tamaño de cluster incorrecto
DIV
CX
JMP
tcluster_rec
INC
AX
; los clusters se numeran desde 2
SHR
BX,1
AND
DX,DX
; ¿sobra un «cacho» de cluster?
MOV
AX,tsect
JZ
clust_eval
; redondear: ¡es preferible que
MUL
BX
INC
AX
; sobre un poco de FAT a que falte!
JC
tcluster_mal
XOR
DX,DX
; resultado en DX:AX
CMP
AX,31*1024
JA
tcluster_mal
; cluster de más de 31 Kb
MOV
tcluster,BL
; sectores por cluster
MOV
tamcluster,AX ; tamaño de cluster
; ------------ Preparar el BPB del disco virtual según los parámetros
MOV
CX,param_f
;
y forzar que el DOS lo lea indicando cambio de disco.
MOV
nfats,CL
MOV
SI,3
preparar_BPB
PROC
MOV
CX,param_f
MOV
AX,tsect
SHL
SI,CL
MOV
bytes_sector,AX
SHR
SI,1
MOV
AL,tcluster
CALL
eval_clust
MOV
sect_cluster,AL
CMP
AX,4086
MOV
AX,tdir
JAE
fat16
MOV
entradas_raiz,AX
MOV
CX,3
MOV
AX,numsect
MUL
CX
MOV
num_sect,AX
SHR
DX,1
MOV
AL,nfats
RCR
AX,1
MOV
num_fats,AL
JMP
calc_sfat
MOV
AX,sfat
MOV
SI,4
MOV
sectores_fat,AX
MOV
CX,param_f
MOV
cambiado,0FFh
SHL
SI,CL
SHR
SI,1
CALL
eval_clust
SHL
AX,1
RCL
DX,1
; clusters * 2
;
entrar. Se procederá a copiar la rutina necesaria en
DIV
tsect
; AX = nº sectores de FAT aprox.
;
función del tipo de memoria que gestiona el disco.
AND
DX,DX
;
Después, se copiarán las variables que gestionan TDSK
JZ
fat_ok
;
sobre la copia residente, así como el nuevo BPB.
INC
AX
MOV
sfat,AX
prep_driver
PROC
MOV
AX,numsect
; nº total de sectores
MOV
AL,tipo_soporte
DEC
AX
; descontar BOOT
LEA
SI,procesa_xms
SUB
AX,sdir
; descontar ROOT
MOV
CX,tam_proc_xms
SUB
AX,sfat
; descontar FAT
DEC
AL
MOV
CL,tcluster
JZ
prep_mem
XOR
CH,CH
LEA
SI,procesa_ems
XOR
DX,DX
MOV
CX,tam_proc_ems
DIV
CX
; AX = número real de clusters
DEC
AL
INC
AX
; se numeran desde 2
JZ
prep_mem
MOV
ultclus,AX
LEA
SI,procesa_con
MOV
CX,tam_proc_con
LEA
DI,procesa_io
; tamaño por defecto
; ¿cabe seguro un cluster?
; DX:AX = tamaño de cluster clust_eval:
ENDP
eval_clust
PROC
; CX = 2 * tamcluster + SI
RET
; considerar número de FATs
eval_clust
; obtener nº más alto de cluster
; el nº más alto supera 4085
; clusters * 3
; clusters * 3 / 2 = clusters * 1,5
; considerar número de FATs
ENDP
; ha habido «cambio» de disco
RET preparar_BPB
ENDP
; ------------ Preparar el disco para operar. ES apunta al disco al
; redondeo
RET adaptar_param
; DX:AX = nsect * tamsect * 2
prep_mem:
; instalar rutina XMS
; instalar rutina EMS
; instalar rutina memoria conv.
CLD ; obtener el nº más alto de cluster
XPUSH
MOV
AX,numsect
REP
MOVSB
DEC
AX
; restar BOOT
XPOP
SUB
AX,sdir
; restar ROOT
XPUSH
MUL
tsect
; DX:AX = nsect * tamsect
POP
ES
; instalar rutina en el disco
203
CONTROLADORES DE DISPOSITIVOS
prep_driver
REP
MOVSB
; y en el propio TDSK.EXE (para
POP
ES
; usarla después al formatear)
LEA
CX,f_tdsk_ctrl
;
En versiones del DOS anteriores a la 3.3, el sistema
LEA
SI,i_tdsk_ctrl
;
inexplicablemente hace caso omiso del cambio de disco
SUB
CX,SI
;
(¿?), por lo que hay que avisarle ¡dos veces!, con el
MOV
DI,SI
;
correspondiente doble cambio del byte descriptor de
REP
MOVSB
;
medio, para que se tome en serio el cambio de disco.
LEA
CX,fin_bpb
;
Por fortuna desde el DOS 3.3 ya no es preciso hacer
LEA
SI,bpb
;
esta extraña maniobra. Para que el DOS acceda al disco,
SUB
CX,SI
;
se le pregunta simplemente el espacio libre del mismo.
MOV
DI,SI
REP
MOVSB
; actualizar variables
; actualizar BPB
; ------------ Inicializar la BOOT, FAT y ROOT del disco virtual.
formatear_tdsk PROC
RET
PUSH
ES
ENDP
PUSH
DS
; *
POP
ES
; ------------ Autorelocalización de TDSK.EXE
LEA
SI,sector_cero
;
Es necesario si se reserva memoria convencional para el
LEA
DI,area_trabajo
;
disco virtual. El motivo es evitar que al inicializar
MOV
CX,128
;
la BOOT, la FAT y el ROOT al inicio del disco, si éste
CLD
;
está justo encima de TDSK, TDSK se autodestruya. Por
REP
MOVSB
;
ello, TDSK se autocopiará en la mitad de los 128 Kb del
XOR
AX,AX
;
mayor bloque de memoria libre, nunca utilizados por el
MOV
CX,tam_a_trabajo-128
;
disco (aunque este bloque no haya sido reservado, ¡como
REP
STOSB
;
está libre!). Finalmente pasará a correr en ese nuevo
LEA
DI,area_trabajo
;
destino. Se copia TODO, pila incluida. La copia se hace
ADD
DI,tsect
;
en «segm_reubicar» que apunta a la mitad de esos 128 Kb
MOV
[DI-2],0AA55h
; marca de sector válido
;
con objeto de evitar solapamientos origen/destino (TDSK
CALL
escribe_sectAX
; escribir sector BOOT (AX=0)
;
ocupa sólo alrededor de 16 Kb en memoria).
LEA
DI,area_trabajo
MOV
CX,tsect
relocalizar
PROC
REP
STOSB
MOV
AX,sfat
MOV
CX,param_f
SHL
AX,CL
CMP
tipo_soporte,3
JE
procede_reloc
; usada memoria convencional
RET
; a 0 resto del área de trabajo
; borrar area de trabajo
; considerar número de FATs
ES
; * preservar ES
SHR
AX,1
MOV
ES,segm_reubicar
; segmento de reubicación
ADD
AX,sdir
XOR
SI,SI
CMP
AX,1
XOR
DI,DI
JE
pfat
MOV
BX,SS
; final de TURBODSK (pila)
CALL
escribe_sectAX
; inicializar directorio raiz
MOV
CX,DS
; inicio de _PRINCIPAL
DEC
AX
; y últimos sectores de la FAT
SUB
BX,CX
; tamaño de TDSK en párrafos
JMP
ini_fat
MOV
CL,4
LEA
DI,area_trabajo
SHL
BX,CL
; ahora en bytes
MOV
BYTE PTR [DI],media
ADD
BX,tam_pila+16
; 16 por si acaso
MOV
AX,0FFFFh
MOV
CX,BX
; CX = bytes a relocalizar
MOV
DS:[DI+1],AX
CMP
ultclus,4086
REP
MOVSB
; auto-copiaje arriba
JB
pfat_ok
MOV
AX,ES
MOV
DS:[DI+3],AL
MOV
DS,AX
; nuevo segmento de datos
MOV
AX,1
POP
ES
; * restaurar ES
CALL
escribe_sectAX
MOV
BX,CS
CALL
fecha_hora
SUB
AX,BX
LEA
SI,dir_raiz
MOV
BX,SS
MOV
[SI+22],AX
; hora actual
ADD
BX,AX
MOV
[SI+24],DX
; fecha actual
MOV
SS,BX
; actualizar segmento de pila
LEA
DI,area_trabajo
POP
AX
; dirección de retorno cercano
MOV
CX,32
PUSH
DS
; segmento de «retorno»
REP
MOVSB
PUSH
AX
; offset
MOV
AX,sfat
; retorno cargando CS:
MOV
CX,param_f
SHL
AX,CL
procede_reloc: PUSH
ini_fat:
pfat:
CLD
RETF relocalizar
; primeros 128 bytes del BOOT
ENDP
; ES - CS --> cuantía del salto
pfat_ok:
; AX = sectores fat + dir. raiz
; inicializar 3 bytes FAT...
; ¿menos de 4085 clusters?
; inicializar 4º byte FAT
; primer sector FAT preparado
; considerar número de FATs
203
formateado:
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
SHR
AX,1
ADC
AH,0
INC
AX
POP
DX
CALL
escribe_sectAX
; primer sector raiz preparado
POP
ES
; *
fecha_hora
CMP
dosver,31Eh
JAE
formateado
; DOS 3.3+
; ------------ Cambiar el nombre al bloque de control de memoria para
NOT
ES:media_byte
; cambiar descriptor de medio
;
mejorar la información del comando MEM del sistema si
MOV
AH,36h
; «obtener espacio libre»
;
el disco se define en memoria convencional/superior.
MOV
DL,ES:letra_unidad
SUB
DL,'A'-1
renombrar_mcb
PROC
PUSH
DX
INT
21h
POP
DX
NOT
ES:media_byte
MOV
ES:cambiado,0FFh
MOV
AH,36h
INT
21h
; unidad de disco virtual
ENDP
PUSH
ES
MOV
AL,letra_unidad
MOV
BYTE PTR nombre_tdsk+5,AL
; restaurar descriptor de medio
MOV
BYTE PTR nombre_tdsk+4,'('
; nuevo «cambio» de disco
MOV
BYTE PTR nombre_tdsk+6,')'
MOV
AX,segm_psp
DEC
AX
MOV
ES,AX
LEA
SI,nombre_tdsk
MOV
DI,8
; ---- Escribir el sector nº AX del disco virtual. No
MOV
CX,DI
;
CLD
; primer acceso al disco
; acceder otra vez al disco
RET
formatear_tdsk ENDP
se utiliza INT 26h (imposible desde el CONFIG).
escribe_sectAX PROC PUSHF
XOR
BP,BP
; indicar escritura
LEA
DI,area_trabajo
; ES:DI buffer
MOV
BX,AX
; número de sector
MOV
AX,1
; 1 sector
CALL
io_proc
; acceder al disco directamente
XPOP
REP
MOVSB
POP
ES
RET
; preservar bit DF
XPUSH
renombrar_mcb
ENDP
; ------------ Informar sobre el disco virtual instalado.
info_disco
PROC CALL
InitMultiPrint
LEA
DX,ayuda_txt
; ayuda en español
POPF
CMP
param_h,ON
; ¿solicitud de ayuda?
RET
JNE
cont_info
; no
JMP
info_exit
TEST
err_grave,0FFFFh
JZ
info_no_fatal
LEA
DX,err_grave_gen
; texto de encabezamiento
CALL
imprimir
; imprimir errores graves:
LEA
DX,e0
TEST
err_grave,ERROR0
JZ
otro_fallo
CALL
imprimir
escribe_sectAX ENDP cont_info: ; ---- Obtener fecha y hora del sistema en DX y AX
fecha_hora
; * recuperar fecha
RET
PROC MOV
AH,2Ah
INT
21h
MOV
AL,32
MUL
DH
SUB
CX,1980
MOV
SP,tam_pila
SHL
CL,1
; (año-1980)*2
PUSH
segm_psp
; en DOS 1.x hay que terminar
ADD
AH,CL
; sumar (año-1980)*512
XOR
AX,AX
; con CS = PSP
MOV
CL,DL
; CX = dia (CH=0)
PUSH
AX
ADD
AX,CX
PUSH
AX
MOV
AH,2Ch
INT
21h
MOV
AL,32
MUL
CL
MOV
; obtener fecha del sistema
; AX = mes * 32
RETF otro_fallo:
; ejecutar INT 20h de PSP:0
LEA
DX,e1
TEST
err_grave,ERROR1
JNZ
info_g
LEA
DX,e2
TEST
err_grave,ERROR2
CL,3
JNZ
info_g
SHL
CH,CL
LEA
DX,e3
XOR
CL,CL
JMP
info_exit
ADD
AX,CX
SHR
DH,1
ADD
AL,DH
; * guardar fecha
; obtener hora del sistema
; AX = minutos*32
; CX = hora*2048
info_g:
info_no_fatal: CMP ; segundos/2
; no es error de DOS incorrecto
ES:tipo_soporte,0 ; error no fatal
JNE
info_reporte
LEA
DX,info_ins
; disco no formateado
203
CONTROLADORES DE DISPOSITIVOS
CALL
imprimir
CALL
print_32
CALL
impr_unidad
LEA
DX,inf_mem
LEA
DX,info_ins2
CALL
imprimir
CMP
lista_err,0
MOV
AL,ES:tipo_soporte
JE
info_exit
; sin mensajes de advertencia
LEA
DX,inf_mem_xms
CALL
imprimir
; ... o con ellos
DEC
AL
JMP
info_err
JZ
mem_ifdo
CALL
pr_info
LEA
DX,inf_mem_ems
CMP
lista_err,0
DEC
AL
JE
info_ret
; sin mensajes de advertencia
JZ
mem_ifdo
LEA
DX,cab_adv_txt
; ... o con ellos
LEA
DX,inf_mem_con
CALL
imprimir
; cabecera de advertencias
CMP
ES:mem_handle,0A000h
MOV
AX,lista_err
JB
mem_ifdo
; memoria convencional
LEA
BX,tabla_mens-2
; tabla de mensajes
LEA
DX,inf_mem_sup
; memoria superior
MOV
CX,16
; 16 posibles mensajes
CALL
imprimir
ADD
BX,2
LEA
DX,inf_nclusters
SHR
AX,1
CALL
imprimir
JC
informa
LOOP
busca_err
MOV
AX,ES:entradas_raiz
JMP
info_ret
MOV
BX,32
LEA
DX,mens_cabec
MUL
BX
; bytes ocupados por directorio
CALL
imprimir
DIV
ES:bytes_sector
; AX = sectores del directorio
MOV
DX,[BX]
ADD
AX,ES:sect_reserv
CALL
imprimir
ADD
AX,ES:sectores_fat
JMP
mas_mens
SUB
AX,ES:num_sect
info_exit:
CALL
imprimir
NEG
AX
info_ret:
RET
XOR
DX,DX
info_disco
ENDP
MOV
BL,ES:sect_cluster
XOR
BH,BH
DIV
BX
info_reporte:
info_err:
busca_err:
mas_mens:
informa:
pr_info
; disco formateado
mem_ifdo:
; no se produce ese error
; inicio común a los mensajes
; dirección de ese mensaje
; acabar con todos
PROC
; memoria XMS
; memoria EMS
; AX = sectores libres
; AX = nº de clusters
LEA
DX,info_txt
XOR
DX,DX
CALL
imprimir
MOV
CL,5
CALL
impr_unidad
CALL
print_32
LEA
DX,inf_tsect
LEA
DX,inf_tfat
CALL
imprimir
CALL
imprimir
MOV
AX,ES:bytes_sector
LEA
DX,inf_tfat12
XOR
DX,DX
CMP
AX,4085
MOV
CL,5
JB
ifat_ok
CALL
print_32
LEA
DX,inf_tfat16
LEA
DX,inf_tdir
CALL
imprimir
CALL
imprimir
LEA
DX,inf_final
MOV
AX,ES:entradas_raiz
CALL
imprimir
XOR
DX,DX
RET
MOV
CL,5
CALL
print_32
LEA
DX,inf_tdisco
CALL
imprimir
MOV
AX,ES:num_sect
MUL
ES:bytes_sector
XPUSH
MOV
BX,1024
MOV
AL,letra_unidad
DIV
BX
MOV
AH,0
MOV
CL,5
MOV
WORD PTR area_trabajo,AX
CALL
print_32
LEA
DX,area_trabajo
LEA
DX,inf_tcluster
CALL
imprimir
CALL
imprimir
XPOP
MOV
AL,ES:sect_cluster
RET
XOR
AH,AH
XOR
DX,DX
MOV
CL,5
ifat_ok:
pr_info
; ¿FAT12?
ENDP
; --- Imprimir letra de unidad en AL.
impr_unidad
impr_unidad
PROC
ENDP
; --- Imprimir un nº decimal de 32 bits en DXAX formateado por CL.
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
; rep_sub_pr32:
; Entradas: ;
Si bit 4
;
bits
= 1 --> se imprimirán signos separadores de millar
0-3 = nº total de dígitos (incluyendo separadores de
; bits
CL,0FFh
INC
CL
SUB
AX,SI
SBB
DX,DI
JNC
millar y parte fraccional)
;
MOV
5-7 = nº de dígitos de la parte fraccional (cuantos
; DXAX = DXAX - DISI
rep_sub_pr32
; restar el factor cuanto se
pueda
;
dígitos de DXAX, empezando por la derecha,
ADD
AX,SI
; subsanar el desbordamiento:
;
se consideran parte fraccional, e irán precedidos
ADC
DX,DI
; DXAX = DXAX + DISI
;
del correspondiente separador)
ADD
CL,'0'
; pasar binario a ASCII
;
MOV
[BX],CL
; Salidas: nº impreso, ningún registro modificado.
POP
CX
;
INC
BX
LOOP
digit_pr32
; * Ejemplo, si DXAX=9384320 y ;
CL=010 1 1011
se imprimirá ( '_' representa un espacio en blanco ):
; CX se recupera ahora
; próximo dígito del número
STD
__93.843,20
; transferencias (MOVS) hacia
atrás print_32
DEC
BX
PUSH
DS
MOV
final_pr32,BX
PUSH
ES
MOV
PUSH
CS
PUSH
CS
MOV
CL,5
POP
DS
MOV
AL,formato_pr32
POP
ES
SHR
AL,CL
PUSH
AX
AND
AL,AL
PUSH
BX
JZ
no_frac_pr32
PUSH
CX
MOV
CL,AL
PUSH
DX
XOR
CH,CH
PUSH
SI
MOV
SI,final_pr32
PUSH
DI
MOV
DI,SI
INC
DI
PROC
formato_pr32,CL
; preservar todos los registros
digit_pr32:
factor_pr32:
REP
; byte del formato de impresión
frontera
parte
; AL = nº de decimales
; ninguno
MOVSB
MOV
CX,idioma_seps
INC
final_pr32
MOV
millares_pr32,CH
; separador de millares
MOV
AL,fracc_pr32
MOV
fracc_pr32,CL
; separador parte fraccional
MOV
MOV
BX,OFFSET tabla_pr32
MOV
CX,10
PUSH
CX
PUSH
AX
PUSH
DX
XOR
DI,DI
MOV
SI,1
; DISI = 1
; correr cadena arriba (hacer
DEC
CX
; CX - 1
JCXZ SAL RCL
DI,1
MOV
[DI],AL
; poner separador de parte
fraccional
no_frac_pr32:
MOV
ent_frac_pr32,SI
MOV
AL,formato_pr32
TEST
; indicar nueva frontera
AL,16
; interpretar el formato
especificado JZ
poner_pr32
; imprimir como tal
MOV
CX,final_pr32
; añadir separadores de millar
SUB
CX,ent_frac_pr32
hecho_pr32
ADD
CX,3
SI,1
MOV
SI,final_pr32
MOV
DI,SI
DX,DI
INC
DI
MOV
AX,SI
REP
SAL
SI,1
RCL
DI,1
MOV
AL,millares_pr32
SAL
SI,1
MOV
[DI],AL
RCL
DI,1
INC
final_pr32
ADD
SI,AX
MOV
ent_frac_pr32,SI
SUB
SI,OFFSET tabla_pr32
CMP
SI,3
JAE
entera_pr32
MOV
BX,final_pr32
MOV
BYTE PTR [BX+1],0 ; delimitador de fin de cadena
MOV
BX,OFFSET tabla_pr32
MOV
principio_pr32,BX ; inicio de cadena
ADC
DI,DX
entera_pr32:
; DISI * 2
LOOP
factor_pr32
; DISI * 8
; DISI = DISI*8 + DISI*2 =
; DISI = DISI*10*10* ... (CX-1 poner_pr32:
veces) POP
DX
; luego DISI = 10 elevado a
(CX-1) POP
AX
MOVSB
; correr cadena arriba (hacer
hueco)
DISI*10
hecho_pr32:
;
hueco)
elegido
separ_pr32:
; último dígito
ent_frac_pr32,BX
entera/fraccional
PUSHF MOV
; BX apunta al último dígito
; CX se recuperará más tarde
; poner separador de millares
; usar esta variable como puntero
; próximo separador
203
CONTROLADORES DE DISPOSITIVOS
limpiar_pr32:
acabar_pr32:
print_32
MOV
AL,[BX]
CMP
AL,'0'
JE
blanco_pr32
; cero a la izda --> poner " "
CMP
AL,millares_pr32
; separador millares a la izda
JE
blanco_pr32
;
resto: DX). Si el cociente excede los 16 bits, CF = 1
CMP
AL,fracc_pr32
;
y todos los registros intactos.
JNE
acabar_pr32
MOV
BYTE PTR [BX-1],'0' ; reponer 0 antes de la coma
divCX
PROC
DEC
principio_pr32
MOV
AL,formato_pr32
AND
AL,00001111b
XOR
AH,AH
MOV
print_32
; ------------ Dividir DX:AX / CX sin desbordamientos (cociente: AX,
XPUSH MOV
SI,32
XOR
BX,BX
SHL
AX,1
DX,final_pr32
RCL
DX,1
SUB
DX,AX
RCL
BX,1
INC
DX
CMP
BX,CX
AND
AX,AX
JB
dividido
SUB
BX,CX
INC
AL
DEC
SI
JNZ
divmas
AND
DX,DX
JNZ
; imprimir
divmas:
; DX = offset 'principio'
format_pr32
; longitud especificada por el
usuario
format_pr32:
MOV
DX,principio_pr32 ; longitud obtenida del número
CALL
imprimir
POPF
dividido:
; restaurar todos los registros
POP
DI
JZ
div_ok
POP
SI
XPOP
POP
DX
STC
POP
CX
POP
BX
POP
; "no cabe"
; 1 al cociente
; error
JMP
div_fin
MOV
DX,BX
; resto en DX y cociente en AX
AX
ADD
SP,4
; «sacar» sin sacar DX y AX
POP
ES
CLC
POP
DS
; recuperar CX, SI y BX
div_ok:
div_fin:
RET blanco_pr32:
ENDP
MOV
XPOP RET
; salida del procedimiento BYTE PTR [BX],' ' ; sustituir 0 ó separador de
divCX
ENDP
millares INC
BX
; a la izda. por espacio en
formato_pr32
; ------------ Impresión en color o monocroma (esta última ;
redireccionable). Desde el CONFIG.SYS se imprime en
INC
principio_pr32
;
monocromo para no llamar la atención, a menos que
CMP
BX,final_pr32
;
indiquen /M, al contrario que desde el DOS.
JB
limpiar_pr32
MOV
DX,BX
imprimir
PROC
JMP
SHORT acabar_pr32 ; imprimir
PUSH
AX
DB
0
MOV
AL,param_m
DB
5 DUP (' ')
CMP
modo,CONFIG
; ¿en CONFIG.SYS?
JNE
m_ok
; no
XOR
AL,ON
; sí: /M opera al revés
MOV
pr_mono,AL
CALL
print
POP
AX
blanco
; es el número 0.000.000.00X
; espacios en blanco para cubrir
la ; mayor plantilla que pueda ser m_ok:
espe; cificada en el formato tabla_pr32
DT
0
; reservar 14 bytes (nº más .,
RET
más ASCIIZ) ; aquí se solapa un buffer de 32
imprimir
ENDP
DW
0,0
millares_pr32
DB
'.'
; separador de millares
; ------------ Imprimir cadena en DS:DX delimitada por un 0 ó un 255.
fracc_pr32
DB
','
;
parte fraccional
;
Si acaba en 0, se imprime como tal; en caso contrario,
; offset al último byte a
;
se supone que el mensaje es multilingüe y los diversos
;
idiomas (1, 2, ... N) separan sus cadenas por sucesivos
;
códigos 255. El carácter de control 127 realiza una
;
pausa hasta que se pulsa una tecla.
print
PROC
bytes
final_pr32
DW
"
0
imprimir principio_pr32 DW ent_frac_pr32
0
DW
;
"
"
0
primer
"
"
"
; offset a la frontera
entero-fracc. DT
0
; $ - tabla_pr32 = 32 bytes
XPUSH
usados por ;
INT
21h
al
principio
de
CMP
idioma,0
203
pr_cod:
pr_busca_cod:
pr_cod_tfno:
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
CMP
BYTE PTR [BX],255
JNE
pr_busca_ter
AH,30h
INC
BX
INT
21h
LOOP
pr_busca_msg
XCHG
AH,AL
MOV
BX,DX
MOV
CX,AX
DEC
BX
CMP
param_i,ON
INC
BX
MOV
AX,codigo_tfno
CMP
BYTE PTR [BX],0
MOV
BX,1234h
JE
prlong_ok
JNE
pr_busca_cod
CMP
BYTE PTR [BX],127 ; carácter de pausa
MOV
BX,AX
JE
prpausa
MOV
AL,0FFh
CMP
BYTE PTR [BX],255
CMP
BX,255
JNE
pr_cad_lon
JAE
pr_cod
; código mayor o igual de 255
JMP
prlong_ok
MOV
AL,BL
; código menor de 255
PUSH
BX
CMP
CX,200h
MOV
CX,BX
JAE
pr_cod_tfno
SUB
CX,DX
CMP
CX,200h
CALL
pr_cad
MOV
AX,1
MOV
AH,1
JB
pr_habla_ax
INT
16h
MOV
AL,0
JZ
pr_notec
LEA
DX,area_trabajo
MOV
AH,0
MOV
AH,38h
INT
16h
JMP
pr_limpbuf
MOV
AH,0
INT
16h
POP
BX
INC
BX
MOV
DX,BX AL,27
JNE
pr_decidir
PUSH
DX
MOV
; *
pr_usar_ese: ; CX = versión del DOS pr_cad_lon:
; parámetro /I=cod no indicado
prpausa:
; DOS >= 2.X
; inglés para DOS < 2.X
pr_limpbuf:
XPUSH
pr_habla_ax:
pr_busca_idi:
pr_habla_ese:
pr_decidir:
pr_busca_msg:
pr_busca_ter:
; obtener información del pais
pr_notec:
INT
21h
XPOP
JC
pr_habla_ax
CMP
CX,20Bh
JE
pr_habla_ax
CMP
CX,300h
CMP
MOV
AX,1
STC
JB
pr_habla_ax
MOV
AX,BX
LEA
BX,area_trabajo
MOV
CH,[BX+7]
MOV
CL,[BX+9]
MOV
idioma_seps,CX
LEA
BX,info_paises-2
MOV
CX,1
ADD
BX,2
MOV
DX,[BX]
; MOV
AH,40h
CMP
AX,DX
; MOV
BX,1
JE
pr_habla_ese
; INT
AND
DX,DX
MOV
SI,DX
JNZ
pr_busca_idi
LEA
DI,area_trabajo
INC
CX
PUSH
DS
CMP
[BX+2],DX
POP
ES
JNE
pr_busca_idi
MOV
idioma,CL
POP
DX
; fallo en la función
; DOS 2.11: AX cód. telefónico
JE
pr_ret
JMP
pr_cad_lon
MOV
CX,BX
; separador de millares
SUB
CX,DX
; separador de decimales
CALL
pr_cad
; 2.x excepto 2.11: mala suerte
prlong_ok:
; acaba en 255 pero no es ese
; calcular longitud
; imprimir hasta el código 127
; limpiar buffer del teclado
; esperar tecla
; ¿tecla ESC?
; imprimir el resto
; terminar impresión
CLC pr_ret: ; supuesto idioma 1
XPOP
; CF=1 si se pulsó
ESC RET pr_cad:
; será otro idioma
21h
; imprimir con el DOS
; por si acaso
CLD
; no es fin de la tabla
; *
REP
MOVSB
MOV
[DI],CL
LEA
DX,area_trabajo
CALL
MultiPrint
; ASCIIZ
; imprimir en color
MOV
CL,idioma
MOV
CH,0
MOV
BX,DX
MOV
DX,BX
DEC
BX
; ------------ Impresión en pantalla, en color o monocromo, usando el
INC
BX
;
BIOS o el DOS respectivamente. Antes deberá ejecutarse
CMP
BYTE PTR [BX],0
;
InitMultiPrint para inicializar.
JE
pr_usar_ese
;
intenta respetar el posible
RET
; nº de idioma a usar (1..N) print
; acaba en 0: no buscar más
ENDP
color
Al
hacer scroll se
global
de
fondo.
203
CONTROLADORES DE DISPOSITIVOS
;
Con «pr_mono» en ON se solicita imprimir en monocromo.
;
JE
pr_derecha
CMP
AL,10
;
- El texto a imprimir es apuntado por DS:DX.
JE
pr_crlf
;
- Códigos de control soportados:
MOV
AH,9
MOV
BH,pr_pagina
;
0 -> final de cadena
MOV
BL,pr_color
;
1 -> el siguiente carácter indica el color (BIOS)
MOV
CL,pr_veces
;
2 -> el siguiente carácter indica el nº de veces que
XOR
CH,CH
PUSH
DX
INT
10h
POP
DX
;
;
se imprimirá el que viene detrás
; ;
3 -> avanzar cursor a la derecha 10 -> retorno de carro y salto de línea estilo UNIX pr_derecha:
MultiPrint
pr_rut_ok:
pr_otro:
pr_ASCII:
pr_setcolor:
pr_setveces:
; código de control 10: CR & LF
; imprimir carácter
ADD
DL,pr_veces
PROC
MOV
pr_veces,1
XPUSH
CMP
DL,pr_maxX
PUSH
DS
JBE
pr_av
POP
ES
XOR
DL,DL
; volver al inicio de línea
PUSH
CS
INC
DH
; salto a la siguiente
POP
DS
CMP
DH,pr_maxY
LEA
AX,pr_AL_dos
JBE
pr_av
CMP
pr_mono,ON
DEC
DH
JE
pr_rut_ok
PUSH
DX
LEA
AX,pr_AL_bios
MOV
AX,601h
MOV
pr_rut,AX
MOV
BH,pr_colorb
MOV
BX,DX
XOR
CX,CX
MOV
AL,ES:[BX]
MOV
DL,pr_maxX
PUSH
BX
MOV
DH,pr_maxY
CMP
AL,' '
INT
10h
JAE
pr_ASCII
POP
DX
AND
AL,AL
MOV
BH,pr_pagina
JZ
pr_exit
MOV
AH,2
CMP
AL,1
INT
10h
JE
pr_setcolor
CMP
AL,2
JE
pr_setveces
CALL
pr_rut
POP
BX
CMP
AL,3
INC
BX
JNE
pr_no_der
JMP
pr_otro
MOV
AL,' '
MOV
AL,ES:[BX+1]
CMP
AL,10
MOV
pr_color,AL
JNE
pr_dos
POP
BX
MOV
AL,13
; código de control 10: CR & LF
ADD
BX,2
CALL
pr_dos
; llamada "recursiva"
JMP
pr_otro
MOV
AL,10
MOV
AL,ES:[BX+1]
MOV
CL,pr_veces
MOV
pr_veces,AL
XOR
CH,CH
POP
BX
MOV
pr_veces,1
ADD
BX,2
MOV
DL,AL
JMP
pr_otro
XPOP
pr_crlf:
; instalar rutina de impresión
; no es un código de control pr_av: ; código de control 0: final
RET
; código de control 1: color pr_AL_bios
ENDP
pr_AL_dos
PROC
; es preciso hacer scroll
; color por defecto
; hacer scroll usando BIOS
; posicionar cursor ; retorno del procedimiento
; código de control 2: repetir
pr_no_der: ; actualizar color
pr_dos: ; actualizar repeticiones
pr_chr:
; imprimir usando DOS
; código de control 3: avanzar
XPUSH MOV
AH,2
RET
INT
21h
MultiPrint
ENDP
XPOP
LOOP
pr_chr
pr_AL_bios
PROC
pr_exit:
; código de control 3: avanzar
; imprimir carácter
RET
; imprimir en color usando BIOS pr_AL_dos
ENDP
PUSH
AX
MOV
AH,3
MOV
BH,pr_pagina
INT
10h
POP
AX
PUSH
CS
CMP
AL,3
POP
DS
InitMultiPrint PROC ; DX = coordenadas del cursor
XPUSH
203
pr_i_80?:
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
JZ
xms_general
INC
BP
pr_mono,OFF
PUSH
ES
MOV
AH,0Fh
PUSH
DI
; segmento:offset fuente
INT
10h
PUSH
BP
; handle fuente (BP=0)
CMP
AH,80
; ¿80 ó más columnas?
SHL
CX,1
; palabras -> bytes
JAE
pr_i_video_ok
; así es
RCL
BP,1
; BP era 0
MOV
AX,3
PUSH
BP
; tamaño bloque (parte alta)
INT
10h
PUSH
CX
; tamaño bloque (parte baja)
JMP
pr_i_80?
MOV
SI,SP
MOV
pr_veces,1
MOV
pr_color,15
MOV
xms_general:
; forzar modo de 80 columnas
; hacer BP = 0
pr_maxX,AH
; inicializar máxima coord. X
PUSH
SS
MOV
pr_pagina,BH
; inicializar página activa
POP
DS
; DS:SI apuntando a la pila
MOV
AX,40h
MOV
AH,0Bh
; función para mover EMB
MOV
ES,AX
; ES: -> variables del BIOS
CALL
llama_XMS
; mover EMB (DS no importa)
MOV
AL,ES:[84h]
; variable de nº líneas - 1
ADD
SP,16
; equilibrar pila
CMP
AL,24
; ¿el BIOS define la variable?
CMP
AL,1
; ¿falló el controlador?
JB
pr_i_maxy_ok
; no
JE
xms_proc_ok
MOV
pr_maxY,AL
; inicializar máxima coord. Y
MOV
AX,0C81h
; anomalía general
MOV
AH,8
; (BH = página)
XCHG
AH,AL
; colocar resultado
INT
10h
; obtener color por defecto
MOV
pr_colorb,AH
XPOP
MOV
DX,DS
; handle en DS (si utilizado)
CALL
CS:xms_driver
; ejecutar función XMS
pr_i_video_ok: MOV
pr_i_maxy_ok:
; valores por defecto
RET
xms_proc_ok:
RET procesa_xms
ENDP
llama_XMS
PROC
InitMultiPrint ENDP
RET
pr_pagina
DB
0
; página de visualización activa
pr_veces
DB
1
; veces que se imprime cada carácter
pr_color
DB
15
; color BIOS para imprimir
pr_colorb
DB
?
; color por defecto en pantalla
pr_maxX
DB
80
; máxima coordenada X en pantalla
pr_maxY
DB
24
; máxima coordenada Y en pantalla
; ------------ Rutina de gestión de memoria convencional. Se copiará
pr_mono
DB
OFF
; a ON si imprimir en monocromo
;
sobre la de memoria EMS si se utiliza memoria conv.
pr_rut
DW
?
; apunta a pr_AL_bios / pr_AL_dos procesa_con
PROC
llama_XMS
ENDP
tam_proc_xms
EQU
$-OFFSET procesa_xms
; tamaño de esta rutina
; ------------ Rutina de gestión de memoria XMS. Se copiará sobre
JC
con_exit
; sistema inicializándose
;
la de memoria EMS si se utiliza memoria XMS.
MOV
BX,16
; bytes por párrafo
;
En esta rutina se emplea la pila para pasar los
DIV
BX
; AX = segmento, DX = offset
;
parámetros al controlador XMS.
ADD
AX,CS:mem_handle
; segmento de inicio datos
MOV
DS,AX
procesa_xms
MOV
SI,DX
; DS:SI inicio de datos
MOV
DS,CS:mem_handle
DEC
BP
; y ES:DI destino del buffer
JNC
no_xmslib
JZ
con_general
; es lectura
SI,DI
; escritura: intercambiar
PROC
.286
; rutina ejecutada desde 286+
XCHG
PUSHA
; sistema reinicializando:
XPUSH
MOV
AH,0Dh
CALL
llama_XMS
MOV
AH,0Ah
CALL
llama_XMS
XPOP ; desbloquear EMB (prudente)
xms_escribe:
CLD CMP
CS:cpu386,ON
JE
con_tr32bit
POPA
REP
MOVSW
.8086
JMP
con_tr_fin
SHR
CX,1
; nº palabras de 32 bit a mover
JCXZ
con_trdo
; evitar desgracia
; liberar EMB
con_tr32bit:
RET no_xmslib:
con_general:
DEC
BP
; leer/escribir en el disco
JNZ
xms_escribe
PUSH
ES
PUSH
DI
; segmento:offset destino
XOR
EAX,EAX
; asegurar no violación
PUSH
BP
; handle destino (BP=0)
DEC
AX
; de segmento-64K
PUSH
DX
AND
ECX,EAX
; EAX = 0FFFFh
PUSH
AX
; desplazamiento DX:AX
AND
ESI,EAX
PUSH
DS
; handle fuente/destino
AND
EDI,EAX
.386 PUSHAD
203
CONTROLADORES DE DISPOSITIVOS
REP con_trdo:
MOVSD
; transferencia ultrarrápida
sector_cero
; POPAD falla en muchos 386
JMP
NOP
; arreglar fallo de POPAD
NOP
MOV
con_exit:
RET
procesa_con
ENDP
EQU
AX,100h
; todo fue bien, por supuesto
$-OFFSET procesa_con
; tamaño de esta rutina
; ************ Datos no residentes para la instalación
ON
EQU
1
OFF
EQU
0
DB
"TDSK 2.3"
; identificación del sistema
DW
512
; tamaño de sector por defecto
tcluster
DB
?
; sectores por cluster
DW
1
; sectores reservados
nfats
DB
?
; número de FAT's
tdir
DW
?
; número de entradas al dir. raiz
numsect
DW
?
; nº sectores del disco (<=32Mb)
DB
media
; descriptor de medio
DW
?
; sectores por FAT
DW
1, 1
; sectores por pista / cabezas
DD
0
; sectores ocultos
DD
0
; nº total de sectores (si > 32Mb)
DB
7 DUP (0)
; 7 bytes reservados
DB
0EAh
; código de JMP FAR...
DW
0,0FFFFh
; ...FFFF:0000 (programa BOOT)
DB
"(C)1992 CiriSOFT"; resto de primeros 64 bytes
EQU
1
; TURBODSK ejecutado desde el CONFIG
AUTOEXEC
EQU
2
; TURBODSK se ejecuta desde el DOS
emm_id
nombre_tdsk
DB
DB
". Grupo Universi"
DB DB
"tario de Informá"
DB
"tica (GUI) - Val"
DB
"ladolid (España)"; resto de primeros 128 bytes
sfat
; constantes booleanas
CONFIG
"EMMXXXX0"
"TDSK U: "
SHORT botar
tsect
.8086 con_tr_fin:
tam_proc_con
LABEL BYTE
POPAD
botar:
; identificación del controlador EMS
; para nombrar handle EMS y el MCB
modo
DB
?
; CONFIG/AUTOEXEC
dosver
DW
?
; versión del DOS
DB
"TURBODSK
top_ram
DW
0
; segmento más alto de la RAM
DB
8
; etiqueta de volúmen
segm_psp
DW
0
; segmento del PSP
DB
10 DUP (0)
; reservado
segm_tdsk
DW
0
; segmento donde reside TURBODSK
DW
?
; hora (inicializado al formatear)
segm_reubicar
DW
0
; segmento donde reubicar TURBODSK
DW
?
; fecha
ems4
DB
OFF
; a ON si EMS versión 4.0+
DW
0,0,0
; últimos bytes (hasta 32)
cpu286
DB
OFF
; a ON si 286 ó superior
idioma
DB
0
; selecciona el número de idioma
dir_raiz
"; Directorio raiz: primera entrada
; ------------ Areas de datos para información del disco virtual
(1..N) idioma_seps
DW
",."
; separadores de millares/decimales
param_unidad
DB
0
; letra de unidad (si indicada)
param_tdiscof
DB
OFF
; a ON si se define tamaño de disco
param_tdisco
DW
0
; tamaño de disco (si se define)
param_tsect
DW
0
param_tdir
DW
; --- Código telefónico de países de habla ;
hispana (mucha o poca).
DW
54
; Argentina
DW
591
; Bolivia
; tamaño de sector (si se define)
DW
57
; Colombia
0
; número de entradas (si se define)
DW
506
; Costa Rica
param_tcluster DW
0
; tamaño de cluster (si se define)
DW
56
; Chile
param_a
DB
OFF
; a ON si indicado parámetro /A o /X
DW
593
; Ecuador
param_e
DB
OFF
; a ON si indicado parámetro /E
DW
503
; El Salvador
param_b
DB
OFF
; a ON si indicado parámetro /B
DW
34
; España
param_c
DB
OFF
; a ON si indicado parámetro /C
DW
63
; Filipinas
param_h
DB
OFF
; a ON si indicado parámetro /? o /H
DW
502
; Guatemala
param_m
DB
OFF
; a ON si indicado parámetro /M
DW
504
; Honduras
param_i
DB
OFF
; Y ON si indicado parámetro /I
DW
212
; Marruecos
param_f
DW
1
; nº de FATs (1-2): parámetro /F=
DW
52
; México
DW
505
; Nicaragua
info_paises
codigo_tfno
DW
?
; valor de /I= si se indica
DW
507
; Panamá
tdisco
DW
?
; tamaño de disco (Kb)
DW
595
; Paraguay
ultclus
DW
?
; número más alto de cluster
DW
51
; Perú
tamcluster
DW
?
; tamaño de cluster (bytes)
DW
80
; Puerto Rico
sdir
DW
?
; sectores para directorio raiz
DW
508
; República Dominicana
xms_kb
DW
0
; Kb de memoria XMS libres
DW
598
; Uruguay
ems_kb
DW
0
; Kb de memoria EMS libres
DW
58
; Venezuela
con_kb
DW
0
; Kb de memoria convencional libres
DW
3
; Latinoamérica
DW
0
; fin de la información
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
; --- Código telefónico de países de habla alemana.
inf_tdisco
info_ins2
" ",1,colA,"│",1,colC,10
DB
2,12,3,1,colA,"│ Tamaño:
DB
255
41
; Switzerland
DW
43
; Austria
DW
49
; Germany
DB
" ",1,colA,"│",1,colC,10
DB
2,10,3,1,colA,"│ Größe:",2,10," ",1,colB," "
DB
255
DB
" ",1,colA,"│",1,colC,10
DB
2,12,3,1,colA,"│ Size:",2,4," ",1,colB," "
DB
0
DB
" Kbytes
DB
255
DW
0
; fin de la información
DW
0
; no más idiomas
DB
10,1,10,"TURBODSK 2.3 - Unidad ",255
DB
10,1,10,"TURBODSK 2.3 - Laufwerk ",255
inf_tcluster
DB
10,1,10,"TURBODSK 2.3 - Drive ",0
"
DB
": sin formatear.",10,1,14,255
DB
": nicht formatiert.",10,1,14,255
DB
": unformatted.",10,1,14,0
DB
; ------------ Cuadro de información
colA EQU 11+1*16
; color del recuadro y los mensajes
colB EQU 15+1*16
; color de los parámetros de operación del disco
"
DB
" Kbytes
Sektoren/Cluster:",2,3,"
",1,colA,"│ Sectors/cluster: ",1,colB,"
"
inf_mem
DB
0
DB
" ",1,colA,"│",1,colC,10 2,12,3,1,colA,"│ Memoria:
colC EQU 15+0*16
; color de lo que rodea a la ventana
colD EQU 10+1*16
; color de «TURBODSK»
DB
255
inf_tdir
",1,colA,"│
255
DB
inf_tsect
KB
",1,colA,"│ Sectores/cluster:",1,colB,"
",1,colB," "
DB
info_txt
",1,colB," "
DW
; ------------ Mensaje de no formateado
info_ins
DB
",1,colB
DB
10,2,12,3,1,colA,"┌",2,27,"─┬",2,25,"─┐",1,colC
DB
" ",1,colA,"│",1,colC,10
DB
10,2,12,3,1,colA,"│ ",1,colD,"TURBODSK 2.3",1,colA
DB
2,10,3,1,colA,"│ Speicher: ",1,colB
DB
" - Unidad ",1,colB
DB
255
DB
255 DB
" ",1,colA,"│",1,colC,10
DB
10,2,10,3,1,colA,"┌",2,28,"─┬",2,28,"─┐",1,colC
DB
2,12,3,1,colA,"│ Memory:
DB
10,2,10,3,1,colA,"│ ",1,colD,"TURBODSK 2.3",1,colA
DB
0
DB
" - Laufwerk ",1,colB
DB
255
DB
" ",1,colA,"│",1,colB," ",255
DB
"
DB
1,colA,"│",1,colB," ",0
DB
1,colA," clusters (",1,colB,"FAT",255
DB
1,colA," Cluster
DB
1,colA," clusters (",1,colB,"FAT",0
inf_tfat12
DB
"12",0
inf_tfat16
DB
"16",0
inf_final
inf_nclusters
DB
10,2,12,3,1,colA,"┌",2,26,"─┬",2,25,"─┐",1,colC
DB
10,2,12,3,1,colA,"│ ",1,colD,"TURBODSK 2.3",1,colA
DB
" - Drive ",1,colB
DB
0
inf_tfat
",1,colB
",1,colA,"│",1,colB," ",255
(",1,colB,"FAT",255
│ Tamaño de sector:",1,colB," ",255
DB
":",1,colA,"
DB
":",1,colA," │ Sektorgröße:",2,8," ",1,colB," ",255
DB
":",1,colA,"
DB
" ",1,colA,"│",1,colC,10,2,12,3
DB
1,colA,")
DB
1,colA,"├",2,27,"─┤ Nº entradas raiz:",1,colB," "
DB
1,colA,"└",2,27,"─┴",2,25,"─┘",1,colC,10
DB
255
DB
255
DB
" ",1,colA,"│",1,colC,10,2,10,3
DB
1,colA,")",2,5," ",1,colA,"│",1,colC,10
DB
1,colA,"├",2,28,"─┤ Verzeichniseinträge:",1,colB, "
DB
2,10,3,1,colA,"└",2,28,"─┴",2,28,"─┘",1,colC,10
DB
255
DB
255 DB
1,colA,")
DB
" ",1,colA,"│",1,colC,10,2,12,3
DB
1,colA,"└",2,26,"─┴",2,25,"─┘",1,colC,10
DB
1,colA,"├",2,26,"─┤ Root entries:",2,4," ",1,colB,"
DB
0
DB
0
DB
"Extendida (XMS)",255
│ Sector size:",2,5," ",1,colB," ",0
"
",1,colA,"│",1,colC,10,2,12,3
",1,colA,"│",1,colC,10,2,12,3
" inf_mem_xms
203
CONTROLADORES DE DISPOSITIVOS
DB
"Erweitert (XMS)",255
DB
"Extended (XMS) ",0
DB
"Expandida (EMS)",255
DB
"Expansion (EMS)",255
DB
"Expanded (EMS) ",0
DB
" Superior (UMB) ",255
DB
"Oberer Sp. (UMB)",255
DB
"
DB
"
Convencional ",255
DB
"
Konventionell",255
DB
"
Conventional ",0
DB
255
DB inf_mem_ems
inf_mem_sup
Upper (UMB)
"- Syntaxfehler oder ungültiger Parameter. Die
RAM-Disk ist zur
",10,2,8,3
DB
"
nicht
definiert
bzw.
wurde
nicht
DB
255
DB
"- Syntax error and/or parameter out of range. The
Ramdisk is not",10,2,8,3
",0
DB inf_mem_con
Zeit
modifiziert.",10
"
defined
now
or
the
previous
one
is
not
modified.",2,14," ",10
m1
DB
0
DB
"- El parámetro /C o la letra de unidad sólo han de
; ------------ Errores «leves»
emplearse",2,4," ",10,2,8,3
ERROR0
EQU
1
ignoraré).",2,6," ",10
ERROR1
EQU
2
ERROR2
EQU
4
ERROR3
EQU
8
; TURBODSK es muy flexible y se instala
ERROR4
EQU
16
; casi de cualquier forma, aunque a
ERROR5
EQU
32
; veces no se reserve memoria y sea
ERROR6
EQU
64
; necesario volver a ejecutarlo después
ERROR7
EQU
128
; desde el DOS para «formatearlo».
ERROR8
EQU
256
ERROR9
EQU
512
ERROR10
EQU
1024
ERROR11
EQU
2048
ERROR12
EQU
4096
ERROR13
EQU
8192
ERROR14
EQU
16384
ERROR15
EQU
32768
DB
"
DB
255
DB bei Aufrufen
desde la línea de comandos o el AUTOEXEC (les
"- Parameter /C und Laufwerksbuchstaben können nur
",2,4," ",10,2,8,3 DB
"
von TURBODSK in der AUTOEXEC verwendet werden.
",2,6," ",10
be used when
DB
255
DB
"- The /C parameter and the driver letter only can
",10,2,8,3 DB
"
executing TURBODSK in command line or AUTOEXEC
(now, ignored).",10 DB
m2
0
DB
"- Para poder emplear memoria expandida hay que
incluir la opción",10,2,8,3 lista_err
DW
0
DB
; palabra que indica los mensajes a imprimir
"
/A en CONFIG.SYS, con objeto de dejar espacio
para las rutinas",10,2,8,3 mens_cabec
DB
DB
2,8,3,0
"
de control EMS: la memoria ocupada crecerá de
432 a 608 bytes.",10 tabla_mens
DB
DW
m0,m1,m2,m3,m4,m5,m6,m7
DW
m8,m9,m10,m11,m12,m13,m14,m15
DB
10,2,8,3,1,12
DB cab_adv_txt
DB
CONFIG.SYS
"- Zur Verwendung von EMS müssen Sie Option /A in
",10,2,8,3 DB
"Advertencias y/o errores de TURBODSK:",2,27,"
255
"
setzen, um Speicher für die EMS-Unterstützung zu
reservieren. ",10,2,8,3
",10,1,10 DB
DB
255
"
Dadurch erhöht sich der Speicherbedarf von 432
auf 608 Bytes. ",10 DB
10,2,8,3,1,12
DB
"Warnungen
und
Fehlermeldungen
DB
255
DB
"- In order to use expanded memory you must include
von
TURBODSK:",2,27," ",10,1,10 DB
255
the /A option",10,2,8,3
DB
10,2,8,3,1,12
the EMS support",10,2,8,3
DB
"Warnings and errors of TURBODSK:",2,32," ",10,1,10
DB
0
DB
DB
DB
estaba definido.
" ",10
routines: the memory used will increase from 432
0
"- Error de sintaxis o parámetro fuera de rango. m3
No se define el",10,2,8,3 DB
"
in CONFIG.SYS, needed to reserve too space for
to 608 bytes.",10 DB
m0
"
disco virtual ahora o no se modifica el que
DB
"- El tamaño de sector es mayor que el definido en
cualquier otro",10,2,8,3 DB
"
controlador de dispositivo: indíquese ese tamaño
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
DB
en CONFIG.SYS",10,2,8,3 DB
"
para que el DOS ajuste sus buffers (¡más consumo
",10
DB
de memoria!).",10 DB
0
255 m7
DB anderen Treibern;
"- There is not XMS memory available: try to
request EMS (/A).
"- Die Sektorengröße ist größer als in allen
su lugar (/E)
"
Sie
müssen
die
Sektorgröße
in
DB
"
Speicherverbrauch)
Puffergröße
anpassen
muß
(höherer
"- Kein EMS verfügbar: Versuchen Sie, XMS zu
verwenden (/E).
",10
DB
",10
DB
255
CONFIG.SYS
festlegen, da DOS die",10,2,8,3 DB
"- No existe memoria EMS: pruebe a indicar XMS en
DB
",10,2,8,3
DB
DB ",10
255
255 DB
DB by
any
"- Sector size is greater than any other defined
"
memory spent!).
m8
"
because DOS need adjust buffers length (more
DB
memoria extendida.
"- Fallo del controlador XMS: imposible usar
",10
DB
",10
DB
0
driver loaded: you must indicate the sector size
in CONFIG.SYS",10,2,8,3 DB
",10
DB
device",10,2,8,3 DB
"- There is not EMS memory available: try to
request XMS (/E).
255
0 DB
m4
DB
ha rebajado.
"- La cantidad de memoria solicitada no existe, se
unmöglich.
"- Fehler des XMS-Managers: Verwendung von XMS ",10
DB
",10 DB
255
DB
"- Die gewünschte Speichergröße existiert nicht und
255
DB
DB
wurde reduziert.",10 DB
"-
"- The amount of memory requested does not exist:
DB
memoria expandida.
"- No hay memoria XMS/EMS disponible: no la
unmöglich.
255
DB
"
"- Fehler des EMS-Managers: Verwendung von EMS ",10
DB
reservo; ejecute TDSK",10,2,8,3
255
de nuevo desde el DOS para utilizar memoria DB
convencional.",2,5," ",10
"-
255
DB
"- Kein XMS/EMS verfügbar: Führen Sie TDSK nochmals
DB
m10
",10,2,8,3 DB
"
konventionellen Speicher.
Kommandozeile
aus
und
benutzen
Sie
DB
DB
",2,5," ",10
"- There is not XMS/EMS memory available: execute
"
DOS
command
line
or
AUTOEXEC
and
"- No existe suficiente memoria convencional para
255
DB
255
DB
"- There is not sufficient conventional memory for
DB
0
"- No existe memoria XMS: pruebe a indicar EMS en m11 255
DB
"- Tamaño de sector incorrecto: lo establezco por
defecto.",2,7," ",10 DB
verwenden (/A).
"- Nicht genügend konventioneller Speicher für
TURBODSK.",2,5," ",10
0
",10
DB
use
use
DB
DB
to
TURBODSK verfügbar.",2,6," ",10
conventional memory.",2,5," ",10
su lugar (/A)
imposible
255
DB
DB
failure:
TURBODSK.",2,6," ",10
TDSK again from",10,2,8,3
m6
controller
0
DB
DB
EMS
expanded memory.",2,5," ",10
DB
DB
use
0
DB
DB
to
"- Fallo del controlador EMS: imposible usar
DB
von der
imposible
",10
DB
",10
m5
failure:
0
m9
DB
controller
255
DB size reduced.
XMS
extended memory.",2,5," ",10
255
"- Kein XMS verfügbar: Versuchen Sie, EMS zu ",10 255
DB
"- Ungültige Sektorengröße angegeben, Vorgabewert
wird verwendet.",2,7," ",10 DB
255
203
CONTROLADORES DE DISPOSITIVOS
err_grave_gen DB
DB
10,1,10,"TURBODSK 2.3",10,1,12,0
"- Incorrect sector size indicated: default values e0
assumed.",2,6," ",10
DB
DB
0
superior.",10,255
DB
"- Número de entradas incorrecto: lo establezco por
2.0.",10,255
255
above.",10,0
"
DB m12
DB
defecto.",2,5," ",10 DB
DB
"-
Ungültige
Anz.
von
Verzeichnisanträgen,
e1
255
DB
"
- This Ram Disk needs at least DOS 2.0 or
DB
"
- Instale primero TURBODSK desde CONFIG.SYS (con
DB
"
- Puede solicitar ayuda con TDSK /?",10
DB
255
DB
"
- Sie müssen zuerst TURBODSK von der CONFIG.SYS
"
(mit DEVICE). Hilfe erhalten Sie durch Eingabe
"- Incorrect number of root entries: default value
assumed.",2,6," ",10 DB
aus installieren",10
0
DB m13
- Diese RAM-Disk erfordert mindestens DOS
DEVICE).",10
Vorgabewert wird verwendet.",2,5," ",10 DB
"
- Este disco virtual requiere DOS 2.0 o
DB
"- Tamaño de cluster incorrecto: lo establezco por
von TDSK /?",10 DB
defecto.",2,6," ",10 DB
255
255 DB
DB
"- Ungültige Clustergröße angegeben, Vorgabewert
wird verwendet.",2,6," ",10 DB
255
DB
"- Incorrect cluster size indicated: default value
-
You
must
install
first
TURBODSK
from
e2
DB
"
DB
0
DB "
- Help is available with TDSK /?",10
- La unidad indicada no es un dispositivo TURBODSK
2.3",10,255
assumed.",2,6," ",10 DB
"
CONFIG.SYS (using DEVICE).",10
DB "
0
- Angegebener Laufwerksbuchstabe bezeichnet keinen
Treiber von TURBODSK.", 10,255 m14
DB
DB
DB "
"- FATAL: fallo al liberar la memoria que ocupaba
- Drive letter indicated does not is a TURBODSK 2.3
device.",10,0
el disco.",2,6," ",10 255
e3 DB
"-
ACHTUNG:
Freigabe
des
belegten
Speichers
DB
DB
gescheitert.",2,6," ",10 DB
DB
"
TURBODSK dentro de WINDOWS. Configúrelo con
255
"- FATAL: imposible to free memory alocated by DB
TURBODSK.",2,9," ",10 DB
"
-
TURBODSK
kann
nicht
innerhalb
einer
WINDOWS-Sitzung modifiziert werden.",10
0
DB m15
- No pueden modificarse las características de
anterioridad.",10
255
DB
"
operación de",10
DB
"- Para discos de más de 32 Mb, hace falta un
tamaño de sector de",10,2,8,3 DB
"
DB
255
"
Sie
müssen
die
Einstellungen
vorher
durchführen.",10 DB
255
DB
"
al menos 1024 bytes.",2,42," ",10 - Operational characteristics of disk can not be
altered inside",10,2,4 DB
DB
"- Laufwerke mit mehr als 32 MB erfordern eine
" a WINDOWS session. You must configure TURBODSK
before.",10
Sektorgröße",10,2,8,3 DB
"
DB
255
DB
von mindestens 1024 Bytes.",2,42," ",10
0
; ------------ Ayuda DB
"- In drives over 32 Mb, sector size must be at
least 1024 bytes.",10 DB
0
; ------------ Errores «graves» (se imprime sólo el más importante)
err_grave
DW
0
; tipo de error grave a imprimir
colorA
EQU 15+4*16+128
; color de «TURBODSK»
colorAm
EQU 14+1*16
; color del marco de fondo de «TURBODSK»
colorB
EQU 13+1*16
; color de la fecha
colorC
EQU 10+1*16
; color de sintaxis y parámetros
colorD
EQU 15+1*16
; color principal del texto
colorDm
EQU 11+1*16
; color del marco de fondo
colorDmx EQU 11+0*16
; color de la esquina del marco
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
",1,colorC,"/C"
colorE
EQU 11+1*16
; color del nombre del autor
colorF
EQU 14+1*16
; color para llamar la atención
colorG
EQU 12+1*16
; color para la dirección de mail
colorH
EQU
; color para mensaje de dominio público
9+1*16
DB
1,colorD," se pide el uso de memoria convencional.
DB
"█",10
",1,colorDm
DB ayuda_txt
DB
10,3,1,colorDm,"
",1,colorA,"
TURBODSK
DB
2.3
",1,colorAm,"▄"
Tras
ser
" ejecutar desde el DOS para cambiar el tamaño
",1,colorDm
DB
1,colorB,2,51," 12/12/95 ",1,colorDmx,"▄",10
DB
3,1,colorE,"
DB
"
DB
"(Mail: [email protected]).",1,colorDm,"█",10
DB
",1,colorAm,2,14,"▀",1,colorE
DB
3,1,colorE,"
(C)
Grupo
"█",10
DB almacenados):
(C) 1995 Ciriaco García de Celis. ",1,colorG
Universitario
de
3,1,colorD," del disco (perdiéndose los datos
con " DB
"un tamaño 0 se anula el ",1,colorDm,"█",10
DB
3,1,colorD," disco por completo, liberándose la
DB
"Utilizando memoria convencional ",1,colorDm,"█",10
memoria. "
Informática. " DB
"Apartado
6062,
Valladolid
(España).
",1,colorDm,"█",10 DB Público
3,1,colorD,32,1,colorF,"",1,colorD,"
instalado, se puede"
LABEL BYTE
DB 3,1,colorH,2,18," ","* * *
Programa de Dominio
DB
* * *" DB
2,18," ",1,colorDm,"█",10
DB
3,1,colorD,"
Bienvenido
al
disco
DB
virtual
",1,colorF,"MUY",1,colorD,"
"disco previo antes de modificar su tamaño. Con
"█",10
DB 1,colorD,", con soporte de memoria EMS, XMS y
3,1,colorD," más de un disco presente se pueden
distinguir "
",1,colorDm,"█",10 DB
es
",1,colorDm
",1,colorF,"más rápido" DB
3,1,colorD,"
conveniente anular el "
3,1,colorD," convencional; redimensionable, fácil
DB
"indicando la letra de unidad. ",1,colorDm,"█",10
DB
3,1,1*16,"▄",1,colorDm,2,76,"▄█",10
DB
255
de usar. En DOS " DB
"5 ocupa 432-608 bytes. ",1,colorDm,"█",10
DB
3,1,colorC,2,77," ",1,colorDm,"█",10
DB
3,1,colorC," DEVICE=TDSK.EXE [tamaño [tsector "
DB
"[nfich
[scluster]]]]
[/E]
[/A|X]
[/C]
DB [/M]
",1,colorDm,"█",10 DB DB
3,1,colorC,2,77," ",1,colorDm,"█",10 3,1,colorD," ",1,colorF,"",1,colorD," El tamaño
debe de estar en " DB
10,3,1,colorDm,"
",1,colorA,"
TURBODSK
2.3
",1,colorAm,"▄" DB
1,colorB,2,52," 12/12/95 ",1,colorDmx,"▄",10
DB
3,1,colorE,"
DB
"
DB
"(Mail: [email protected]). ",1,colorDm,"█",10
(C) 1995 Ciriaco García de Celis. ",1,colorG
DB
"el rango 8 - 65534 Kb; son válidos sectores de
",1,colorAm,2,14,"▀",1,colorE
3,1,colorE,"
(C)
Grupo
Universitario
de
Informática. "
",1,colorDm DB
"█",10
DB
3,1,colorD," 32 a 2048 bytes (en potencias de dos,
DB
"sistema sólo los soporta ",1,colorDm,"█",10
DB
DB
aunque algún "
DB
3,1,colorD," de 128 a 512). El número de ficheros
DB
Valladolid
(Spanien).
3,1,colorC,2,78," ",1,colorDm,"█",10
DB
3,1,colorD,"
DB "raiz debe estar entre 1 ",1,colorDm,"█",10
6062,
Willkommen
bei
der
",1,colorF,"schnelleren"
del directorio " DB
"Apartado
",1,colorDm,"█",10
1,colorD,"
RAM-Disk,
die auch EMS-, XMS- und
konven- ",1,colorDm,"█",10 DB
3,1,colorD," y 65534 y el de sectores por cluster
3,1,colorD,"
tionellen
Speicher
unterstützt;
größenverstellbar,"
entre 1 y 255 (" DB
"en algún sistema han de ",1,colorDm,"█",10
DB
3,1,colorD," ser potencia de dos). Según el tamaño
DB
"
DB
einfache
Bedienung wie ",1,colorDm,"█",10
3,1,colorD," bei DOS-RAM-Disks, erfordert maximal
608 Bytes. "
se ajustará " DB DB
"lo demás automáticamente. ",1,colorDm,"█",10 3,1,colorD," ",1,colorF,"",1,colorD," Se puede
DB
indicar ",1,colorC DB
DB
"/E",1,colorD," para emplear memoria extendida XMS,
1,colorH,"
Das
Programm
ist
Freeware!.
",1,colorDm,"█",10 3,1,colorC,2,78," ",1,colorDm,"█",10
DB
3,1,colorC,"
DEVICE=TDSK.EXE
[Größe
[Sekt.
[Dateien [Cluster]]]]"
y ",1,colorC DB
"/A",1,colorD," o ",1,colorC,"/X",1,colorD," para
la ",1,colorDm DB DB
"█",10 3,1,colorD," expandida EMS; aunque por defecto,
DB
" [/E] [/A|X] [/C] [/M]
DB
3,1,colorC,2,78," ",1,colorDm,"█",10
DB
" automáticamente estas ",1,colorDm,"█",10 3,1,colorD," memorias si puede. Con la opción
",1,colorDm,"█",10
3,1,colorD," ",1,colorF,"",1,colorD," Zulässig
für Größe: 8-65534 KB;" DB
TURBODSK utilizará" DB
DB
" zulässig für Sektoren: 32-2048 Bytes (2er-
",1,colorDm,"█",10 DB
3,1,colorD," Potenz),
obwohl einige DOS-Versionen
203
CONTROLADORES DE DISPOSITIVOS
DB
nur 128," DB
"
256
DB
und
512
unterstützen. ",1,colorDm,"█",10
3,1,colorD,
"
Zulässige
Anzahl
"1-65534,
Sektoren/Cluster:
3,1,colorD,"
(einige
resizeable,
der
Verzeichniseinträge: " DB
1-255
erforden
DB
"
Nur
die
Größenangabe
ist
notwendig.
3,1,colorD,"
",1,colorF,"",1,colorD,"
Bei
",1,colorC, "/E",1,colorD DB
DB
3,1,colorD," in DOS 5.0 it takes only about 432-608
1,colorH,"This
program
is
freeware!.",2,4,"
DB
3,1,colorC,2,77," ",1,colorDm,"█",10
DB
3,1,colorC," DEVICE=TDSK.EXE [size [s_sector [files
DB
" wird EMS,
DB
" [/E] [/A|X] [/C] [/M] ",1,colorDm,"█",10
DB
3,1,colorC,2,77," ",1,colorDm,"█",10
DB
und bei ",1,colorC, "/C",1,colorD,"
3,1,colorD," ",1,colorF,"",1,colorD," Size must
"8 - 65534 Kb; are valid sectors from 32 to 2048
",1,colorDm
"█",10,3,1,colorD,
"
Speicher
benutzt.
Normalerweise versucht TURBODSK," " XMS oder EMS zu benutzen. ",1,colorDm,"█",10
DB
DB
be in the range "
wird konventioneller ",1,colorDm
3,1,colorD,32,1,colorF,"",1,colorD,"
" CONFIG.SYS sollte
Nach
der
TURBODSK später nochmal ausge-
"█",10
DB
3,1,colorD," bytes (in power of 2), though some DOS
DB
"support 128, 256 & 512 ",1,colorDm,"█",10
DB
3,1,colorD," bytes. Files of root may be 1 to 65534
DB
"by cluster can vary from ",1,colorDm,"█",10
DB
3,1,colorD," 1 to 255 (some systems need a power of
DB
"size is necessary.",2,6," ",1,colorDm,"█",10
and sectors "
",1,colorDm,"█",10 3,1,colorD," führt werden,
DB
versions only "
Installation in"
DB
"use like DOS RAM disks, ",1,colorDm,"█",10
DB
" wird XMS, bei ",1,colorC, "/A",1,colorD," oder
",1,colorC,"/X",1,colorD
DB
Full
[s_cluster]]]]"
DB
DB
memory.
",1,colorDm,"█",10
2er-Potenzen)."
",1,colorDm,"█",10
conventional
DB
DB Systeme
and
bytes. "
",1,colorDm,"█",10 DB
3,1,colorD,"
easy to "
um die Größe zu ändern 2). Only the "
(den" DB
"
Speicherverbrauch);
dadurch
wird
",1,colorDm,"█",10
DB
DB
3,1,colorD," der Inhalt der
RAM-Disk
gelöscht.
Durch Größe 0 wird" DB
" die RAM-Disk komplett ",1,colorDm,"█",10
DB
3,1,colorD,"
gelöscht,
bei
DB
Verwendung
von
" TURBODSK's installiert ",1,colorDm,"█",10 3,1,colorD,"
sind,
können
diese
DB
durch
ihren
DB
" angesteuert werden. ",2,6," ",1,colorDm,"█",10
DB
3,1,1*16,"▄",1,colorDm,2,77,"▄█",10
DB
255
10,3,1,colorDm,"
",1,colorA,"
TURBODSK
2.3
",1,colorAm,"▄" DB
1,colorB,2,51," 12/12/95 ",1,colorDmx,"▄",10
DB
3,1,colorE,"
DB
"
DB
"(Mail: [email protected]).",1,colorDm,"█",10
",1,colorAm,2,14,"▀",1,colorE
(C) 1995 Ciriaco Garcia de Celis. ",1,colorG
3,1,colorE,"
(C)
Grupo
Universitario
de
Informática. " DB
"Apartado
6062,
Valladolid
(Spain).
",1,colorDm,"█",10 DB
3,1,colorC,2,77," ",1,colorDm,"█",10
DB
3,1,colorD,"
Welcome
to
the
",1,colorF,"faster",1,colorD DB
" RAM disk!,
which includes support of both EMS,
XMS " DB
"/X",1,colorD," indicates the use of EMS memory
"█",10 3,1,colorD," and ",1,colorC,"/C",1,colorD," the
conventional. By "
Laufwerksbuchstaben"
DB
DB
DB
3,1,colorF," VOR",1,colorD," der Größenveränderung
DB
"use of XMS memory, ",1,colorC,"/A",1,colorD," and
DB
" kann eine Annulierung ",1,colorDm,"█",10
DB
DB
",1,colorDm
sinnvoll sein. Wenn mehrere" DB
",1,colorF,"",1,colorC,"
",1,colorC
konventionellem Speicher" DB
3,1,colorD,"
/E",1,colorD," force the "
1,colorDm,"█",10
",1,colorDm
"default, TURBODSK try to use XMS or EMS memory.
203
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
DB
"█",10
DB
3,1,colorD," ",1,colorF,"",1,colorD," After been
DB
"CONFIG.SYS, TURBODSK must be executed in AUTOEXEC
DB
"█",10
installed in "
",1,colorDm
DB
3,1,colorD," or command line in order to vary the
disk size (the " DB
"amount of memory used); ",1,colorDm,"█",10
DB contents.
3,1,colorD,"
this
operation
erase
the
disk
A size 0 " DB
"can be used to complitely ",1,colorDm,"█",10
DB
3,1,colorD," anulation of the disk freezen the
memory: when using " DB
"conventional memory it ",1,colorDm,"█",10
DB
3,1,colorD,"
is
useful
to
annulate
the
disk
",1,colorF,"BEFORE" DB
1,colorD," resizing. When more than one TURBODSK
",1,colorDm DB DB
"█",10 3,1,colorD," is installed, they can be identified
using in " DB
"adition
the
drive
letter.",2,5,"
",1,colorDm,"█",10
tam_a_trabajo
DB
3,1,1*16,"▄",1,colorDm,2,76,"▄█",10
DB
0
EQU
4096
; tamaño del mayor sector
soportado ; por TURBODSK o del mayor texto ; a imprimir area_trabajo
EQU
$
DB
tam_a_trabajo DUP (?)
_PRINCIPAL
ENDS
tam_pila
EQU
_PILA
SEGMENT STACK 'STACK' DB
_PILA
2048
; 2 Kb de pila son suficientes
tam_pila DUP (?)
ENDS
END
main
11.8. - LOS CONTROLADORES DE DISPOSITIVO Y EL DOS. Una vez instalado el controlador de dispositivo, puede ser necesario para los programas del usuario interaccionar con él. Para ello se ha definido oficialmente un mecanismo de comunicación: el control IOCTL. En principio, un controlador de dispositivo puede ser hallado recorriendo la cadena de controladores de dispositivo para localizarlo y acceder directamente a su código y datos. Sin embargo, en los controladores más evolucionados, el método IOCTL es el más recomendable. El control IOCTL (que permite separar el flujo de datos con el dispositivo de la información de control) se ejerce por medio de la función 44h del DOS, siendo posible lo siguiente: - Averiguar los atributos de un controlador de dispositivo, a partir del nombre. Esto permite, entre otras
CONTROLADORES DE DISPOSITIVOS
203
cosas, distinguir entre un dispositivo real y un fichero con el mismo nombre. Seguro que el lector ha construido alguna vez un programa que abre un fichero de salida de datos con el nombre que indica el usuario: hay usuarios muy pillines que en lugar del clásico PEPE.TXT prefieren indicar, por ejemplo, CON, estropeando la bonita pantalla que tanto trabajo había costado pintar. Una solución consiste, antes de abrir el fichero de salida, en asegurarse de que es realmente un fichero. - Leer del controlador o enviarle una tira de caracteres de control. Esto sólo es posible si el controlador soporta IOCTL. Por ejemplo, un driver encargado de gestionar un puerto serie especial podría admitir cadenas del tipo "9600,n,8,1" para fijar la velocidad de transmisión, paridad, etc. El trabajo que requiere codificar la rutina IOCTL OUTPUT, encargada de recibir estos datos, puede en muchos casos merecer la pena. - Averiguar el estado del controlador: saber si tiene caracteres disponibles, o si ya ha transmitido el último enviado. Esta característica, entre otras, es implementada por la orden IOCTL INPUT del controlador. Para obtener información detallada acerca de la función 44h del DOS hay que consultar, lógicamente, la bibliografía al respecto (recomendable el INTERRUP.LST).
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
Capítulo XII: EL HARDWARE DE APOYO AL MICROPROCESADOR
En este capítulo se mostrará detenidamente el funcionamiento de todos los chips importantes que lleva el ordenador en la placa base y alguno de los colocados en las tarjetas de expansión. Nota:Por limitaciones técnicas, al describir los circuitos integrados las señales que son activas a nivel bajo no tendrán la tradicional barra negadora encima; en su lugar aparecerán precedidas del signo menos: -CS, -WR, -MEMR, ... En algunos casos, acceder directamente a los chips no es necesario: en general, es mejor dejar el trabajo al DOS, o en su defecto a la BIOS. Sin embargo, hay casos en que es estrictamente necesario hacerlo: por ejemplo, para programar temporizaciones, hacer sonidos, comunicaciones serie por interrupciones, acceso a discos de formato no estándar, etc. Algunas veces bastará con la información que aparece en el apartado donde se describe la relación del chip con los PC; sin embargo, a menudo será necesario consultar la información técnica del apartado ubicado inmediatamente antes, para lo que bastan unos conocimientos razonables de los sistemas digitales. Los ordenadores modernos normalmente no llevan los integrados explicados en este capítulo; sin embargo, poseen circuitos equivalentes que los emulan por completo. 12.1. - LAS CONEXIONES DEL 8088. Resulta interesante tener una idea global de las conexiones del 8086 con el exterior de cara a entender mejor la manera en que interacciona con el resto de los elementos del ordenador. Se ha elegido el 8088 por ser el primer procesador que tuvo el PC; a efectos de entender el resto del capítulo es suficiente con el 8088. El 8088 puede trabajar en dos modos: mínimo (pequeñas aplicaciones) y máximo (sistemas multiprocesador). Los requerimientos de conexión con el exterior cambian en función del modo que se decida emplear, aunque una parte de las señales es común en ambos. ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▌ GND ██▌ 1 ▌ A14 ██▌ 2 ▌ A13 ██▌ 3 ▌ A12 ██▌ 4 ▌ A11 ██▌ 5 ▌ A10 ██▌ 6 ▌ A9 ██▌ 7 ▌ A8 ██▌ 8 ▌ AD7 ██▌ 9 ▌
▐ 40 ▐██ Vcc ▐ 39 ▐██ A15 ▐ 38 ▐██ A16/S3 ▐ 37 ▐██ A17/S4 ▐ 36 ▐██ A18/S5 ▐ 35 ▐██ A19/S6 ▐ 34 ▐██ -SS0 ▐ 33 ▐██ MN/-MX ▐ 32 ▐██ -RD ▐
AD6 ██▌ 10 ▌ AD5 ██▌ 11 ▌ AD4 ██▌ 12 ▌ AD3 ██▌ 13 ▌ AD2 ██▌ 14 ▌ AD1 ██▌ 15 ▌ AD0 ██▌ 16 ▌ NMI ██▌ 17 ▌ INTR ██▌ 18 ▌ CLK ██▌ 19 ▌
31 ▐██ HOLD
(-RQ/-GT0)
▐ 30 ▐██ HLDA
(-RQ/-GT1)
▐ 29 ▐██ -WR
(-LOCK)
▐ 28 ▐██ IO/-M
(S2)
▐ 27 ▐██ DT/-R
(-S1)
▐ 26 ▐██ -DEN ▐ 25 ▐██ ALE ▐ 24 ▐██ -INTA ▐ 23 ▐██ -TEST ▐ 22 ▐██ READY ▐
(-S0)
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
GND ██▌ 20 ▌
LÍNEAS COMUNES AL MODO MÁXIMO Y MÍNIMO DEL 8088.
21 ▐██ RESET '8088
▐
AD7..0:Address Data Bus. Son líneas multiplexadas, que pueden actuar como bus de datos
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
o de direcciones, evidentemente en tiempos distintos. A15..8:Address Bus. En todo momento almacenan la parte media del bus de direcciones. A19..16/S6..3:Address/Status. Parte alta del bus de direcciones, multiplexada: cuando no salen direcciones, la línea S5 indica el estado del banderín de interrupciones; las líneas S4:S3 informan del registro de segmento empleado para realizar el acceso a memoria: 00-ES, 01-SS, 10-CS, 11-DS; S6 no se usa. -RD:Read. Indica una lectura de memoria o de un dispositivo de entrada/salida. READY:Ready. Línea de entrada que indica el final de la operación de memoria o E/S. INTR:Interrupt Request. Línea de petición de interrupciones enmascarables; el 8088 la observa periódicamente. -TEST:Test. En respuesta a la instrucción máquina WAIT (¡no TEST!), el 8088 se para a comprobar esta línea hasta que se ponga a 0. NMI:Non-maskable Interrupt. Línea de petición de la interrupción de tipo 2, que no puede ser enmascarada. RESET:Provoca una inicialización interna que culmina saltando a FFFF:0. MN/-MX:Esta línea indica si se trata de un sistema mínimo o máximo. LÍNEAS EXCLUSIVAS DEL MODO MÍNIMO DEL 8088. IO/-M:Status Line. Indica si se trata de un acceso a memoria o a un puerto de entrada/salida. No es válida todo el tiempo (solo a ratos). -wr:Write. Indica una escritura en memoria o en un dispositivo de entrada/salida (según el estado de IO/-M). -INTA:Interrupt Acknowledge. Es la señal de reconocimiento de interrupción (solicitada a través de INTR o NMI). ALE:Address Latch Enable. Indica al exterior que las líneas de dirección contienen una dirección válida, con objeto de que la circuitería externa la almacene en una pequeña memoria (latch). Señal necesaria sólo por culpa de la multiplexación. DT/-R:Data Transmit/Receive. Señal necesaria para emplear un transceiver 8286/8287 en el bus, con objeto de controlar el flujo de datos a través del mismo (si se recibe/transmite). -DEN:Data Enable. Necesario también para emplear el transceiver: sirve como entrada de habilitación para el mismo. HOLD:Hold. Línea de entrada para solicitar al 8088 que se desconecte de los buses. Empleada por los controladores de DMA. HLDA:Hold Acknowledge. Línea complementaria de HOLD: el 8088 envía una señal de reconocimiento cuando se desconecta del bus. -SS0:Status Line. Línea de apoyo que, junto con IO/-M y DT/-R, permite determinar con precisión el estado del bus: IO/-M
DT/-R
-SS0
─────── ─────── ─────── 1
0
0
1
Reconocimiento de interrupción
0
1
1
1
Lectura de puerto E/S
0
1
Escritura en puerto E/S
1
0
1
0
0
Estado Halt
0
0
0
Estado del bus
──────────────────────────────
Acceso a código
1
1
Lectura de memoria
0
0
Escritura en memoria
1
1
Inactivo
LÍNEAS EXCLUSIVAS DEL MODO MÁXIMO DEL 8088. -S0/-S1/-S2:Status. Estas líneas indican el estado del bus: -S2
-S1
-S0
─────── ─────── ─────── 0
0
0
0
Reconocimiento de interrupción
0
0
1
1 0
1 1
Lectura de puerto E/S
0
Escritura en puerto E/S
1
1
1
0 0 1
Estado del bus
──────────────────────────────
0 1 0
Estado Halt Acceso a código Lectura de memoria Escritura en memoria
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
1
1
1
Inactivo
-RQ/-GT0..1:Request/Grant. Estas patillas bidireccionales permiten a los demás procesadores conectados al bus forzar al 8088 a que libere el bus al final del ciclo en curso. -LOCK:Lock. Línea que sirve al 8088 para prohibir el acceso al bus a otros procesadores (se activa tras la instrucción máquina LOCK y dura mientras se ejecuta la siguiente instrucción -la que sigue a LOCK, que es realmente un prefijo-). También se activa automáticamente en los momentos críticos de un ciclo de interrupción. QS1/QS0:Queue Status. Permite determinar el estado de la cola de instrucciones del 8088.
DIFERENCIAS IMPORTANTES CON EL 8086. El 8086 cambia el patillaje sensiblemente, aunque la mayoría de las señales son similares. En lugar de 8 líneas de datos y direcciones multiplexadas (AD0..7) el 8086 posee 16, ya que el bus de datos es de 16 bits. Existe una línea especialmente importante en el 8086, -BHE/S7 (Bus High Enables/Status), que normalmente indica si se accede a la parte alta del bus de datos o no (operaciones 8/16 bits). El 8086 posee una cola de instrucciones de 6 bytes, en lugar de 4.
FORMATO DE LAS INSTRUCCIONES DEL 8086. Resulta absurdo estudiar la composición binaria de las instrucciones máquina de ningún procesador; en los casos en que sea necesario se pueden ver los códigos con alguna utilidad de depuración. Sin embargo, a título de curiosidad, se expone a continuación el formato general de las instrucciones (aunque hay algunas excepciones y casos especiales). ┌───┬───┬───┬───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┬───┬───┬───┐ ┌─────────────────────┐ ┌─────────────────────┐ │
Código de Operación
│ D │ W │ │
MOD
│
REG
│
REG/MEM
│ │ byte/palabra despl. │ │ byte/palabra inmed. │
└───┴───┴───┴───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┴───┴───┴───┘ └─────────────────────┘ └─────────────────────┘
El código de operación ocupa 6 bits; el bit D indica si es el operando fuente (=0) el que está en el campo registro (REG) o si lo es el operando destino (=1): la razón es que el 8086 sólo admite un operando a memoria, como mucho (o el fuente, o el destino, no los dos a la vez). El bit W indica el tamaño de la operación (byte/palabra). MOD indica el modo de direccionamiento: 00-sin desplazamiento (no existe campo de desplazamiento), 01-desplazamiento de 8 bits, 10-desplazamiento de 16 bits y 11-registro (tanto fuente como destino están en registro). El campo REG indica el registro involucrado en la instrucción, que puede ser de 8 ó 16 bits (según indique W): 0AX/AL, 1-CX/CL, 2-DX/DL, 3-BX/BL, 4-SP/AH, 5-BP/CH, 6-SI/DH, 7-DI/BH; en el caso de registros de segmento sólo son significativos los dos bits de menor peso: 00-ES, 01-CS, 10-SS, 11-DS. El campo R/M, en el caso de modo registro (MOD=11) se codifica igual que el campo REG; en caso contrario se indica la forma en que se direcciona la memoria: 0: [BX+SI+desp], 1: [BX+DI+desp], 2: [BP+SI+desp], 3: [BP+DI+desp], 4: [SI+desp], 5: [DI+desp], 6: [BP+desp], 7: [BX+desp].
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
12.2. - EL INTERFAZ DE PERIFÉRICOS 8255. El PPI 8255 es un dispositivo de E/S general, programable, capaz de controlar 24 líneas con diferentes configuraciones (entrada/salida) y en hasta 3 modos de operación. 12.2.1 - DESCRIPCIÓN DEL INTEGRADO. Conexiones del 8255 con el exterior: ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▌
▐
PA3 ██▌ 1
40 ▐██ PA4
▌
▐
PA2 ██▌ 2
39 ▐██ PA5
▌
▐
PA1 ██▌ 3
38 ▐██ PA6
▌
▐
PA0 ██▌ 4
37 ▐██ PA7
▌
▐
-RD ██▌ 5
36 ▐██ -WR
▌
▐
-CS ██▌ 6
35 ▐██ RESET
▌
DESCRIPCIÓN FUNCIONAL
▐
GND ██▌ 7
34 ▐██ D0
▌
▐
A1 ██▌ 8
33 ▐██ D1
▌
▐
A0 ██▌ 9
32 ▐██ D2
▌
▐
PC7 ██▌ 10
31 ▐██ D3
▌
▐
PC6 ██▌ 11
30 ▐██ D4
▌
Las dos líneas de direcciones definen cuatro puertos de E/S en el ordenador: los tres primeros permiten acceder a los puertos A, B y C; el cuarto sirve para leer o escribir la palabra de control. El 8255 está dividido en dos grupos internos: el grupo A, formado por el puerto A y los 4 bits más significativos del puerto C; y el grupo B, constituido por el puerto B junto a los 4 bits menos significativos del puerto C. El puerto C está especialmente diseñado para ser dividido en dos mitades y servir de apoyo a los puertos A y B en algunos sistemas.
▐
PC5 ██▌ 12
29 ▐██ D5
▌
▐
PC4 ██▌ 13
28 ▐██ D6
▌
▐
PC0 ██▌ 14
27 ▐██ D7
▌
▐
PC1 ██▌ 15
26 ▐██ Vcc
▌
▐
PC2 ██▌ 16
25 ▐██ PB7
▌
▐
PC3 ██▌ 17
24 ▐██ PB6
▌
▐
PB0 ██▌ 18
23 ▐██ PB5
▌
▐
PB1 ██▌ 19
22 ▐██ PB4
▌
▐
PB2 ██▌ 20 ▌
D0..D7:Bus de datos bidireccional de 3 estados. RESET:Esta señal borra el registro de control y todos los puertos (A, B y C) son colocados en modo entrada. -RD:Utilizada por la CPU para leer información de estado o datos procedentes del 8255. -WR:Utilizada por la CPU para enviar palabras de control o datos al 8255. A0..A1:Líneas de dirección: permiten seleccionar uno de los tres puertos o el registro de control. PA0..PA7:Puerto A: puerto de entrada/salida de 8 bits. PB0..PB7:Puerto B: puerto de entrada/salida de 8 bits. PC0..PC7:Puerto C: puerto de entrada/salida de 8 bits.
21 ▐██ PB3 '8255
▐
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
PROGRAMACIÓN DEL 8255 El 8255 soporta 3 modos de operación: el modo 0 (entrada y salida básica), el modo 1 (entrada y salida con señales de control) y el modo 2 (bus bidireccional de comunicaciones). Tras un Reset, los 3 puertos quedan configurados en modo entrada, con las 24 líneas puestas a "1" gracias a la circuitería interna. Esta configuración
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
por defecto puede no obstante ser alterada con facilidad. El modo para el puerto A y B se puede seleccionar por separado; el puerto C está dividido en dos mitades relacionadas con el puerto A y el B. Todos los registros de salida son reseteados ante un cambio de modo, incluyendo los biestables de estado. Las configuraciones de modos son muy flexibles y se acomodan a casi todas las necesidades posibles. Los tres puertos pueden ser accedidos en cualquier momento a través de la dirección E/S que les corresponde, como se vio en el apartado anterior. La palabra de control a enviar a la 4ª dirección es: ┌───────┬───────┬───────┬───────┼───────┬───────┬───────┬───────┐ │ │
│ 1
│
│ D6
│
│
│ D5
│
│
│ D4
│
│
│ D3
│ D2
│ D1
│ D0
│
│
GRUPO B: --------
Modo
Χ────────────────────┘
00 - 0, 01 - 1, 1X - 2 Puerto A
│
│
│
└───────┴───┬───┴───┬───┴───┬───┼───┬───┴───┬───┴───┬───┴───┬───┘ │
│
│
GRUPO A:
│
│
│
--------
└───┬───┘
│
│
│
└─Ψ Puerto C (parte baja)
│
│
│
│
│
│
│
└─────────Ψ Puerto B
Χ────────────────────────────┘
1 - Entrada, 0 - Salida
1 - Entrada, 0 - Salida
│
│
│
└─────────────────Ψ Modo
1 - Entrada, 0 - Salida
Puerto C (Parte alta) Χ────────────────────────┘
0 ó 1
1 - Entrada, 0 - Salida
Si el bit más significativo de la palabra de control está borrado, es tratada entonces como un comando especial que permite activar o inhibir selectivamente los bits del puerto C: ┌───────┬───────┬───────┬───────┼───────┬───────┬───────┬───────┐ │ │
│ 0
│
│
│ D6
│
│
│ D5
│
│
│ D4
│
│ │
│ D3
│
│ D2
│
│
│ D1
│
│ │
│ D0
│ │
└───────┴───┬───┴───┬───┴───┬───┼───┬───┴───┬───┴───┬───┴───┬───┘ └───────┼───────┘
└───────┼───────┘
Ϊ
Ϊ
No importa su valor
└───Ψ Nuevo valor de ese bit
Bit del puerto C a cambiar (0..7)
Esto es particularmente útil para los modos 1 y 2, donde las interrupciones generadas por las líneas del puerto C pueden ser activadas o inhibidas simplemente poniendo a 1 ó 0, respectivamente, el flip-flop interno INTE correspondiente a la interrupción que se trate. Todos son puestos a cero tras establecer el modo. MODOS DE OPERACIÓN DEL 8255 MODO 0:Esta configuración implementa simples funciones de entrada/salida para cada bit de los 2 puertos de 8 bits y los 2 puertos de 4 bits; los datos son leídos y escritos sin más, sin ningún tipo de control adicional. Los puertos pueden ser configurados de entrada (sin latch) o salida (los datos permanecen memorizados en un latch). MODO 1:Este modo es el strobed input/output (entrada/salida a través de un protocolo de señales). Existen dos grupos (A y B) formados por los puertos A y B más el puerto C, que es repartido a la mitad entre ambos grupos para gestionar las señales de control. Tanto si se configura de entrada como de salida, los datos permanecen en un latch. Con este modo es factible conectar dos 8255 entre sí para realizar transferencias de datos en paralelo a una velocidad considerable, con posibilidad de generar interrupciones a la CPU en el momento en que los datos son recibidos o hay que enviar uno nuevo (consúltese documentación técnica). MODO 2:En este modo se constituye un bus bidireccional de 8 bits, por el que los datos pueden ir en un sentido o en otro, siendo el flujo regulado de nuevo por señales de control a través del puerto C. Este modo sólo puede operar en el Grupo A. Tanto las entradas como salidas son almacenadas en latch. NOTA:Existen varias combinaciones posibles de estos modos, en las que las líneas del puerto C que no son empleadas como señales de control pueden actuar como entradas o salidas normales, quedando las líneas de control fuera del área de influencia de los comandos que afectan a las restantes.
12.2.2 - EL 8255 EN EL PC. El 8255 es exclusivo de los PC/XT; ha sido eliminado de la placa base de los AT y PS/2, en los que ciertos registros realizan algunas funciones que en los PC/XT realiza el 8255; por ello, en estas máquinas NO se puede programar el 8255 (ha sido eliminado y no existe nada equivalente). El 8255 de los PC/XT está conectado a la dirección base E/S 60h; por ello, los puertos A, B y C se acceden, respectivamente, a través de
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
los puertos de E/S 60h, 61h y 62h; la palabra de control se envía por el puerto 63h: la BIOS del PC y XT programa el 8255 con una palabra de control 10011001b, que configura todos los puertos en el modo 0, con el A y C de entrada y el B de salida. El 8255 es empleado, básicamente, para almacenar los datos que llegan del teclado (puerto A), para leer la configuración del ordenador en los conmutadores de la placa base (puerto C) y para controlar el altavoz y la velocidad en los XT-Turbo (puerto B). 12.2.3 - UN MÉTODO PARA AVERIGUAR LA CONFIGURACIÓN DEL PC/XT. Aviso: los PC tienen un byte de identificación 0FFh; los XT 0FEh (este byte está en la posición de memoria 0FFFF:0Eh); por otro lado, parte de esta información es accesible también por medio de la variable BIOS ubicada en 40h:10h, método mucho más recomendable. Puerto A (60h): tiene una doble función: cuando el bit 7 del puerto B está a 1, el puerto A recibe el código de rastreo de la tecla pulsada, que luego puede ser leído desde la interrupción del teclado. Si el bit 7 del puerto B está a 0, entonces el puerto A devuelve información sobre la configuración del sistema en los PC (no en los XT): en el bit 0 (a 1 si hay disqueteras), bits 2..3 (número de bloques de 16 kb de memoria ¡que obsoleto e inútil!), bits 4..5 (tipo de pantalla: 11 MDA, 10 Color 80x25, 01 Color 40x25) y bits 6..7 (número de unidades de disco, si el bit 0=1). Puerto B (61h): bit 0 (PC/XT: conectado a la línea GATE del contador 2 del 8253), bit 1 (PC/XT: conectado al altavoz), bit 2 (sólo PC: selecciona el contenido del puerto C), bit 3 (en XT: selecciona contenido del puerto C; en PC: a 0 para activar el motor del casete), bit 4 (PC/XT: a 0 para activar la RAM), bit 5 (PC/XT: a 0 para activar señales de error en el slot de expansión), bit 6 (PC/XT: a 1 activa la señal de reloj del teclado), bit 7 (en PC: empleado para seleccionar la función del puerto A; tanto en PC como en XT sirve además para enviar una señal de reconocimiento al teclado). Puerto C (62h): Si el bit 2 del puerto B (PC) o el bit 3 del puerto B (XT) están a 1: - En los PC: los bits 0..3: mitad inferior del 2º banco de conmutadores de la placa base (RAM en slots de expansión); bit 4 (entrada de casete). - En los XT: bit 1 (activo si coprocesador instalado), bits 2..3 (bancos de RAM en placa base). - En PC/XT: bit 5 (OUT del contador 2 del 8253), bit 6 (a 1 si comprobar errores en slots de expansión), bit 7 (1 si comprobar error de paridad). Si el bit 2 del puerto B (PC) o el bit 3 del puerto B (XT) están a 1: - En los PC: bits 0..3 parte alta del segundo banco de conmutadores de configuración (no usada). - En los XT: bits 0..1 tipo de pantalla (11 MDA, 10 color 80x25, 01 color 40x25), bits 2..3 (nº de disqueteras menos 1). - En PC/XT: los bits 4..7 están igual que en el caso anterior (no dependen del bit 2 ó 3 del puerto B).
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
12.3. - EL TEMPORIZADOR 8253 U 8254. El 8253/4 es un chip temporizador que puede ser empleado como reloj de tiempo real, contador de sucesos, generador de ritmo programable, generador de onda cuadrada, etc. En este capítulo, la información vertida estará relacionada con el 8254 que equipa a los AT, algo más potente que el 8253 de los PC/XT; sin embargo, las pocas diferencias serán comentadas cuando llegue el caso. 12.3.1 - DESCRIPCIÓN DEL INTEGRADO. Este circuito integrado posee 3 contadores totalmente independientes, que pueden ser programados de 6 formas diferentes. D7..D0:BUS de datos bidireccional de 3 estados. CLK 0:CLOCK 0, entrada de reloj al contador 0. OUT 0:Salida del contador 0. GATE 0:Puerta de entrada al contador 0. CLK 1:CLOCK 1, entrada de reloj al contador 1. OUT 1:Salida del contador 1. GATE 1:Puerta de entrada al contador 1. CLK 2:CLOCK 2, entrada de reloj al contador 2. OUT 2:Salida del contador 2. GATE 2:Puerta de entrada al contador 2. A0..A1:Líneas de dirección para seleccionar uno de los tres contadores o el registro de la palabra de control. -CS:Habilita la comunicación con la CPU. -WR:Permite al 8254 aceptar datos de la CPU. -RD:Permite al 8254 enviar datos a la CPU.
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▌
▐
D7 ██▌ 1
24 ▐██ Vcc
▌
▐
D6 ██▌ 2
23 ▐██ -WR
▌
▐
D5 ██▌ 3
22 ▐██ -RD
▌
▐
D4 ██▌ 4
21 ▐██ -CS
▌
▐
D3 ██▌ 5
20 ▐██ A1
▌
▐
D2 ██▌ 6
19 ▐██ A0
▌
▐
D1 ██▌ 7
18 ▐██ CLK 2
▌
▐
D0 ██▌ 8
17 ▐██ OUT 2
▌
▐
CLK 0 ██▌ 9
16 ▐██ GATE 2
▌
▐
OUT 0 ██▌ 10
15 ▐██ CLK 1
▌
▐
GATE 0 ██▌ 11
14 ▐██ GATE 1
▌
▐
GND ██▌ 12
13 ▐██ OUT 1
▌
▐
'8254
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
DESCRIPCIÓN FUNCIONAL El diagrama funcional del 8254, con la estructura interna de las diversas partes que lo componen, se muestra a la izquierda. A la derecha, diagrama de los bloques internos de un contador: ═══════════════════════════════════════════════════════════════ ═══════╦═══════════════════Ω═══════════════╦════════╦═════Ω══Ω═ ║ ║
║
║
║
║
║
║
┌─────────────┐
║ ║
┌────────────┐
┌──────Ϊ──────┐
┌─────╨─────┐
║
║
║
║
│
║ ║
│
│ REGISTRO DE │
│ LATCH DE
│ ┌───────║─────┐
║
║
║
│Χ═════Ψ║ ║Χ═════Ψ│ CONTADOR 0 │Χ── GATE 0
│ LA
PALABRA │
│
│ │ ┌───┐ ║
║
║
║
│ DE
CONTROL │
└─────Ω─────┘ │ │ ┌─Ϊ─Ϊ─┐
┌Ϊ──Ϊ─┐
║
║
BUFFER
│
│Χ── CLK 0
╔══Ψ│
DEL
║
│
DE DATOS
│
║ ║
│
Ϊ
└─────────────┘
║ ║
└─────Ω──────┘
└──────┬──┬───┘
║
│ │ │ CR
│
│ CR
│
║
║
║ ║
│
│
│
║
│ │ │
│
│
│
║
║
║ ║
┌────────┘
│
│
└──╥──┘
║
║
║ ║
│
│
└─────────Ψ│ REGISTRO
-RD ─Ψ┌─────────────┐
║ ║
│
┌────────────┐
-WR ─Ψ│
║ ║
│
│
BUS
D0..D7
│ DE
LÓGICA
│
│──Ψ OUT 0
│Χ── CLK 1
LECTURA │Χ═════Ψ║ ║Χ═════Ψ│ CONTADOR 1 │Χ── GATE 1
ESTADO
M
│
┌─────╨─────┐ │ │ └──╥──┘
L
│ │ │
║
║
║
║
┌─────Ϊ───────┐
│ DE ESTADO │ │ │
║
║
║
║
│
│
└──Ω─────Ω──┘ │ │
║
║
║
║
│
├────────│─────│────┘ │ ┌──Ϊ────────Ϊ──┐
║
║
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
A0 ─Ψ│ Y ESCRITURA ├─┐
║ ║
│
│
│
LÓGICA
├────────│─────│──────┘ │
A1 ─Ψ└─────Ω───────┘ │
║ ║
│
└──────Ω─────┘
│──Ψ OUT 1
│
DE
├────────│─────│───────Ψ│
│
│
║ ║
│
│
│
CONTROL
│Χ───────│─────│────────│
-CS ────────┘
│
║ ║
├─────────┘
│
├────────┘
│
║ ║
│
│
┌─────────────┐ │
║ ║
│
┌────────────┐
│
│ REGISTRO DE │Χ┘
║ ║
│
│
└──Ω───Ω───┬──┘
│Χ── CLK 2
│ LA
PALABRA │Χ══════║ ║Χ═════Ψ│ CONTADOR 2 │Χ── GATE 2
│ DE
CONTROL │
║ ║
│
│
└──────┬──────┘
║ ║
│
└──────Ω─────┘
│
║
║
│
║
║
│
║
║
└───╥────────╥─┘
║
║
├──────────────│────────────║──────┐ ║
║
║
├──────────────│──────────┐ ║
│ ║
║
║
│ ║
│ ║
║
║
├─────────────────┘
┌─Ϊ─Ϊ─┐
┌─Ϊ─Ϊ─┐
║
║
CLK n │
│
│ OL
│
│ OL
│
║
║
│
│
│
│
│
│
║
║
└──╥──┘
║
║
╚══════╝
║
│
│──Ψ OUT 2
└──────────────║ ║────┴─────────┘
│
│
│
GATE n │
║ ║
CE
M
└──╥──┘
Ϊ
║
L
╚══════════════════╝
OUT n
El buffer del bus de datos, de 8 bits y tres estados, comunica el 8254 con la CPU. La lógica de lectura y escritura acepta entradas del bus y genera señales de control para las partes funcionales del 8254. Las líneas A0..A2 seleccionan uno de los tres contadores o el registro de la palabra de control, para poder leerlos o escribirlos. El registro de la palabra de control es seleccionado cuando A0=A1=1, este registro sólo puede ser escrito (se puede obtener información de estado, como se verá más adelante, con el comando read-back del 8254, no disponible en el 8253). Los contadores 1, 2 y 3 son idénticos en su funcionamiento, por lo que sólo se describirá uno; son totalmente independientes y cada uno de ellos puede ser programado en una modalidad diferente. Si se observa el esquema de un contador, a la derecha, se verá el registro de la palabra de control: aunque no es parte del contador propiamente dicho, afecta a su modo de funcionamiento. El registro de estado, cuando es transferido al correspondiente latch, contiene el valor en curso del registro de la palabra de control y alguna información adicional (como se verá después en el comando read-back). El contador propiamente dicho está representado en la figura por CE (Counting Element) y es un contador descendente síncrono de 16 bits que puede ser inicializado. OLM y OLL son dos latch de 8 bits (OL significa Output Latch; los subíndices M y L están relacionados con el más y el menos significativo byte, respectivamente); ambos son referenciados normalmente como un conjunto denominado OL a secas. Estos latches siguen normalmente la cuenta descendente de CE, pero la CPU puede enviar un comando para congelarlos y poder leerlos; tras la lectura continuarán siguiendo a CE. La lógica de control del contador se encarga de que un sólo latch esté activo a un tiempo, ya que el bus interno del 8254 es de 8 bits. CE no puede ser nunca leído directamente (lo que se lee es OL). De manera análoga, existen un par de registros CRM y CRL (CR significa Count Register) que almacenan la cuenta del contador y se la transmiten convenientemente a CE. Los valores de cuenta se escriben siempre sobre CR (y no directamente sobre CE). La lógica de control gestiona la conexión con el exterior a través de las líneas CLK, GATE y OUT. DESCRIPCIÓN OPERACIONAL Tras el encendido del ordenador, el 8254 está en un estado indefinido; con un modo, valor de cuenta y estado de salida aleatorios. Es entonces cuando hay que programar los contadores que se vayan a emplear; el resto, no importa dejarlos de cualquier manera. Programación del 8254. Para programar un contador del 8254 hay que enviar primero una palabra de control y, después, un valor de cuenta inicial. Los contadores se seleccionan con las líneas A0 y A1; el valor A0=A1=1 selecciona la escritura de la palabra de control (en la que se identifica el contador implicado). Por tanto, el 8254 ocupa normalmente 4 direcciones de E/S consecutivas ligadas a los contadores 0, 1, 2 y al registro de la palabra de control. Para enviar la cuenta inicial se utiliza simplemente el puerto E/S ligado al contador que se trate. El formato de la palabra de control es: D7 D6 D5 D4 D3 D2 D1 D0 ┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │
│
│
│
│
│
│
│
│
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
│
SC1
│
│
SC0
│
│
RW1
│
│
RW0
│
│
M2
│
│
M1
│
│
M0
│
│
BCD
│
│ │
└────┬────┴────┬────┴────┬────┴────┬────┼────┬────┴────┬────┴────┬────┴────┬────┘ ┌───┘
└───────┐ │
│
Contador:
│ ┌───────────┘
│
└──┐ ┌────┘ │ │
│
└───────────────┐ │ │
0
Binario 16 bits
│ │ Elegir contador:
│ │
│ │ │
1
BCD de 4 décadas
0 0
Contador 0
│ │
Operación:
│ │ │
Modo:
0 1
Contador 1
0 0
Comando de enclavamiento
0 0 0
Modo 0
1 0
Contador 2
0 1
Leer/escribir byte bajo
0 0 1
Modo 1
1 1
Comando Read Back
1 0
Leer/escribir byte alto
X 1 0
Modo 2
1 1
Leer/escribir byte bajo
X 1 1
Modo 3
y después el alto
1 0 0
Modo 4
1 0 1
Modo 5
Operaciones de escritura. El 8254 es muy flexible a la hora de ser programado. Basta con tener en cuenta dos cosas: por un lado, escribir siempre primero la palabra de control, antes de enviar la cuenta inicial al contador. Por otro, dicha cuenta inicial debe seguir exactamente el formato seleccionado en la palabra de control (enviar sólo byte bajo, enviar sólo byte alto, o bien enviar ambos consecutivamente). Teniendo en cuenta que cada contador tiene su propio puerto y que la palabra de control indica el contador al que está asociada, no hay que seguir un orden especial a la hora de programar los contadores. Esto significa que, por ejemplo, se puede enviar la palabra de control de cada contador seguida de su cuenta inicial, o bien enviar todas las palabras de control para los 3 contadores y después las 3 cuentas iniciales; también es válida cualquier combinación intermedia de estas secuencias (por ejemplo: enviar la palabra de control para el contador 0, después la palabra de control para el contador 1, después la parte baja de la cuenta para el contador 0, luego la parte baja de la cuenta para el contador 1, la parte alta de la cuenta para el contador 0, etc...). Un nuevo valor de cuenta inicial puede ser almacenado en un contador en cualquier momento, sin que ello afecte al modo en que ha sido programado (el resultado de esta operación dependerá del modo, como se verá más adelante). Si se programa el contador para leer/escribir la cuenta como dos bytes consecutivos (bajo y alto), el sentido común indica que entre ambos envíos/recepciones no conviene transferir el control a una subrutina que utilice ese mismo contador para evitar un resultado incorrecto. Operaciones de lectura. Existen tres posibles métodos para leer el valor de un contador en el 8254. El primero es el comando Read-Back, sólo disponible en el 8254 (y no en el 8253), como luego veremos. El segundo consiste en leer simplemente el contador accediendo a su puerto correspondiente: este método requiere inhibir la entrada CLK al contador (por ejemplo, a través de la línea GATE o utilizando circuitería exterior de apoyo) con objeto de evitar leer la cuenta en medio de un proceso de actualización de la misma, lo que daría un resultado incorrecto. El tercer método consiste en el comando de enclavamiento. Comando de enclavamiento (Counter Latch Command). Este comando se envía cual si de una palabra de control se tratara (A1=A0=1): para diferenciarlo de ellas los bits 5 y 4 están a cero. En los bits 7 y 6 se indica el contador afectado. Los demás bits deben estar a cero para compatibilizar con futuras versiones del chip. Cuando se envía el comando, el OL del contador seleccionado queda congelado hasta que la CPU lo lee, momento en el que se descongela y pasa de nuevo a seguir a CE. Esto permite leer los contadores al vuelo sin afectar la cuenta en curso. Se pueden enviar varios de estos comandos a los diversos contadores, cuyos OL's quedarán enclavados hasta ser leídos. Si se envían varios comandos de enclavamiento al mismo contador, separados por un cierto intervalo de tiempo, sólo se considerará el primero (por tanto, la cuenta leída corresponderá al valor del contador cuando fue enclavado por vez primera). Por supuesto, el contador debe ser leído utilizando el formato que se definió al enviar la palabra de control; aunque en el caso de leer 16 bits, las dos operaciones no han de ser necesariamente consecutivas (se
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
pueden insertar en el medio otras acciones relacionadas con otros contadores). Otra característica interesante (¿disponible tal vez sólo en el 8254?) consiste en la posibilidad de mezclar lecturas y escrituras del mismo contador. Por ejemplo, si ha sido programado para cuentas de 16 bits, es válido hacer lo siguiente: 1) leer el byte menos significativo, 2) escribir el nuevo byte menos significativo, 3) leer el byte más significativo, 4) escribir el nuevo byte más significativo. Comando Read-Back. Sólo está disponible en el 8254, no en el 8253. Este comando permite leer el valor actual de la cuenta, así como averiguar también el modo programado para un contador y el estado actual de la patilla OUT, además de verificar el banderín de cuenta nula (Null Count) de los contadores que se indiquen. El formato del comando Read-Back es el siguiente: D7 D6 D5 D4 D3 D2 D1 D0 ┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │ │
│ 1
│
│
1
│
│
│
│ -COUNT
│ -STATUS │
│
│
│
│ CNT 2
│
│
│ CNT 1
│
│
│ CNT 0
│
│
│ 0
│
│ │
└─────────┴─────────┴────┬────┴────┬────┼────┬────┴────┬────┴────┬────┴─────────┘ Ϊ 0
│
Si enclavar la cuenta
│
de los contadores
│
seleccionados
│
└─────────┼─────────┘ Ϊ a 1 los contadores seleccionados
Ϊ 0
Si enclavar el byte de estado del contador seleccionado
El comando Read-Back permite enclavar la cuenta en varios OL's de múltiples contadores de una sola vez, sin requerir múltiples comandos de enclavamiento, poniendo el bit 5 a cero. Todo funciona a partir de aquí como cabría esperar (los contadores permanecen enclavados hasta ser leídos, los que no son leídos permanecen enclavados, si el comando se reitera sólo actúa la primera vez reteniendo la primera cuenta...). También es posible enviar información de estado al latch OL, enclavándola para que puede ser leída con comodidad por el puerto que corresponda a ese contador. La palabra de estado tiene el siguiente formato: D7 D6 D5 D4 D3 D2 D1 D0 ┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │
│
│ OUTPUT
│
NULL
│ │
│
│
COUNT
│
│ RW1
│
│ RW0
│
│ │
│ M2
│
│ M1
│
│
│ M0
│
│
│ BCD
│
│ │
└────┬────┴────┬────┴─────────┴─────────┼────┬────┴────┬────┴────┬────┴────┬────┘ Ϊ
└────────┐
valor de la patilla OUT
│ │
└─────────┼─────────┘
Ϊ
Contador:
Ϊ
0
Binario 16 bits
1
BCD 4 décadas
modo activo
1
"Null Count"
0
Cuenta disponible para ser leída
En D0..D5 se devuelve justo la misma información que se envió en la última palabra de control; en el bit D7 se entrega el estado actual de la patilla OUT del 8254, lo que permite monitorizar por software las salidas del temporizador economizando hardware en ciertas aplicaciones. El bit NULL COUNT (D6) indica cuándo la última cuenta escrita en CR ha sido transferida a CE: el momento exacto depende del modo de funcionamiento del contador. Desde que se programa un nuevo valor de cuenta, pasa un cierto tiempo hasta que éste valor pasa de CR a CE: leer el contador antes de que se haya producido dicha transferencia implica leer un valor no relacionado con la nueva cuenta. Por ello, según las aplicaciones, puede llegar a ser necesario esperar a que NULL COUNT alcance el valor 0 antes de leer. El funcionamiento es el siguiente: Operación Consecuencias A - Escribir al registro de la palabra de control (1) NULL COUNT = 1
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
B - Escribir al registro contador (CR) (2) C - Nueva cuenta cargada en CE (CR -Ψ CE)
NULL COUNT = 1 NULL COUNT = 0
Notas:(1) Sólo el contador especificado por la palabra de control tiene su NULL COUNT a 1; los demás contadores, lógicamente, no ven afectado su correspondiente bit NULL COUNT. (2) Si el contador es programado para cuentas de 16 bits, NULL COUNT pasa a valer 1 inmediatamente después de enviar el segundo byte.
Si se enclava varias veces seguidas la palabra de estado, todas serán ignoradas menos la primera, por lo que el estado leído será el correspondiente al contador en el momento en que se enclavó por vez primera la palabra de estado. Se pueden enclavar simultáneamente la cuenta y la palabra de estado (en un comando Read-Back con D5=D4=0), lo que equivale a enviar dos Read-Back consecutivos. En este caso, y con independencia de quién de los dos hubiera sido enclavado primero, la primera lectura realizada devolverá la palabra de estado y la segunda la cuenta enclavada (que automáticamente quedará de nuevo desenclavada). MODOS DE OPERACIÓN DEL 8254 MODO 0: Interrupt On Terminal Count (Interrupción al final de la cuenta). Es empleado típicamente para contar sucesos. Tras escribir la palabra de control, OUT está inicialmente en estado bajo, y permanecerá así hasta que el contador alcance el cero: entonces se pone a 1 y no volverá a bajar hasta que se escriba una nueva cuenta o una nueva palabra de control. La entrada GATE puesta a 0 permite inhibir la cuenta, sin afectar a OUT. El contador sigue evolucionando tras llegar a cero (0FFFFh, 0FFFEh, ...) por lo que lecturas posteriores del mismo devuelven valores pseudoaleatorios. Tras escribir la cuenta inicial y la palabra de control en el contador, la cuenta inicial será cargada en el próximo pulso del reloj conectado (CLK), pulso que no decrementa el contador: para una cuenta inicial N, OUT permanecerá a 0 durante N+1 pulsos del reloj tras escribir la cuenta inicial. Si se escribe una nueva cuenta en el contador, será cargada en el próximo pulso del reloj y el contador comenzará a decrementarse; si se envía una cuenta de dos bytes, el primer byte enviado inhibe la cuenta y OUT es puesto a cero inmediatamente (sin esperar a CLK): tras escribir el segundo byte, la cuenta será cargada en el siguiente pulso del reloj. Esto permite sincronizar la secuencia de conteo por software. Si se escribe una nueva cuenta mientras GATE=0, ésta será cargada en cualquier caso en el siguiente pulso del reloj: cuando GATE suba, OUT se pondrá en alto tras N pulsos del reloj (y no N+1 en este caso). ┌──┐ CLK
──┘ ───┐
┌──┐
└──┘
┌──┐
└──┘
(N=5)
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
└──
┌────────────────────────────────────────────────── └─────────────┘
-WR
──────────────────────────────┐
┌────────────────────────── └──────────┘
GATE ─────────────────┐ OUT
┌──┐
└──┘
┌──
└───────────────────────────────────────────────┘ 5
4
3
2
1
0
MODO 1: Hardware Retriggerable One-Shot (Monoestable programable). OUT será inicialmente alta y bajará en el pulso de reloj que sigue al flanco de subida de GATE, permaneciendo en bajo hasta que el contador alcance el cero. Entonces, OUT sube y permanece activo hasta el pulso del reloj que siga al próximo flanco de subida de GATE. Tras escribir la palabra de control y la cuenta inicial, el contador está preparado. Un flanco de subida de GATE provoca la carga del contador (CR ─Ψ CE) y que OUT baje en el próximo pulso del reloj, comenzando el pulso One-Shot de N ciclos de reloj de duración; el contador vuelve a ser recargado si se produce un nuevo flanco de subida de GATE, de ahí que OUT permanezca en bajo durante N pulsos de reloj tras la última vez que suceda esto. El pulso One-Shot puede repetirse sin necesidad de recargar el contador con el mismo valor. GATE
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
no influye directamente en OUT. Si se escribe una nueva cuenta durante un pulso One-Shot, el One-Shot en curso no resulta afectado, a menos, lógicamente, que se produzca un nuevo flanco de subida de GATE: en ese caso, el contador sería recargado con el nuevo valor. ┌──┐ ──┘
CLK
┌──┐
└──┘
───┐
┌──┐
└──┘
(N=4)
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
└──
┌───────────────────────────────────────────────── └──────────────┘
-WR ┌──────────┐
┌───────────────────────────────────
────────────┘
GATE
┌──┐
└──┘
└────────┘
─────────────────┐
┌──────── └─────────────────────────────────────────┘
OUT
4
3
2
4
3
2
1
0
MODO 2: Rate Generator (Generador de ritmo). En este modo, el contador funciona como un divisor por N. Es empleado típicamente para las interrupciones de los relojes de tiempo real. OUT estará inicialmente en alto. Cuando el contador se decremente hasta el valor 1, OUT pasará a estado bajo durante un pulso del reloj; tras ello, volverá a subir y el contador se recargará con la cuenta inicial, repitiéndose el proceso. Este modo es, por tanto, periódico, y la misma secuencia se repite indefinidamente. Para una cuenta inicial N, la secuencia se repite cada N ciclos de reloj (CLK). Si GATE=0 la cuenta descendiente se detiene: si GATE es bajado durante un pulso de salida, OUT sube inmediatamente. Un flanco de subida en GATE provoca una recarga del contador con el valor de cuenta inicial en el siguiente pulso del reloj (después, como cabría esperar, OUT bajará tras los N pulsos del reloj correspondientes): GATE puede ser utilizado para sincronizar el contador. Tras escribir la palabra de control y la cuenta inicial, el contador será cargado en el próximo pulso del reloj: OUT bajará N pulsos de reloj después, lo que permite también una sincronización por software. Escribir un nuevo valor de cuenta durante el funcionamiento del contador no afecta a la actual secuencia de cuenta; si se recibe un flanco de subida de GATE antes del final del período el contador se recargará con ese nuevo valor de cuenta inicial tras el próximo pulso del reloj y volverá a comenzar, en caso contrario se recargará con el nuevo valor tras finalizar con normalidad el ciclo en curso. ┌──┐ CLK
──┘
┌──┐
└──┘
──────┐
┌──┐
└──┘
(N=4) -WR
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌───────────────────────────────────┐
┌──┐
└──┘
(N=3)
┌──┐
└──┘
┌──┐
└──┘
└──
└─────────┘ ┌─────────────────┐
└─────┘
OUT 3
┌──┐
└──┘
┌────────────────────────────
└──────────┘
─────────────────────────────────────────┐ 4
┌──┐
└──┘
2
1
0(4)
┌───────────┐
└─────┘ 3
2
1
0(3)
┌──
└─────┘ 2
1
0
MODO 3: Square Wave Mode (Generador de onda cuadrada). Este modo es empleado normalmente para la generación de una señal de onda cuadrada. Este modo es similar al 2, con la diferencia de que la salida OUT conmuta al transcurrir la mitad de la cuenta: inicialmente está en alto, pero al pasar la mitad de la cuenta pasa a estado bajo hasta que la cuenta finaliza. Este modo es también periódico: la onda resultante para una cuenta inicial N tiene un período de N ciclos. Si GATE=0 la cuenta descendiente se detiene: si GATE es bajado durante un pulso de salida, OUT sube inmediatamente sin esperar ningún CLK. Un flanco de subida en GATE provoca una recarga del contador con el valor de cuenta inicial en el siguiente pulso del reloj: GATE puede ser utilizado para sincronizar el contador. Tras escribir la palabra de control y la cuenta inicial, el contador será cargado en el próximo pulso del reloj: también puede ser sincronizado por software. Escribir un nuevo valor de cuenta durante el funcionamiento del contador no afecta a la actual secuencia de cuenta; si se recibe un flanco de subida de GATE antes del final del medio-período el contador se recargará con ese nuevo valor de cuenta inicial tras el próximo pulso del reloj y volverá a comenzar, en caso
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
contrario se recargará con el nuevo valor tras finalizar con normalidad el medio-ciclo en curso. Para valores de cuenta impares, la duración a nivel alto de OUT será un período de reloj mayor que la duración a nivel bajo. ┌──┐ ──┘
CLK
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
4 3 2 ┌───────────┐
┌──┐
└──┘
┌──┐
└──┘
1
└───────────┘
─────┘
┌──┐
└──┘
4 3 2 ┌───────────┐
5 4 3 2 ┌─────────────────┐ OUT (N=5)
┌──┐
└──┘
1
─────┘
OUT (N=4)
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
4 3 2 ┌───────────┐
1
└───────────┘
1
┌──┐
└──┘
└──
4 3 ┌────────
└───────────┘
5 4 3 2 ┌─────────────────┐
└───────────┘
┌──┐
└──┘
1
5 4 3 2 ┌─────────────────┐
└───────────┘
└──
MODO 4: Software Triggered Mode (Pulso Strobe iniciado por software). OUT está en alto al principio; cuando la cuenta inicial expira, OUT baja durante un pulso de reloj y luego vuelve a subir. El proceso se inicia cuando se escribe la cuenta inicial. GATE=0 inhibe el contador y GATE=1 lo habilita; GATE no influye en OUT. Tras escribir la palabra de control y la cuenta inicial, el contador será cargado en el próximo pulso del reloj: como ese pulso no decrementa el contador, para una cuanta inicial N, OUT no bajará hasta N+1 pulsos de CLK. Si se escribe una nueva cuenta durante el proceso, se cargará en el próximo pulso CLK y continuará el proceso de cuenta con la nueva cuenta escrita; si la cuenta es de 2 bytes, al escribir el primero no se altera el funcionamiento del contador hasta que se envíe el segundo. ┌──┐ ──┘
CLK
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
────────┐ (N=4)
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
└──
┌───────────────────────────────────────────────────────────────────────── └─────────┘
-WR ──────────────────────────┐
┌───────────────────────────────────────────────────────
GATE
└─────────┘
4
4
3
2
1
─────────────────────────────────────────────────────────────────┐
0
┌──────────────────── └─────┘
OUT
MODO 5: Hardware Triggered Strobe (Pulso Strobe iniciado por hardware). OUT estará en alto al principio: con el flanco de subida de la señal GATE, el contador comienza a decrementar la cuenta. Cuando llega a cero, OUT baja durante un pulso CLK y luego vuelve a subir. Después de escribir la palabra de control y la cuenta inicial, el contador no será cargado hasta el pulso de reloj posterior al flanco de subida de GATE. Este pulso CLK no decrementa el contador: por ello, ante una cuenta inicial N, OUT no bajará hasta que pasen N+1 pulsos de reloj. GATE no afecta a OUT. Si una nueva cuenta inicial es escrita durante el proceso, la actual secuencia del contador no será alterada; si se produce un flanco de subida en GATE antes de que la nueva cuenta sea escrita pero después de que expire la cuenta actual, el contador será cargado con la nueva cuenta en el próximo pulso del reloj. ┌──┐ ──┘
CLK
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌────┐ GATE
───────────────┘ 4
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──┐
└──┘
┌──
└──┘
┌────────────────────────────────────── └──3──┘
4
3
2
1
0
─────────────────────────────────────────────────────┐
┌───── └─────┘
OUT
╓─────────────────────────────────────────────────────────────────╥──────────────────────┐ ║
║ Rango de las cuentas │
Operación de GATE
┌────────╫─────────────────────┬─────────────────────┬─────────────────────╫───────────┬──────────┤ │
MODO
║
Bajo o Bajando
│
Subiendo
│
Alto
║
Mínima
│
Máxima
│
├────────╫─────────────────────┼─────────────────────┼─────────────────────╫───────────┼──────────┤ │
0
║ Desactiva la cuenta │
--
│
Activa la cuenta
║
1
│
0
│
├────────╫─────────────────────┼─────────────────────┼─────────────────────╫───────────┼──────────┤
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
│ │
║ 1
│
║
│ 1) Inicia la cuenta │ --
║
│ 2) Resetea OUT tras │ │
║ --
el siguiente CLK │
║
│ 1
║
│
│ 0
│
│ │
├────────╫─────────────────────┼─────────────────────┼─────────────────────╫───────────┼──────────┤ │ │
║ 1) Desactiva cuenta │ 1) Carga contador 2
│
│
║ 2) Pone OUT en alto │ 2) Inicia la cuenta │ ║
inmediatamente
│
║ Activa la cuenta
│
║
│ 2
║
│
│ 0
│
│ │
├────────╫─────────────────────┼─────────────────────┼─────────────────────╫───────────┼──────────┤ │ │
║ 1) Desactiva cuenta │ 1) Carga contador 3
│
│
║ 2) Pone OUT en alto │ 2) Inicia la cuenta │ ║
inmediatamente
│
║ Activa la cuenta
│
║
│ 2
║
│
│ 0
│
│ │
├────────╫─────────────────────┼─────────────────────┼─────────────────────╫───────────┼──────────┤ │
4
║ Desactiva la cuenta │
--
│
Activa la cuenta
║
1
│
0
│
├────────╫─────────────────────┼─────────────────────┼─────────────────────╫───────────┼──────────┤ │
5
║
--
│
Inicia la cuenta
│
--
║
1
│
0
│
└────────╨─────────────────────┴─────────────────────┴─────────────────────╨───────────┴──────────┘
12.3.2 - EL 8254 EN EL ORDENADOR. Todos los AT y PS/2 llevan instalado un 8254 o algo equivalente; los PC/XT van equipados con un 8253, algo menos versátil; los PS/2 más avanzados tienen un temporizador con un cuarto contador ligado a la interrupción no enmascarable, si bien no lo consideraremos aquí. Todos los contadores van conectados a un reloj que oscila a una frecuencia de 1.193.180 ciclos por segundo (casi 1,2 Mhz). La dirección base en el espacio de E/S del ordenador elegida por IBM cuando diseñó el PC es la 40h. Por tanto, los tres contadores son accedidos, respectivamente, a través de los puertos 40h, 41h y 42h; la palabra de control se envía al puerto 43h. La señal GATE de los contadores 0 y 1 está siempre a 1; en el contador 2 es seleccionable el nivel de la línea GATE a través de bit 0 del puerto E/S 61h. La BIOS programa por defecto el contador 0 en el modo 3 (generador de onda cuadrada) y el contador 1 en el modo 2 (generador de ritmo); el usuario normalmente programa el contador 2 en el modo 2 ó 3. La salida del contador 0 está conectada a IRQ 0 (ligado a la INT 8, que a su vez invoca a INT 1Ch); este contador está programado por defecto con el valor cero (equivalente a 65536), por lo que la cadencia de los pulsos es de 1.193.180/65.536 = 18,2 veces por segundo, valor que determina la precisión del reloj del sistema, ciertamente demasiado baja. Se puede modificar el valor de recarga de este contador en un programa, llamando a la vieja INT 8 cada 1/18,2 segundos para no alterar el funcionamiento normal del ordenador, si bien no es conveniente instalar programas residentes que cambien permanentemente esta especificación: los programas del usuario esperan encontrarse el temporizador a la habitual y poco útil frecuencia de 18,2 interrupciones/segundo. La salida del contador 1 controla el refresco de memoria en todas las máquinas, su valor normal para el divisor es 18; aumentándolo se puede acelerar el funcionamiento del ordenador, con el riesgo -eso sí- de un fallo en la memoria, detectado por los chips de paridad -si los hay-, que provoca generalmente el bloqueo del equipo. De todas maneras, en los PC/XT se puede aumentar entre 19 y 1000 sin demasiados riesgos, acelerándose en ocasiones hasta casi un 10% la velocidad de proceso del equipo. En los AT la ganancia de velocidad es mucho menor y además este es un punto demasiado sensible que conviene no tocar para no correr riesgos, aunque se podría bajar hasta un valor 2-17 para ralentizar el sistema. Sin embargo, no es conveniente alterar esta especificación porque, como se verá más adelante, hay un método para realizar retardos (empleado por la BIOS y algunas aplicaciones) que se vería afectado. El contador 2 puede estar conectado al altavoz del ordenador para producir sonido; alternativamente puede emplearse para temporizar. Es el único contador que queda realmente libre para el usuario, lo que suele dar quebraderos de cabeza a la hora de producir sonido. 12.3.3 - TEMPORIZACIÓN.
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
Los contadores 0 y 1, especialmente este último, ya están ocupados por el sistema; en la práctica el único disponible es el 2. Este contador ha sido conectado con el doble propósito de temporizar y de generar sonido. Para emplearlo en las temporizaciones, es preciso habilitar la puerta GATE activando el bit 0 del puerto 61h; también hay que asegurarse de que la salida del contador no está conectada al altavoz (a menos que se desee música mientras se cronometra) poniendo a 0 el bit 1 del mismo puerto (61h): IN
AL,61h
AND
AL,11111101b
; borrar bit 1
OR
AL,00000001b
; activar bit 0 (línea GATE del contador 2)
(conexión contador 2 con el altavoz)
JMP
SHORT $+2
; estado de espera para E/S
OUT
61h,AL
El siguiente programa de ejemplo, CRONOS.ASM, incluye dos subrutinas para hacer retardos de alta precisión. La primera de ellas, inic_retardo, hay que llamarla al principio para que programe el contador 2 del temporizador; la rutina retardo se encarga de hacer el retardo que se indique en AX (en unidades de 1/1193180 segundos). ; ******************************************************************** ; * ; *
CRONOS.ASM
-
Subrutinas para hacer retardos de precisión.
; *
retardo
PROC
*
PUSH
AX
*
PUSH
BX
*
CLI
; *
INIT_RETARDO: llamarla al principio del todo.
*
OUT
42h,AL
; *
RETARDO:
Entregar en AX el nº de 1193180-avos de
*
MOV
AL,AH
segundo que dura el retardo (máximo 65400).
*
JMP
SHORT $+2
*
OUT
42h,AL
; ********************************************************************
JMP
SHORT $+2
IN
AL,61h
XOR
AL,1
SEGMENT
JMP
SHORT $+2
ASSUME CS:programa, DS:programa
OUT
61h,AL
XOR
AL,1
JMP
SHORT $+2
OUT
61h,AL
; * ; *
programa
ORG
100h
; parte baja de la cuenta
; parte alta
; bajar GATE
; subir GATE
STI
inicio:
retard:
inic_retardo
JMP
SHORT $+2
MOV
BX,0FFFFh
MOV
AL,10000000b
OUT
43h,AL
retardo
JMP
SHORT $+2
LOOP
retard
IN
AL,42h
INT
20h
MOV
AH,AL
JMP
SHORT $+2
IN
AL,42h
XCHG
AH,AL
CALL
inic_retardo
MOV
CX,20
; 20 retardos
MOV
AX,59659
; de 50 milisegundos
CALL
PROC PUSH
AX
CMP
AX,BX
IN
AL,61h
MOV
BX,AX
AND
AL,11111101b
JBE
retardando
OR
AL,1
POP
BX
JMP
SHORT $+2
POP
AX
OUT
61h,AL
RET
MOV
AL,10110100b
JMP
SHORT $+2
OUT
43h,AL
POP
AX
RET inic_retardo
retardando:
; contador 2, modo 2, binario
retardo
ENDP
programa
ENDS END
; enclavamiento
; leer contador
; AX = valor del contador
inicio
ENDP
El procedimiento inic_retardo programa el contador 2 en el modo 2, con datos en binario y dejándolo
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
listo para enviar/recibir secuencias de 2 bytes para la cuenta (primero el byte menos significativo y luego el alto). Las instrucciones JMP SHORT $+2 colocadas oportunamente (para saltar a la siguiente línea) evitan que las máquinas AT más antiguas fallen en dos operaciones de E/S consecutivas demasiado rápidas. El procedimiento retardo envía el nuevo valor de cuenta. A continuación baja y vuelve a subir la señal GATE, con objeto de provocar un flanco de subida en esta línea, lo cual provoca que el contador se cargue con el valor recién enviado de manera inmediata (de lo contrario, no se recargaría hasta acabar la cuenta anterior). Finalmente, entramos en un bucle donde se enclava continuamente la cuenta y se espera hasta que acabe. Lo más intuitivo sería comprobar si la cuenta es cero, pero esto es realmente difícil ya que cambia nada menos que ¡más de 1 millón de veces por segundo!. Por tanto, nos limitamos a comprobar si tras dos lecturas consecutivas la segunda es mayor que la primera ...¡no puede ser!... sí, si puede ser, si tras llegar a 0 el contador se ha recargado. De esta manera, el mayor valor admitido en AX al llamar es 65535, aunque no conviene que sea superior a 65400, para permitir que las recargas puedan ser detectadas en la máquina más lenta (un XT a 4.77 y en 135/1193180 segundos dispone de unos 540 ciclos, en los que holgadamente cubre este bucle). A la hora de emplear las rutinas anteriores hay que tener en cuenta dos consideraciones. Por un lado, están diseñadas para hacer pequeños retardos: llamándolas repetidamente, el bucle que hay que hacer (y las interrupciones que se producen durante el proceso) provoca que retarden más de la cuenta. Por ejemplo, en el programa principal, poniendo 1200 en CX en lugar de 20, el retardo debería ser de 60 segundos; sin embargo, comparando este dato con el contador de hora de la BIOS (en una versión ligeramente modificada del programa) resulta ser de casi 60,2 segundos. La segunda consideración está relacionada con las interrupciones: de la manera que está el listado, se puede producir una interrupción en la que algún programa residente utilice el contador 2 del temporizador, alterando el funcionamiento de las rutinas de retardo (por ejemplo, una utilidad de click en el teclado) o incluso provocando un fallo en la misma (si a ésta no le da tiempo a comprobar que ya es la hora): este es un aspecto a tener en cuenta en un caso serio. Se puede, por ejemplo, inhibir todas las interrupciones (o enmascar sólo las más molestas), aunque anular la interrupción del temporizador, la más peligrosa, provocaría un retraso de la hora del ordenador. Para hacer retardos o temporizaciones de más de 50 milisegundos, es más conveniente emplear el contador de hora de la BIOS (variable de 32 bits en 0040h:006Ch) que la INT 8 se encarga de incrementar 18,2 veces cada segundo y de volver a ponerlo a cero cada 24 horas. No es conveniente mirar el valor del contador de hora de la BIOS, sumarle una cantidad y esperar a que alcance dicha cantidad fija: la experiencia demuestra que eso produce a veces cuelgues del ordenador, no solo debido a que suele fallar cuando son las 23:59:59 sino también porque cuando se alcanza el valor esperado, por cualquier motivo (tal como un alargamiento excepcional de la rutina que controla INT 8 ó INT 1Ch debido a algún programa residente) puede que el programa principal no llegue a tiempo para comprobar que ya es la hora... y haya que esperar otras 24 horas a probar suerte. Lo ideal es contar las veces que cambia el contador de hora de la BIOS. Por último, como ejemplo ameno, el siguiente fragmento de programa hace que la hora del ordenador vaya diez veces más rápida -poco recomendable, aunque muy divertido- programando el contador 0 con un valor de cuenta 6553 (frente al 0=65536 habitual), de la siguiente manera: MOV
AL,00110110b
OUT
43h,AL
; contador 0, operación 11b, datos binarios
MOV
BX,6553 ; valor de cuenta
MOV
AL,BL
JMP
SHORT $+2
OUT
40h,AL
MOV
AL,BH
JMP
SHORT $+2
OUT
40h,AL
; enviar byte bajo
; enviar byte alto
Un método genial para hacer retardos y controlar timeouts en AT. Aunque ausente en todos los manuales de referencia técnica y en todos los libros relacionados con la programación de PC, existe un método muy fácil y eficiente para temporizar disponible en todos los ordenadores AT. Pese a no estar documentado, un programa muy usual como es el KEYB del MS-DOS (a partir
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
de la versión 5.0 del sistema) lo utiliza en todos los AT, sin importar el modelo. Por ello, cabe suponer que seguramente los futuros equipos mantendrán la compatibilidad en este aspecto. Sucede que la salida del contador 1 del 8254, encargada del refresco de la memoria, controla de alguna manera desconocida (tal vez a través de un flip-flop) la generación de una onda cuadrada de unos 33 KHz que puede leerse a través del bit 4 del puerto 61h (no se trata de la salida OUT del contador 1: éste está programado en modo 2 y no genera precisamente una onda cuadrada). El contador 1 es programado por la BIOS en todos los PC con una cuenta 18, conmutando el nivel de la salida cada segundo 1193180/18 = 66287,77 veces. Para hacer un determinado retardo basta con contar las veces que el bit cambia de nivel: la función en ensamblador retardo_asm() del programa de ejemplo lo ilustra. Este método es especialmente interesante en los programas residentes que precisen retardos de precisión, para sonido u otras tareas, tales como limitar la duración máxima de una comprobación en un bit de estado a unos milisegundos o microsegundos (control de timeouts); la principal ventaja es que no se modifica en absoluto la configuración de ningún chip que pueda estar empleando el programa principal, empezando por el 8254. Además, no requiere preparación previa alguna. Para los más curiosos, decir que el bit 5 del puerto 61h es la salida OUT del contador 2 del 8254 (la línea OUT del contador 2 del 8253 de los PC/XT también puede consultarse a través del bit 5, pero del puerto 62h). El único inconveniente del método es la alta frecuencia con que cambia el bit: esta misma rutina escrita en C podría no ser suficientemente ágil para detectar todas las transiciones en las máquinas AT más lentas a 6 MHz. A partir de 8 MHz sí puede ser factible, como evidencian las pruebas realizadas, aunque hay que extremar las precauciones para que el código compilado sea lo bastante rápido: utilizar las dos variables registro que realmente soportan los compiladores y huir de la aritmética de 32 bits, como puede observarse en la función retardo_c() del programa de ejemplo. Una mala codificación o compilador podrían hacer inservible el método incluso en una máquina a 16 ó 20 MHz. Para no tener problemas, es mejor emplear la versión en ensamblador, escrita en un C no mucho menos estándar. La macro MICRO() ayuda a seleccionar con más comodidad el retardo, indicándolo en µs, aunque implica una operación en coma flotante que por sí sola añade unos 100 µs de retardo adicionales en un 386-25 sin coprocesador y con las librerías de Borland. Anécdota: Para los más curiosos, decir que los programadores de Microsoft emplean este método en el KEYB en dos ocasiones: para limitar a un
tiempo razonable la espera hasta que el registro de entrada del 8042 se llene (15 ms) y, en otra ligera variante, para controlar la duración del pitido de error. Los aficionados al ensamblador pueden comprobarlo personalmente aplicando el comando U del DEBUG sobre el KEYB para desensamblar a partir de los offsets 0E39 y 0D60, respectivamente: en el primer caso, la subrutina sólo es ejecutada en AT; en el segundo, veréis como el KEYB se asegura de que el equipo es un AT comprobando el valor de BP antes de saltar a 0D70 (ejecuta un bucle vacío en las demás máquinas). Esta nueva técnica ha permitido eliminar respecto a anteriores versiones del programa algunos test sobre tipos de ordenadores, cuya finalidad más común era ajustar las constantes de retardo. Son válidos tanto el KEYB del MS-DOS 5.0 castellano como el del MS-DOS 6.0 en inglés o castellano indistintamente (¡las direcciones indicadas coinciden!). También en las BIOS modernas suele haber ejemplos de esta técnica, aunque las direcciones ya no coinciden... /********************************************************************/
retardo_c
(66267L);
/* ahora en C */
/*
retardo_c
(MICRO(1000000L));
/* la otra alternativa */
*/
/*
Programa de demostración del método de retardo basado en la
*/
/*
monitorización de los ciclos de refresco de memoria del AT.
*/
/*
}
*/
/********************************************************************/
void retardo_asm (long cuenta)
/* método ensamblador recomendado */
{ #include
#define MICRO(microseg) ((long)(microseg/15.08573727))
void retardo_asm(), retardo_c();
void main() { /* cuatro formas de hacer un mismo retardo de precisión */
retardo_asm (66267L);
/* un segundo */
retardo_asm (MICRO(1000000L));
/* otro segundo (¡más claro!) */
asm
push
ax
asm
push
cx
asm
push
dx
asm
mov
cx,word ptr cuenta
asm
mov
dx,word ptr [cuenta+2]
/* DX:CX = cuenta */
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
esp_ref:
fin_l:
fin_ret:
asm
jcxz
fin_l
asm
in
al,61h
/* posible cuenta baja nula */
asm
and
al,10h
asm
cmp
al,ah
asm
je
esp_ref
asm
mov
ah,al
asm
loop
esp_ref
asm
and
dx,dx
asm
jz
fin_ret
asm
dec
dx
asm
jmp
esp_ref
asm
pop
dx
asm
pop
cx
asm
pop
ax
/* aislar bit 5 */
/* esperar cambio de nivel */
/* completar cuenta baja */
/* posible cuenta alta nula */
/* completar cuenta alta */
}
void retardo_c (long cuenta)
/* método en C no recomendado */
{ register a, b; unsigned cuenta_h, cuenta_l;
cuenta_h=cuenta >> 16;
cuenta_l=cuenta & 0xFFFF;
do do { while (a==(b=inportb(0x61) & 0x10)); a=b; } while (cuenta_l--); while (cuenta_h--); }
12.3.4 - SÍNTESIS DE SONIDO. La producción de sonido es uno de los puntos más débiles de los ordenadores compatibles, que sólo superan por muy escaso margen a alguno de los micros legendarios de los 80, si bien las tarjetas de sonido han solventado el problema. Pero aquí nos conformaremos con describir la programación del altavoz. En todos los PCs existen dos métodos diferentes para generar sonido, con la utilización del 8254 o sin él, que veremos por separado. Control directo del altavoz. El altavoz del ordenador está ligado en todas las máquinas al bit 1 del puerto E/S 61h. Si se hace cambiar este bit (manteniéndolo durante cierto tiempo alto y durante cierto tiempo bajo, repitiendo el proceso a gran velocidad) se puede generar una onda cuadrada de sonido. Cuanto más deprisa se realice el proceso, mayor será la frecuencia del sonido. Por fortuna, la baja calidad del altavoz del PC redondea la onda cuadrada y produce un sonido algo más musical de forma involuntaria. No existe, en cualquier caso, control sobre el volumen, que dada la calidad del altavoz también está en función de la frecuencia. Este método de producción de sonido tiene varios inconvenientes. Por un lado, la frecuencia con que se hace vibrar al bit que lo produce, si no se tiene mucho cuidado, está a menudo más o menos ligada a la capacidad de proceso del ordenador: esto significa que el sonido es más grave en máquinas lentas y más agudo en las rápidas. Esto es particularmente grave y evidente cuando las temporizaciones se hacen con bucles de retardo con registros de la CPU: la frecuencia del sonido está totalmente a merced de la velocidad de la máquina en que se produce. Es por ello que el pitido de error que produce el teclado es a menudo distinto de unos ordenadores a otros, aunque tengan el mismo KEYB instalado. Otro gran inconveniente de este método es que las interrupciones, fundamentalmente la del temporizador, producen fuertes interferencias sobre el sonido. Por ello, es normal tenerlas inhibidas, con
EL HARDWARE DE APOYO AL MICROPROCESADOR
245
el consiguiente retraso de la hora. Por último, un tercer gran inconveniente es que la CPU está completamente dedicada a la producción de sonido, sin poder realizar otras tareas mientras tanto. Antes de comenzar a producir el sonido con este método hay que bajar la línea GATE del 8254, ya que cuando está en alto y se activa también el bit 1 del puerto E/S 61h, el temporizador es el encargado de producir el sonido (este es el segundo método, como veremos). Por tanto, es preciso poner primero a cero el bit 0 del mismo puerto (61h): CLI IN
otro_ciclo:
retardo:
; evitar posible INT 8, entre otras AL,61h
AND
AL,11111110b
JMP
SHORT $+2
; estado de espera para E/S
OUT
61h,AL
; bajar GATE del contador 2 del 8254
MOV
CX,100h ; 256 vueltas
PUSH
CX
IN
AL,61h
XOR
AL,2
JMP
SHORT $+2
OUT
61h,AL
MOV
CX,300
LOOP
retardo
POP
CX
LOOP
otro_ciclo
; invertir bit 1
; constante de retardo
STI
Control del altavoz por el temporizador. El otro método posible consiste en emplear el contador 2 del temporizador conectado al altavoz; así, enviando el período del sonido (1.193.180/frecuencia_en_Hz) a dicho contador (programado en modo 3), éste se encarga de generar el sonido. Esto permite obtener sonidos idénticos en todos los ordenadores. Existe el pequeño problema de que la duración del sonido ha de ser múltiplo de 1/18,2 segundos si se desea utilizar el reloj del sistema para determinarla (un bucle de retardo sería, una vez más, dependiente de la máquina) ya que el contador 2 está ahora ocupado en la producción de sonido y no se puede usar para temporizar (al menos, no sin hacer malabarismos). Alternativamente, se podría evaluar la velocidad de la CPU para ajustar las constantes de retardo o aumentar la velocidad de la interrupción periódica. Para emplear este sistema, primero se prepara el contador 2 para temporizar (poniendo a 1 el bit 0 del puerto 61h) y luego se conecta su salida al altavoz (poniendo a 1 el bit 1 del puerto 61h). Al final, conviene borrar ambos bits de nuevo. Ahora no es preciso inhibir las interrupciones para garantizar la calidad del sonido:
demora: LOOP
MOV
AL,10110110b
; contador 2, modo 3, operación 11b, datos binarios
OUT
43h,AL
; programar contador 2
MOV
AX,2711 ; 1.193.180 / 440 Hz (nota LA) = 2711
JMP
SHORT $+2
OUT
42h,AL
MOV
AL,AH
JMP
SHORT $+2
OUT
42h,AL
JMP
SHORT $+2
IN
AL,61h
OR
AL,00000011b
JMP
SHORT $+2
OUT
61h,AL
MOV
CX,0
demora
; frecuencia programada
; altavoz sonando ; esperar un cierto tiempo por el peor método
IN
AL,61h
AND
AL,11111100b
JMP
SHORT $+2
OUT
61h,AL
; altavoz callado
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Las frecuencias en Hz de las distintas notas musicales están oficialmente definidas y los músicos suelen tenerlas en cuenta a la hora de afinar los instrumentos. La escala cromática temperada, adoptada por la American Standards Asociation en 1936, establece el LA4 como nota de referencia en 440 Hz. En general, una vez conocidas las frecuencias de las notas de una octava, las de la octava siguiente o anterior se obtienen multiplicando y dividiendo por dos, respectivamente. La fórmula de abajo permite obtener las frecuencias de las notas asignándolas un número (a partir de 6 y hasta 88; el LA de 440 Hz es la nota 49) con una precisión razonable, máxime teniendo en cuenta que van a ir a parar al altavoz del PC. Tal curiosa relación se verifica debido a que la respuesta del oído humano es logarítmica, lo que ha permitido reducir a simples matemáticas el viejo saber milenario de los músicos. 41
frec =
43
46
48
50
53
55
58
60
62
─┬──▄▄▄─▄▄▄──┬──▄▄▄─▄▄▄─▄▄▄──┬──▄▄▄─▄▄▄──┬──▄▄▄─▄▄▄─▄▄▄──┬─ ... │ ███ ███ │ ███ ███ ███ │ ███ ███ │ ███ ███ ███ │ ... │ ███ ███ │ ███ ███ ███ │ ███ ███ │ ███ ███ ███ │ ... │ 40│ 42│ 44│ 45│ 47│ 49│ 51│ 52│ 54│ 56│ 57│ 59│ 61│ 63│ ... ─┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴─
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
12.4. - EL CONTROLADOR DE INTERRUPCIONES 8259. 12.4.1. - COMO Y POR QUE DE LAS INTERRUPCIONES. Los ordenadores se comunican con el exterior por medio de los dispositivos de entrada y salida. Estos dispositivos son normalmente lentos en comparación con la elevada velocidad de la unidad central. Un ejemplo típico puede ser el teclado: entre las pulsaciones de cada tecla hay un espacio de tiempo impredecible y dependiente del usuario. Una manera simple de gestionar los dispositivos de E/S consiste en comprobar continuamente si alguno de ellos tiene un dato disponible o lo está solicitando. Sin embargo, esto supone una importante pérdida de tiempo para el microprocesador, que mientras tanto podría estar haciendo otras cosas. En una máquina multitarea y/o multiusuario, resulta más interesante que los periféricos puedan interrumpir al microprocesador para solicitarle una operación de entrada o salida en el momento necesario, estando la CPU liberada de la misión de comprobar cuándo llega ese momento. Cuando se produce la interrupción, el microprocesador ejecuta la correspondiente rutina de servicio y después continúa con su tarea normal. Los compatibles PC poseen un hardware orientado por completo a la multitarea (otra cosa es que el 8086 y el DOS no la aprovechen) y la entrada/salida se gestiona casi por completo mediante interrupciones en todas las máquinas. Por ejemplo, en las operaciones de disco, cuando acaba la transferencia de datos se produce una interrupción de aviso y una rutina de la BIOS activa una variable que lo indica, en el segmento de memoria 40h. Las propias funciones de la BIOS para acceder al disco se limitan a chequear continuamente esa variable hasta que cambie, lo que significa un evidente desaprovechamiento de las posibilidades que la gestión por interrupciones pone a nuestra disposición. Las interrupciones añaden cierta complejidad al diseño del hardware: en principio, es necesario jerarquizarlas de alguna manera para decidir cuál se atiende en el caso de que se produzcan dos simultáneamente. También es importante el control de prioridad para el caso de que se produzca una interrupción mientras se está procesando otra: sólo se la atenderá si es de mayor prioridad. En este capítulo sólo consideraremos las interrupciones hardware, no las de software ni las excepciones del procesador. 12.4.2. - DESCRIPCIÓN DEL INTEGRADO 8259. Este circuito integrado está especialmente diseñado para controlar las interrupciones en sistemas basados en el 8080/8085 y en el 8086. Puede controlar hasta 8 interrupciones vectorizadas. Además, a un 8259 se le pueden conectar en cascada un máximo de 8 chips 8259 adicionales, lo que permite gestionar sistemas con hasta 64 interrupciones, como veremos. D0 ██▌ 11 ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▌ -CS ██▌ 1 ▌ -WR ██▌ 2 ▌ -RD ██▌ 3 ▌ D7 ██▌ 4 ▌ D6 ██▌ 5 ▌ D5 ██▌ 6 ▌ D4 ██▌ 7 ▌ D3 ██▌ 8 ▌ D2 ██▌ 9 ▌ D1 ██▌ 10 ▌
▐ 28 ▐██ Vcc ▐ 27 ▐██ A0 ▐ 26 ▐██ -INTA ▐
18 ▐██ IR0
▌
▐
CAS 0 ██▌ 12
17 ▐██ INT
▌
▐
CAS 1 ██▌ 13
16 ▐██ -SP/-EN
▌
▐
GND ██▌ 14 ▌
15 ▐██ CAS 2 '8259
▐
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
25 ▐██ IR7 ▐ 24 ▐██ IR6
El significado e interpretación de las señales se muestra a la derecha:
▐ 23 ▐██ IR5 ▐ 22 ▐██ IR4 ▐ 21 ▐██ IR3 ▐ 20 ▐██ IR2 ▐ 19 ▐██ IR1 ▐
-CS:Habilita la comunicación con la CPU. -WR:Permite al 8259 aceptar comandos de la CPU. -RD:Permite al 8259 dejar la información en el bus de datos. D7..D0:Bus de datos bidireccional, por el que se transmite la información de control/estado y el número de vector de interrupción. CAS0..CAS2:Líneas de cascada, actúan como salida en el 8259 maestro y como entrada en los 8259 esclavos, en un sistema con varios 8259 interconectados, constituyendo un bus local.
245
-SP/-EN:Pin de doble función: en el buffered mode del 8259 actuará como -EN, para habilitar los buffers del bus; en el modo normal indicará si el 8259 es maestro o esclavo (-SP). INT:Conectado a la patilla INT de la CPU para producir la interrupción cuando llegue el momento. IR0..IR7:Líneas asíncronas de petición de interrupción. Una petición de interrupción se ejecuta manteniendo IR en alto hasta que se recibe el reconocimiento (modo por flancos) o simplemente poniendo en alto la línea IR (modo por niveles). -INTA:Línea de reconocimiento de interrupción, por medio de esta línea se fuerza al 8259 a depositar en el bus la información del vector de interrupción. INTA es independiente de -CS. A0:En conjunción con -CS, -WR y -RD es empleada para enviar las palabras de comando al 8259 y para solicitar información al mismo. Suele ir conectada a la línea A0 de la CPU.
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
DESCRIPCIÓN FUNCIONAL El diagrama funcional del 8259, con la estructura interna de las diversas partes que lo componen, es el siguiente: ║ ║
INTA
INT Ω
│
│
║ ║ ┌───────────────┐ ╔═Ψ│
BUFFER DEL
║ ║
│ Χ═════Ψ ║ ║
║
│
BUS DE DATOS │
║
└───────────────┘
┌──────────Ϊ─────────────────────┴────────────┐ │
┌────║ ║─────┤
LÓGICA
│
DE CONTROL
│
│
║ ║
║
│
║ ║
D0..D7 Χ╝
│
║ ╚═══════════════════════════════════════════════════════════
│
║ ╔══════════Ω═══════════════════════════════════Ω════════════
│
║ ║
║
│
│
│
║
│
║ ║
║
│
│
│
║
│
║ ║
║
│
│
│
║
│
║ ║
┌─────╨───Ϊ─┐
┌─────Ϊ─────┐
┌──┴──╨─────┐
│
│
│
│
│
│
LÓGICA
│
│
│
│
DE
│
│
┌───────────────┐
└────────┬─────────────Ω──────────────Ω───────┘ │
│
│
-RD ──Ψ│
LÓGICA DE
│
│
║ ║
│
-WR ──Ψ│
LECTURA Y
│Χ───┤
║ ║
│
A0 ──Ψ│
ESCRITURA
│
│
║ ║
│
└───────Ω───────┘
│
║ ║
│
│
║ ║
│
│
║ ║
│ Register) │
│ PRIORIDAD │
│ Register) │Χ── IR5
│
║ ║
│
│
│
│
│
│Χ── IR6
│
║ ║
│
│
│
│
│
│Χ── IR7
│
║ ║
└──────Ω────┘
-CS ───────────┘
┌───────────────┐ CAS 0 Χ──Ψ│
BUFFER DE
│
│
║ ║
CAS 1 Χ──Ψ│
CASCADA Y
│Χ───┘
║ ║
CAS 2 Χ──Ψ│
COMPARADOR
│
║ ║Χ═══Ψ│
└───────Ω───────┘ -SP/-EN Χ──────────┘
I.S.R. (In Service
│
│Χ═══Ψ│ │
│
GESTIÓN DE
│Χ── IR0 I.R.R.
│Χ── IR1 │Χ── IR2
│Χ════│(Interrupt │Χ── IR3 │
└─────Ω─────┘
│
Request
│Χ── IR4
└────Ω──────┘
│
│
┌─────┴────────────────┴────────────────┴─────┐ IMR
│
║ ║
│
║ ║
└─────────────────────────────────────────────┘
(Interrupt Mask Register)
│
║ ║ bus interno
Los principales registros internos del 8259 son el IRR (Interrupt Request Register) y el ISR (In Service Register). El IRR almacena todas las peticiones de interrupción pendientes; el ISR almacena todas las interrupciones que están siendo atendidas en un momento dado. La lógica de gestión de prioridad determina qué interrupción, de las solicitadas en el IRR, debe ser atendida primero: cuando lleguen las señales INTA dicha interrupción será la primera procesada y su bit correspondiente se activará en el ISR. El buffer del bus de datos conecta el 8259 con el bus de datos de la placa principal del ordenador: su diseño en 3 estados permite desconectarlo cuando sea necesario; a través de este bus circulan las palabras de control y la información de estado. La lógica de lectura y escritura acepta los comandos que envía la CPU: aquí hay registros para almacenar las palabras de inicialización y operación que envía el procesador; también sirve para transferir el estado del 8259 hacia el bus de datos. El buffer de cascada/comparador almacena y compara las identificaciones de todos los 8259 que posea el sistema: el 8259 maestro envía la identificación del 8259 esclavo en las líneas CAS, los 8259 esclavos la leen y el implicado en la operación coloca en el bus de datos la dirección (vector) de la rutina que atenderá la interrupción en los 2 próximos (o el próximo) ciclos INTA. FUNCIONAMIENTO DEL 8259 El funcionamiento del 8259 varía ligeramente en función del sistema en que esté instalado, según sea este un 8086 o un 8080/8085. Veremos primero el caso del 8086: 1)Una o más líneas IR son activadas por los periféricos, lo que pone a 1 el correspondiente bit del IRR. 2)El 8259 evalúa la prioridad de estas interrupciones y solicita la interrupción a la CPU (línea INT) si es necesario.
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
3)Cuando la CPU reconoce la interrupción, envía la señal -INTA. 4)Nada más recibida la señal -INTA de la CPU, el 8259 activa el bit correspondiente a la interrupción de mayor prioridad (la que va a ser procesada) en el ISR y lo borra en el IRR. En este ciclo, el 8259 aún no controla el bus de datos. 5)Cuando la CPU envía un segundo ciclo -INTA, el 8259 deposita en el bus de datos un valor de 8 bits que indica el número de vector de interrupción del 8086, para que la CPU lo pueda leer. 6)En el modo AEOI del 8259, el bit de la interrupción en el ISR es borrado nada más acabar el segundo pulso INTA; en caso contrario, ese bit permanece activo hasta que la CPU envíe el comando EOI al final de la rutina que trata la interrupción (caso más normal). En el caso de sistemas basados en el 8080/8085, el funcionamiento es idéntico hasta el punto (3), pero a continuación sucede lo siguiente: 4)Nada más recibida la señal -INTA de la CPU, el 8259 activa el bit correspondiente a la interrupción de mayor prioridad (la que va a ser procesada) en el ISR y lo borra en el IRR. En este ciclo, el 8259 deposita en el bus de datos el valor 11001101b, correspondiente al código de operación de la instrucción CALL del 8080/85. 5)Esta instrucción CALL provoca que la CPU envíe dos pulsos -INTA. 6)El 8259 utiliza estos dos pulsos -INTA para depositar en el bus de datos, sucesivamente, la parte baja y alta de la dirección de memoria del ordenador de la rutina de servicio de la interrupción (16 bits). 7)Esto completa la instrucción CALL de 3 bytes. En el modo AEOI del 8259, el bit de la interrupción en el ISR es borrado nada más acabar el tercer pulso -INTA; en caso contrario, ese bit permanece activo hasta que la CPU envíe el comando EOI al final de la rutina que trata la interrupción. Si en el paso (4), con ambos tipos de microprocesador, no está presente la petición de interrupción (por ejemplo, porque ha sido excesivamente corta) el 8259 envía una interrupción de nivel 7 (si hubiera un 8259 conectado en IR7, las líneas CAS permanecerían inactivas y la dirección de la rutina de servicio de interrupción sería suministrada por el 8259 maestro). PROGRAMACIÓN DEL 8259 El 8259 acepta dos tipos de comandos generados por la CPU: los ICW (Inicialization Command Word) que inicializan el 8259, y los OCW (Operation Command Word) que permiten programar la modalidad de funcionamiento. Antes de que los 8259 de un sistema comiencen a trabajar deben recibir una secuencia de ICW que los inicialice. Los ICW y OCW constan de secuencias de 2 a 4 comandos consecutivos que el 8259 espera recibir secuencialmente, unos tras otros, a través del bus de datos, según sea necesario (el propio 8259 se encarga de contarlos midiendo los pulsos de la línea -WR). Los OCW pueden ser enviados en cualquier momento, una vez realizada la inicialización. La comunicación con el 8259 emplea las líneas -WR y -RW, así como A0. El hecho de que exista una sola línea de direcciones implica que el 8259 sólo ocupa dos direcciones de puerto de E/S en el espacio de entrada y salida del ordenador. ICWS (Inicialization Command Words). ICW1:Cuando un comando es enviado con A0=0 y D4=1, el 8259 lo interpreta como la primera palabra de la inicialización (ICW1) e inicia dicha secuencia de inicialización, lo que implica lo siguiente: - Se resetea el circuito sensible a los niveles, lo que quiere decir que hasta nueva orden las líneas IR serán sensibles por flancos de transición bajo-alto. - Se limpia el IMR. - A la línea IR7 se le asigna un nivel de prioridad 7. - Se desactiva el Special Mask Mode. Se queda listo para devolver IRR en la próxima lectura OCW3. - Si IC4 (bit D0) es 0, todas las funciones seleccionadas en ICW4 serán puestas a 0 (non buffered mode, no AEOI, sistema 8080/85) e ICW4 no será necesaria.
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
A0 D7 D6 D5 D4 D3 D2 D1 D0 ┌─────────┼─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │ │
│ 0
│
│
│ A7
│
│
│ A6
│
│
│
A5
│
│
│
1
│
│
│ LTIM
│
│
│
ADI
│
│
│ SNGL
│
│
│ │
IC4
│
│
└─────────┼────┬────┴────┬────┴────┬────┴─────────┼────┬────┴────┬────┴────┬────┴────┬────┘ └─────────┼─────────┘
│
Ϊ
│
Ϊ
│
"Call Address
│
a 0 si ICW4 innecesaria
dirección del vector de interrupción,
│
interval":
│
líneas A7..A5 (sólo 8080/85)
│
1 - 4 bytes
│
│
0 - 8 bytes
│
Ϊ
Ϊ
Ϊ
1 - IR por niveles
1 modo single
0 - IR por flancos
0 en cascada
Notas:Si SNGL es 1 significa que el 8259 es único en el sistema y no será enviada ICW3. Si IC4 es 0, tampoco será enviada ICW4. En el 8080/85, las diversas interrupciones generan CALL's a 8 direcciones adyacentes separadas 4 u 8 bytes (según indique ADI): para componer la dirección, el 8259 inserta A0..A4 (o A0..A5) convenientemente, según la interrupción que se trate. En el 8086, A7..A5 y ADI son ignoradas. ICW2:Se envía con A0=1, para diferenciarlo de ICW0 (hacer OUT a la siguiente dirección de puerto). A0 D7 D6 D5 D4 D3 D2 D1 D0 ┌─────────┼─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │ │
│ 1
│
A15
│ │
│
A14
│ ó T7
│
│
A13
│ ó T6
│
A12
│
│
ó T5
│
│
A11
│ ó T4
│
│ │
ó T3
│ A10
│
│
│ A9
│
│
│ │
A8
│
│
└─────────┼─────────┴─────────┴─────────┴─────────┼─────────┴─────────┴─────────┴─────────┘
Notas:En el 8080/85, A15..A8 completan la dirección de la rutina de servicio; en el 8086, T7..T3 determinan los cinco bits más significativos del número de vector de interrupción a invocar (los 3 bajos los suministra el 8259 según la interrupción que se trate). ICW3:Se envía sólo en el caso de que haya más de un 8259 en el sistema (bit SNGL de ICW1 a cero), en caso contrario en su lugar se enviaría ICW4 (si procede). Formato de ICW3 a enviar a un 8259 maestro: A0 D7 D6 D5 D4 D3 D2 D1 D0 ┌─────────┼─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ │ │ │
│ 1
│ │
│ S7
│ │
│ S6
│
│
S5
│
│
│ S4
│
│ │
│ S3
│
│ S2
│
│
│ S1
│
│
│ │
S0
│
│
└─────────┼────┬────┴────┬────┴────┬────┴────┬────┴────┬────┴────┬────┴────┬────┴────┬────┘ └─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┤ Ϊ 0
-
La
línea
IR
correspondiente
no
tiene
conectado
un
8259
esclavo 1 - La línea IR correspondiente va conectada a un 8259 esclavo
Formato de ICW3 a enviar a un 8259 esclavo para que memorice de qué línea IR del maestro cuelga: A0 D7 D6 D5 D4 D3 D2 D1 D0 ┌─────────┼─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
│ │
│ 1
│
│
│ 0
│
│
│ 0
│
│
│ 0
│
│
│ 0
│
│
│ 0
│
│
│ ID2
│
│
│ ID1
│
│
│ ID0
│
│ │
└─────────┼─────────┴─────────┴─────────┴─────────┼─────────┴────┬────┴────┬────┴────┬────┘ ├─────────┴─────────┘ Ϊ ID (identificación) del esclavo (0..7)
ICW4:Se envía sólo si IC4=1 en ICW1, con objeto de colocar el 8259 en un modo de operación distinto del establecido por defecto (que equivale a poner a cero todos los bits de ICW4). A0 D7 D6 D5 D4 D3 D2 D1 D0 ┌─────────┼─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │ │
│ 1
│
│
│ 0
│
│
│ 0
│
│
│ 0
│
│
│ SFNM
│
│
│ BUF
│
│
│ M/S
│
│
│ AEOI
│
│
│ µPM
│
│ │
└─────────┼─────────┴─────────┴─────────┴────┬────┼────┬────┴────┬────┴────┬────┴────┬────┘ ┌───────────────────┘
│
│
│
Ϊ
┌──────┘
│
│
1 - modo 8086
1 Special Fully Nested Mode
│ ┌──────────────┘
│
0 - " 8080/85
0 Not Special Fully Nested Mode
│ │
│
Ϊ
└───────┐
0 X
non buffered mode
1 0
buffered mode esclavo
│
1 1
buffered mode maestro
Ϊ 1 - Auto EOI 0 - EOI normal
Notas:El Special Fully Nested Mode, el buffered mode y la modalidad AEOI serán explicadas más tarde. Nótese que con el 8086 es obligatorio enviar ICW4 para seleccionar esta CPU. OCWS (Operation Command Words). Una vez inicializado, el 8259 está listo para procesar las interrupciones que se produzcan. Sin embargo, durante su funcionamiento normal está capacitado para recibir comandos de control por parte de la CPU. OCW1: A0 D7 D6 D5 D4 D3 D2 D1 D0 ┌─────────┼─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │ │
│ 1
│
│
│ M7
│
│
│ M6
│
│
│ M5
│
│
│ M4
│
│
│ M3
│
│
│ M2
│
│
│ M1
│
│
│ M0
│
│ │
└─────────┼─────────┴─────────┴─────────┴─────────┼─────────┴─────────┴─────────┴─────────┘
Este comando activa y borra bits en el IMR (Interrupt Mask Register). Los bits M0..M7 de OCW1 se corresponden con sus correspondientes bits del IMR. Un bit a 1 significa interrupción enmascarada (inhibida) y a 0, interrupción habilitada. OCW2: A0 D7 D6 D5 D4 D3 D2 D1 D0 ┌─────────┼─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │ │ │
│ 0
│
│ R
│
│
│ SL
│
│
│ EOI
│
│ │
│ 0
│ │
│ 0
│ │
│ L2
│
│ L1
│
│
│ L0
│
│ │
└─────────┼────┬────┴────┬────┴────┬────┴─────────┼─────────┴────┬────┴────┬────┴────┬────┘ │
│
│
└─────────┼─────────┘
│ ┌───────┘
│
Ϊ
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
│ │ ┌───────────────┘
Nivel de IR sobre el que actuar
│ │ │ 0 0 1
EOI no específico ─────┬──Ψ
0 1 1
(*) EOI específico ────┘
Fin de interrupción
1 0 1
Rotar en comando EOI no específico ────┐
1 0 0
Activar rotación en modo AEOI
0 0 0
Desactivar rotación en modo AEOI
1 1 1
(#) EOI específico asignando prioridad ────┬───Ψ Rotación específica
1 1 0
(#) Comando para asignar prioridad ────────┘
0 1 0
No operación
────────┼──Ψ Rotación automática ─────┘
(*) Usados L0..L2 (#) en L0..L2 se indica la línea IR que recibirá la menor prioridad (en IR+1 queda la mayor).
OCW3: A0 D7 D6 D5 D4 D3 D2 D1 D0 ┌─────────┼─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │ │
│ 0
│
│ │
│ 0
│
│ ESMM
│
│
│ SMM
│
│
│ 0
│
│
│
│
1
│
│ P
│
│
│ RR
│
│
│ RIS
│
│ │
└─────────┼─────────┴────┬────┴────┬────┴─────────┼─────────┴────┬────┴────┬────┴────┬────┘ │
│
┌───────────────────┘
│
│
┌───────────────────┘
│
│
┌─────────────────────┘
│
│ ┌───────────────────────────┘
│
│ ┌─────────────────────────────┘
│ │
Modo de máscara especial:
│
│ │
│ │
-------------------------
│
│ │
0 X
-
No actuar
│
0 X
-
No actuar
1 0
-
Inhibir Special Mask Mode
│
1 0
-
Leer IRR en próximo pulso -RD
1 1
-
Activar Special Mask Mode
│
1 1
-
Leer ISR en próximo pulso -RD
Comando de lectura de registro: -------------------------------
Ϊ 1 - Comando POLL 0 - No es comando POLL
TRABAJANDO CON EL 8259 En las ICW y, sobre todo, en las OCW, se han introducido un aluvión de elementos nuevos que serán explicados a continuación. Fully Nested Mode. Por defecto, el 8259 opera en esta modalidad (modo de anidamiento completo), a menos que se le programe de otra manera. En este modo las interrupciones quedan ordenadas, por prioridades, de 0 (máxima) a 7 (mínima). Cuando se produce un reconocimiento de interrupción por parte de la CPU, el 8259 evalúa cuál es la interrupción pendiente de mayor prioridad, coloca su número de vector en el bus y activa su bit correspondiente en el ISR. Este bit permanece activo hasta que el 8259 recibe el comando EOI (situación más normal); sin embargo, en el modo AEOI, ese bit se bajaría inmediatamente después del último -INTA. Mientras el bit del ISR esté activo, todas las interrupciones de igual o menor prioridad que lleguen permanecen inhibidas; sin embargo, las de mayor prioridad podrán interrumpir. En el caso del 8086, cuando comienza el tratamiento de la interrupción, un bit del registro de estado de la CPU mantiene inhibidas todas las interrupciones: lo normal es que el programa de control comience con STI para permitir que el 8086 envíe nuevas señales INTA al 8259, así el 8259 podrá enviar las interrupciones de mayor prioridad que le lleguen. Tras la secuencia de inicialización, las interrupciones quedan ordenadas de mayor (IR0) a menor prioridad (IR7), aunque este orden puede modificarse en la modalidad de prioridad rotatoria o con el comando de asignación de prioridad. Nótese que cuando se utiliza el modo AEOI o el Special Mask Mode no se respeta el modo Fully Nested Mode (debido a que una interrupción de menor prioridad podría interrumpir a una rutina que gestiona otra de mayor prioridad).
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Special Fully Nested Mode. Se emplea en sistemas que tienen varios 8259 conectados. Sólo el 8259 maestro es programado en este modo, lo que implica las siguientes diferencias respecto al Fully Nested Mode normal: - Cuando se atiende una interrupción de un 8259 esclavo, si viene otra de mayor prioridad de ese mismo 8259 esclavo, se provoca una interrupción al maestro (normalmente, el 8259 esclavo estaría enmascarado mientras se procesa una de sus interrupciones). - Cuando acaba la rutina de servicio de interrupción, hay que enviar un EOI no-específico al 8259 esclavo; además hay que leer a continuación su ISR y comprobar si es cero: en ese caso, hay que enviar además otro EOI al 8259 maestro (si no es cero significa que aún hay interrupciones en proceso en el 8259 esclavo). Modos de EOI. El EOI (End Of Interrupt) sirve para bajar el bit del ISR que representa la interrupción que está siendo procesada. El EOI puede producirse automáticamente (AEOI) al final de la última señal INTA que envía la CPU al 8259 para una interrupción dada (tercer ciclo INTA en el 8080/85 y segundo en el 8086); sin embargo, la mayoría de los sistemas requieren una gestión de prioridades en las interrupciones, lo que significa que es más conveniente que EOI lo envíe el propio procesador al 8259, a través de OCW2, cuando acabe la rutina de gestión de interrupción, para evitar que mientras se gestiona esa interrupción se produzcan otras de igual o menor prioridad. En un sistema con varios 8259, el EOI debe ser enviado no sólo al 8259 esclavo implicado sino también al maestro. Hay dos modalidades de EOI: la específica y la no-específica. En el EOI no específico, el 8259 limpia el bit más significativo que esté activo en el ISR, que se supone que es el correspondiente a la última interrupción producida (la de mayor prioridad y que está siendo procesada). Esto es suficiente para un sistema donde se respeta el Fully Nested Mode. En el caso en que no fuera así, el 8259 es incapaz de determinar cuál fue el último nivel de interrupción procesado, por lo que la rutina que gestiona la interrupción debe enviar un EOI específico al 8259 indicándole qué bit hay que borrar en el ISR. Rotación de prioridades. Hay sistemas en que varios periféricos tienen el mismo nivel de prioridad, en los que no interesa mantener un orden de prioridades en las líneas IR. En condiciones normales, nada más atender una interrupción de un periférico, podría venir otra que también se atendería, mientras los demás periféricos se cruzarían de brazos. La solución consiste en asignar el menor nivel de prioridad a la interrupción recién atendida para permitir que las demás pendientes se procesen también. Para ello se envía un EOI que rote las prioridades: si, por ejemplo, se había procesado una IR3, IR3 pasará al menor nivel de prioridad e IR4 al mayor, quedando las prioridades ordenadas (de mayor a menor): IR4, IR5, IR6, IR7, IR0, IR1, IR2, IR3. Existe también una rotación específica de prioridades, a través de OCW2, que puede realizarse en un comando EOI o independientemente del mismo (comando para asignar prioridad). Special Mask Mode. Hay ocasiones en las que mientras se ejecuta una rutina de servicio de interrupción es necesario permitir que se produzcan ciertas interrupciones de menor prioridad en algunos momentos, o prohibirlo en otros, sin ser quizá interesante enviar el EOI antes de tiempo. Esto implica alterar la estructura normal de prioridades. La manera de realizar esto es activando el Special Mask Mode a través de OCW3 durante la rutina de servicio de interrupción (es más que conveniente inhibirlo de nuevo al final). Una vez activado este modo, el IMR indica qué interrupciones están permitidas (bit a 0) y cuáles inhibidas (bit a 1). Por ello, suele ser conveniente activar el bit del IMR correspondiente a la IR en servicio (para evitar que se produzca de nuevo cuando aún no ha sido procesada). Al final hay que enviar un EOI específico, ya que este modo de trabajo altera el Fully Nested Mode habitual. Comando POLL. En esta modalidad poco habitual, habilitada a través de OCW3, no se emplea la salida INT del 8259 o bien el microprocesador trabaja con las interrupciones inhibidas. El servicio a los periféricos es realizado por software utilizando el comando POLL. Una vez enviado el comando POLL, el 8259 interpreta la próxima lectura que se realice como un reconocimiento de interrupción, actualizando el ISR y consultando el nivel de prioridad. Durante esa lectura, la CPU obtiene en el bus de datos la palabra POLL que indica (en el bit 7) si hay alguna interrupción pendiente y, en ese caso, cuál es la de mayor prioridad (bits 0-2).
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
Lectura de información del 8259. El IMR puede ser leído a través de OCW0; para leer el contenido del IRR y el ISR hay que emplear OCW3. Para estos dos últimos registros hay que enviar una OCW3 que elija el IRR o el ISR; a continuación se puede leer el bus de datos (A0=0) sin necesidad de enviar más OCW3 (el 8259 es capaz de recordar si tiene que leer el IRR o el ISR). Esto último no es así, evidentemente, en el caso de utilizar el comando POLL (tras enviarlo, la próxima lectura se interpreta como un INTA). Tras inicializarse, el 8259 queda preparado por defecto para devolver IRR a la primera lectura. Buffered Mode. Al emplear el 8259 en grandes sistemas, donde se requieren buffers en los buses de datos, si se va a emplear el modo cascada existe el problema de la habilitación de los buffers. Cuando se programa el modo buffer, la patilla -SP/-EN del 8259 actúa automáticamente como señal de habilitación del los buffers cada vez que se deposita algo en el bus de datos. Si se programa de esta manera el 8259 (bit BUF de ICW4) será preciso distinguir por software si se trata de un 8259 maestro o esclavo (bit M/S de ICW4). 12.4.3. - EL 8259 DENTRO DEL ORDENADOR. Los PC/XT vienen equipados con un 8259 conectado a la dirección base E/S 20h; este controlador de interrupciones es accedido, por tanto, por los puertos 20h (A0=0) y 21h (A0=1). En los AT y máquinas superiores, adicionalmente, existe un segundo 8259 conectado en cascada a la línea IR2 del primero. Este segundo controlador es accedido a través de los puertos 0A0h y 0A1h. La BIOS del ordenador, al arrancar la máquina, coloca la base de interrupciones del primer controlador en 8, lo que significa que las respectivas IR0..IR7 están ligadas a los vectores de interrupción 8..15; el segundo 8259 de los AT genera las interrupciones comprendidas entre 70h y 77h. La asignación de líneas IR para los diversos periféricos del ordenador es la siguiente (por orden de prioridad): IRQ 0
Temporizador
IRQ 1
Teclado
(INT 09h)
(INT 08h)
IRQ 2
En los PC/XT: canal E/S IRQ 8 Reloj de tiempo real
(INT 0Ah) (INT 70h) ─┐ (INT 71h)
│
IRQ 10 Reservado
(INT 72h)
│
IRQ 11 Reservado
(INT 73h)
│
IRQ 12 Reservado
(INT 74h)
│Ψ Sólo AT y PS/2
IRQ 9
Simulación de IRQ2
IRQ 13 Coprocesador aritmético (INT 75h)
│ │
IRQ 14 Controlador de disco duro
(INT 76h)
IRQ 15 Reservado
(INT 77h) ─┘
IRQ 3
COM2
(INT 0Bh)
IRQ 4
COM1
(INT 0Ch)
IRQ 5
Disco duro PC/XT (LPT2 en el AT)
(INT 0Dh)
IRQ 6
Controlador de disquetes
(INT 0Eh)
IRQ 7
LPT1
(INT 0Fh)
En los AT, la línea IR2 del 8259 maestro es empleada para colgar de ella el segundo 8259 esclavo. Como la línea IR2 está en el slot de expansión de 8 bits, por razones de compatibilidad los AT tienen conectado en su lugar la IR9 que simula la IR2 original. Cuando se produce una IR9 debido a un periférico de XT que pretendía generar una IR2, el AT ejecuta una rutina de servicio en INT 71h que salta simplemente a la INT 0Ah (tras enviar un EOI al 8259 esclavo). La colocación de IRQ0-IRQ7 en el rango INT 8-INT 15 fue bastante torpe por parte de IBM, al saltarse la especificación de Intel que reserva las primeras 32 interrupciones para el procesador. En modo protegido, algunas de esas excepciones es estrictamente necesario controlarlas. Por ello, los sistemas operativos que trabajan en modo extendido y ciertos extensores del DOS (como las versiones 3.x de WINDOWS) se ven obligados a mover de sitio estas interrupciones. En concreto, WINDOWS 3.x las coloca en INT 50h-INT 57h (por software, las máquinas virtuales 8086 emulan las correspondientes INT 8-INT 15). Además, en el modo protegido del 286/386 (o el virtual-86 del 386) la tradicional tabla de vectores de interrupción es sustituida por
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
otra de descriptores, aunque el funcionamiento global es similar. La interrupción no enmascarable del 80x86 no está controlada por el 8259: es generada por la circuitería que controla la memoria si se detecta un error de paridad. La interrupción no enmascarable puede ser enmascarada en los ordenadores compatibles gracias a la circuitería de apoyo al procesador, aunque no es frecuente; en los AT el bit 7 del puerto 70h controla su habilitación (si es cero, la NMI está habilitada) sin embargo también se podría inhibir el control de paridad directamente (activando los bits 2 y 3 de la dirección E/S 61h, respetando el resto de los bits de ese puerto por medio de una lectura previa). En los PC/XT, es el puerto 0A0h el que controla la habilitación de la NMI, también con el bit 7 (con la diferencia de que debe estar a cero para inhibirla). Durante la inicialización del ordenador, la BIOS envía sucesivamente al 8259 las palabras ICW1 a ICW4 de la siguiente manera (listado extraído directamente de la BIOS): ; Inicialización del 8259 maestro (XT/AT) MOV
AL,10001b
; funcionamiento por flancos, cascada, ICW4 necesaria
OUT
20h,AL
; enviar ICW1
JMP
SHORT $+2
; estado de espera para E/S
MOV
AL,8
; base de interrupciones en INT 8
OUT
21h,AL
; enviar ICW2
JMP
SHORT $+2
MOV
AL,4
; hay un esclavo en IR2 (S2=1)
OUT
21h,AL
; enviar ICW3
¡poner 0 en PC/XT!
JMP
SHORT $+2
MOV
AL,1
; modo 8086, EOI normal, not buffered mode
OUT
21h,AL
; enviar ICW4: completada la inicialización del 8259-1
JMP
SHORT $+2
MOV
AL,255
OUT
21h,AL
; enmascarar todas las interrupciones
JMP
SHORT $+2
; Inicialización del 8259 esclavo (sólo AT)
MOV
AL,10001b
; funcionamiento por flancos, cascada, ICW4 necesaria
OUT
0A0h,AL
; enviar ICW1
JMP
SHORT $+2
MOV
AL,70h
; base de interrupciones en INT 70h
OUT
0A1h,AL
; enviar ICW2
JMP
SHORT $+2
MOV
AL,2
; es el esclavo conectado a IR2
OUT
0A1h,AL
; enviar ICW3
JMP
SHORT $+2
MOV
AL,1
; modo 8086, EOI normal, not buffered mode
OUT
0A1h,AL
; enviar ICW4: completada la inicialización del 8259-2
JMP
SHORT $+2
MOV
AL,255
OUT
0A1h,AL
; enmascarar todas las interrupciones
Como se puede observar, la rutina de arriba enmascara todas las interrupciones a través del IMR. El objetivo de esta medida es evitar que se produzcan interrupciones antes de desviar los correspondientes vectores, pudiendo incluso mientras tanto estar habilitadas las interrupciones con STI. Cuando se produce una interrupción de la CPU (bien por software o por hardware), el indicador de interrupciones del registro de estado del 8086 se activa para inhibir otra posible interrupción mientras se procesa esa (la instrucción IRET recuperará los flags del programa principal devolviendo las interrupciones a su estado previo). Lo normal suele ser que las rutinas que gestionan una interrupción comiencen por un STI con objeto de permitir la generación de otras interrupciones; las interrupciones sólo deben estar inhibidas en brevísimos momentos críticos. Sin embargo, cuando se procesa una interrupción hardware, el registro de interrupciones activas (ISR) indica qué interrupción en concreto está siendo procesada; si en ese momento llega otra
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
interrupción hardware de menor o igual prioridad le será denegada la petición, si es de mayor prioridad le será concedida (si la rutina comenzaba por STI). Cuando acaba de procesarse la interrupción hardware, la instrucción IRET no le dice nada al 8259, por lo que el programador debe preocuparse de borrar el ISR antes de acabar. Si, por ejemplo, se gestiona la interrupción del temporizador sin limpiar al final el ISR, a partir de ese momento quedarán bloqueados el teclado, los discos ... Conviene aquí señalar que una rutina puede apoyarse en una interrupción hardware sin necesidad de reprogramarla por completo. Ejemplo: STI PUSH
AX
IN
AL,puerto_teclado
CALL
anterior_int9
; ;
procesar tecla
; POP
Al producirse la INT 9 se lee el código de rastreo de la tecla y luego se llama a la rutina que gestionaba con anterioridad a ésta la INT 9: ella se encargará de limpiar el ISR que, por tanto, no es tarea de nuestra rutina. Si hubiera que limpiar el ISR, bastaría con un EOI no específico (OCW2: enviar un valor 20h al puerto 20h para el 8259-1 y al puerto 0A0h para el 8259-2; en las IRQ8-IRQ15 hay que enviar el EOI a ambos controladores de interrupción).
AX
IRET
Aviso:
Aunque el funcionamiento del 8259 es suficientemente lógico como para pasar casi inadvertido, hay veces en que hay que tenerlo en cuenta. Por ejemplo, al utilizar el servicio 86h de la INT 15h del AT (con objeto de hacer retardos) desde una interrupción hardware comprendida entre IRQ 0 e IRQ 7, conviene limpiar el ISR antes de llamar: no basta con hacerlo al final de la rutina. La causa es que la BIOS utiliza las interrupciones asociadas al reloj de tiempo real para hacer el retardo, y en algunas máquinas es poco precavido y no limpia el ISR al principio, lo que deja totalmente bloqueado el ordenador.
12.4.4. - EJEMPLO: CAMBIO DE LA BASE DE LAS INTERRUPCIONES. La siguiente utilidad reprograma el 8259 maestro para desviar las INT 8-INT 15 a los nuevos vectores INT 50h-INT 57h (que invocan a los originales, para que el sistema siga funcionando con normalidad). Esta nueva ubicación no ha sido elegida por capricho, y es la misma que emplea WINDOWS 3.x. La razón es que el 386 trabaja normalmente en modo virtual-86 bajo MS-DOS 5.0; cuando se produce una interrupción se ejecuta una rutina en modo protegido. El EMM386 del MS-DOS 5.0 no está preparado para soportar las IRQ0-IRQ7 en otra localización que no sea la tradicional INT 8-INT 15 ó en su defecto INT 50h-INT 57h (por compatibilidad con WINDOWS). Con el QEMM386 o, simplemente, sin controlador de memoria expandida instalado, no habría problemas y se podría elegir otro lugar distinto. Por cierto: si se entra y se sale de WINDOWS, la nueva localización establecida, ya sea en 50h o en otro sitio, deja de estar vigente: esto significa que WINDOWS reprograma la interrupción base al volver al DOS. Personalmente he comprobado que aunque IRQDEMO fuera más elegante (empleando funciones de la especificación VCPI), nuestro querido WINDOWS no lo sería: ¡para qué molestarse!. Sin embargo, IRQDEMO sí se toma la molestia de comprobar si la máquina es un XT o un AT para enviar correctamente la ICW3 del 8259. IRET
; ******************************************************************** ; *
IRQDEMO.ASM
-
Utilidad residente de demostración, que desvía *
; *
las interrupciones hardware INT 8-INT 15 hacia *
; *
los vectores INT 50h a INT 57h.
*
irq2:
10
IRET irq3:
INT
11
IRET
; ******************************************************************** irq4: irqdemo
INT
INT
12
IRET
SEGMENT ASSUME CS:irqdemo, DS:irqdemo
irq5:
INT
13
IRET ORG
irq6:
100h
INT
14
IRET
inicio: JMP
irq7:
main
INT
15
IRET ; ------------ Area residente tam_resid irq0:
INT
8
IRET irq1:
INT
; podría aprovechar también 9
EQU
($-OFFSET inicio+256+15)/16
; simular IRQ's normales (se
; para hacer algo más útil).
; ------------ Código de instalación
245
main
otra_int:
main
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
PUSHF
PROC
POP
AX
AND
AX,0F000h
AX
CMP
AX,0F000h
PUSH
BX
MOV
AX,1
MOV
AH,25h
JNE
es_AT
MOV
DX,[BX]
DEC
AX
INT
21h
POP
BX
ADD
BX,2
POP INC CMP
AL,58h
JB
otra_int
CALL
es_AT?
MOV
BL,4
MUL
BL
MOV
BL,AL
CALL
inic_8259
LEA
DX,texto_txt
MOV
AH,9
INT
21h
MOV
ES,ES:[2Ch]
MOV
AH,49h
INT
21h
MOV
AH,31h
MOV
DX,tam_resid
INT
21h
LEA
BX,tabla_ints
MOV
AL,50h
PUSH
; nueva base para IRQ's 0-7
RET
es_AT?
ENDP
AX
tabla_ints
DW
irq0, irq1, irq2, irq3, irq4, irq5, irq6, irq7
AL
texto_txt
DB
13,10,"Las interrupciones 8-15 son ahora 50-57h."
DB
13,10,"$"
irqdemo
PROC
; BL = 4 en AT y 0 en PC/XT
; mensaje de instalación
; liberar entorno
; terminar residente
; Inicialización 8259 maestro
MOV
AL,0FFh
OUT
21h,AL
JMP
SHORT $+2
MOV
AL,10001b
; flancos, maestro, sí ICW4
OUT
20h,AL
; enviar ICW1
JMP
SHORT $+2
; estado de espera E/S
MOV
AL,50h
; base interrupciones INT 50h
OUT
21h,AL
; enviar ICW2
JMP
SHORT $+2
MOV
AL,BL
; 4 en AT y 0 en PC/XT
OUT
21h,AL
; enviar ICW3
JMP
SHORT $+2
MOV
AL,1
; modo 8086, EOI normal
OUT
21h,AL
; enviar ICW4
JMP
SHORT $+2
MOV
AL,0
OUT
21h,AL
; enmascarar todas las IRQ
; permitir todas las IRQ
RET ENDP
es_AT?
PROC
; comprobar si es XT ó AT
PUSHF POP
AX
AND
AX,0FFFh
PUSH
AX
POPF
ENDS END
ENDP
inic_8259
; indicar PC/XT
es_AT:
; desviar INT 50h-57h
; ------------ Subrutinas de apoyo a la instalación.
inic_8259
; indicar AT
inicio
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
12.5 - EL CHIP DMA 8237. 12.5.1 - EL ACCESO DIRECTO A MEMORIA. El acceso directo a memoria es una técnica de diseño del hardware que permite a los periféricos conectados a un sistema realizar transferencias sobre la memoria sin la intervención del procesador. De esta manera, las lentas operaciones de entrada y salida de bloques de datos, se pueden realizar en la sombra mientras la CPU se dedica a otras tareas más útiles. Como la memoria del ordenador sólo puede ser accedida a un tiempo por una fuente, en el momento en que el DMA realiza las transferencias el microprocesador se desconecta de los buses, cediéndole el control. El funcionamiento del controlador de DMA se basa en unos registros que indican la dirección de memoria a ser accedida y cuántas posiciones de memoria quedan aún por transferir. La transferencia de datos entre los periféricos y la memoria por DMA no suele efectuarse de golpe, sino más bien poco a poco, robándole algunos ciclos a la CPU. Los controladores de DMA suelen disponer de varias líneas de petición de DMA, pudiendo atender las necesidades de varios periféricos que soliciten una transferencia, quienes deben haber sido diseñados expresamente para soportar el DMA. 12.5.2 - DESCRIPCIÓN DEL INTEGRADO 8237. El 8237 es un controlador de DMA de 4 canales programables en 3 modos diferentes, con posibilidad de ser conectado en cascada con otros de su misma especie. Además de las funciones tradicionales, el 8237 soporta también transferencias memoria-memoria, incluyendo la posibilidad de rellenar un área de la memoria con cierto dato. La arquitectura es de 16 bits, tanto para direcciones como datos, por lo que está especialmente diseñado para sistemas basados en el Z80 y 8085; aunque puede operar también con procesadores más avanzados, como la serie 80x86, pero sin alcanzar a aprovechar todas sus posibilidades. ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▌ -IOR ██▌ 1 ▌ -IOW ██▌ 2 ▌ -MEMR ██▌ 3 ▌ -MEMW ██▌ 4 ▌ NC ██▌ 5 ▌ READY ██▌ 6 ▌ HLDA ██▌ 7 ▌ ADSTB ██▌ 8 ▌ AEN ██▌ 9 ▌ HRQ ██▌ 10 ▌ -CS ██▌ 11 ▌ CLK ██▌ 12 ▌ RESET ██▌ 13 ▌ DACK2 ██▌ 14 ▌ DACK3 ██▌ 15 ▌
▐ 40 ▐██ A7 ▐ 39 ▐██ A6 ▐ 38 ▐██ A5 ▐ 37 ▐██ A4 ▐ 36 ▐██ -EOP ▐ 35 ▐██ A3 ▐ 34 ▐██ A2 ▐ 33 ▐██ A1 ▐ 32 ▐██ A0 ▐ 31 ▐██ Vcc ▐ 30 ▐██ DB0 ▐ 29 ▐██ DB1 ▐ 28 ▐██ DB2 ▐ 27 ▐██ DB3 ▐ 26 ▐██ DB4 ▐
DREQ3 ██▌ 16
25 ▐██ DACK0
▌
▐
DREQ2 ██▌ 17
24 ▐██ DACK1
▌
▐
DREQ1 ██▌ 18
23 ▐██ DB5
▌
▐
DREQ0 ██▌ 19
22 ▐██ DB6
▌
▐
GND ██▌ 20 ▌
21 ▐██ DB7 '8237
▐
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
CLK:Señal de reloj básica.
transistor en colector abierto, por lo que requiere una resistencia externa. Cuando llega una
-CS:Línea de habilitación del chip.
señal -EOP, el 8237 finaliza el servicio aunque en el modo de autoinicialización los registros
RESET:Esta señal provoca la limpieza de los
base volverán a ser escritos en los registros en curso del canal implicado. El canal resulta
registros de comando, estado, solicitud y los temporales; borra el banderín last/first y el contador de registro de modo; el registro de máscara se asigna para ignorar las solicitudes. El 8237 queda en Ciclo Inactivo. READY:Señal que puede ser empleada para extender los pulsos de lectura y escritura en memoria del 8237 para trabajar con memorias lentas. HLDA:Hold Acknowledge, línea por la que la CPU indica que ha liberado los buses. DREQ0..3:DMA Request; son 4 líneas asíncronas de petición de DMA. En el modo de prioridad fija, DREQ0 tiene la máxima y DREQ3 la mínima. Los periféricos solicitan el servicio de DMA en estas líneas y esperan a bajarlas hasta el correspondiente DACK. La polaridad de DREQ es programable. Las líneas no usadas deben ser enmascaradas. DB0..DB7:BUS de datos bidireccional y triestado. Durante los ciclos de DMA, los 8 bits más significativos de la dirección son colocados en el bus de datos con objeto de ser almacenados en un latch exterior controlado
por
ADSTB.
En
las
operaciones memoria-memoria, el bus de datos recibe y envía los bytes a transferir. -IOR:I/O Read. Línea bidireccional de 3 estados. En el
ciclo
inactivo
es
una
entrada
empleada por la CPU para leer los registros de control; en el ciclo activo actúa como línea de salida para que el 8237 controle la lectura de datos de los periféricos. -IOW:I/O Write. Línea bidireccional de 3 estados. En el ciclo inactivo es una entrada empleada por la CPU para escribir los registros del 8237; en el ciclo activo actúa como línea de salida para que el 8237 controle la escritura de datos en los periféricos. -EOP:End Of Process. Línea bidireccional que informa de la finalización del servicio DMA. El 8237 permite que un ente exterior fuerce el final de un servicio bajando esta línea. El propio 8237 genera un pulso en ella cuando se alcanza un TC (Terminal Count, fin de cuenta) en algún canal, salvo en el modo memoria-memoria del canal 0 (en ese caso, la señal se produce al alcanzarse el TC del canal 1). Esta patilla está conectada en el interior del chip a un
enmascarado salvo en el caso del modo de autoinicialización.
EL HARDWARE DE APOYO AL MICROPROCESADOR
245
A0..A3:Líneas bidireccionales triestado de direcciones. En el ciclo inactivo son entradas empleadas para direccionar los registros internos a leer o escribir. En el ciclo activo, son salidas y proveen los 4 bits menos significativos de la dirección. A4..A7:Líneas triestado de salida de direcciones. Proveen los 4 bits altos de la dirección durante el ciclo activo. HRQ:Hold Request. Línea de salida para solicitar los buses a la CPU, en el caso en que haya que realizar una transferencia. En los sistemas en que el 8237 controla totalmente el bus, esta patilla puede ir directamente conectada a HLDA. DACK0..3: DMA Acknowledge. Avisa a los periféricos de que ha sido atendida su petición. El nivel de operación de esta línea es programable. RESET las baja. AEN:Address Enable. Habilita el latch de 8 bits que guarda la parte alta de la dirección. Sirve también para inhibir el acceso al bus por parte de otras fuentes. ADSTB:Address Strobe. Línea que controla el almacenamiento de la parte alta de la dirección, cuando está en el bus de datos, en el latch externo. -MEMR:Memory Read. Salida triestado empleada para acceder a la memoria durante la lectura o las transferencias memoria-memoria. -MEMW:Memory Write. Salida triestado empleada para acceder a la memoria durante la escritura o las transferencias memoria-memoria.
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
DESCRIPCIÓN FUNCIONAL Los modos de operación del 8237 están diseñados para soportar transferencias de una sola palabra de datos y flujos de datos discontinuos entre la memoria y los periféricos. El controlador de DMA es realmente un circuito secuencial generador de señales de control y direcciones que permite la transferencia directa de los datos sin necesidad de registros temporales intermedios, lo que incrementa drásticamente la tasa de transferencia de datos y libera la CPU para otras tareas. Las operaciones memoria-memoria precisan de un registro temporal intermedio, por lo que son al menos dos veces más lentas que las de E/S, aunque en algunos casos aún más veloces que la propia CPU (no es el caso de los ordenadores compatibles). El 8237 consta internamente de varios bloques: un bloque de control de tiempos que genera las señales de tiempo internas y las señales de control externas; un bloque de gestión de prioridades, que resuelve ┌────────────────────────────────────────┬─────────┬──────────────┐ los conflictos de prioridad │ Tipo de registro │ Tamaño │ Nº registros │ cuando varios canales de DMA ├────────────────────────────────────────┼─────────┼──────────────┤ son accedidos a la vez; también │ Registro base de dirección │ 16 bits │ 4 │ posee un elevado número de │ Registro base contador de palabras │ 16 bits │ 4 │ registros para gestionar el │ Registro de dirección en curso │ 16 bits │ 4 │ funcionamiento. Los registros │ Registro contador de palabras en curso │ 16 bits │ 4 │ internos del 8237 están │ Registro temporal de dirección │ 16 bits │ 1 │ resumidos en la figura de la │ Registro temporal contador de palabras │ 16 bits │ 1 │ derecha. │ Registro de estado │ 8 bits │ 1 │ │ Registro de comandos
│
8 bits │
1
│
│ Registro temporal
│
8 bits │
1
│
│ Registro de modo
│
6 bits │
4
│
│ Registro de máscara
│
4 bits │
1
│
│ Registro de petición
│
4 bits │
1
│
└────────────────────────────────────────┴─────────┴──────────────┘
OPERACIÓN DEL DMA En un sistema, los buses del 8237 están conectados en paralelo al bus general del ordenador, siendo necesario un latch externo para almacenar la parte alta de la dirección de memoria. Cuando está inactivo, el 8237 está desconectado de los buses; cuando se produce una petición de DMA pasa a controlar los buses y a generar las señales necesarias para realizar las transferencias. La operación que realiza el 8237 es consecuencia de la programación realizada previamente en los registros de comando, modo, base de dirección y contador de palabras a transferir. Para comprender mejor el funcionamiento del 8237 es conveniente considerar los estados generados por cada ciclo. El DMA opera básicamente en dos ciclos: el activo y el inactivo (o idle). Tras ser programado, el DMA permanece normalmente inactivo hasta que se produce la solicitud de DMA en algún canal o vía software. Cuando ésta llega, si ese canal no estaba enmascarado (es decir, inhibido) el 8237 solicita los buses a la CPU y se pasa al ciclo activo. El ciclo activo se compone de varios estados internos, en función de la manera en que sea programado el chip. El 8237 puede asumir 7 diferentes estados, cada uno de ellos compuesto de un ciclo de reloj completo. El estado 1 (S1) es el estado inactivo o idle. En él se entra cuando no hay pendiente una petición de DMA válida, al final de la secuencia de transferencia, o tras un reset o un Master Clear (que se verá más adelante). En S1 el DMA está inactivo pero puede ser programado por el microprocesador del sistema. El estado 0 (S0) es el primer estado de servicio DMA. El 8237 ha solicitado los buses a la CPU a través de la línea HRQ pero la CPU aún no ha respondido a través de HLDA. En esta situación, el 8237 puede aún todavía ser programado. Una vez que la CPU responde, la labor del 8237 puede comenzar: los estados S2, S3 y S4 se suceden entonces para realizar el servicio. Si se necesitara más tiempo, está prevista la posibilidad de insertar estados de espera entre S2 ó S3 y S4 a través de la patilla READY.
EL HARDWARE DE APOYO AL MICROPROCESADOR
245
Téngase en cuenta que los datos son pasados directamente de la memoria hacia/desde los periféricos, por lo tanto no cruzan a través del DMA (las líneas -IOR y -MEMW, o -IOW y -MEMR, son activadas al mismo tiempo). El caso de las operaciones memoria-memoria es especial, ya que para cada palabra a mover hay que realizar la operación de lectura (en unos estados denominados S11, S12, S13 y S14) y después la de escritura (estados S21, S22, S23, S24). Ciclo Inactivo. Este es el estado en el que el 8237 espera pacientemente a que aparezca alguna solicitud de DMA, comprobando las líneas DREQ en los flancos de bajada de las señales de reloj: en esto consisten los estados S1. En esta situación, el 8237 puede ser programado por la CPU. Para ello, las líneas A0..A3 seleccionan el registro interno y -IOR e -IOW indican si se trata de leer o escribir. Como algunos de los registros internos son de 16 bits, existe un flip-flop interno que conmuta en cada operación de escritura sobre ellos, para que el 8237 sepa si está recibiendo el byte alto o el bajo (este flip-flop es puesto a cero en un Reset o en un comando Master Clear, existiendo también comandos especiales para controlarlo). Algunas combinaciones de A0..A3 y las líneas -IOR e -IOW, en lugar de acceder a los registros, constituyen comandos especiales. Ciclo Activo. Cuando el 8237 está en el ciclo inactivo y se produce una petición por software o un canal no enmascarado solicita servicio DMA, se pasa al estado activo y se opera en uno de estos 4 modos: Single Transfer Mode (Modo de transferencia única): El dispositivo es programado para realizar una única transferencia. El registro contador de palabras es decrementado y el de direcciones se incrementa/decrementa según ha sido programado. Cuando el registro contador de palabras se desborda (pasa de 0 a 0FFFFh) se activa el bit Terminal Count (fin de cuenta) en el registro de estado y la patilla -EOP genera un pulso. Si el canal estaba programado para autoinicializarse esto es lo que realiza; en caso contrario, se activa automáticamente el bit de máscara para inhibir hasta nueva orden ese canal. DREQ debe permanecer activo hasta que DACK responda. Sin embargo, si DREQ permanece activo hasta que acaba el proceso de transferencia, la línea HRQ baja y se ceden momentáneamente los buses al sistema. Después, vuelve a subir, y cuando se recibe el HLDA de la CPU se pueden realizar más transferencias de este tipo. En la serie 8080 y 80x86, esto asegura al menos un ciclo para la CPU entre las sucesivas transferencias del DMA. Block Transfer Mode (Modo de transferencia de bloque). Se diferencia del anterior en que en lugar de transferir una sola palabra se mueven todas las necesarias hasta que el registro contador de palabras se desborda. Lógicamente, también se acaba el proceso si alguien actúa sobre la patilla -EOP. DREQ sólo es preciso activarlo hasta que DACK responde. Demand Transfer Mode (Modo de transferencia por demanda). Se diferencia del anterior en que la transferencia se realiza sólo mientras DREQ permanece activo. Esto significa que se pueden transferir datos hasta agotar las posibilidades del dispositivo; cuando el dispositivo tenga más datos listos puede volver a activar DREQ para continuar donde lo dejó. Esta modalidad permite dejar ciclos a la CPU cuando no es realmente necesario que el DMA opere. Además, en los períodos de inactividad, los valores de dirección en curso y contador de palabras son almacenados en el Registro de direcciones en curso y en el Registro contador de palabras en curso correspondientes al canal implicado; mientras tanto, otros canales de mayor prioridad pueden ser atendidos por el 8237. Conexión en cascada de varios 8237. Esta conexión es empleada para conectar más de un 8237 en el sistema. La línea HRQ de los 8237 hijo es conectada a la DREQ del 8237 padre; la HLDA lo es a la DACK. Esto permite que las peticiones en los
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
diversos 8237 se propaguen de uno a otro a través de la escala de prioridades del 8237 del que cuelgan. La estructura de prioridades es por tanto preservada. Teniendo en cuenta que el canal del 8237 padre es empleado sólo para priorizar el 8237 adicional que cuelga (hijo), no puede emitir direcciones ni señales de control por sí mismo: esto podría causar conflictos con las salidas del canal activo en el 8237 hijo. Por tanto, el 8237 padre se limita en el canal del que cuelga el 8237 hijo a controlar DREQ, DACK y HRQ, dejando inhibidas las demás señales. El -EOP externo será ignorado por el 8237 padre, pero sí tendrá efecto en el 8237 hijo correspondiente. Cuando de un 8237 cuelga otro, estamos ante un sistema DMA de dos niveles. Si del DMA hijo cuelga a su vez otro, sería un sistema DMA de tres niveles, como el mostrado a continuación:
┌────────────────┐
┌────────────────┐
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
┌────────────────┐
│
│
│
DREQ │Χ───────│ HRQ
│
│
│
│
DACK │───────Ψ│ HLDA
│
│
│
│
│
│
C.P.U.
│ │
'8237
│
│
│
│
│Χ───────│ HRQ
│
│
│
│───────Ψ│ HLDA
│
│
DREQ │Χ───────│ HRQ
│
│
│
│
│
│
DACK │───────Ψ│ HLDA
│
└────────────────┘
│
│
└────────────────┘
'8237
│ │
┌────────────────┐
│
│ │
│
│
│
│
│
│
│
│
│
│
│
┌────────────────┐
│ '8237
│
│
│
│
DREQ │Χ───────│ HRQ
│
│
│
│
DACK │───────Ψ│ HLDA
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
└────────────────┘
│
│
│
│
│
│
│
│
│
│
│
│
└────────────────┘
│
'8237
└────────────────┘ Primer nivel
Segundo Nivel
Tercer Nivel
Al programar los 8237 en cascada, se debe empezar por el primer nivel. Tras un Reset, las salidas DACK son programadas por defecto para ser activas a nivel bajo y son colocadas en alto. Si están conectadas directamente a HLDA, el segundo nivel de 8237 no puede ser programado hasta que la polaridad de DACK no se cambie para que sea activa a nivel alto. Los bits de máscara de canales del 8237 padre funcionan como cabría esperar, permitiendo inhibir 8237's de niveles inferiores. Modos de transferencia. Cada uno de los 3 modos de transferencia puede realizar 3 tipos distintos de transferencias: lectura, escritura y verificación. La lectura pasa datos de la memoria al dispositivo E/S (activando -IOW y -MEMR); la
EL HARDWARE DE APOYO AL MICROPROCESADOR
245
escritura mueve datos desde los dispositivos E/S a la memoria (activando -IOR y -MEMW). Las transferencias de tipo verificación son pseudotransferencias: el funcionamiento es similar a la lectura o escritura pero sin tocar las líneas de control de la memoria ni de los periféricos; durante el modo de verificación se ignora la línea READY; este modo no es permitido en las operaciones memoria-memoria. Autoinicialización. Cualquier canal puede ser programado para incluir esta característica. En el momento de programar el chip, los registros base de dirección y base contador de palabras son cargados a la vez y con el mismo valor que los registros de dirección en curso y contador de palabras en curso. Los registros base permanecen inalterados en todo momento, por lo que al final del servicio sirven, en este modo de trabajo, para recargar de nuevo los registros en curso. Esto sucede justo tras la señal -EOP, quedando el 8237 listo para repetir de nuevo la misma transferencia (cuando se solicite a través de la línea DREQ o por software). En esta modalidad, los bits de máscara están a 0. Memoria-Memoria. En este tipo de transferencia se emplean siempre los canales 0 y 1. La transferencia comienza activando la línea DREQ del canal 0, bien por hardware o por software. El 8237 solicita entonces un servicio de DMA ordinario, con el que lee el byte de la memoria a través de 4 estados y empleando el Block Transfer Mode visto con anterioridad. El registro de dirección en curso del canal 0, que indica la dirección origen en la memoria, es incrementado/decrementado (según haya sido programado) y el dato es almacenado en el registro temporal del 8237. En otros 4 estados más, el dato es pasado del 8237 de nuevo a la memoria, usando la dirección del registro de dirección en curso del canal 1, que indica la dirección destino en memoria, el cual es también incrementado/decrementado según proceda. Además, se decrementa el registro contador de palabras en curso del canal 1: si al decrementar se desborda (pasa de 0 a 0FFFFh) se activa el bit TC del registro de estado (Terminal Count, fin de cuenta) y se genera un pulso -EOP, finalizando el proceso. En el caso de que el valor del registro contador de palabras del canal 0 pase de 0 a 0FFFFh, sin embargo, no se actúa sobre TC ni sobre EOP (no finaliza el proceso) aunque este canal se autoinicializa si así estaba programado. Si se desea una autoinicialización total en este tipo de transferencias, los registros contadores de palabras del canal 0 y 1 han de ser programados con el mismo valor inicial; de lo contrario, sólo uno de los dos canales se autoinicializará (el que primero desborde su registro contador de palabras). El canal 0 puede ser también programado para retener siempre la misma dirección durante todas las transferencias, lo que permite copiar un mismo byte en todo un bloque de la memoria. El 8237 puede responder a señales -EOP externas durante este tipo de transferencias, pero sólo cede el control de los buses después de completar la transferencia de la palabra que tenga entre manos. Los circuitos para comparar datos en búsquedas de bloques pueden emplear -EOP para terminar la operación tras encontrar lo que buscan. Las operaciones memoria-memoria se pueden detectar por hardware como una combinación de AEN activo sin que al mismo tiempo se produzcan salidas DACK. Prioridad. El 8237 tiene dos maneras de codificar la prioridad, seleccionables por software. La primera es la prioridad fija, basada en el número del canal (0-máxima, 3-mínima). Una vez que un canal es atendido, los demás esperan hasta que acabe. La segunda modalidad es la prioridad rotatoria: el último canal servido pasa a tener la menor prioridad y el que le sigue la máxima. La rotación de prioridades se produce cada vez que se devuelven los buses a la CPU. Esta última modalidad de prioridad asegura que un canal sea atendido al menos después de haber atendido los otros 3, evitando que un solo canal monopolice el uso del DMA. Con independencia del tipo de prioridad programada, ésta es evaluada cada vez que el 8237 recibe un HLDA. Compresión de tiempo.
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
De cara a mejorar el rendimiento en los sistemas más potentes, el 8237 puede ser programado para comprimir el tiempo de transferencia a dos ciclos de reloj. En cualquier caso, esta posibilidad no está disponible en las transferencias memoria-memoria. Generación de direcciones. Para reducir el número de pines, el 8237 tiene multiplexada la parte alta del bus de direcciones. En el estado S1, los 8 bits más significativos de la dirección son depositados en un latch externo a través del bus de datos. La línea AEN indica a la circuitería externa que debe habilitar el latch como parte alta del bus de direcciones cuando llega el momento (la parte baja la suministra directamente el 8237). En el Block Transfer Mode y en el Demand Transfer Mode, que implican múltiples transferencias, el 8237 es suficientemente inteligente como para generar estados S1 sólo cuando hay acarreo en la parte baja del bus de direcciones (1 de cada 256 veces) evitando acceder al latch externo cuando no es necesario modificarlo y ahorrando tiempo. PROGRAMACIÓN DEL 8237 El 8237 puede ser programado cuando HLDA está inactivo, siendo responsabilidad del programador que esto sea así (es decir, programarlo antes de que comience a operar). En cualquier caso, puede existir el riesgo de que mientras se programa un canal, se produzca una petición de DMA en el mismo antes de acabar la programación, y probablemente en un punto crítico (cuando, por ejemplo, se acababa de enviar la mitad de un valor de 16 bits). Para evitar este riesgo, antes de comenzar a programar un canal puede ser necesario enmascararlo, desinhibiéndolo después. Registros internos del 8237. Current Address Register (Registro de dirección en curso). Cada canal tiene un registro de dirección en curso que almacena la dirección de memoria empleada durante las transferencias del DMA. Su contenido es incrementado/decrementado después de cada transferencia. Este registro es inicializado por la CPU enviando dos bytes consecutivos; en modo autoinicialización, su contenido inicial se restaura cuando ésta se produce. Current Word Register (Registro contador de palabras en curso). Cada canal tiene un registro contador de palabras en curso, que determina el número de bytes a transferir en la operación menos uno (para un valor inicial 100, por ejemplo, se transmiten 101 bytes). Tras cada transferencia se decrementa: cuando pasa de 0 a 0FFFFh se genera el TC (Terminal Count) y el proceso finaliza. Este registro es inicializado por la CPU enviando dos bytes consecutivos; en modo autoinicialización, su contenido inicial se restaura cuando ésta se produce; de lo contrario continúa con un valor 0FFFFh. Base Address & Base Word Count Registers (Registros base de dirección y base contador de palabras). Cada canal tiene también un registro base de dirección y otro base contador de palabras. Estos registros almacenan el valor inicial de los registros de dirección en curso y contador de palabras en curso, ya que ambos tipos de registros se cargan simultáneamente durante la programación. El valor almacenado en estos registros se emplea en la autoinicialización, para recargar los registros en curso. ┌───────┬─────────────────────────────────────────────────────────────┬──────────┬─────────────┐ │ Canal │ │
Registro(s)
│
│
│
│
│ A3 A2 A1 A0 │
Dirección
│
├───────┼─────────────────────────────────────────────────────────────┼──────────┼─────────────┤ │
│
Base de dirección y de dirección en curso
│ Escribir │
0
0
0
0 │
│
De dirección en curso
│
│
0
0
0
0 │
│
│
Base contador de palabras y contador de palabras en curso
│ Escribir │
0
0
0
1 │
│
│
Contador de palabras en curso
│
0
0
0
1 │
│
0
Leer Leer
│
├───────┼─────────────────────────────────────────────────────────────┼──────────┼─────────────┤
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
│
│
Base de dirección y de dirección en curso
│ Escribir │
0
0
1
0 │
│
De dirección en curso
│
│
0
0
1
0 │
│
│
Base contador de palabras y contador de palabras en curso
│ Escribir │
0
0
1
1 │
│
│
Contador de palabras en curso
│
0
0
1
1 │
│
1
Leer Leer
│
├───────┼─────────────────────────────────────────────────────────────┼──────────┼─────────────┤ │
│
Base de dirección y de dirección en curso
│ Escribir │
0
1
0
0 │
│
De dirección en curso
│
│
0
1
0
0 │
│
│
Base contador de palabras y contador de palabras en curso
│ Escribir │
0
1
0
1 │
│
│
Contador de palabras en curso
│
0
1
0
1 │
│
2
Leer Leer
│
├───────┼─────────────────────────────────────────────────────────────┼──────────┼─────────────┤ │
│
Base de dirección y de dirección en curso
│ Escribir │
0
1
1
0 │
│
De dirección en curso
│
│
0
1
1
0 │
│
│
Base contador de palabras y contador de palabras en curso
│ Escribir │
0
1
1
1 │
│
│
Contador de palabras en curso
│
0
1
1
1 │
│
3
Leer Leer
│
└───────┴─────────────────────────────────────────────────────────────┴──────────┴─────────────┘ Direcciones E/S de los registros de direcciones y contadores
Command Register (Registro de comandos). Es un registro de 8 bits que controla el funcionamiento del 8237. Se borra tras un Reset o un comando Master Clear: ┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │
│
│
7
│
│
│
│
│
6
│
5
│
│
│
4
│
│
│
3
│
│
│
2
│
│
│
1
│
│
│
0
│
│ │
└────┬────┴────┬────┴────┬────┴────┬────┼────┬────┴────┬────┴────┬────┴────┬────┘ │
│
│
│
│
│
0 DACK sensible │
Ϊ
│
│
│
│
│
0
no es memoria-memoria
Ϊ 1
modo memoria-memoria
a nivel bajo │
│
│
│
│
│
1 DACK sensible │
│
│
│
│
Ϊ
a nivel alto │
│
│
│
│
0
no fijar dirección en canal 0
│
│
│
│
│
1
fijar dirección canal 0
│
│
│
│
│
X
si bit 0 = 0
│
│
│
│
Ϊ
Ϊ
│
│
│
0
controlador habilitado
0 DREQ sensible en alto │
│
│
1
controlador inhibido
1 DREQ sensible en bajo │
│
Ϊ
│
│
0
compresión de tiempo inhibida
│
│
1
compresión de tiempo activada
│
│
X
si bit 0 = 1
│
Ϊ
│
0
prioridad fija
│
1
prioridad rotatoria
Ϊ 0
escritura posterior activa
1
escritura extendida activa
X
si bit 3 = 1
Mode Register (Registro de modo). Cada canal tiene un registro de modo asociado, de 6 bits. Cuando se escribe el registro de modo, se envía un byte al 8237 que selecciona (en los bits 0 y 1) el canal cuyo registro de modo se desea escribir, y el resto de los bits cargan el registro de modo. Cuando se lee, dichos bits estarán a 1 (para leer un registro de modo hay que utilizar antes el comando Clear Mode Register Counter, como se verá en la sección de comandos). ┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │
│
│
│
│
│
│
│
│
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
│
7
│
│
6
│
│
5
│
│
4
│
│
3
│
│
2
│
│
1
│
│
0
│
│ │
└────┬────┴────┬────┴────┬────┴────┬────┼────┬────┴────┬────┴────┬────┴────┬────┘ └────┬────┘
┌────┘
│
└────┬────┘
│
│
Ϊ
│
│
│
0 sin autoinicialización │
00
seleccionar canal 0
│
│
1 con autoinicialización │
01
seleccionar canal 1
│
Ϊ
│
10
seleccionar canal 2
11
seleccionar canal 3
│
0
modo incremento de direcciones
│
│
1
modo decremento de direcciones
│
│
└────┬────┘ Ϊ
Ϊ
Ϊ
00
transferencia de verificación
00
Demand Transfer Mode
01
transferencia de escritura
01
Single Transfer Mode
10
transferencia de lectura
10
Block Transfer Mode
11
ilegal
11
Conexión en cascada
XX
si bits 6 y 7 ambos activos
Request Register (Registro de petición de DMA). El 8237 puede responder a peticiones de DMA tanto por hardware (línea DREQ) como por software. En este registro posee un bit para cada canal de DMA. Las peticiones por software no se pueden enmascarar, aunque están sujetas a la lógica de evaluación de prioridades. Cada bit de este registro es activado o borrado selectivamente por software. Todo el registro es borrado ante un Reset. Para modificar sus bits, se debe enviar el comando Write Request register. Si se lee el registro, los bits 0 al 3 muestran el estado de las peticiones en los canales 0 al 3 (los demás bits están a 1). Las peticiones de DMA por software pueden serlo indistintamente en el modo single o en el block. Para operaciones memoria-memoria, hay que hacer una petición de DMA por software en el canal 0. Mask Register (Registro de máscara de DMA). Cada canal tiene asociado un bit de máscara que puede ser activado para inhibir las solicitudes de DMA a través de la línea DREQ. Este bit es automáticamente activado cada vez que se produce un -EOP (al final de la transferencia) a menos que el canal esté en modo autoinicialización. Cada bit de máscara puede ser modificado por separado, o todos a la vez, con el comando apropiado. Todo el registro es puesto a 1 a través del comando Master Clear o debido a un Reset, lo que inhibe las solicitudes de DMA por hardware hasta que se envía un comando para limpiar el registro de máscara (o se borran los bits que se desee en el mismo). Existen tres órdenes para actuar sobre el registro de máscara; la primera es a través del comando Clear Mask Register, que borra todos los bits de máscara; la segunda es por medio del comando Write Single Mask Bit, modificando un solo bit; la tercera forma consiste en los comandos Read y Write All Mask Bits, con los que se pueden consultar y alterar todos los bits de máscara a la vez. Status Register (Registro de estado). Contiene información de estado lista para ser leída por la CPU. Los bits 0 al 3 indican si los respectivos canales han alcanzado un TC (Terminal Count) o se les ha aplicado una señal -EOP externa. Estos bits se borran ante un Reset, un comando Master Clear o, simplemente, al leer el propio registro de estado. Los bits 4 al 7 indican qué canales están solicitando servicio, con independencia de que estén enmascarados o no. De esta manera, enmascarando todos los canales y leyendo el registro de estado, por software se puede decidir qué canales conviene desenmascarar, pudiendo el sistema operativo aplicar la gestión de prioridades que desee llegado el caso. Estos bits (4 al 7) son actualizados cuando el reloj está en alto; un Reset o un comando Master Clear los borran. ┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │ │ │
│ 7
│ │
│ 6
│ │
│ 5
│ │
│ 4
│ │
│ 3
│ │
│ 2
│ │
│ 1
│ │
│ 0
│ │
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
└────┬────┴────┬────┴────┬────┴────┬────┼────┬────┴────┬────┴────┬────┴────┬────┘ Ϊ
Ϊ
Ϊ
Ϊ
Ϊ
Ϊ
Ϊ
Ϊ
canal 3 │
canal 2 │
canal 1 │
canal 0 │
canal 3 │
canal 2 │
canal 1 │
canal 0 │
└─────────┴────┬────┴─────────┘
└─────────┴────┬────┴─────────┘
Ϊ
Ϊ
a 1 si hay una petición de DMA
a 1 si se ha alcanzado el TC
Temporary Register (Registro temporal). Se emplea para contener los bytes que se transfieren en las operaciones memoria-memoria. Tras completar el proceso de transferencia, la CPU puede averiguar la última palabra transferida leyendo este registro, a no ser que el registro haya sido borrado por un Reset o un comando Master Clear. Comandos del 8237. A continuación se citan algunos comandos especiales que pueden ser ejecutados leyendo o escribiendo sobre el 8237. A diferencia de cuando hay que acceder a los registros de direcciones y contadores, aquí el bit A3 está activo. Por tanto, de los 16 puertos de E/S que ocupa el 8237 en cualquier sistema, los 8 últimos están relacionados con los comandos y los registros especiales. En el siguiente cuadro se recogen todos, y después se explican los más confusos. ┌───────────────────────────────────────────────────────────────────────┬──────────┬─────────────┐ │
Comando
│
u operación
│ Modo de
│
│
│ A3 A2 A1 A0 │
acceso
Dirección
│
├───────────────────────────────────────────────────────────────────────┼──────────┼─────────────┤ │
Read Status Register
│
Write Command Register
(escribir registro de comandos)
│
Read Request Register
(leer registro de petición de DMA)
│
Write Request Register
│
Read Command Register
│
Write Single Mask Bit
│
Read Mode Register
│
Write Mode Register
│
Set Byte Pointer F/F
│
Clear Byte Pointer F/F
│
Read Temporary Register
│
Master Clear
│
Clear Mode Register Counter
│
Clear Mask Register
│
Read All Mask Bits
│
Write All Mask Bits
│
│
1
0
0
0 │
│ Escribir │
1
0
0
0 │
│
│
1
0
0
1 │
│ Escribir │
1
0
0
1 │
(leer registro de comandos)
│
│
1
0
1
0 │
(escribir un solo bit de máscara de DMA)
│ Escribir │
1
0
1
0 │
│
│
1
0
1
1 │
│ Escribir │
1
0
1
1 │
│
│
1
1
0
0 │
│ Escribir │
1
1
0
0 │
│
│
1
1
0
1 │
│ Escribir │
1
1
0
1 │
│
│
1
1
1
0 │
│ Escribir │
1
1
1
0 │
│
│
1
1
1
1 │
│ Escribir │
1
1
1
1 │
(leer registro de estado)
(escribir registro de petición de DMA)
(leer registro de modo) (escribir registro de modo) (activar flip-flop primero/último) (borrar flip-flop primero/último) (leer registro temporal)
(inicialización principal) (limpiar contador de registro de modo)
(borrar registro de máscara de DMA) (leer todos los bits de máscara de DMA) (escribir todos los bits de máscara de DMA)
Leer Leer Leer Leer Leer Leer Leer Leer
└───────────────────────────────────────────────────────────────────────┴──────────┴─────────────┘ Direcciones E/S de los comandos
Clear first/last flip-flop (borrar flip-flop primero/último). Dado que los valores de 16 bits se envían de dos veces, existe un flip-flop interno que permite al 8237 conocer si lo que le llega es la primera mitad del dato o la segunda. Por precaución, se puede borrar primero para asegurar que el primer byte enviado se interprete como el menos significativo y, el segundo, como el más significativo. Set first/last flip-flop (activar flip-flop primero/último). Dado que los valores de 16 bits se envían de dos veces, existe un flip-flop interno que permite al 8237 conocer si lo que le llega es la primera mitad del dato o la segunda. Por precaución, se puede activar primero
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
para asegurar que el primer byte enviado se interprete como el más significativo y, el segundo, como el menos significativo. Master Clear (inicialización principal). Este comando tiene el mismo efecto que un Reset hardware. Los registros de comando, estado, petición de DMA, temporales y los flip-flops internos (first/last y mode register counter) son puestos a cero, siendo el registro de máscaras rellenado con bits a 1 (inhibir canales). El 8237 entra en estado inactivo. Read/Write Request Register (leer/escribir registro de petición de DMA). El comando Write es empleado para escribir al registro de petición de DMA y provocar una petición de DMA por software; también se puede utilizar Read para consultar su estado: los bits 0 al 3 muestran entonces el estado de las peticiones en los canales 0 al 3 (los demás bits están a 1). El formato para escribir es el siguiente: ┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │ │
│ 7
│
│
│ 6
│
│
│ 5
│
│
│ 4
│
│
│ 3
│
│
│ 2
│
│
│ 1
│
│
│ 0
│
│ │
└────┬────┴────┬────┴────┬────┴────┬────┼────┬────┴────┬────┴────┬────┴────┬────┘ │
│
│
└─────────┴─────────┼─────────┴─────────┘
│
│
│
│
└────┬────┘
Ϊ
│
00
seleccionar canal 0
No importa su valor al escribir
│
01
seleccionar canal 1
Bits 4..7 a 1 al leer
│
10
seleccionar canal 2
│
11
seleccionar canal 3
Ϊ
Ϊ 0
borrar bit de petición
1
activar bit de petición
Clear Mask Register (borrar registro de máscara de DMA). Este comando limpia los bits de máscara de los 4 canales, habilitándoles para recibir peticiones de DMA por hardware. Write Single Mask bit (escribir un sólo bit de máscara de DMA). Con este comando se puede seleccionar el bit de máscara que se desea modificar (activándolo o borrándolo). ┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │ │
│ 7
│
│
│ 6
│
│
│ 5
│
│
│ 4
│
│
│ 3
│
│
│ 2
│
│
│ 1
│
│
│ 0
│
│ │
└────┬────┴────┬────┴────┬────┴────┬────┼────┬────┴────┬────┴────┬────┴────┬────┘ │
│
│
└─────────┴─────────┼─────────┴─────────┘
│
│
│
│
└────┬────┘
Ϊ
│
00
seleccionar canal 0
No importa su valor al escribir
│
01
seleccionar canal 1
│
10
seleccionar canal 2
│
11
seleccionar canal 3
Ϊ
Ϊ 0
borrar bit de máscara
1
activar bit de máscara
Read/Write All Mask bits (leer/escribir todos los bits de máscara de DMA). Este comando permite consultar o establecer el estado de todos los bits de máscara de DMA a la vez, en los 4 canales.
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐ │ │
│ 7
│
│ │
│ 6
│
│
5
│
│
│ │
4
│
│
│
│
3
│
2
│
│
│ 1
│
│
│ 0
│
│ │
└────┬────┴────┬────┴────┬────┴────┬────┼────┬────┴────┬────┴────┬────┴────┬────┘ └─────────┴────┬────┴─────────┘
Ϊ
Ϊ
Ϊ
Ϊ
Ϊ
canal 3
canal 2
canal 1
canal 0
No importa al escribir
│
│
│
│
└─────────┴────┬────┴─────────┘
Todos a 1 al leer
Ϊ 0
limpiar su bit de máscara
1
activar su bit de máscara
Clear Mode Register Counter (limpiar contador de registro de modo). Cuando se escribe el registro de modo, se envía un byte al 8237 que selecciona (en los bits 0 y 1) el canal cuyo registro de modo se desea escribir, y el resto de los bits cargan el registro de modo. Sin embargo, al leer, ¿cómo seleccionar el canal cuyo registro de modo se desea leer?. La solución consiste en hacer n lecturas consecutivas, en las que el 8237 devuelve unos seguidos de otros los 4 registros de modo, gracias a un contador interno de 2 bits que le dice qué tiene que devolver a continuación. Con este comando se borra dicho contador, de manera que a la siguiente lectura el 8237 devuelva el registro de modo del canal 0 (habrá que seguir leyendo más hasta obtener el registro de modo del canal deseado). Cuando se lee el registro de modo de cualquier canal, los bits 0 y 1 del byte devuelto aparecen siempre activos. 12.5.3 - EL 8237 EN EL ORDENADOR. Todos los ordenadores compatibles vienen equipados con un 8237 accesible a partir de la dirección E/S base 0. Es por tanto el chip del ordenador donde resulta más fácil traducir las direcciones E/S de las tablas técnicas del fabricante a la dirección del espacio de E/S del PC. Los AT y PS/2 poseen un 8237 adicional, accesible a partir de la dirección E/S 0C0h. Los puertos están direccionados en intervalos de 2, al repetirse en dos direcciones adyacentes (esto permite en los IBM y otros muchos hacer un OUT de 16 bits en lugar de dos consecutivos de 8, pero no todas las máquinas lo soportan). En los AT, este 2º controlador de DMA actúa como maestro y está encargado de las operaciones de 16 bits; su canal 0 es empleado para colgar de él otro 8259 que realiza las operaciones de 8 bits, por compatibilidad con el PC. Por ello, los AT poseen 7 canales de DMA, frente a los 4 de los PC/XT. La siguiente tabla resume todos los puertos de entrada y salida a emplear para acceder a ambos controladores de DMA (el de 16 bits, recuérdese, sólo disponible en AT): ┌───────────────────────────────┬─────────────────────┬─────────┬─────────┐ │
Comando
o registro
│
Modo de acceso
│
8 bits │ 16 bits │
├───────────────────────────────┼─────────────────────┼─────────┼─────────┤ │
Registro dirección canal 0
│ lectura y escritura │
00
│
C0
│
│
Registro de cuenta canal 0
│ lectura y escritura │
01
│
C2
│
│
Registro dirección canal 1
│ lectura y escritura │
02
│
C4
│
│
Registro de cuenta canal 1
│ lectura y escritura │
03
│
C6
│
│
Registro dirección canal 2
│ lectura y escritura │
04
│
C8
│
│
Registro de cuenta canal 2
│ lectura y escritura │
05
│
CA
│
│
Registro dirección canal 3
│ lectura y escritura │
06
│
CC
│
│
Registro de cuenta canal 3
│ lectura y escritura │
07
│
CE
│
│
Status Register
│
lectura
│
08
│
D0
│
│
Command Register
│
escritura
│
08
│
D0
│
│
Request Register
│ lectura y escritura │
09
│
D2
│
│
Command Register
│
lectura
│
0A
│
D4
│
│
Single Mask Bit
│
escritura
│
0A
│
D4
│
│
Mode Register
│ lectura y escritura │
0B
│
D6
│
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
│
Set Byte Pointer F/F
│
lectura
│
0C
│
D8
│
│
Clear Byte Pointer F/F
│
escritura
│
0C
│
D8
│
│
Temporary Register
│
lectura
│
0D
│
DA
│
│
Master Clear
│
escritura
│
0D
│
DA
│
│
Clear Mode Register Counter
│
lectura
│
0E
│
DC
│
│
Clear Mask Register
│
escritura
│
0E
│
DC
│
│
Read/Write All Mask bits
│ lectura y escritura │
0F
│
DE
│
└───────────────────────────────┴─────────────────────┴─────────┴─────────┘ Direcciones E/S de los controladores de DMA
Los PC/XT utilizan el canal 0 de su 8237 para el refresco de la memoria, el 2 para los disquetes y el 3 para el disco duro. El único canal que queda libre es el 1. Sin embargo, en los AT el panorama cambia bastante. El 8237 encargado de las transferencias de 8 bits (esclavo) que cuelga del que controla las transferencias de 16 bits (maestro) define los canales 0 al 3, de los cuáles sólo el canal 2 está ocupado en las operaciones de disquetes, al igual que los PC/XT. El 8237 encargado de las operaciones de 16 bits define los canales 5, 6 y 7 (el 4 está ocupado en colgar de él el otro 8237), estando todos ellos libres. La razón es que en los AT la memoria no se refresca por el DMA y el disco duro por lo general se accede directamente, también sin DMA. Por tanto, en estas máquinas quedan nada menos que 6 canales de DMA libres (el 0, 1 y 3 del DMA de 8 bits y el 5, 6 y 7 del DMA de 16 bits). Seguramente, el lector se habrá dado cuenta de que los registros de direcciones del DMA son de 16 bits, mientras que la serie 80x86 puede direccionar entre 1 Mb y 4 Gb ┌───────┬────────────────────┐ de memoria. Si tiene algo de sentido común, se le habrá ocurrido la │ Canal │ Puerto E/S del │ pregunta: ¿Cómo es posible entonces que el DMA acceda a la memoria │ DMA │ registro de página │ del ordenador, con direcciones de 20 a 32 bits?. La solución técnica ├───────┼────────────────────┤ 87h (sólo AT) │ adoptada por los diseñadores del PC consistió en añadir unos registros │ 0 │ │ 1 │ 83h │ externos, ubicados fuera del 8237, que se encargan de suministrar los │ 2 │ 81h │ bits de direcciones que faltan: son los denominados registros de página │ 3 │ 82h │ de DMA, habiendo uno por cada canal. │ 5 │ 8Bh (sólo AT) │ │
6
│
89h (sólo AT)
│
│
7
│
8Ah (sólo AT)
│
└───────┴────────────────────┘
En los PC/XT, los registros de página de DMA poseen sólo 4 bits significativos y generan la parte alta de la dirección de memoria. En los AT, son significativos los 8 bits completos del registro de página de DMA en el 8237 que controla las operaciones de 8 bits y 7 en el que gestiona las operaciones de 16 bits. El siguiente esquema muestra cómo se generan las direcciones de memoria:
PC/XT
┌──── Registro de página
┌──── Registro de direcciones del 8237
Ϊ
Ϊ
┌───┬───┬───┬───┐
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│A19│A18│A17│A16│
│A15│A14│A13│A12│A11│A16│ A9│ A8│ A7│ A6│ A5│ A4│ A3│ A2│ A1│ A0│
└───┴───┴───┴───┘
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
D3
AT (DMA 8)
D2
D1
D0
┌──── Registro de página
┌──── Registro de direcciones del 8237
Ϊ
Ϊ
┌───┬───┬───┬───┬───┬───┬───┬───┐
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│A23│A22│A21│A20│A19│A18│A17│A16│
│A15│A14│A13│A12│A11│A16│ A9│ A8│ A7│ A6│ A5│ A4│ A3│ A2│ A1│ A0│
└───┴───┴───┴───┴───┴───┴───┴───┘
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
D7
D6
D5
D4
D3
D2
D1
┌──── Registro de página
D0
┌──── Registro de direcciones del 8237
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
Ϊ
Ϊ
┌───┬───┬───┬───┬───┬───┬───┐ AT (DMA 16)
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ ┌───┐
│A23│A22│A21│A20│A19│A18│A17│
│A16│A15│A14│A13│A12│A11│A16│ A9│ A8│ A7│ A6│ A5│ A4│ A3│ A2│ A1│ │ 0 │
└───┴───┴───┴───┴───┴───┴───┘
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ └───┘
D7
D6
D5
D4
D3
D2
Ω
D1
siempre a cero ────┘
Los restantes bits del espacio de direcciones (líneas A24 a A31 del 386) no se pueden emplear, de ahí que algunas implementaciones de Unix tuvieran problemas para soportar más de 16 Mb de memoria. En general, desde el punto de vista del DMA, se puede imaginar la memoria como 16 bloques de 64 Kb (caso del PC/XT), como 256 bloques de 64 Kb (en accesos de 8 bits en el AT) o bien como 128 bloques de 128 Kb (en accesos de 16 bits también en el AT). En el DMA que trabaja con 16 bits, se transfieren sólo palabras (65536 palabras = 128 Kb) y siempre en direcciones pares, de ahí que A0=0. Nota:
Con los controladores de memoria expandida actuales (EMM386), los diseñadores han sido suficientemente cautos como para colocar los primeros 640 Kb de la memoria virtual justo en los primeros 640 Kb de memoria física del ordenador. La memoria de pantalla y la de la tarjeta VGA también están en su sitio. Por tanto, bajo las últimas versiones del DOS es factible (y probablemente lo seguirá siendo) programar directamente el DMA para realizar transferencias sobre la memoria normal. Sin embargo, sobre la memoria superior tampoco hay problemas. Aunque la dirección virtual ya no coincide con la física, cuando se ejecuta una instrucción OUT sobre un registro de página, el controlador de memoria detecta la circunstancia, ya que al parecer está protegido el acceso a esos puertos. A continuación, averigua qué instrucción ha provocado la excepción y modifica convenientemente el valor con el que se pretendía hacer OUT para adecuarlo a la dirección de memoria física y permitir que siga funcionando. Esto explica por qué una instrucción de E/S sobre uno de estos puertos puede tardar nada menos que ¡1000 ciclos! en un 386.
La BIOS del AT inicializa los 8237 con un valor 0 en el Command Register. Casi todos los canales son establecidos por defecto (y así permanecen cuando no se usan) en el modo single, transferencia de verificación, autoinicialización inhibida y modo incremento. Por ello, en el 8237 esclavo se escribe el valor 40h en el registro de modo del canal 0, el 41h en el canal 1, el 42h en el canal 2 y el 43h en el canal 3. En el 8237 maestro, el registro de modo del canal 4 (canal 0 de este chip) se programa con 0C0h, que equivale al modo cascada; los demás canales se programan como en el otro 8237. El siguiente listado ha sido extraído directamente de la BIOS del AT: SUB
AL,AL
; DACK sensible en bajo, DREQ sensible en alto
OUT
8,0
; Escritura posterior, prioridad fija, sin compresión, ; controlador habilitado, sin fijar dirección en canal 0, ; memoria-memoria deshabilitado
OUT
0D0h,AL
; lo mismo con el segundo controlador
MOV
AL,40h
; establecer modo para el canal 0
OUT
0Bh,AL
MOV
AL,0C0h
OUT
0D6h,AL
JMP
SHORT $+2
MOV
AL,41h
OUT
0Bh,AL
OUT
0D6h,AL
JMP
SHORT $+2
MOV
AL,42h
OUT
0Bh,AL
OUT
0D6h,AL
JMP
SHORT $+2
MOV
AL,43h
; modo cascada en el canal 4
; establecer modo para el canal 1 ; y para el 5 ; establecer modo para el canal 2 ; y para el 6 ; establecer modo para el canal 3
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
OUT
0Bh,AL
OUT
0D6h,AL
; y para el 7
La BIOS del PC/XT inicializa el canal 0 del DMA para el refresco de la memoria. El refresco de las memorias dinámicas consiste en ir leyéndolas con suficiente rapidez como para que no se borre su contenido; en realidad, dada su organización en filas y columnas, se puede refrescar a la vez un gran número de bytes leyendo uno sólo. Para una memoria de 1 Mb, basta con acceder a cualesquiera 1024 posiciones de memoria consecutivas, cada menos de 4 milisegundos, para garantizar la fiabilidad del sistema. Para ello, el canal 0 del DMA es colocado en modo single, en modo incremento de direcciones, con autoinicialización y en modo transferencia de lectura (enviando el valor 58h al registro de modo). A continuación, dicho canal es desenmascarado, comenzando el refresco de la memoria. La razón es que la salida del contador 1 del temporizador 8253 está conectada a la línea de petición del canal 0 del DMA, por lo que periódicamente el 8237 sustrae el control de los buses al 8086 para continuar el refresco por la dirección de memoria en que se llegara (el contador 1 del 8253 está programado con una cuenta 18, igual que en los AT: aunque éstos últimos no refrescan la memoria por DMA utilizan una base de tiempos compatible). El registro de página del canal 0 no existe en los PC/XT; sin embargo, debido al diseño de la placa, es el registro de página del canal 3 el que actúa. En cualquier caso, es indiferente la dirección de memoria base empleada para refrescar. Los restantes canales DMA, así como el Command Register, son programados del mismo modo que sus colegas en el AT. 12.5.4 - RALENTIZAR UN EQUIPO AT CON EL DMA. La posibilidad de emplear el DMA para realizar transferencias memoria-memoria en los ordenadores compatibles, a través de los canales 0 y 1, es poco atractiva. En los PC/XT es factible, como demuestran algunas rutinas de dominio público, aunque ello suponga anular momentáneamente el refresco de la memoria dinámica (la propia transferencia de bytes la refresca); sin embargo es más complicado y el movimiento se realizaría dentro de un único segmento de 64 Kb. En los AT no existen todas estas complicaciones, pero aquí no es recomendable por dos motivos: por un lado, el registro interno del 8237 encargado de almacenar el byte a transferir es de 8 bits (es decir, nada de emplear un canal de DMA de 16 bits, que sería mucho más rápido) y, por otro lado, el más modesto 286 es bastante más rápido que el DMA (por algo el disco duro del AT se lee sin DMA). No digamos un 386 u otra máquina superior. Cierto célebre libro de soluciones para programadores de compatibles afirma en la página 328 que los AT emplean el DMA automáticamente en las instrucciones MOVS para mejorar el rendimiento. Fuera del ámbito de la ciencia-ficción, aquí propondremos otro uso no más común pero, en cambio, factible: ralentizar el funcionamiento de los ordenadores AT. La auténtica utilidad del DMA, conviene recordarlo, está ligada al acceso a los disquetes, aunque de ello hay ejemplos en el apartado donde se trata la programación del NEC765. El truco, cuya idea original hay que atribuir a Jesús Arias, consiste en programar un canal en modo autoinicialización, para que se ponga a trabajar continuamente. Programándolo en modo single, le va robando ciclos a la CPU de manera continua. En teoría, en el modo block se debería quedar bloqueado el ordenador, aunque las máquinas en donde lo he probado esto no sucede. En los PC/XT no conseguí un resultado exitoso, además de que no tiene mucho sentido hacerlos más lentos. Sin embargo, en los AT es bastante sencillo el proceso y funciona en todas las máquinas en que se probó. A la hora de elegir un canal, se puede optar por el 0, 1, 3, 5, 6 ó 7. Casi todos son válidos, pero el 0 y 1 no son recomendables: son los canales de más prioridad y, si se utilizan para ralentizar el ordenador, las disqueteras dejan de funcionar (utilizan el canal 2). Este es otro de los motivos por los que no es conveniente hacer esto en los PC/XT (su único canal disponible es el 1). Por tanto, la elección queda relegada al canal 3 (de 8 bits) o al 5, 6 ó 7 (de 16 bits). De esta manera, los disquetes pueden continuar funcionando, ya que su canal de DMA toma el control cuando es necesario debido a su mayor prioridad. Resulta interesante observar cómo ralentiza más emplear un canal de 8 bits que uno de 16: en el sistema 386-25 donde lo probé, el famoso test de velocidad de LANDMARK estima la velocidad habitualmente en 27,8 MHz. Poniendo en marcha el canal 7, de 16 bits, la velocidad cae nada menos que a 7,3 MHz; utilizando el 3 (de 8 bits) baja a 6,3 MHz. Combinando ambos canales a la vez, el descenso es aún mayor, hasta los 4,3 MHz.
245
EL HARDWARE DE APOYO AL MICROPROCESADOR
Las tradicionales utilidades de dominio público para ralentizar los AT suelen emplear la interrupción del temporizador, parando por completo el ordenador durante algunos instantes y dejándole a toda velocidad el resto del tiempo. La ventaja de ralentizar por DMA es que el ordenador baja la velocidad de una manera uniforme y no va a saltitos. Por otro lado, ralentiza también los juegos que controlan por su propia cuenta la interrupción del temporizador. Además, casi ningún programa comercial se ocupa de programar los canales del DMA, ni el propio BIOS toca los que no le incumben; por ello, una vez activado, es seguro que el efecto durará cuanto desee el usuario. Por último, el método es aún más elegante porque ni siquiera se trata de un programa residente: ¡consume 0 bytes!. Combinando el método de ralentización por DMA con un aumento de los ciclos de refresco de la memoria (a través del canal 1 del 8254) se puede bajar todavía aún más la velocidad, de manera también uniforme. En concreto, en la máquina citada anteriormente, si se programa el canal 1 del 8254 con un valor de cuenta 2 la velocidad cae a 1,4 MHz, según el test de Landmark: los ciclos de refresco de memoria castigan mucho a la CPU cuando la restan pocos MHz... El inconveniente de ralentizar demasiado, combinando los dos métodos citados, es que el teclado comienza a fallar en mayor o menor medida (se enganchan las teclas de Shift y Ctrl, siendo preciso pulsarlas de vez en cuando para desengancharlas; aparecen números en los cursores expandidos...). En el siguiente programita de demostración, existen dos niveles de freno seleccionables. Utiliza el peor método para comprobar si el ordenador es un AT, a través del byte de identificación de la ROM (es 0FCh en un gran número de ATs y 0F8h en los PS/2-80), aunque es sin duda una de las maneras más rápidas de hacerlo. Las funciones dmako() se encargan de poner K.O. el canal correspondiente, activando el DMA. Las recíprocas dmaok() devuelven el canal asociado a la normalidad, inhibiendo el DMA. #include
dmako3(); dmako7();
#include
printf ("\n }
void dmacnt(), dmako3(), dmako7(), dmaok3(), dmaok7();
void main(int argc, char **argv) { unsigned nivel;
printf ("\nDMAKO 1.1 + AT-Ralentizador por DMA
(c) 1992 CiriSOFT");
if ((peekb(0xF000,0xFFFE)!=-4) && (peekb(0xF000,0xFFFE)!=-8)) { printf("\n
Este programa necesita máquina AT o superior\n");
exit (1); }
if ((argc<2) || ((nivel=atoi(argv[1]))>3)) { printf("\n
");
printf("Indicar nivel de freno (1, 2 ó 3) ó 0 para acelerar.\n"); exit (2); }
dmacnt(); if (nivel==1) { dmaok3(); dmaok7(); dmako7(); printf ("\n
Ralentización moderada activa.\n");
} else if (nivel==2) { dmaok3(); dmaok7(); dmako3(); printf ("\n
Ralentización elevada activa.\n");
} else if (nivel==3) {
Ralentización máxima activa.\n");
245
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
else { dmaok3(); dmaok7(); printf ("\n
Ralentización desactivada.\n");
} }
void dmacnt() { outportb(0x07, 0xFF);
/* cuenta del canal 3 a 0xFFFF */
outportb(0x07, 0xFF); outportb(0xCE, 0xFF);
/* cuenta del canal 7 a 0xFFFF */
outportb(0xCE, 0xFF); }
void dmako3 (void) { outportb (0x0B, 0x5B);
/* canal 3: autoinic., read */
outportb (0x0A, 3);
/* desenmascarar */
}
void dmaok3 (void) { outportb (0x0A, 7);
/* enmascarar */
outportb (0x0B, 0x43);
/* canal 3: modo normal */
}
void dmako7 (void) { outportb (0xD6, 0x5B);
/* canal 7: autoinic., read */
outportb (0xD4, 3);
/* desenmascarar */
}
void dmaok7 (void) { outportb (0xD4, 7);
/* enmascarar */
outportb (0xD6, 0x43);
/* canal 7: modo normal */
}
27,8 ██████ ██████ ██████ ██████ Velocidad estimada tras la
ejecución
de
DMAKO.C
en un
AT
386-25.
Datos
calculados con test
de
el
LANDMARK
██████ ██████ ██████ ██████ ██████ ██████
7,3
██████
██████
6,3
██████
██████
██████
4,3
██████
██████
██████
██████
██████
██████
██████
██████
DMAKO 0
DMAKO 1
DMAKO 2
DMAKO 3
EL HARDWARE DE APOYO AL MICROPROCESADOR
245
12.5.5 - ACERCA DE LAS PAGINAS DE DMA. Al emplear el DMA conviene tener cuidado con evitar un desbordamiento en el offset 0FFFFh de la página de 64K empleada (DMA 8 bits). Esto se verá con más detalle en el apartado dedicado al controlador de disquetes. Hay que tener en cuenta que una dirección segmentada aparentemente inocente puede estar cruzando una frontera de DMA. Por ejemplo, 512 bytes contenidos a partir de 3FF2:0000 (que llegan hasta 3FF2:01FF) ocupan las direcciones físicas 3FF20 a la 4011F, estando contenidos en las páginas 3 y 4. Un intento de acceso DMA al límite de una página no produce error alguno, pero el resultado es la corrupción indeseada de zonas de memoria no previstas, ya que al llegar al final del segmento se vuelve de nuevo a pasar por el principio del mismo. Tratándose del DMA de 16 bits, el problema estaría en rebasar una frontera de 128 Kb. Realmente, estos problemas no se deben al propio DMA en sí y no suelen presentarse en los sistemas que emplean el DMA. Lo que sucede es que los IBM PC, AT, etc. utilizan un DMA con direcciones de 16 bits concebido para máquinas con 64K de memoria...
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
12.6 - EL CONTROLADOR DE DISQUETES NEC 765 12.6.1 - LA TECNOLOGÍA DE GRABACIÓN EN DISCO Simple y Doble densidad: MF y MFM. La superficie magnética de un disco está dividida en pistas concéntricas, en cualquiera de las cuales el cabezal de lectura/escritura puede ser posicionado con ayuda de un motor paso a paso. Los únicos datos que se almacenan en el disco son bits, como se verá. El cabezal de la unidad de disco es, en esencia, una bobina en la que se verifican dos leyes fundamentales de la física electrónica: por un lado, una corriente alterna en dicha bobina provoca un campo magnético que varía al mismo ritmo que la corriente (lo que permite magnetizar la superficie del disco para grabar los datos); por otro lado, aplicando un campo magnético variable de manera constante a la bobina se genera una tensión constante en la misma (lo que permite leer los datos previamente registrados sobre esa superficie magnética, dejando el cabezal deslizarse sobre la misma). A simple vista, por tanto, se podría intuir que registrar datos en un disco es una tarea sencilla: se podrían representar los bits (a 1 ó 0) según la presencia/ausencia de magnetización en cada punto de la superficie. Sin embargo, la electrónica y mecánicas de precisión necesarias para este tipo de grabación se escapan aún de las posibilidades tecnológicas actuales. La solución adoptada consiste en registrar, junto a los bits de datos, una frecuencia de reloj de referencia que permita localizar los bits sin problemas: entre dos registros magnéticos de referencia en el disco (marcados con '*'), puede existir o no otro registro (que es lo que implica que el dato sea un 1 ó un 0): * │
* │
│
* │
│
* │
│
* │
│
* │
│
* │
│
* │
│
│
╔╗
╔╗
╔╗
╔╗
╔╗
╔╗
╔╗
╔╗
╔╗
╔╗
╔╗
╔╗
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
║║
═╝╚═══╝╚═══╝╚═══╝╚═══╝╚════════╝╚═══╝╚═══╝╚════════╝╚════════╝╚════════╝╚═══╝╚═ 1
1
0
1
0
0
0
1
Esto es lo que se denomina grabación en simple densidad (MF). Al final, la superficie magnética se puede considerar como un conjunto de pequeños imanes magnetizados en un sentido u otro: cuando se recorra el disco con el cabezal en modo lectura, la variación magnética inducirá una corriente cuya interpretación permitirá recuperar los datos grabados. La electrónica de este sistema trabaja con dos tiempos básicos diferentes: el que transcurre entre dos impulsos del reloj de referencia (bits a 0) y el que separa un impulso del reloj de referencia de los bit a 1. Un impulso de referencia suele durar unos 500 nanosegundos y la distancia entre estos impulsos es de 8 microsegundos. Por ello, para un byte de datos son necesarios 64 microsegundos: como la disquetera da 300 vueltas por minuto, emplea 200 milisegundos en cada vuelta; esto significa que en cada pista podría almacenar teóricamente 200000/64 = 3125 bytes. En un disco convencional de 80 cilindros y dos caras (160 pistas), esto supone 500000 bytes; sin embargo, estos discos suelen almacenar 1.000.000 (doble densidad) y hasta 2.000.000 de bytes (alta densidad) antes de ser formateados (típicamente 720 Kb y 1,44 Mb tras el formateo). ¿Cómo se las apañan para doblar o cuadruplicar los discos actuales esta capacidad?. La respuesta consiste en emplear los formatos de doble y alta densidad, respectivamente. La técnica de grabación en doble densidad (MFM) consiste en prescindir de los impulsos de referencia en la medida de lo posible. El método se basa en no emplearlos para registrar bits a 1, o bien bits a 0 aislados: tan solo se usarán para registrar secuencias de varios bits consecutivos a 0 (de lo contrario, una secuencia de bits a 0, sin impulsos de referencia, implicaría una pérdida de sincronización). Aquí existen ahora tres tiempos diferentes: el intervalo elemental es el lapsus de tiempo entre dos bits a 1; un intervalo de doble duración que éste representa la secuencia de bits 1-0-1; por último, un tercer lapso de tiempo correspondiente a 1,5 intervalos
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
de tiempo elementales es empleado para crear los impulsos de referencia (marcados con '*') o abandonar su generación. Aunque en el gráfico no queda quizá muy claro, este método permite grabar el doble de datos en un mismo intervalo de tiempo que el método de simple densidad: *
*
│
│
│
│
│
│
│
│
│
│
│
│
│ │
╔╗ │ ╔╗ │
│ ╔╗ │
╔╗
╔╗
│ ╔╗
║║ │ ║║ │
│ ║║ │
║║
║║
│ ║║
║║ │ ║║ │
│ ║║ │
║║
║║
│ ║║
║║ │ ║║ │
│ ║║ │
║║
║║
│ ║║
║║ │ ║║ │
│ ║║ │
║║
║║
│ ║║
══╝╚═══╝╚════════╝╚══════╝╚═══╝╚═════╝╚═ 1
1
0
1
0
0
0
1
Las unidades de alta densidad y las (ya difuntas) de extra alta densidad se basan en una mayor depuración de la electrónica de control, que permite reducir los tiempos de los diversos intervalos. El formateo del disco: Ejemplo con el NEC 765. La división del disco en pistas no es suficiente, ya que la cantidad de datos que almacenan es demasiado elevada (unos 9 Kb por cada cilindro y cara en los discos de alta densidad actuales). Por tanto, se comprende la necesidad de subdividir cada pista en unidades lógicas menores (sectores) de un tamaño razonable, que puedan ser accedidas por separado. En esto consiste el proceso de formateo, en el que el disco queda estructurado como se describirá a continuación. Se ha tomado como referencia el proceso de formateo que realiza el FDC (Floppy Disk Controller) 765 de NEC en MFM (en MF varía ligeramente). El disco posee una perforación de índice (el pequeño agujerito de la superficie) que es comprobada por un sensor óptico, lo que permite detectar el inicio de la información grabada en cada pista. Nada más comenzar la pista, hay 80 bytes con el valor 4Eh (ver esquema de la página siguiente): es lo que se denomina el GAP 4A (GAP significa algo así como hueco o espacio). La razón de existencia de este pequeño área se debe a la necesidad de sincronizar las distintas unidades de disco, ya que no todos los sensores ópticos actúan de manera totalmente idéntica. Tras el GAP 4Ah se escriben 12 bytes a 0 en un área denominada SYNC. La misión de estos bytes a cero es crear un área de marcas de sincronismo para que el controlador de disco se sincronice con el reloj de referencia. Tras el campo SYNC viene un área especial de tres bytes denominada Index Address Mark o IAM (marca de dirección índice), que existe sólo al principio de la pista. Tras ella aparece un byte 0FCh y, detrás, un GAP 1, en esta ocasión de 50 bytes con el valor 4Eh: su misión es dar tiempo a que el FDC procese la marca de dirección índice, que será decodificada e interpretada por hardware. Después, a continuación vienen ya los sectores de datos del disco, que tienen todos el mismo formato. Los sectores comienzan por 12 bytes de SYNC (a 0), a los que sigue la ID Address Mark o ID-AM (marca de dirección de identificación), también de 3 bytes. Detrás, un byte 0FEh. Tras todo esto, aparece el campo de ID: son 4 bytes que contienen la siguiente información: número de cilindro, cara del disco, número de sector y tamaño de sector (en la forma (LOG2 bytes_por_sector)-7). Esto permite identificar a cada sector por separado. Por razones de seguridad, se realiza una comprobación CRC (especie de suma de seguridad) de 16 bits entre la ID-AM y los 4 bytes del campo ID, cuyo resultado se almacena en los dos bytes inmediatamente siguientes, con objeto de detectar futuros fallos en la integridad de la información. Para dar tiempo al FDC a que se prepare para leer los datos que se vienen encima, hay después un nuevo GAP 2 de 22 bytes con el valor 4Eh. Entre otras razones, este área le sirve al FDC, en las operaciones de escritura, para abandonar la lectura y prepararse para la inminente escritura (tarea que siempre lleva algo de tiempo). Detrás vienen otros 12 bytes SYNC. Tras él otros 3 bytes: constituyen la DATA Address Mark o DATA-AM (similar a la ID-AM o a la IAM) y, finalmente, un byte 0FBh. ¡Ahora sí!, tras ello vienen los datos del sector: puede tener una longitud de 128, 256, 512, 1024, 2048 ó 4096 bytes (según haya sido definido) que nada más ser formateado es inicializado con un valor seleccionable por el usuario. Por supuesto, a este área de datos se le aplica también un algoritmo CRC (junto con los bytes de la DATA AM y el byte 0FBh) y los 2 bytes que se obtienen se graban a continuación. Finalmente, aparece el GAP 3, formado por cierto número de bytes 4Eh seleccionable por el
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
usuario al formatear (típicamente entre 54 y 116). Este último GAP tiene una función muy importante: al escribir un sector en el disco, es difícil que la velocidad de la unidad sea totalmente idéntica a la de la unidad que formateó el disco: si es menor, no sucede nada (el sector ocuparía un pelo menos de disco) pero si es mayor, el GAP 3 evita que se invada el siguiente sector. Cuando se escriben datos, el GAP 3 es mucho menor que cuando se formatea (del orden de la mitad de tamaño), para asegurar que no se invadirá la zona del siguiente sector si la unidad es algo más rápida de lo previsto. Los sectores se suceden unos tras otros hasta completar la pista. Después, el resto del espacio hasta que aparezca de nuevo la perforación de índice se rellena con el GAP 4B final. Todo esto, en MFM (en MF, por ejemplo, los bytes añadidos entre sectores por el 765 -excluyendo el GAP 3- no son 62 en total sino 31). GAP 4A SYNC IAM I-FC GAP 1 ┌─────────────┬─────────────┬─────────┬─────────┬─────────────┬─ Principio de pista ──Ψ │ 80 bytes 4E │ 12 bytes 00 │ 3 bytes │ byte FC │ 50 bytes 4E │ ... └─────────────┴─────────────┴─────────┴─────────┴─────────────┴─ SYNC ID-AM I-FE ID CRC GAP 2 SYNC ─┬─────────────┬─────────┬─────────┬─────────┬─────────┬─────────────┬─────────────┬─
┐
... │ 12 bytes 00 │ 3 bytes │ byte FE │ 4 bytes │ 2 bytes │ 22 bytes 4E │ 12 bytes 00 │ ... │ ─┴─────────────┴─────────┴─────────┴────┬────┴─────────┴─────────────┴─────────────┴─ └─Ψ Cilindro, cara, nº sector, tamaño
│ │ │Ψ SECTOR
DATA-AM
I-FB
DATOS DEL SECTOR
CRC
GAP 3
─┬─────────┬─────────┬─────────────────────────────────┬─────────┬─────────────────┬─ ... │ 3 bytes │ byte FB │
128, 256, 512,..., 4096 bytes
┘
GAP 4B ─┬─────────────────────────────┐
... │ otro sector │ ... │ ─┴─────────────┴─
│
│ 2 bytes │ 54-116 bytes 4E │ ... │
─┴─────────┴─────────┴─────────────────────────────────┴─────────┴─────────────────┴─
─┬─────────────┬─
│
│ Χ── Fin de pista
100-400 bytes 4E
─┴─────────────────────────────┘
12.6.2 - DESCRIPCIÓN DEL FDC (Floppy Disk Controller) 765. Este controlador de disquetes es un chip muy evolucionado que realiza tareas de un nivel relativamente alto. Fabricado inicialmente por NEC, también lo comercializan Rockwell (R 6765) e Intel (i8272). Sus principales características son: tamaño de sector programable (128, 256, 512, 1024, 2048 ó 4096 bytes), posibilidad de programar todos los datos de las unidades, capacidad para controlar 4 disqueteras, transferencia con o sin DMA, generación de interrupciones; es compatible con múltiples microprocesadores (Z80, 8086,...) y trabaja con un reloj sencillo de una sola fase (4 u 8 Mhz). Soporta densidades MF (simple densidad) y MFM (doble densidad) en unidades estándar de 3, 3½, 5¼ y 8 pulgadas. ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▌ RESET ██▌ 1 ▌ -RD ██▌ 2 ▌ -WR ██▌ 3 ▌ -CS ██▌ 4 ▌ A0 ██▌ 5 ▌ DB0 ██▌ 6 ▌ DB1 ██▌ 7 ▌ DB2 ██▌ 8
▐ 40 ▐██ Vcc ▐ 39 ▐██ -RW/SEEK ▐ 38 ▐██ LCT/DIR ▐ 37 ▐██ FR/STP ▐ 36 ▐██ HDL ▐ 35 ▐██ RDY ▐ 34 ▐██ WP/TS ▐ 33 ▐██ FLT/TRK0
▌ DB3 ██▌ 9 ▌ DB4 ██▌ 10 ▌ DB5 ██▌ 11 ▌ DB6 ██▌ 12 ▌ DB7 ██▌ 13 ▌ DRQ ██▌ 14 ▌ -DACK ██▌ 15 ▌ TC ██▌ 16 ▌
▐ 32 ▐██ PS0 ▐ 31 ▐██ PS1 ▐ 30 ▐██ WR DATA ▐ 29 ▐██ DS0 ó US0 ▐ 28 ▐██ DS1 ó US1 ▐ 27 ▐██ HDSEL ▐ 26 ▐██ MFM ▐ 25 ▐██ WE ▐
EL HARDWARE DE APOYO AL MICROPROCESADOR
IDX ██▌ 17
24 ▐██ VCO
▌
▐
INT ██▌ 18
SEÑALES DEL 765
23 ▐██ RD DATA
▌
▐
CLK ██▌ 19
22 ▐██ DWIN
▌
▐
GND ██▌ 20 ▌
284
21 ▐██ WR CLK '765
▐
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
Interface con la CPU. RESET:Reset. Línea de reinicialización al estado por defecto. -CS:Chip Selection. Línea de selección del integrado. -RD:Read. Patilla por la que la CPU lee datos del FDC. -WR:Write. Patilla por la que la CPU escribe datos en el FDC. A0:Address. Esta línea de dirección define dos direcciones de E/S para comunicar con la CPU. Suele ir conectada al A0 de la CPU. DB0..7:Data Bus. 8 líneas de datos bidireccionales. INT:Interrupt. Salida de petición de interrupción a la CPU del FDC, por cada byte transferido. Señales para el modo DMA. DRQ:DMA Request. Solicitud de DMA al controlador de DMA. -DACK:DMA Acknowledge. Señal de reconocimiento de solicitud concedida. TC:Terminal Count. Línea que indica el final de la cuenta de transferencia en modo DMA; cuando no se emplea el DMA sirve también para acabar la transferencia en sistemas controlados por interrupciones.
Señales para el interface con la disquetera. DS0-1:Drive Select 0-1. También conocidas como US0-1 (Unit Select). Selecciona una de las cuatro disqueteras conectadas. HDSEL:Head Select. Selecciona el cabezal en unidades de doble cara. HDL:Head Load. Empleado para provocar el contacto físico del cabezal sobre el disquete o levantarlo. IDX:Index. Entrada del sensor óptico que detecta el inicio de la pista gracias a la perforación de índice del disquete. RDY:Ready. Señal enviada por la disquetera indicando que el disco gira a velocidad adecuada (el FDC espera a que se cumpla RDY). WE:Write Enable. Salida que habilita la escritura de datos en el disquete. -RW/SEEK:Read Write/Seek. Algunas de las líneas que comunican el FDC con la disquetera tienen doble función (para ahorrar patillas en el chip): esta señal permite elegir la función de las 4 siguientes patillas. FR/STP:Fit Reset/Step. La función FR permite borrar el error de flip-flop de algunas unidades. La función STP, mucho más utilizada, mueve un paso (un cilindro) la cabeza de lectura/escritura (en la dirección que indica LCT/DIR). FLT/TRK0:Fault/Track0. La señal FLT es generada por algunas disqueteras en caso de error, pudiendo borrarse a través de la patilla anterior (FR/STP). La salida TRK0 indica cuándo el cabezal alcanza el cilindro 0, gracias a un sensor óptico o mecánico, tras el comando de programación Seek o el de recalibración. LCT/DIR:Low Current/Direction. La señal LCT es necesaria para limitar la corriente de escritura al acceder a los cilindros más internos, por razones físicas. DIR indica en modo Seek el sentido del movimiento del cabezal. WP/TS:Write protect/Two Side. La señal WP indica si el disco está protegido contra escritura y es comprobada en las operaciones de lectura/escritura; la señal TS se comprueba en las operaciones Seek y sólo es necesaria en unidades de dos cabezales. WR DATA: Write Data. Línea de entrada en serie de los datos de escritura (para escribir sector, para formatear,...). PS0-1:Pre Shift 0-1 (Precompensation). En el formato MFM, el FDC indica a la circuitería electrónica adecuada cómo debe ser escrito el flujo de datos: para la precompensación caben tres estados posibles (Early, Normal y late). RD DATA:Read Data. Entrada al FDC de datos en serie (bits) procedentes de la disquetera y leídos del disquete. DW:Data Window. Señal obtenida en un separador de datos a partir de los datos leídos. VCO:VCO Syn. Esta señal es precisa en el separador de datos PLL para el control del VCO. MFM:MFM Mode. Indica al FDC si se trabaja en simple o doble densidad. Alimentación y señales de reloj. Vcc:Entrada de +5v, el chip no suele consumir más de 150 mA. GND:Masa. CLK:Entrada de reloj: 4 u 8 MHz habitualmente. WR CLK:Entrada de reloj para controlar la transferencia: determina la velocidad de transferencia de datos con la disquetera.
PROGRAMACIÓN DEL '765 La única línea de direcciones del integrado (A0) define dos únicos puertos de E/S: el primero es el registro principal de estado que sólo puede ser leído. A través del segundo puerto, de lectura/escritura, se
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
accede al registro de datos, a través del cual se programa el FDC, se envían y reciben los datos y se obtienen los resultados. Con el FDC se trabaja en tres fases diferenciadas: la fase de comando u orden es empleada para enviar al FDC información sobre lo que tiene que hacer, lo que puede implicar enviar hasta 9 bytes en algunos comandos. A continuación viene la fase de ejecución. Finalmente, la fase de resultados puede obligar a leer del FDC hasta siete informaciones de estado diferentes (hasta que no se leen, el FDC no admite más órdenes). Este es el esquema general, si bien algunas órdenes carecen de fase de resultados, otras no tienen fase de ejecución... El FDC dispone de 5 registros de estado internos. El principal puede ser accedido directamente como se vio (A0=0) en cualquier momento. Los otros 4 registros (ST0, ST1, ST2 y ST3) sólo son accesibles en algunas órdenes y durante la fase de resultados. 1) COMANDO LEER DATOS. Para que el FDC lea los datos del disco hay que enviarle 9 bytes de información en la fase de órdenes. Este activa la señal Head Load y espera el tiempo de Head Load programado. El FDC comienza a leer los ID's (identificadores) de los sectores hasta encontrar el sector buscado, con lo que pasa a la fase de ejecución, o hasta encontrar por segunda vez la perforación de índice del disco (en ese caso se pasa a la fase de resultados para dar el error). En la fase de ejecución, los datos son leídos del disco y enviados al procesador o al DMA, a razón de un byte cada 8, 16, 26.67 ó 32 microsegundos (según la densidad empleada: a 1000, 500, 300 y 250 Kbit/seg respectivamente). Tras acabar la transferencia del último byte del último sector hay que dar un impulso en la patilla TC (Terminal Count) del 765 para evitar que siga leyendo los sectores que van detrás en el proceso denominado multi-sector-read (se leen más sectores hasta llegar al final de la pista). En este comando, al igual que en alguno más, se puede igualar el último sector de la pista al primero a ser accedido, pudiéndose prescindir en ese caso de la señal TC al acceder a un solo sector. De todas maneras, al emplear el DMA, la transferencia finalizará realmente cuando el registro contador del DMA alcanza el valor 0, al encargarse el propio controlador de DMA de activar la señal TC, pudiéndose leer por tanto el número de sectores deseado. Personalmente he comprobado que el último número de sector en la pista es más bien el último sector al que se desea acceder. Este comando produce 7 bytes en la fase de resultados, que deben ser leídos obligatoriamente para que el FDC pueda admitir más órdenes. FORMATO DEL COMANDO LEER DATOS: ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 0
│ MT
│ MF
│ SK
│
0
│
0
│
1
│
1
│
0
│
└──┬──┴──┬──┴──┬──┴─────┼─────┴─────┴─────┴─────┘ │
│
│
│
│
└─Ψ Skip-bit: a 1 si saltar sectores borrados
│
│
│
└─Ψ a 0 si MF, a 1 si MFM
│ └─Ψ Multitrack bit: a 1 si la función multi-sector debe continuar en la segunda cara (unidades de 2 cabezales) ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 1
│
X
│
X
│
X
│
X
│
X
│ HD
│ US1 │ US0 │
└─────┴─────┴─────┴─────┼─────┴──┬──┴──┬──┴──┬──┘ │ Cabezal (0 ó 1) Χ─┘
└──┬──┘ └─Ψ Unidad (0-3)
┌───────────────────────────────────────────────┐ Byte 2
├─
Número de cilindro
─┤
Byte 3
├─
Número de cabeza
─┤
Byte 4
├─
Número de sector
─┤
Byte 5
├─
Tamaño de sector: (LOG2 nºbytes)-7
─┤
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
Byte 6
├─
Ultimo número de sector en la pista
─┤
Byte 7
├─
Tamaño del GAP 3
─┤
Byte 8
├─ Longitud de datos (si tamaño de sector = 0) ─┤ └───────────────────────────────────────────────┘
RESULTADO (OBLIGATORIO LEERLO): ┌───────────────────────────────────────────────┐ Byte 0
├─
Registro de estado 0
─┤
Byte 1
├─
Registro de estado 1
─┤
Byte 2
├─
Registro de estado 2
─┤
Byte 3
├─
Número de cilindro
─┤
Byte 4
├─
Número de cabeza
─┤
Byte 5
├─
Número de sector
─┤
Byte 6
├─
Tamaño de sector
─┤
└───────────────────────────────────────────────┘
2) COMANDO ESCRIBIR DATOS. Este comando es totalmente análogo al de lectura, pero actuando en escritura sobre el disco. La secuencia de bytes a enviar y recibir es idéntica: sólo cambian algunos bits del primer byte de comando. ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 0
│ MT
│ MF
│
0
│
0
│
0
│
1
│
0
│
1
│
└─────┴─────┴─────┴─────┼─────┴─────┴─────┴─────┘ Bytes 1 al 8 y fase de resultados: Igual que el comando LEER DATOS.
3) COMANDO LEER DATOS BORRADOS. Por sector borrado se entiende aquel cuyo DATA-AM está borrado (por haber sido grabado dicho sector con el comando Escribir Datos Borrados): estos sectores son ignorados en las operaciones normales de lectura y escritura, aunque esta orden también permite leerlos. Por supuesto, esto no tiene relación alguna con la recuperación de ficheros borrados en la unidad y la utilidad de este comando es bastante cuestionable. ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 0
│ MT
│ MF
│ SK
│
0
│
1
│
1
│
0
│
0
│
└─────┴─────┴─────┴─────┼─────┴─────┴─────┴─────┘ Bytes 1 al 8 y fase de resultados: Igual que el comando LEER DATOS.
4) COMANDO ESCRIBIR DATOS BORRADOS. Este comando graba sectores con el DATA-AM borrado, con objeto de que sólo puedan ser leídos con el comando Leer Datos Borrados. La secuencia de bytes a enviar/recibir es idéntica al comando Leer Datos: sólo cambian algunos bits del primer byte. ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 0
│ MT
│ MF
│
0
│
0
│
1
│
0
│
0
│
1
│
└─────┴─────┴─────┴─────┼─────┴─────┴─────┴─────┘ Bytes 1 al 8 y fase de resultados: Igual que el comando LEER DATOS.
5) COMANDO LEER PISTA. Este comando es similar a Leer Datos, se diferencia en que se leen todos los sectores de la pista (si el último número de sector se indica correctamente) empezando cuando se detecta el paso de la perforación de
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
índice (si el sector inicial indicado no es realmente el primer sector de la pista, se producirá error). Aún en caso de error de CRC en el campo de ID o en el de datos, se continúa leyendo la pista. ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 0
│
0
│ MF
│ SK
│
0
│
0
│
0
│
1
│
0
│
└─────┴─────┴─────┴─────┼─────┴─────┴─────┴─────┘ Bytes 1 al 8 y fase de resultados: Igual que el comando LEER DATOS.
6) COMANDO FORMATEAR PISTA. Este comando de 6 bytes realiza de manera automática y sin dar trabajo al programador todas las tareas necesarias para inicializar una pista del disquete. Tras enviar el comando, habrá que pasar al FDC 4 bytes por cada sector que haya en la pista a formatear: en ellos, para cada sector se indica el número de sector deseado, lo que permite numerar los sectores de manera no consecutiva. El factor de Interleave 1:N de un disco equivale al número N de vueltas que hay que dar para acceder una vez a toda la pista (depende de que los sectores estén numerados consecutivamente o no); elegir un interleave óptimo es decisivo para mejorar el rendimiento (si la unidad gira lo bastante rápida como para que no de tiempo a acceder a dos sectores físicamente consecutivos, el interleave debería ser mayor de 1:1; de lo contrario sería necesaria una vuelta completa del disco cada vez que se accede a dos sectores de número consecutivo, que resulta ser además lo más frecuente). El formateo comienza cuando el sensor correspondiente detecta el inicio de la pista (por la perforación de índice), por ello todas las pistas quedan con los sectores colocados exactamente en la misma posición física: así, el sector N en una cara del disco coincide en su posición con el de la otra y con el del cilindro adyacente (si se numeran todas las pistas igual, claro). ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 0
│
0
│ MF
│
0
│
0
│
1
│
1
│
0
│
1
│
└─────┴─────┴─────┴─────┼─────┴─────┴─────┴─────┘ Byte 1
Idéntico al byte 1 del comando LEER DATOS ┌───────────────────────────────────────────────┐
Byte 2
├─
Tamaño de sector: (LOG2 nºbytes)-7
─┤
Byte 3
├─
Sectores por pista
─┤
Byte 4
├─
Tamaño del GAP 3
─┤
Byte 5
├─
Byte de relleno al formatear
─┤
└───────────────────────────────────────────────┘ RESULTADO (OBLIGATORIO LEERLO): El mismo que en el comando LEER DATOS. Una vez enviado el comando, para cada sector de la pista habrá que pasar al FDC: ┌───────────────────────────────────────────────┐ 1º Byte
├─
Número de cilindro
─┤
2º Byte
├─
Número de cabeza
─┤
3º Byte
├─
Número de sector
─┤
4º Byte
├─
Tamaño de sector: (LOG2 nºbytes)-7
─┤
└───────────────────────────────────────────────┘
7) COMANDO LEER ID. Este comando permite leer del disquete el siguiente ID que aparezca. El ID asociado a cada sector son los 4 bytes asignados durante el formateo, y consiste en información relativa al número de cilindro, número de cabeza, número de sector y tamaño del mismo. Estos números suelen coincidir con los valores físicos reales relacionados con la posición que ocupa el sector en el disco, si bien se pueden falsear en técnicas de protección de datos, aunque los copiones más ordinarios esquivan sin problemas estas trampas tan simples. Este comando consta de sólo 2 bytes; en la fase de resultado devuelve la misma información que el comando Leer Datos
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
(precisamente, la información solicitada). ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 0
│
0
│ MF
│
0
│
0
│
1
│
0
│
1
│
0
│
└─────┴─────┴─────┴─────┼─────┴─────┴─────┴─────┘ Byte 1 y fase de resultados: igual que el comando LEER DATOS
8), 9) y 10) COMANDOS PARA VERIFICAR (SCAN). El comando verificar (SCAN) permite al FDC comparar los datos almacenados en el disquete con un byte enviado por el procesador. Hay 3 comandos Scan de verificación, que indican el modo de comparación por cada byte cotejado: igual, menor o igual, mayor o igual. El comando finaliza cuando se cumple el criterio de comparación elegido en todo el sector dado, cuando se comprueba el último sector de la pista o bien cuando se activa la patilla TC. La secuencia de bytes a enviar (9 en total) y a recibir es casi idéntica al comando Leer Datos: ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 0
│ MT
│ MF
│ SK
│
1
│
│
│
0
│
1
│
└─────┴─────┴─────┴─────┼──┬──┴──┬──┴─────┴─────┘ │ ┌───┘ 00 - IGUAL
10 - MENOR O IGUAL
Modo:
11 - MAYOR O IGUAL
Bytes 1 al 8 y fase de resultados: Igual que el comando LEER DATOS. Nota: Tras este comando, hay que enviar al FDC el byte que usará para la comparación.
11) COMANDO DE RECALIBRADO. Este comando mueve el cabezal al cilindro 0 del disco. El FDC comienza a generar impulsos (por medio de la línea ST) para mover el motor paso a paso hasta que se le informe que ya se ha alcanzado el cilindro 0 (a través de la patilla TRK0 del 765); en cualquier caso, el comando finaliza tras enviar un máximo de 77 impulsos a la unidad (de ahí que pueda ser preciso repetirlo en las actuales unidades de 80 cilindros, que siguen comportándose así por compatibilidad). Este comando carece de fase de resultados (puede evaluarse el resultado por medio del registro de estado) y consta de sólo 2 bytes. ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 0
│
0
│
0
│
0
│
0
│
0
│
1
│
1
│
1
│
└─────┴─────┴─────┴─────┼─────┴─────┴─────┴─────┘ Byte 1
Idéntico al byte 1 del comando LEER DATOS
12) COMANDO DE POSICIONAMIENTO DEL CABEZAL (SEEK). El 765 posee 4 registros internos que memorizan la posición del cabezal (sobre qué cilindro se halla) en las 4 unidades de disco soportadas; tras el comando de recalibrado son puestos a 0. Cuando se envía este comando al FDC, para colocar el cabezal sobre un cierto cilindro, éste comprueba si ya se encuentra sobre el mismo: en caso contrario, genera las señales de control necesarias para instruir a la disquetera. Este comando no posee fase de resultados: para comprobar el éxito de la operación hay que emplear la orden Leer Estado de Interrupciones obligatoriamente (de lo contrario, el FDC no aceptará más órdenes de lectura o escritura). En cualquier caso, si la siguiente operación es de escritura, tras este comando hay que hacer una breve pausa (15 ms vale) porque si el cabezal no ha dejado de vibrar acarrearía una escritura incorrecta (se detectaría gracias al CRC en una lectura posterior, pero ¡casi nadie verifica tras escribir!: mejor asegurar que no hay error). Si la siguiente operación es de lectura, no es necesaria dicha pausa ya que en caso de fallar, sería reintentada y no tendría mayor consecuencia. Si se trata de seleccionar el otro cabezal en el mismo cilindro, después de haber posicionado el otro, tampoco es necesaria pausa alguna. Abusar de las pausas podría acarrear una ralentización
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
del acceso, al no hallarse en ocasiones el sector buscado hasta la siguiente vuelta del disco. 3 bytes: ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ │
Byte 0
│
0
│
0
0
│
0
│
│
1
1
│
│
1
1
│
└─────┴─────┴─────┴─────┼─────┴─────┴─────┴─────┘ Byte 1
Idéntico al byte 1 del comando LEER DATOS ┌───────────────────────────────────────────────┐ │
Byte 2
│
Número de cilindro
└───────────────────────────────────────────────┘
13) COMANDO LEER ESTADO DE INTERRUPCIONES (REGISTRO DE ESTADO 0). El 765 genera interrupciones al final de un comando Seek/Recalibrado o debido a un cambio en la señal RDY (Ready) de alguna unidad; en modo NO-DMA las genera además al inicio de la fase de resultados y durante la fase de ejecución. Las dos últimas causas pueden ser reconocidas con facilidad por el microprocesador, pero con las primeras es preciso emplear este comando para conocer la causa con exactitud, gracias a los bits del registro ST0. Esta orden se compone de un solo byte, devolviendo otros 2 en la fase de resultado: ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 0
│
0
│
0
│
0
│
0
│
1
│
0
│
0
│
│
0
└─────┴─────┴─────┴─────┼─────┴─────┴─────┴─────┘ RESULTADO (OBLIGATORIO LEERLO): ┌───────────────────────────────────────────────┐ Byte 0
├─
Registro de estado 0
─┤
Byte 1
├─ Nº cilindro en que quedó el cabezal (SEEK)
─┤
└───────────────────────────────────────────────┘
14) COMANDO LEER ESTADO DE UNIDAD (REGISTRO DE ESTADO 3). Esta orden permite obtener el contenido del registro de estado ST3 de la unidad deseada, siendo éste el único medio de conseguirlo. Consta de sólo dos bytes, obteniéndose un solo byte de resultado: ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 0
│
0
│
0
│
0
│
0
│
0
│
1
│
0
│
│
0
└─────┴─────┴─────┴─────┼─────┴─────┴─────┴─────┘ Byte 1
Idéntico al byte 1 del comando LEER DATOS
RESULTADO (OBLIGATORIO LEERLO): ┌───────────────────────────────────────────────┐ Byte 0
├─
Registro de estado 3
─┤
└───────────────────────────────────────────────┘
15) COMANDO SPECIFY (ESTABLECER DATOS DE LA UNIDAD). Aunque descrito en último lugar, este comando debería ser el primero ejecutado antes de comenzar las operaciones de disco. Sirve para indicar si se va a trabajar con DMA o no, así como los tres tiempos básicos que regirán la operación del chip. Estos tiempos están en función de la velocidad de reloj empleada, dependiente de la densidad de disco seleccionada. El comando emplea 3 bytes y carece de fase de resultados. Step Rate Time: Tiempo comprendido entre dos impulsos consecutivos en la señal que mueve el motor paso a paso del cabezal (lo que determina el tiempo de acceso cilindro-cilindro). Depende de las características físicas de la unidad. El valor para los bits SR se calcula con la fórmula (16SR)*2 en unidades DD y con (16-SR) en unidades HD (tiempos expresados en milisegundos). Head Load Time: Tiempo de demora tras activar la señal Head Load, sólo relevante por lo general en unidades de 8" (en las demás suele
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
cargarse el cabezal nada más activarse la señal Motor On). El tiempo 'Head Load' (bits HL) se calcula con la fórmula (HL+1)*4 en unidades DD y (HL+1)*2 en las unidades HD. La unidad de medida es el milisegundo. Head Unload Time: Tiempo esperado, tras el último acceso al disco, hasta que la señal Head Load vuelva a ser inactiva (sólo suele ser realmente significativo, una vez más, en las unidades de 8"). Las viejas unidades de 8" normalmente estaban girando continuamente (para evitar sus lentas aceleraciones y frenados por la inercia) y levantar o bajar el cabezal era un medio de protección de la superficie magnética. El tiempo 'Head Unload' (bits HU) se calcula con la fórmula HU*32 en unidades DD y con HU*16 en unidades HD. La unidad de medida es el milisegundo. ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 0
│
0
│
0
│
0
│
0
│
0
│
0
│
1
│
1
│
└─────┴─────┴─────┴─────┼─────┴─────┴─────┴─────┘ ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ Byte 1
│ SR3 │ SR2 │ SR1 │ SR0 │ HU3 │ HU2 │ HU1 │ HU0 │ └─────┴─────┴─────┴─────┼─────┴─────┴─────┴─────┘ ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐
Byte 2
│ HL6 │ HL5 │ HL4 │ HL3 │ HL2 │ HL1 │ HL0 │
│
└─────┴─────┴─────┴─────┼─────┴─────┴─────┴──┬──┘ └─Ψ 0 - Modo DMA / 1 - NO DMA
LOS REGISTROS DE ESTADO DEL 765. Como se comentó, el 765 dispone de 5 registros de estado: el registro principal de estado, que puede ser accedido en cualquier momento; los registros ST0, ST1 y ST2 que se obtienen como resultado de diversas órdenes; y el registro ST3. Los registros ST1 y ST2 no se pueden leer directamente (sólo se obtienen como resultado de algunas órdenes), pero ST0 y ST3 pueden ser leídos con un comando al efecto. El Registro Principal de Estado. En este registro se representan en todo momento los datos más importantes sobre el estado del FDC. Sirve también para regular la comunicación entre el microprocesador y el FDC. Significado de sus bits: Bit 7 (RQM):Request For Master (listo para E/S). Cuando este bit está a 1, el FDC está listo para recibir o enviar bytes a través del registro de datos; en caso contrario no es posible la transferencia. Bit 6 (DIO):Data Input/Output (entrada/salida de datos). Cuando este bit está a 1, significa que el FDC tiene un byte preparado para el procesador. Cuando está a 0, quiere decir que está esperando un byte del procesador. Este bit no es válido hasta que RQM=1. Bit 5 (NDM):Non DMA Mode (Modo no-DMA). En modo no DMA estará a 1 si empezó la fase de ejecución; pasa a valer 0 cuando dicha fase finaliza. bit 4 (CB):FDC Busy (FDC ocupado). Cuando está a 1, el FDC está elaborando una orden de lectura o escritura y, por tanto, no puede procesar más comandos. Este bit se pone a 1 nada más recibir el primer byte de un comando, y baja cuando es leído el último byte de resultados. Bits 0..3 (DB):FDD0..3 Busy (unidad ocupada). Cada bit está asociado a una unidad (de la A:-D:). Cuando se inicia un comando Seek o un recalibrado en alguna unidad, su bit se activa: mientras alguno de estos bits esté a 1, no se podrán enviar órdenes de lectura o escritura al FDC, pero sí más comandos Seek o de recalibrado de las demás unidades. Estos bits no se ponen a 0 por sí solos: se borran enviando el comando Leer Estado de Interrupciones (si había finalizado ya el comando Seek o el recalibramiento).
El Registro de Estado 0 (ST0). Este registro se denomina también registro de estado de interrupciones, ya que en modo no DMA permite identificar la causa de las interrupciones. Bits 7, 6:Interrupt Code (código de interrupción). Con la notación Bit7-Bit6 se tiene: 00 - Normal Termination ó NT: comando finalizado con éxito. 01 - Abnormal Termination ó AT: terminación brusca (comando iniciado pero no terminado): puede deberse a un error real o puede que no, ya que algunos sistemas no emplean la señal TC y es necesario programar en ellos el último sector de la pista como el último sector a acceder. 10 - Invalid Command Issue (IC): comando inválido (comando que no puede empezar al ser ilegal;
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
puede producirse también si se ejecuta el comando Leer estado de Interrupciones sin haber ninguna en ese momento). 11 - Terminación anormal (esta señal se produce ante una variación de la línea RDY (Ready) durante el comando, que empieza pero no finaliza -por ejemplo, si se retira el disquete de la unidad en medio de una operación-). Bit 5 (SE):Seek End (Fin de Seek). Este bit se pone a 1 cuando acaba la operación Seek. Bit 4 (EC):Equipment Check (comprobación de equipo). Este bit se pone a 1 si la unidad informa de un error; también puede ponerse a 1 si, tras un recalibrado, no aparece aún la señal TRK0 que indica que se ha alcanzado el cilindro 0. Esto puede suceder si el cabezal está sobre un cilindro superior al 77, ya que el obsoleto FDC (y las más modernas controladoras de disco, por compatibilidad) sólo lo mueven un máximo de 77 cilindros antes de considerar que el intento ha fallado (repítase el recalibrado). Bit 3 (NR):Not Ready (no preparado). Se activa cuando la unidad informa de esta condición; también cuando se intenta acceder al segundo cabezal en unidades que solo tienen uno. Bit 2 (HD):Head Address (dirección de cabezal). Indica el cabezal activo en el momento de la interrupción. Bits 1, 0 (US):Unit Select (Unidad activa): unidad activa durante la interrupción (0-A y 1-B; en PS/2 01-A y 10-B).
El Registro de Estado 1 (ST1). Este registro informa, durante la fase de resultados, sobre el desarrollo de la fase de ejecución de los diversos comandos. Bit 7 (EN):End of Cylinder. Este bit se pone a 1 si se intenta acceder a un sector tras alcanzar el fin de pista programado. Bit 6:No utilizado (a 0). Bit 5 (DE):Data Error (error de datos). Se pone a 1 si al leer los datos y calcular su CRC (o al calcular el CRC de los campos de ID), éste no coincide con el CRC almacenado en el disco junto a dichos datos ó IDs cuando fueron grabados. Bit 4 (OR):Overrun (excedido el tiempo de transferencia). Los datos transitan entre el microprocesador y el FDC a una velocidad mínima determinada (8, 16, 26.67 ó 32 microsegundos). Si al leer datos del FDC el procesador no es suficientemente rápido, puede llegar un dato sobrescribiendo el anterior cuando aún no había sido leído, lo que provoca que este bit se ponga a 1 para señalar el error. Bit 3:No utilizado (a 0). Bit 2 (ND):No Data (no hay datos). Se pone a 1 durante la lectura o scan si el FDC no puede hallar el sector indicado. Se pone también a 1 con el comando leer ID si el FDC no puede leer sin errores el campo ID (si falla el CRC). Por último, también se pone a 1 si en el comando leer pista el sector inicial no es encontrado. Bit 1 (NW):Not Writable (escritura no permitida). Se pone a 1 al ejecutar algún comando que implique modificar el contenido del disco, si este está protegido contra escritura. Bit 0 (MA):Missing Address Mark (Address Mark perdida). Se pone a 1 cuando en la lectura el FDC no halla, al cabo de una vuelta completa del disco, la ID de sector. La ausencia de Data Address Mark (y la ausencia también de una Data Address Mark borrada) pone a 1 este bit (junto al bit MD del registro de estado 2).
El Registro de Estado 2 (ST2). Bit 7:No utilizado (a 0). Bit 6 (CM):Control mark (marca de control). Se pone a 1 si el FDC halla una Data Address Mark borrada durante una lectura o comando de scan. Bit 5 (DD):Data Error in Data Field (error en campo de datos). Se pone a 1 si hay error de CRC, pero sólo en el CRC correspondiente al campo de datos. Bit 4 (WC):Wrong Cylinder (cilindro erróneo). Al formatear la pista, se graba para cada sector información relativa al número de cilindro, número de cabeza, número de sector y tamaño del mismo. Si al leer después dicha pista hay contradicción entre el nº de cilindro solicitado y el nº de cilindro que fue registrado al formatear (debido normalmente a un posicionamiento del cabezal en un cilindro erróneo), este bit se pone a 1. Bit 3 (SH):Scan Equal Hit (resultado de scan igual). Tras un comando de scan con la condición de igual, este bit se pone a 1 para indicar que la comparación resultó correcta en todos los bytes. Bit 2 (SN):Scan Not Satisfied (scan no satisfecho). Si tras un comando de scan cualquiera no se halla ningún sector en la pista que corresponda con las especificaciones, este bit se pone a 1. Bit 1 (BC):Bad Cylinder (cilindro defectuoso). Este bit es similar al WC, con la diferencia de que se pone a 1 si el número de cilindro leído es 0FFh y no coincide con el de la orden. Bit 0 (MD):Missing Address Mark in Data Field (falta marca de direcciones en campo de datos). Se pone a 1 si en la lectura de datos no aparece una Data Address Mark (ni siquiera borrada).
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
El Registro de Estado 3 (ST3). Este registro de estado sólo puede ser consultado por medio de la orden Leer estado de unidad. Se obtiene la siguiente información: Bit 7 (FT):Fault (fallo). Este bit se corresponde con la línea Fault de algunas unidades. Bit 6 (WP):Write protected (protección contra escritura). Si este bit está a 1, significa que el disco introducido en la unidad está protegido contra escritura. Bit 5 (RDY):Ready (preparado). Este bit se corresponde con la línea RDY (Ready) de la unidad. Si está a 1, la unidad está preparada. Bit 4 (T0):Track 0 (cilindro 0). Este bit se corresponde con la línea TRK0 de la unidad. Si está a 1, el cabezal de la unidad y cara elegidas se encuentra en ese momento en el cilindro 0. Bit 3 (TS):Two Side (dos caras). Si este bit está a 1, la unidad de disco posee dos cabezales. Bit 2 (HD):Head Address (dirección del cabezal). Este bit se corresponde con la línea Head Select del FDC. Bits 1, 0 (US):Unit Select (unidad seleccionada). Estos bits se corresponden con el estado de dichas líneas del FDC.
12.6.3 - EL 765 DENTRO DEL ORDENADOR. El controlador de disquetes es accedido a través de dos puertos de E/S, en la dirección 3F4h (registro de estado) y en la 3F5h (datos). Adicionalmente, existe un registro denominado Registro de Salida Digital, en la dirección E/S 3F2h, que controla los motores de las unidades y permite reinicializar el sistema de disco y seleccionar la modalidad de operación (con o sin DMA). Los valores de bits establecidos para el registro de salida digital son los siguientes (los PS/2 sólo soportan dos disqueteras y el bit 1 está reservado): 7 6 5 4 3 2 1 0 ┌─────┬─────┬─────┬─────┼─────┬─────┬─────┬─────┐ │
│
│
│
│
│
│
│
│
└──┬──┴──┬──┴──┬──┴──┬──┼──┬──┴──┬──┴──┬──┴──┬──┘ │
│
│
│
│
│
│ ┌───┘
A 1 si activar motor de D: Χ─┘
│
│
│
│
│
0 0 - seleccionar A:
A 1 si activar motor de C: Χ─┘
│
│
│
│
0 1 - seleccionar B:
A 1 si activar motor de B: Χ─┘
│
│
│
1 0 - seleccionar C:
A 1 si activar motor de A: Χ─┘
│
│
1 1 - seleccionar D:
A 1 si interrupciones y DMA activos (reservado en PS/2) Χ─┘
└─Ψ A 0 si reinicializar el FDC
Tras poner a 0 el bit que reinicializa el FDC hay que devolverlo a 1 y (con o sin las interrupciones habilitadas en el bit 3) esperar la interrupción de disquete que vendrá (IRQ6 ─Ψ INT 0Eh) ejecutando después el comando leer estado de interrupciones; también hay que recalibrar, ya que el registro interno del FDC que indica el cilindro actual es puesto a 0. En las máquinas 486 en particular, es necesario hacer una leve pausa tras bajar este bit, ya que devolviéndolo inmediatamente a 1 sucede que en ocasiones el 765 no se entera del cambio ¡y no se resetea! (algunos microsegundos bastan). Efectuar un reset es conveniente tras un error de disco. En las máquinas AT o con controladoras de alta densidad existe otro registro más al que se accede en lectura, el Registro de Entrada Digital (3F7h). Su bit más significativo indica si ha habido cambio de disco en la última unidad seleccionada a través del registro de salida digital; los restantes bits se emplean para gestionar el disco duro. Una vez detectada la condición de cambio de disco, hay que bajar este bit para detectar futuros nuevos cambios por el procedimiento, un tanto extraño y quizá absurdo de llevar el cabezal al cilindro 1 y después al 0. Para leer la línea de cambio de disco el motor debe estar encendido (se puede encender, leer la línea y volver a apagarlo después tan deprisa que el usuario no note siquiera parpadear el led de la disquetera). Si no se puede bajar este bit será debido a que no hay disquete introducido. También a través del puerto 3F7h, pero actuando como salida, se accede al Registro de Control del Disquete, que permite seleccionar la velocidad de transferencia de la unidad en sus dos bits menos significativos: 00 -
500.000 bits/segundo
(disquetes de alta densidad de 1.2M y 1.44M)
01 -
300.000 bits/segundo
(disquetes de 360K en unidades de 1.2M)
10 -
250.000 bits/segundo
(disquetes de 3½ - 720K).
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
11 - 1.000.000 bits/segundo
(disquetes de 3½ - 2.88M).
Seleccionar la velocidad correcta en los AT es un requisito totalmente indispensable para lograr enviar y recibir datos del disco. Las unidades de alta densidad de 1.2M siempre trabajan con 80 cilindros, lo que sucede es que pueden leer discos de doble densidad saltando los cilindros de dos en dos. Esto significa que para leer el cilindro 15 de un disco de 360K, será necesario mover el cabezal al cilindro 30 (y programar el 765 para leer el 15, por supuesto, ya que ha sido formateado con ese número). La BIOS automatiza este tipo de operaciones, pero cuando se accede directamente al disco no queda más remedio que considerarlas. En los discos de 3½ nunca es necesario esto, ya que tienen siempre 80 cilindros. En la terminología anglosajona, la velocidad de transferencia se denomina data transfer rate y el movimiento doble del cabezal en los discos de doble densidad recibe el nombre de double stepping. Los PS/2 poseen en 3F0h y en 3F1h dos registros de estado adicionales que no es preciso considerar. Un consejo útil para los programadores en ensamblador es que realicen siempre una pequeña pausa de algunos microsegundos (40-60) entre bytes sucesivos de un comando enviado al 765. La razón para ello no está muy clara, pero las BIOS AMI de 486 hacen esto y sus motivos tendrán. Accediendo desde un lenguaje de alto nivel o en procesadores 386 o inferiores esto probablemente no es necesario. 12.6.4 - DENSIDADES DE DISCO Y FORMATOS ESTÁNDAR. Las unidades de 5¼ de doble densidad giran a 300 r.p.m. (revoluciones por minuto); esto significa que dan una vuelta cada 200 milisegundos. La velocidad de transferencia empleada es de 250 Kbit/segundo. Echando cuentas, en 200 ms se pueden registrar unos 250000*0,2 = 50000 bits de datos = 6250 bytes por pista. Los disquetes de 360K poseen 9 sectores de 512 bytes; por cada sector hacen falta además 62 bytes que añade el NEC765 (ver al final del apartado 12.6.1) y otros 80 de GAP 3 que estima oportuno IBM: en total, 654 bytes. Así, en la pista no caben 10 sectores pero sí los 9 citados. Como hay 40 cilindros en estos disquetes (y dos caras) en total caben 9*40*2 = 720 sectores (que equivalen a 360 Kb). Por supuesto, estrechando algo el GAP 3 al formatear sí se pueden introducir 10 sectores, maniobra bastante fiable que realizan ciertos formateadores avanzados. Sin embargo, IBM fue excesivamente conservadora al principio, ya que sólo formateaba 8 sectores por pista; luego se dio cuenta y rectificó. Eran los viejos discos de 320 Kb, totalmente obsoletos aunque soportados aún por el FORMAT del DOS. También han existido antaño formatos de 180 e incluso 160 Kb, basados en unidades de una sola cabeza. Las unidades de 5¼ de alta densidad giran a 360 r.p.m.; esto supone 166,66 ms por cada vuelta del disco. El aumento de velocidad se decidió por motivos de fiabilidad. A nadie se le escapa que si el disco girara más lento y se le enviaran los datos a la misma velocidad, cabrían más datos... pero todo tiene un límite (lo contrario sería un chollo). La pretensión de IBM de elevar excesivamente -para la tecnología del momento- la velocidad de transferencia (de 250 a 500 Kbit/seg) obligó a tomar la medida de acelerar la unidad. Aquí, con los disquetes de doble densidad de 5¼ se emplea la tasa de 300 Kbit/segundo: la mayor velocidad de rotación del disco es compensada exactamente por la proporcionalmente mayor velocidad de transferencia, resultando posible de esta manera leer los discos creados en unidades de doble densidad: 300000*0,16666 = 50000 bits de datos, ¡exactamente igual que en las unidades de doble densidad!. Por supuesto, estas unidades giran siempre a 360 r.p.m. y no es posible alterar la velocidad para leer los viejos formatos, como indican otras publicaciones ¡lo que cambia es la tasa de transferencia!. Las controladoras de alta densidad pueden, por lo tanto, emplear velocidades de 300, 500 y (aunque no usada en 5¼) 250 Kbit/seg. Con disquetes de alta densidad de 5¼ y a 500 Kbit/seg caben 500000*0,16666 = 83333 bits por pista (10416 bytes). El GAP 3 que emplea el FORMAT del DOS es de 84 bytes: cada sector ocupa 512+62+84 = 658 bytes, con lo que caben 15. Esto, unido a los 80 cilindros del disco permite almacenar 1200 Kb en el mismo (en estas unidades se accede a los discos de 360K saltando los cilindros de dos en dos). Las más modernas unidades de 3½ permitieron mantener la velocidad de 500 Kbit/seg con la velocidad de rotación clásica de 300 r.p.m., sin problemas de fiabilidad, lo que eleva aún más la capacidad. Con ello, los disquetes de alta densidad de 3½ almacenan 500000*0,2 = 100000 bits de datos (12500 bytes) en cada pista. El FORMAT del DOS emplea un amplio GAP 3 de 108 bytes; cada sector ocupa por lo tanto 512+62+108 = 682 bytes, con lo que caben 18 por pista en estas condiciones, lo que genera los conocidos discos de 1440 Kb. Antes de las unidades de alta aparecieron las de doble densidad de 3½: estas emplean una velocidad de 250 Kbit/segundo, con lo que sólo admiten 6250 bytes por pista (los mismos que un disquete de doble densidad de
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
5¼) y 720 Kb por disco (también emplean un GAP 3 de 80 bytes). Con controladoras de alta densidad se puede seleccionar con estos disquetes la velocidad de 300 Kbit/segundo, lo que permite formatear discos de 3½ y doble densidad con cerca de 1 Mb, sin problemas de fiabilidad. Sin embargo, el FORMAT del DOS y las rutinas de la BIOS sólo soportan en estos discos la velocidad de 250 Kbit/segundo al ser la única que los PC/XT normalmente admiten. Por supuesto, el usuario siempre puede perforar el disco para convertirlo en uno de alta densidad: la calidad de la superficie magnética en los discos de 360K es suficientemente baja para que den errores en las últimas pistas (las más próximas al centro y con menor longitud de circunferencia) al formatearles en alta densidad; sin embargo, en 3½ los fabricantes no se han complicado la vida y es probable que a veces se puedan formatear los discos de doble densidad como de alta sin problemas, algo que pese a todo no es quizá recomendable. Las unidades de 3½ detectan el tipo ┌─────────────────────────────────────────┬───────────────────┬──────────────────┬───────────────────┬──────────────────┬──────────────────┐ │
FORMATOS DE DISCO ESTÁNDAR
│ 5¼ Doble Densidad │ 5¼ Alta Densidad │ 3½ Doble Densidad │ 3½ Alta Densidad │ 3½ Extra Alta D. │
├─────────────────────────────────────────┼───────────────────┼──────────────────┼───────────────────┼──────────────────┼──────────────────┤ │ Velocidad de rotación (R.P.M.)
│
│
360
│
300
│
300
│
300
│
│ Velocidad de transferencia (bits/seg.)
│ 250000/300000(**) │
500.000
│
250.000
│
500.000
│
1.000.000
│
│ Esquema de codificación de información
│
MFM
│
MFM
│
MFM
│
MFM
│
MFM
│
│ Bytes brutos por pista
│
6.250
│
10.416
│
6.250
│
12.500
│
25.000
│
│ Tamaño de sector en bytes [1]
│
512
│
512
│
512
│
512
│
512
│
│ GAP 3 al formatear con FORMAT [2]
│
80
│
84
│
80
│
108
│
80
│
│ Bytes que usa el 765 entre sectores [3] │
62
│
62
│
62
│
62
│
62
│
│ Bytes ocupados por sector ([1]+[2]+[3]) │
654
│
658
│
654
│
682
│
654
│
│
9
│
15
│
9
│
18
│
36
│
│ Bytes que usa el 765 en inicio de pista │
146
│
146
│
146
│
146
│
146
│
│ Bytes aproximados que restan en GAP 4B
│
218
│
400
│
218
│
78
│
1310
│
│ Cilindros
│
40
│
80
│
80
│
80
│
80
│
│ Caras o cabezales
│
2
│
2
│
2
│
2
│
2
│
│ Sectores en el disco
│
720
│
2400
│
1440
│
2880
│
5760
│
│ Kbytes por disco
│
360
│
1200
│
720
│
1440
│
2880
│
│ Sectores por pista
300/360(*)
└─────────────────────────────────────────┴───────────────────┴──────────────────┴───────────────────┴──────────────────┴──────────────────┘ (*) 300 en unidades de doble densidad y 360 en las de alta densidad (**) 250.000 en unidades de doble densidad y 300.000 en las de alta densidad
de disco y las perforaciones del mismo sólo sirven para que la disquetera sepa qué velocidad de transferencia emplear (sin embargo, en 5¼ no hay perforaciones y la unidad es capaz de detectar la velocidad apropiada). Finalmente, los disquetes de extraalta densidad de 3½ trabajan con 1 Mbit/segundo de velocidad de transferencia, con 25000 bytes por pista y 36 sectores: el doble de datos que en alta densidad, pero a un precio mucho más del doble, lo que les ha convertido en un lujo y un fracaso comercial. Existen unidades de 3½ perfeccionadas por medios ópticos que almacenan 20 megabytes por disco, y que también admiten disquetes de 720K y 1.44M (y a menudo, no los de 2.88M). El secreto de estos discos ópticos (flopticals) es la precisión en el posicionamiento del cabezal, lo que permite almacenar cientos de cilindros en lugar de las 80 habituales. También hay unidades ZIP que admiten disquetes (aproximadamente de 3½) con capacidad de 100 Mb ó 1 Gb, pero menos convencionales (están sectorizadas por hardware). Los discos normales están formateados con sectores de 512 bytes en todos los casos. Estos sectores son numerados a partir de 1 (y no a partir de 0) en el momento del formateo, y así habrán de ser accedidos en el futuro. En una sola vuelta del disco es factible escribir o leer todos los sectores de una pista si se hace de una vez con el comando apropiado, ya que accediendo de sector en sector podría no dar tiempo a acceder al siguiente sector cuando el anterior acaba de pasar por delante del cabezal, lo que además obligaría a dar una vuelta al disco por cada sector, con un desplome en picado del rendimiento. Lo mismo puede suceder si los sectores están excesivamente próximos debido al empleo de un formato no estándar de más capacidad: normalmente, los GAP 3 que separan los sectores son bastante amplios como para dar tiempo al 765, en las operaciones de escritura, a conmutar entre la escritura de los últimos bytes del sector (junto al CRC que va detrás) y la lectura de los ID del sector siguiente; en caso contrario la operación de escritura de múltiples sectores terminaría con error (sector no encontrado), a no ser que fueran escritos de uno en uno, con la
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
consiguiente ralentización del acceso. Experimentalmente se puede afirmar que el GAP 3 en alta densidad no debería ser inferior a 32, ni tampoco inferior a 40 en doble densidad, lo que parece indicar que la unidad necesita que los sectores estén separados al menos entre 0.5 y 1 ms, respectivamente; aunque estas cifras se pueden rebajar incluso casi a la mitad, esos valores son los mínimos recomendados. En caso de tener que infringir esta regla, la solución sería emplear un interleave distinto del 1:1 habitual: en otras palabras, los sectores pueden ser numerados de manera no consecutiva. Por ejemplo, con 9 sectores, se les puede colocar en la pista, sucesivamente, con los números 1, 6, 2, 7, 3, 8, 4 ,9, 5. Así, entre dos sectores de número consecutivo hay otro, y se gana tiempo para poder pillarlo; este ejemplo en concreto corresponde a un interleave 1:2, ya que hay que dar dos vueltas al disco para poder acceder una vez a toda la pista. Hay casos en que al juntar mucho los sectores e intentar escribir una pista no se produce el error: esto puede ocurrir sobre todo con sectores de más de 512 bytes, ya que cuando el cabezal acaba de acceder a un sector y va a por el siguiente (que acaba de pasar de largo), no encuentra los ID del que va detrás hasta pasado un buen rato; de ahí a volver a encontrarse con el sector buscado puede transcurrir bastante menos de una vuelta del disco y finalmente lo encontraría sin devolver error. Naturalmente, esto sigue sin ser interesante, una vez más, por razones de velocidad. Finalmente señalar que el GAP mínimo para operaciones de lectura multisector es mucho menor que para las operaciones de escritura (bastaría con un GAP de 1 ó 2 bytes), ya que la unidad no pierde tiempo en conmutar entre la escritura del sector y la lectura de IDs del siguiente. Un pequeño detalle más: conviene recordar que al formatear una pista, la controladora espera al paso de la marca de índice -el pequeño agujerito del disquete- lo que provoca que si todas las pistas se numeran por igual, en ambas caras del disco están colocados físicamente en la misma posición los mismos números de sector, gracias a esta sincronización, conservando la estructura a lo largo de unos radios imaginarios. Digamos que si el disco es una tarta, al cortar las porciones cada comensal se lleva todos los cilindros del mismo y único sector N que le ha tocado. En la operación habitual del disco, cuando se acaba de acceder a una pista, lo más probable es que haya que continuar en la siguiente (bien en el otro cabezal o en el cilindro adyacente). Esta conmutación de cabezal hace perder cierto tiempo: cuando se acaba de acceder a una pista, el cabezal está al final de la misma y, por consiguiente, muy cerca también del principio (a nadie se le escapa que las pistas son circulares); si se conmuta de cabezal y el disco ya ha girado lo suficiente como para pasar por delante del primer sector de la nueva pista, habrá que volver a dar una vuelta entera. Esto puede suceder si el GAP que hay al final de la pista no es lo suficientemente grande. Y, por desgracia, de hecho sucede con todos los formatos de disco del DOS. Al pasar de una pista a la adyacente, en operaciones de escritura, se pierden unos 18 milisegundos (3 del desplazamiento del cabezal y 15 de espera hasta que éste deje de vibrar) lo que equivale a 1125 bytes en un disco de alta densidad de 3½: ¡unos dos sectores!. Por eso, cuando se acaba con el sector 18 de una pista y se pasa a la siguiente, el cabezal está sobre algún punto del sector 2 ó el 3 y el primer sector que se encuentra es el 3 ó el 4, teniendo que esperar a que pasen otros 15 ó 16 para llegar al 1. La solución a este problema pasa por numerar los sectores, de una pista a otra, deslizando la numeración (técnica conocida como skew o sector sliding): 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Pista N
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Pista N+1
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
Pista N+2
En el esquema se han trazado sólo tres pistas, pero las siguientes tendrían un tratamiento análogo. Realmente, al conmutar de un cabezal a otro en el mismo cilindro no hace falta deslizar tanto la numeración, ya que es una operación más ágil y con menos retardos. En el ejemplo, experimentalmente se puede determinar que en vez de 3 bastaría con desplazar 2 sectores la numeración. En los discos de 5¼ de alta densidad se pueden recomendar los mismos desplazamientos de numeración. Sin embargo, en los de 5¼ y doble densidad bastaría con desplazar un sector el orden al conmutar de cabezal (y los mismos 3 al cambiar de cilindro). En los de doble densidad de 3½ conviene desplazar un sector la numeración al conmutar de cabezal y 2 al cambiar de cilindro. Por supuesto, estos valores son los más convenientes en general, si bien algún ordenador en concreto podría operar mejor con otra numeración similar a ésta aunque no idéntica. En cualquier caso, numerar todos los sectores de las pistas por igual, que es lo que hacen todas las versiones del FORMAT del DOS (al menos hasta la versión 6.0 del sistema), resulta extremadamente ineficiente y puede reducir a la mitad la velocidad de los disquetes. Algunos buenos formateadores (como FDFORMAT con sus opciones /X e /Y) suelen tener en cuenta estos factores. Por supuesto, esta numeración de los sectores no implica la más mínima pérdida de
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
compatibilidad en los disquetes estándar: lo que sucede es que los creadores del DOS no se han preocupado demasiado hasta ahora de optimizar el rendimiento. 12.6.5 - ACCESO A DISCO CON DMA. Los disquetes son gestionados por la BIOS en todas las máquinas empleando el DMA, por medio del canal 2 del 8237. Sin embargo, como veremos en un apartado posterior, es factible realizar las operaciones directamente, sin ayuda del DMA. Al emplear el modo DMA, se produce una interrupción IRQ6 (INT 0Eh) para avisar del término de la operación de disco realizada. Al emplear el DMA conviene tener cuidado con evitar un desbordamiento en el offset 0FFFFh de la página empleada. Por ejemplo, intentar leer o grabar un sector normal de 512 bytes entre las direcciones de memoria 3FF2:0000 y la 3FF2:01FF (direcciones absolutas 3FF20 a la 4011F) resultará fallido al estar implicadas las páginas de DMA 3 y 4, cuando sólo puede estarlo una de las dos. En la práctica, será necesario reservar memoria por importe del doble del tamaño del (o los) sector(es) a ser accedido(s) y hacer cálculos para establecer una dirección de transferencia que coincida dentro de una sola página de DMA. No tener en cuenta este factor es jugar a la lotería con los discos. La BIOS del sistema se encarga de comprobar por software si el buffer facilitado cruza una frontera de DMA antes de realizar las operaciones de E/S, retornando con el error correspondiente en caso afirmativo. Por hardware es imposible detectar esta circunstancia al no producirse errores, pero sí falla la operación: se corrompen zonas de memoria no previstas y ┌─────────────────────────────────────────────────────────────────────────────┐ el resultado probable es disfunción y/o │ 765DEBUG 3.1 - UTILIDAD PARA ANALISIS AVANZADO A BAJO NIVEL DE DISQUETES. │ Programación directa del controlador NEC765 y el DMA 8237. │ cuelgue del sistema (a no ser que haya mucha │ Funcionamiento probado bajo sistemas PC XT, AT, 386 y 486. │ suerte). Sin embargo, cuando el DOS se │ │ Soporte para disquetes de 360K, 720K, 1.2M, 1.44M y 2.88M. │ carga en memoria al principio del arranque, │ │ modifica la INT 13h de la BIOS para que │ (C) 1992, 1993, 1994 - Ciriaco García de Celis. │ esta interrupción nunca devuelva un error │ │ debido a este motivo (en cambio, la INT 40h, │ │ que es quien realmente controla los disquetes │ F2 - Seleccionar unidad/densidad y resetear. │ en la inmensa mayoría de los ordenadores │ F3 - Recalibrar cabezal (necesario tras F2). │ AT y que es invocada desde INT 13h, sí │ │ puede devolver errores de frontera de DMA). │ F4 - Cambiar de cabezal. │ │
F5 - Posicionar cabezal.
│
│
F6 - Leer ID's.
│
│
F7 - Leer sector.
│
│
F8 - Escribir sector.
│
│
F9 - Formatear pista.
│
│
F10 - Conmutar MF/MFM.
│
│
ESC - Salir
│
│
│
│
│
│
Unidad A:
│
0 y Cabezal 0
│ │
│ │
500 Kbit/seg en MFM - Cilindro
│ Elige una opción: _
│
└─────────────────────────────────────────────────────────────────────────────┘
Figura 12.6.5.1
PANTALLA PRINCIPAL DEL PROGRAMA
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
El siguiente programa de ejemplo ha sido realizado íntegramente en Borland C (compilable también sin errores en Turbo C 2.0) y permite practicar al lector con la operación a bajo nivel del disco. Se pueden leer y escribir sectores (con tamaños normales o no), formatear pistas, leer los ID de una pista, y todas las operaciones auxiliares necesarias (seleccionar unidad, velocidad de transferencia, recalibrar, seleccionar cabezal, posicionar cabezal, elegir MF/MFM). La opción de leer ID's es especialmente útil para analizar discos con protecciones anticopia; se trata además de una tarea inevitable que ha de realizar necesariamente cualquier copión, como paso previo a la duplicación del disquete. En esta opción se utiliza una interesante rutina de temporización de alta precisión, empleando el 8254, para poder medir con exactitud los milisegundos de disco que ocupa cada sector en la pista y poder hacerse una idea de cómo está organizada y aprovechada. El formateo también es especialmente versátil, ya que permite editar, sin lujos pero con eficacia, los bytes de los sectores propuestos por defecto -los más razonables por otra ┌──────────────────────────────────────────────────────────────────────────────────┐ │ parte- antes de enviarlos al controlador. Este │ Sector a leer: 1 │ │ programa es un útil banco de pruebas para │ │ medir la fiabilidad de técnicas de formateo │ Tamaño de sector: │ especial, para idear y probar métodos de │ 0 -> 1-128 bytes │ protección anticopia y, en general, para │ 1 -> 256 bytes │ aprender sobre el funcionamiento a bajo │ 2 -> 512 bytes │ nivel de los discos. El dato de la velocidad │ 3 -> 1024 bytes │ de transferencia no es relevante por lo │ 4 -> 2048 bytes │ general en los PC/XT. La selección │ 5 -> 4096 bytes │ incorrecta de una sola opción puede │ │ provocar que el programa falle, aunque al │ Elige: 2 │ │ cabo de unos segundos se recupera el │ │ control. Las dos primeras opciones del │ Resultado de la operación: │ menú no son obligatorias; pero conviene │ │ [ST0=0x01] [ST1=0x00] [ST2=0x00] │ seleccionarlas al principio y, en general, │ [Cilindro 1] [Cabezal 0] [Sector 1] [Tamaño 2] │ cada vez que se cambie de disco. Una línea │ │ inferior informa permanentemente de los │ Pulsa una tecla para ver el sector [ESC=salir]. │ principales parámetros activos, si bien no │ │ conviene creer ciegamente en ella. Por │ │ ejemplo, si se ha intentado posicionar el │ │ cabezal en el cilindro 120 de un disco │ │ formateado, y luego se le vuelve a │ │ posicionar en el 70, en esa línea aparecerá el │ │ valor 70 aunque al leer los ID podríamos └──────────────────────────────────────────────────────────────────────────────────┘ descubrir que está realmente sobre el cilindro 31, ya que esa unidad no soporta ┌──────────────────────────────────────────────────────────────────────────────────┐ │ más de 82 cilindros (numerados de 0 a 81) y │ │ │ no pudo pasar del 81 cuando se le ordenó ir │ │ al 120. En este ejemplo particular, lo más │ 0000: EB 3C 90 4D 53 44 4F 53 - 35 2E 30 00 02 01 01 00 δ<ÉMSDOS5.0..... │ aconsejable después sería recalibrar, ya que │ 0010: 02 E0 00 40 0B F0 09 00 - 12 00 02 00 00 00 00 00 .α.@.Ί.......... │ el programa cree que está sobre el cilindro │ 0020: 00 00 F8 04 00 00 29 EC - 1D 64 3C 4E 4F 20 4E 41 ..°...)¥.d|╣.._£ñ..╞En │ al enviar el comando al controlador. │ 0060: 0F 8B 0E 18 7C 88 4D F9 - 89 47 02 C7 07 3E 7C FB .ï..|êMΧëG.╟.>|Φ │ │
0070:
CD 13 72 79 33 C0 39 06 - 13 7C 74 08 8B 0E 13 7C
═.ry3└9..|t.ï..|
│
│
0080:
89 0E 20 7C A0 10 7C F7 - 26 16 7C 03 06 1C 7C 13
ë. |á.|»&.|...|.
│
│
0090:
16 1E 7C 03 06 0E 7C 83 - D2 00 A3 50 7C 89 16 52
..|...|â╥.úP|ë.R
│
│
00A0:
7C A3 49 7C 89 16 4B 7C - B8 20 00 F7 26 11 7C 8B
|úI|ë.K|╕ .»&.|ï
│
│
00B0:
1E 0B 7C 03 C3 48 F7 F3 - 01 06 49 7C 83 16 4B 7C
..|.├H»£..I|â.K|
│
│
00C0:
00 BB 00 05 8B 16 52 7C - A1 50 7C E8 92 00 72 1D
.╗..ï.R|íP|ΦÆ.r.
│
│
00D0:
B0 01 E8 AC 00 72 16 8B - FB B9 0B 00 BE E3 7D F3
░.Φ¼.r.ïΦ╣..╛π}£
│
00E0:
A6 75 0A 8D 7F 20 B9 0B - 00 F3 A6 74 18 BE 9E 7D
ªu.ì ╣..£ªt.╛_}
│
00F0:
E8 5F 00 33 C0 CD 16 5E - 1F 8F 04 8F 44 02 CD 19
Φ_.3└═.^.Å.ÅD.═.
│ │ │
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
│
│ │ │
Bytes 0000-0255 del sector (1/2) │
│
Utiliza los cursores [ESC=salir]
│ │ │ │ │ └────────────────────────────────────────────────────────── ────────────────────────┘
┌────────────────────────────────────────────────────────── ────────────────────────┐ │ │ │ │ │ │ │ FF │ 00 │ 58 │ 2E │ 00 │ F2 │ 7C │ F9 │ 8B │ 0A │ 20 │ 6D │ 65 │ 61 │ 53 │ AA
0100:
58 58 58 EB E8 8B 47 1A - 48 48 8A 1E 0D 7C 32
XXXδΦïG.HHè..|2 0110:
»π..I|..K|╗..╣.. 0120:
...IO 01F0: DOS
│
20 63 75 61 6C 71 75 69 - 65 72 20 74 65 63 6C
cualquier tecla 01E0:
│
70 6C 61 63 65 20 79 20 - 70 72 65 73 69 6F 6E
place y presione 01D0:
│
64 65 20 73 69 73 74 65 - 6D 61 0D 0A 52 65 65
de sistema..Reem 01C0:
│
45 72 72 6F 72 2C 20 64 - 65 20 64 69 73 63 6F
Error, de disco 01B0:
│
CA 86 E9 8A 16 24 7C 8A - 36 25 7C CD 13 C3 0D
╩åΘè.$|è6%|═.├.. 01A0:
│
C3 B4 02 8B 16 4D 7C B1 - 06 D2 E6 0A 36 4F 7C
├┤.ï.M|▒.╥µ.6O|ï 0190:
│
33 D2 F7 36 1A 7C 88 16 - 25 7C A3 4D 7C F8 C3
3╥»6.|ê.%|úM|°├Χ 0180:
│
3B 16 18 7C 73 19 F7 36 - 18 7C FE C2 88 16 4F
;..|s.»6.|n┬ê.O| 0170:
│
70 00 AC 0A C0 74 29 B4 - 0E BB 07 00 CD 10 EB
p.¼.└t)┤.╗..═.δ³ 0160:
│
15 7C 8A 16 24 7C 8B 1E - 49 7C A1 4B 7C EA 00
.|è.$|ï.I|íK|Ω.. 0150:
│
72 BB 05 01 00 83 D2 00 - 03 1E 0B 7C E2 E2 8A
r╗...â╥....|ΓΓè. 0140:
│
50 52 51 E8 3A 00 72 D8 - B0 01 E8 54 00 59 5A
PRQΦ:.r╪░.ΦT.YZX 0130:
│
F7 E3 03 06 49 7C 13 16 - 4B 7C BB 00 07 B9 03
│
0D 0A 00 49 4F 20 20 20 - 20 20 20 53 59 53 4D SYSMS
│
44 4F 53 20 20 20 53 59 - 53 00 00 00 00 00 55 SYS.....U¬
│
│ │ │
Bytes 0256-0511 del sector (2/2) │
│ │
│
│
│
│
└──────────────────────────────────────────────────────────────────────────────────┘
Figura 12.6.5.2
│ │
Utiliza los cursores [ESC=salir]
│
LECTURA DE UN SECTOR
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Al principio del programa se asignan valores por defecto a las variables, se establece la velocidad de transferencia en 500 Kbit/seg y se reserva memoria para almacenar un sector. Como se vio anteriormente, hay que asegurar que el buffer no cruza una frontera de DMA, por lo que en la práctica se reserva el doble de la memoria necesaria y se asigna el puntero de tal manera que esto no suceda en ningún caso. El programa consta de un menú desde el que se accede a las diversas opciones que desembocan finalmente en funciones independientes. La función seleccionar() permite elegir la unidad activa, reseteándola y enviando el comando specify al FDC. La función recalibrar() envía este comando al FDC y lo repite si falla, por si estaba sobre un cilindro superior al 77; en esta función y en las restantes, para detectar el fin de la operación se espera la llegada de la interrupción de disco correspondiente (IRQ 6, ligada a INT 0Eh). La BIOS se encarga en esta interrupción de activar el bit más significativo de la posición 40h:3Eh. La función esperar_int() espera la llegada de la interrupción comprobando dicho bit durante un par de segundos antes de considerar que la operación ha fallado, devolviendo después dicho bit a 0. Realmente, aunque haya un error la interrupción debe llegar y el comando ha de finalizar. Sin embargo, el FDC es a veces demasiado flexible: por ejemplo, si la portezuela de la unidad (en 5¼) está abierta y hay un disco introducido, se puede quedar esperando indefinidamente. Además, en general, en la programación a bajo nivel es conveniente no hacer nunca bucles infinitos para esperar a que suceda algo. Tras el comando de recalibrado hay que ejecutar el de lectura de estado de interrupciones, cuyo resultado es además impreso en pantalla durante 1,5 segundos para dar tiempo a leerlo sin tener que pulsar teclas (es muy poca información y se puede leer en menos de un segundo...). La función posicionar() lleva el cabezal sobre el cilindro solicitado. Si se está trabajando con una velocidad de 300 Kbit/seg, correspondiente normalmente a un disco de 5¼ y doble densidad (360K), se pregunta al usuario si la unidad es de 80 cilindros (1.2M) y se le pide que confirme que el disco es de 360K. En ese caso, el número de cilindro será multiplicado por dos al enviar el comando seek al FDC, ya que es un disco formateado con 40 pistas. Al final se ejecuta nuevamente el comando de lectura de estado de interrupciones, imprimiendo el resultado y haciendo una pausa para que de tiempo a leerlo, aunque si se omitiera este paso y la siguiente operación fuera de escritura al menos habría que esperar 15 milisegundos para dar tiempo al cabezal a asentarse y dejar de vibrar. Realmente, en este programa ni eso haría falta, ya └────────────────────────────────────────────────────────── ┌──────────────────────────────────────────────────────────────────────────────────┐ │ │
Longitud (ms)
Sector
Tamaño
Cilindro Cabeza
ST0
ST1
ST2
──────────────────- ────── ──────────── ──────── ────── ───── ───── ─────
────────────────────────┘
│ │
│
[
10.77]
10.77
9
512 (
2)
0
0
0x00
0x00
0x00
│
┌──────────────────────────────────────────────────────────
│
[
21.53]
10.76
10
512 (
2)
0
0
0x00
0x00
0x00
│
────────────────────────┐
│
[
32.31]
10.78
11
512 (
2)
0
0
0x00
0x00
0x00
│
│
│
[
43.07]
10.76
12
512 (
2)
0
0
0x00
0x00
0x00
│
Cabeza
ST0
│
[
53.85]
10.78
13
512 (
2)
0
0
0x00
0x00
0x00
│
│
──────────────────-
│
[
64.63]
10.78
14
512 (
2)
0
0
0x00
0x00
0x00
│
────── ───── ───── ─────
│
[
75.52]
10.89
15
512 (
2)
0
0
0x00
0x00
0x00
│
│
│
[
86.30]
10.77
16
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
97.07]
10.77
17
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
111.31]
14.24
18
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
122.07]
10.76
1
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
132.85]
10.78
2
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
143.61]
10.76
3
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
154.38]
10.77
4
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
165.15]
10.77
5
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
175.93]
10.78
6
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
186.69]
10.77
7
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
197.46]
10.77
8
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
208.24]
10.78
9
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
219.00]
10.76
10
512 (
2)
0
0
0x00
0x00
0x00
│
│
[
229.78]
10.79
11
512 (
2)
0
0
0x00
0x00
0x00
│
│ │
│
[ 0x40
│
[ 0x40
│
│
0x00
0x01
0x00
0x04
0x00
0x01
0x00
0x01
0x00
0x01
0x00
0x01
0x00
[ 3595.62] 399.61
──────
────────────
────────
│ 12
512 (
2)
0
0
12
512 (
2)
0
0
12
512 (
2)
0
0
12
512 (
2)
0
0
12
512 (
2)
0
0
12
512 (
2)
0
0
12
512 (
2)
0
0
12
512 (
2)
0
0
12
512 (
2)
0
0
│
│
│
│
│
│
[ 3196.00] 399.61 0x40
│
798.94] 399.62 0x01
Cilindro
│
[ 2796.40] 399.45 0x40
│
0x00
[ 2396.95] 399.41 0x40
│
399.32] 399.32 0x01
Tamaño
│
[ 1997.53] 399.44 0x40
│
Sector
ST2
[ 1598.09] 399.66 0x40
│
ST1
[ 1198.43] 399.50 0x40
│ Una tecla para leer más ID's [ESC=salir].
Longitud (ms)
│
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
0x40
0x04
│
0x00
│
[ 3995.22] 399.61
12
512 (
2)
0
0
0x40
0x01
0x00
│
│
[ 4394.62] 399.40
12
512 (
2)
0
0
0x40
0x04
0x00
│
│
[ 4794.18] 399.56
12
512 (
2)
0
0
0x40
0x04
0x00
│
│
[ 5193.60] 399.42
12
512 (
2)
0
0
0x40
0x04
0x00
│
│
[ 5593.10] 399.50
12
512 (
2)
0
0
0x40
0x01
0x00
│
│
[ 5992.69] 399.59
12
512 (
2)
0
0
0x40
0x01
0x00
│
│
[ 6392.16] 399.47
12
512 (
2)
0
0
0x40
0x01
0x00
│
│
[ 6791.64] 399.48
12
512 (
2)
0
0
0x40
0x04
0x00
│
│
[ 7191.33] 399.70
12
512 (
2)
0
0
0x40
0x01
0x00
│
│
[ 7590.84] 399.50
12
512 (
2)
0
0
0x40
0x01
0x00
│
│
[ 7990.23] 399.40
12
512 (
2)
0
0
0x40
0x01
0x00
│
│
[ 8389.74] 399.51
12
512 (
2)
0
0
0x40
0x01
0x00
│
│ │
│ Una tecla para leer más ID's [ESC=salir].
que no hay humano tan rápido que en menos de 15 ms después de haber escogido la opción de posicionar cabezal pueda elegir la de escribir sector en el menú principal. Pero en otros programas, donde se posicione repetidamente el cabezal y se acceda al disco en escritura repetitivamente, conviene no olvidar hacer la pausa. Bueno, si se olvida, no sucede nada: sólo se podría producir algún error al escribir que no se detectaría hasta una posterior lectura. Lo malo es que estos errores son esporádicos y resulta muy difícil localizar su origen.
│
Las funciones leer_sector() y escribir_sector() son muy parecidas. La principal diferencia es que la primera muestra el sector leído (ver figura 12.6.5.2) y la segunda tiene que preguntar el byte con que rellenará el sector escrito, ya que no permite editarlo. Antes de leer el sector se rellena el buffer en memoria con la signatura 5AA5h. Tras la lectura, el sector es mostrado -incluso si se produjo erroraunque si el usuario observa que contiene precisamente 5AA5h podrá deducir que el error iba muy en serio. Hay casos en que con error y todo puede ser interesante ver el sector, como luego veremos. La lectura y escritura de los sectores se realiza por DMA, el cual es programado por prepara_dma(). └──────────────────────────────────────────────────────────────────────────────────┘
Figura 12.6.5.3
LECTURAS CORRECTA E INCORRECTA DE ID's
La función leer_id() envía 22 veces dicho comando al FDC, para leer los ID (los 4 bytes con que se formateó cada sector) y la información de estado (registros ST0..ST2). Probablemente no habrá más de 21 sectores en una pista, por lo que será posible echar un vistazo detallado a la misma. El primer sector en aparecer no es el 1 ni el de número más separación que entre otros dos sectores cualquiera, debido a los bajo: sencillamente, el primero en pasar por GAP ubicados al final de la pista y al principio de la misma el cabezal al ejecutar el comando; como la (que conviene no reducir demasiado). Para medir el tiempo, se unidad estaba girando con antelación y el programa el 8254 (u 8253 en los PC/XT) con una cuenta usuario elige la opción cuando quiere, el 0xFFFF. A partir de ese momento, se espera que llegue la primer sector visualizado será cualquier interrupción de disco y se comprueba si el contador se ha sector de la pista aleatoriamente. Si hubiera decrementado hasta 0 y se ha vuelto a recargar con 0xFFFF: en más de 21 sectores en la pista, se ese caso, la variable cnth se incrementa para indicar que han visualizarían sólo los 21 primeros en pasar pasado 65535/1193180 segundos más; si llegara a valer más de delante del cabezal. Resulta interesante 8 se abortaría el proceso al considerar que la interrupción tarda saber cuánto tiempo transcurre entre el paso demasiado en llegar (más de 0,4 segundos en los que el disco de un sector y otro, lo que permite conocer más lento ya ha dado dos vueltas). Tras el final de cada su tamaño real (interesante en discos con comando de lectura de ID, se recarga inmediatamente la cuenta protección anticopia) y también ensayar inicial (el valor 0xFFFF) en el contador 2, por el procedimiento nuevos formatos de disco. Por ejemplo, si se de bajar y subir la línea GATE del mismo, con objeto de que formatean más sectores de los que caben en empiece a contar el tiempo para el próximo sector desde ya una pista, el comando de formatear termina mismo. Se lee la información que devuelve el FDC pero no siempre con éxito, pero alguno de los últimos sectores habrá machacado a los primeros, y la manera más sencilla de verlo es examinando los ID a ver si están todos. De hecho, entre el último sector de la pista y el primero debería existir una mayor
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
│ ┌──────────────────────────────────────────────────────────
│
────────────────────────┐
│
He establecido por defecto una tabla con los cuatro
│
│
│ bytes que hay que enviar al controlador, por cada uno
│
│ de los sectores de la pista, que están numerados:
│
│
│
│
│ │ │
Tamaño
de
sector:
0
->
128
bytes
1
->
256
bytes
│ │
2
->
512
bytes
│ │
3
->
1024
bytes
│ │
4
->
2048
bytes
│ │
2
3
4
5
│
21
22
23
24
25
6
7
8
9
10
11
12
13
14
15
16
17
18
19
│
5
->
4096
bytes
│ │ │ │
Elige:
0
│
20 │ │ │ │
Puedes elegir lo siguiente:
│
│ │
1
│
│ │
│
│
│
│
1
- Introducir tú los 4 bytes de un sector.
│
│
2
- Modificar un cierto byte en todos los sectores.
│
│ ESC - Dejar las cosas como están ahora.
│
│
│
│
│
Elige opción.
│
│
│ Sector a alterar: 6
│
│ Nº Cilindro (anterior=0): 0
│
│ Nº cabezal (anterior=0): 0
│
│ Nº sector (anterior=6): 6
│
│ Tamaño sector (anterior=0): 1
│
│ ¿De acuerdo (S/N)?
│
│
│
│
│
└──────────────────────────────────────────────────────────────────────────────────┘
│ │ │
Número
de
sectores:
25
│ │ │ │
Valor
para
el
GAP
3:
50
│ │ │ │
Byte
para
inicializar
sectores:
65
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────
┌──────────────────────────────────────────────────────────────────────────────────┐ │ Resultado de la operación:
│
│
│
│
[ST0=0x01] [ST1=0x00] [ST2=0x00]
│
│
[Cilindro 65] [Cabezal 1] [Sector 0] [Tamaño 0]
│
│
│
│ Formateo correcto. Pulsa una tecla.
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
└──────────────────────────────────────────────────────────────────────────────────┘
────────────────────────┘ ┌──────────────────────────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────
│
────────────────────────┐
│
│
Puntualizaciones
sobre
el
formateo:
│
Longitud (ms)
Sector
Tamaño
Cilindro Cabeza
ST0
ST1
ST2
──────────────────- ────── ──────────── ──────── ────── ───── ───── ───── [
6.25]
6.25
19
128 (
0)
0
0
0x01
0x00
0x00
│ │ │
EL HARDWARE DE APOYO AL MICROPROCESADOR
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
│
[ 0x01
12.52] 0x00
6.26
6.26
6.26
6.27
6.26
12.86
6.27
0x00
0
0
23
128 (
0)
0
0
24
128 (
0)
0
0
25
128 (
0)
0
0
1
128 (
0)
0
0
2
128 (
0)
0
0
3
128 (
0)
0
0
4
128 (
0)
0
0
5
128 (
0)
0
0
6
256 (
1)
0
0
7
128 (
0)
0
0
8
128 (
0)
0
0
9
128 (
0)
0
0
10
128 (
0)
0
0
11
128 (
0)
0
0
12
128 (
0)
0
0
13
128 (
0)
0
0
14
128 (
0)
0
0
│ 6.26 │ 6.26 │ 6.26 │ 6.26 │ 6.26 │ 6.26 │
0x00
138.07] 0x00
0)
│
0x00
131.81] 0x00
6.26
0x00
125.55] 0x00
128 (
│
0x00
119.28] 0x00
6.26
0x00
113.03] 0x00
22
│
0x00
106.77] 0x00
6.27
0x00
100.51] 0x00
0
│
0x00
94.25] 0x00
6.26
0x00
87.98] 0x00
0
│
0x00
81.72] 0x00
6.25
0x00
75.46] 0x00
0)
│
0x00
69.19] 0x00
6.26
0x00
62.93] 0x00
128 (
│
0x00
56.68] 0x00
21
│
0x00
50.42] 0x00
0
│
0x00
37.56] 0x00
0
│
0x00
31.30] 0x00
0)
│
0x00
25.03] 0x00
128 (
│
0x00
18.77] 0x00
20
284
6.26 │
│ │ │ [ESC=salir].
Una tecla para leer más ID's │
└────────────────────────────────────────────────────────── ────────────────────────┘
Figura 12.6.5.4
FORMATEO DE UNA PISTA
se imprime por problemas de velocidad, sino que se almacena en una matriz. La variable cnth y el último valor de cuenta leído del 8254 permiten determinar con precisión milimétrica el tiempo que ha pasado desde el envío del comando de lectura de ID's hasta la obtención del resultado. El primer dato de tiempo leído es incorrecto por doble motivo: por un lado, el cabezal podía estar en medio de un sector cuando se envió el comando y el tiempo medido no sería la longitud del sector anterior sino de medio sector anterior; por otro lado, la cuenta es recargada (cambio de la línea GATE) al final de cada comando en lugar de al principio, por razones de precisión. Por ello, se imprimirán los resultados de las 21 últimas muestras, descartando la primera. En la figura 12.6.5.3 hay dos ejemplos de lectura de ID, de la primera pista de un disquete de 1.44M creado por el FORMAT del DOS. En el primero el resultado es correcto; en el segundo, la velocidad seleccionada era incorrecta (no los 500 Kbit/seg necesarios) y el FDC no ha podido encontrar los sectores, teniendo además que dar dos vueltas al disco (200 ms en cada una de ellas). Si no hubiera disquete o la portezuela estuviera abierta, al
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
cabo de un minuto y medio aparecería una pantalla con datos de tiempo N.D. (no determinado) y todos los demás bytes con ?? para indicar el error. Resulta increíble la precisión media de la medida: 399,5 ms frente a los 400 reales: una desviación media de ¡0,5 milisegundos!, si bien esto dependerá del ordenador: cuanto más rápido, más exacta resulta la medida. La función formatear_pista() pregunta los parámetros básicos (número de sectores, tamaño, GAP y byte de inicialización) y genera una tabla con los 4 bytes que hay que enviar al FDC por cada sector. Sin embargo, permite al usuario editar rudimentariamente dicha tabla con la función editar_tabla_fmt(), para permitir a éste ensayar trucos, ya que los valores propuestos por defecto son por lo general los más convenientes. En esos 4 bytes que hay por cada sector se almacenan el número de cilindro, el de cabezal, el número de sector y el tamaño. En la función de edición se permite cambiar los bytes de un sólo sector, o cambiar uno de los 4 bytes en todos los sectores. Estos 4 bytes identifican cada sector y son comparados con los que se envían en el futuro comando de lectura o escritura de sector, debiendo ┌──────────────────────────────────────────────────────────────────────────────────┐ │ coincidir plenamente para que el FDC │ Sector a leer: 6 │ │ encuentre el sector. El número de cilindro y │ │ el de cabezal suelen coincidir -y así son │ Tamaño de sector: │ propuestos por defecto- con el cilindro y el │ 0 -> 1-128 bytes │ cabezal en que esté dicho sector; cambiar │ 1 -> 256 bytes │ esto puede ser interesante en técnicas de │ 2 -> 512 bytes │ protección de información, ya que el sector │ 3 -> 1024 bytes │ desaparece pero realmente sigue estando │ 4 -> 2048 bytes │ ahí: la diferencia es que a la hora de leerlo │ 5 -> 4096 bytes │ hay que indicar al FDC no el cilindro real │ │ sobre el que está posicionado el cabezal sino │ Elige: 1 │ │ el número de cilindro y cabezal que se │ │ Resultado de la operación: │ programaron al formatear el sector, que │ │ pueden ser cualquier otro. Este programa, a │ [ST0=0x41] [ST1=0x20] [ST2=0x20] │ la hora de leer los sectores no pregunta el │ [Cilindro 0] [Cabezal 0] [Sector 6] [Tamaño 1] │ número de cilindro ni cabezal -para ahorrar │ │ tiempo- por lo que no permite verificar esta │ Error de lectura (el sector puede estar mal leído). │ propiedad, pero con una pequeña y sencilla │ Nota: el buffer de lectura contenía el patrón 5AA5. │ modificación el lector podría comprobarlo │ Pulsa una tecla para ver el sector [ESC=salir]. │ por sí mismo. Lo que sí puede resultar más │ │ interesante es cambiar el número de sector │ │ propuesto por defecto o, mejor aún: su │ │ tamaño. Al formatear la pista, el tamaño de │ │ └──────────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────────┐ │
│
│
│
│
│
│
0000:
41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41
AAAAAAAAAAAAAAAA
│
│
0010:
41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41
AAAAAAAAAAAAAAAA
│
│
0020:
41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41
AAAAAAAAAAAAAAAA
│
│
0030:
41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41
AAAAAAAAAAAAAAAA
│
│
0040:
41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41
AAAAAAAAAAAAAAAA
│
│
0050:
41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41
AAAAAAAAAAAAAAAA
│
│
0060:
41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41
AAAAAAAAAAAAAAAA
│
│
0070:
41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41
AAAAAAAAAAAAAAAA
│
│
0080:
6B 70 4E 4E 4E 4E 4E 4E - 4E 4E 4E 4E 4E 4E 4E 4E
kpNNNNNNNNNNNNNN
│
│
0090:
4E 4E 4E 4E 4E 4E 4E 4E - 4E 4E 4E 4E 4E 4E 4E 4E
NNNNNNNNNNNNNNNN
│
│
00A0:
4E 4E 4E 4E 4E 4E 4E 4E - 4E 4E 4E 4E 4E 4E 4E 4E
NNNNNNNNNNNNNNNN
│
│
00B0:
4E 4E 4E 4E 00 00 00 00 - 00 00 00 00 00 00 00 00
NNNN............
│
│
00C0:
A1 A1 A1 FE 00 00 07 00 - 40 8B 4E 4E 4E 4E 4E 4E
ííín....@ïNNNNNN
│
EL HARDWARE DE APOYO AL MICROPROCESADOR
│ 4E │ FB │ 41
00D0:
4E 4E 4E 4E 4E 4E 4E 4E - 4E 4E 4E 4E 4E 4E 4E
NNNNNNNNNNNNNNNN 00E0:
│
00 00 00 00 00 00 00 00 - 00 00 00 00 A1 A1 A1
............íííΦ 00F0:
284
│
41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41
AAAAAAAAAAAAAAAA
│
│ │ │
Bytes 0000-0255 del sector (1/1) │
│
Utiliza los cursores [ESC=salir] │
│ │ │ │ │ │ └────────────────────────────────────────────────────────── ────────────────────────┘
Figura 12.6.5.5
LECTURA DEL SECTOR DE TAMAÑO TRUCADO
los sectores es asignado al enviar el comando de formateo al FDC: todos los sectores tendrán dicho tamaño, con independencia del tamaño particular que se asigne al enviar los 4 bytes específicos. En otras palabras, si se programa un tamaño 2 (de 512 bytes) en el comando de formateo, todos los sectores serán de 512 bytes, aunque alguno esté definido como de 1024, de 256 bytes,... en el 4º byte de información enviado por cada sector al FDC. Por tanto, ¿Para que sirve este byte?: una vez más, para posibilitar la lectura. Si un sector está programado con tamaño 3 (1024 bytes) habrá de ser leído indicando tamaño 3. Si era de 512 bytes, lo que sucede es que además del sector se leen, ni más ni menos, los GAPs que van detrás, los ID's e incluso parte del siguiente sector; por supuesto que se produce un lógico error de CRC al leer, pero los datos leídos son correctos. La figura 12.6.5.4 constituye un ejemplo de formateo: en un disquete de 360K se colocan 25 sectores de 128 bytes con un GAP 3 de 50 bytes, rellenándolos al formatear con el byte 65 (41h, código ASCII de la A). Teniendo en cuenta los 62 bytes que el FDC añade entre sectores en MFM, (128+62+50)*25=6000, por debajo del límite de 6250 en este tipo de disquetes. Los 4 bytes del sector 6 resultan modificados para asignarle un tamaño 1 (256 bytes), aunque el sector es realmente de 128 bytes. La posterior lectura de ID's demuestra cómo ha quedado la pista, si bien sólo se pueden ver en una pantalla los ID de 21 sectores. En la figura 12.6.5.5 se intenta leer dicho sector y, pese al error de CRC, resulta evidente que es bien leído (junto con todo lo que va detrás). La última línea del volcado hexadecimal es el inicio del siguiente sector de la pista. El lector puede verificar que el esquema del final del apartado 12.6.1 es rigurosa y milimétricamente cierto: todos los GAPs, ID y bytes introducidos por el FDC entre sectores aparecen claramente reflejados en la figura. Por supuesto, una posterior escritura del sector 6 pisaría el 7. De ahí que, anécdotas a parte, no suele resultar muy útil generalmente hacer este tipo de maniobras... ¿o tal vez si?. La función mostrar_resultados() es invocada desde las anteriores, con objeto de leer los 7 bytes que devuelve el FDC al término de los principales comandos e imprimirles en pantalla. La función mostrar_sector() enseña en pantalla el volcado hexadecimal del buffer donde se leen los sectores, en páginas de 256 bytes, teniendo en cuenta el tamaño de los mismos y permitiendo cierta movilidad. La función motor_on() arranca el motor de la unidad si aún no estaba en marcha, ajustando al valor máximo la variable que indica cuándo se detendrá, con objeto de evitarlo en lo posible. Al menos estará girando durante 14 segundos en el peor de los casos. La función motor_off() ajusta dicha variable para que el motor se pare en unos 3 segundos. La función outfdc() envía bytes al FDC pero sin esperar más de 440 ms en caso de que éste, por cualquier error, no esté dispuesto a recibirlos. Su recíproca infdc() lee un byte del FDC considerando un fracaso la operación si éste no responde en menos de 440 ms (en estos casos devuelve un valor negativo para que la función que llama advierta el error). La función esperar_int() ya fue comentada anteriormente. Por último, la función prepara_dma() programa el 8237 para transferir el número de bytes indicado, en el modo apropiado (lectura/escritura) y en la dirección del buffer empleado.
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
case FORMATEAR:
formatear_pista (unidad, mf_mfm, cabezal,
*
case LEERIDS:
leer_id (unidad, mf_mfm,
*
case SALIR:
adios();
/********************************************************************* * *
cilindro, buffer);
* 765DEBUG 3.1 - Programa de análisis avanzado a bajo nivel de
*
los disquetes, programando el 765 y el 8237.
* Compilar en Turbo C 2.0 o Borland C (modelo Large).
*
*
break; break; break;
}
*
*
cabezal);
}
*
*********************************************************************/ void reservar_memoria (unsigned char far **buffer) { unsigned long dir;
#include #include
if ((*buffer=farmalloc(SMAX<<1))==NULL) {
#include
printf("\nMemoria insuficiente\n"); #define SMAX
32768L
exit(1);
/* mayor sector soportado por el programa */
} #define SELECT
1
#define RECALIBRAR 2
dir = ((unsigned long) FP_SEG(*buffer) <<4) + FP_OFF(*buffer);
#define SEEK
3
if ( (dir>>16) != ( (dir+SMAX) >> 16) )
#define LEERIDS
4
#define LEER
5
#define ESCRIBIR
6
#define FORMATEAR
7
#define SALIR
8
*buffer+=SMAX;
/* evitar buffer entre dos páginas de DMA */
}
int menu (unidad, vunidad, mf_mfm, cilindro, cabezal) int unidad, vunidad, *mf_mfm, cilindro, *cabezal;
#define FDCDATA
0x3F5
{
/* registro de datos del 765 */
#define FDCSTATUS 0x3F4
/* registro principal de estado del 765 */
#define ODIGITAL
0x3F2
/* registro de salida digital */
#define CONTROL
0x3F7
/* registro de control del disquete */
int opc, opcion;
clrscr(); puts("765DEBUG 3.1
-
UTILIDAD PARA ANALISIS AVANZADO A BAJO NIVEL DE
DISQUETES."); puts("
int
menu(), infdc();
void
reservar_memoria(), seleccionar(), adios(), recalibrar(),
puts("
posicionar(), leer_sector(), escribir_sector(),
Programación directa del controlador NEC765 y
el DMA 8237."); Funcionamiento probado bajo sistemas PC XT, AT,
386 y 486.");
formatear_pista(), editar_tabla_fmt(), leer_id(),
puts("
mostrar_resultados(), mostrar_sector(), motor_on(),
Soporte para disquetes de 360K, 720K, 1.2M,
1.44M y 2.88M.");
motor_off(), outfdc(), esperar_int(), prepara_dma();
puts(""); puts("
(C) 1992, 1993, 1994 - Ciriaco
García de Celis.");
void main()
puts("");
{ unsigned char far *buffer;
/* buffer para sector de hasta 4 Kb */
puts(""); puts("
int unidad=0, vunidad=0, mf_mfm=1, cabezal=0, cilindro=0;
F2 - Seleccionar unidad/densidad y
resetear."); outportb (CONTROL, vunidad);
puts("
/* velocidad por defecto */
F3 - Recalibrar cabezal (necesario tras
F2).");
reservar_memoria (&buffer);
puts(""); puts("
F4 - Cambiar de cabezal.");
puts("
F5 - Posicionar cabezal.");
break;
puts("
F6 - Leer ID's.");
break;
puts("
F7 - Leer sector.");
puts("
F8 - Escribir sector.");
for (;;) switch (menu (unidad, vunidad, &mf_mfm, cilindro, &cabezal)) { case SELECT:
seleccionar (&unidad, &vunidad);
case RECALIBRAR: recalibrar (unidad,&cabezal,&cilindro); case SEEK:
posicionar (unidad, cabezal, vunidad, &cilindro);
break;
case LEER:
leer_sector (unidad, mf_mfm, cabezal,
case ESCRIBIR:
escribir_sector (unidad, mf_mfm, cabezal,
cilindro, buffer);
cilindro, buffer);
break;
break;
puts("
F9 - Formatear pista.");
puts("
F10 - Conmutar MF/MFM.");
puts("
ESC - Salir");
gotoxy(7,25); cputs("Elige una opción:");
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
(void) infdc(); while (kbhit()) getch(); opcion=0; /**** Enviar comando 'Specify' ****/
do { gotoxy(18, 22);
outfdc (3);
printf("Unidad %c: %4d Kbit/seg en %s - Cilindro %2d y Cabezal %d",
/* comando */
if (*vunidad==3)
unidad+'A', !vunidad?500:vunidad==1?300:vunidad==2?250:1000,
outfdc (0xAF);
!*mf_mfm?"MF ":"MFM", cilindro, *cabezal);
/* tiempo de acceso pista-pista y head unload */
else if (!*vunidad)
gotoxy (25, 25);
outfdc (0xBF);
opc=getch(); if (!opc) opc=getch();
else
switch (opc) {
outfdc (0xDF);
case 60:
opcion=SELECT;
break;
case 61:
opcion=RECALIBRAR;
break;
case 62:
*cabezal^=1;
break;
case 63:
opcion=SEEK;
break;
case 64:
opcion=LEERIDS;
break;
case 65:
opcion=LEER;
break;
void recalibrar (int unidad, int *cabezal, int *cilindro)
case 66:
opcion=ESCRIBIR;
break;
{
case 67:
opcion=FORMATEAR;
break;
case 68:
*mf_mfm^=1;
break;
case 27:
opcion=SALIR;
break;
/* ESC */
clrscr();
case 0x2D:
opcion=SALIR;
break;
/* ALT-X */
printf("\n\n\n\n\n\n\n\n\n\n\n\t\t\t\tRecalibrando...");
default:
opcion=0;
break;
outfdc (2);
/* head load time = 1; modo DMA */
}
int
recal, res, pis;
*cilindro=0;
} } while (!opcion);
motor_on (unidad);
/* asegurar que el motor está en marcha */
return (opcion); /**** Recalibrar hasta dos veces si es preciso ****/
}
for (recal=0; recal<2; recal++) { void seleccionar (int *unidad, int *vunidad) { clrscr(); printf("\n\n\n\n\n\n\n\n\t\t\t
outfdc (7);
/* comando de recalibrado */
outfdc (*cabezal << 2 | unidad);
/* byte 1 de dicho comando */
Unidad (A, B,...): ");
do *unidad=(getch() | 0x20)-'a'; while ((*unidad>3) || (*unidad<0));
esperar_int();
/* esperar interrupción */
outfdc (8);
/* comando 'leer estado de interrupciones' */
/* leer resultado */
printf("%c\n\n\n", *unidad+'A');
printf("\tDensidades:\t 360K en unidad 360K:
250 Kbit/seg -> 2\n");
printf("\t\t\t 360K en unidad 1.2M:
300 Kbit/seg -> 1\n");
res=infdc();
printf("\t\t\t
1.2M:
500 Kbit/seg -> 0\n");
pis=infdc();
printf("\t\t\t
720K:
250 Kbit/seg -> 2\n");
printf("\t\t\t
1.44M:
500 Kbit/seg -> 0\n");
printf("\t\t\t
2.88M: 1000 Kbit/seg -> 3\n");
printf("\n\n\t\t\t
ST0=0x%02X - Pista=%d", res, pis);
if (!((res ^ 32) & (0xF0))) break;
/* resultado correcto */
}
printf("\n\t\tElige densidad: ");
motor_off(); delay (1500);
do *vunidad=getch()-'0'; while ((*vunidad<0) || (*vunidad>3)); } outportb (CONTROL, *vunidad);
void posicionar (unidad, cabezal, vunidad, cilindro)
/**** Modo DMA, arrancar motor y reset ****/
int *cilindro; outportb (ODIGITAL, 1<<(*unidad+4) | *unidad | 8);
/* reset */
{ int r;
delay (1); outportb (ODIGITAL, 1<<(*unidad+4) | *unidad | 8+4); /* fin reset */
clrscr(); esperar_int();
/* esperar interrupción */
printf("\n\n\n\n\n\n\n\n\n\n\n\t\t\t scanf("%d", cilindro);
outfdc (8);
/* comando 'leer estado de interrupciones' */
(void) infdc();
/* leer y desechar resultado */
if ((vunidad==1) && cilindro) {
Cilindro (0..N): ");
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
printf("\n\t\t¿Es disco 5¼-360K en unidad 1.2M-HD? (S/N): ");
outfdc (cilindro);
r=((getch() | 0x20)=='s')+1;
outfdc (cabezal);
printf("%c\n", r==1?'N':'S');
outfdc (sector);
}
outfdc (tsector);
else
outfdc (sector);
r=1;
motor_on (unidad);
outfdc (1);
/* GAP para leer: poco importante */
outfdc (t128);
/* tamaño si tsector=0 */
/* asegurar que el motor está en marcha */ esperar_int();
/* esperar interrupción */
/**** Desplazar cabezal hasta la pista ****/ mostrar_resultados (&r); outfdc (0xF);
/* comando 'Seek' */
outfdc (cabezal << 2 | unidad);
/* byte 1 de dicho comando */
motor_off();
outfdc (*cilindro*r); if (r & 0xC0) { esperar_int();
/* esperar interrupción */
outfdc (8);
/* comando 'leer estado de interrupciones' */
printf("Error de lectura (el sector puede estar mal leído).\n"); printf("Nota: el buffer de lectura contenía el patrón 5AA5.\n"); } printf("
printf("\n\t\t\t printf("
Pulsa una tecla para ver el sector [ESC=salir].");
if (getch()!=27) mostrar_sector (buffer, tsector, t128);
ST0=0x%02X", infdc()); }
Pista=%d", infdc());
motor_off(); delay (1500); void escribir_sector (unidad, densidad, cabezal, cilindro, buffer)
}
unsigned char far *buffer; { void leer_sector (unidad, densidad, cabezal, cilindro, buffer)
int r, sector, tsector, t128, gap, pokete;
unsigned char far *buffer;
long i;
{ int sector, tsector, t128;
clrscr();
long r;
printf("Sector a escribir: "); scanf("%d", §or); printf("\n\nTamaño de sector:\n");
clrscr();
printf("
0 -> 1-128 bytes\n");
printf("Sector a leer: "); scanf("%d", §or);
printf("
1 ->
256
bytes\n");
printf("\n\nTamaño de sector:\n");
printf("
2 ->
512
bytes\n");
printf("
0 -> 1-128 bytes\n");
printf("
3 -> 1024
bytes\n");
printf("
1 ->
256
bytes\n");
printf("
4 -> 2048
bytes\n");
printf("
2 ->
512
bytes\n");
printf("
5 -> 4096
bytes\n");
printf("
3 -> 1024
bytes\n");
printf("\n
printf("
4 -> 2048
bytes\n");
do tsector=getch()-'0'; while ((tsector<0) || (tsector>8));
printf("
5 -> 4096
bytes\n");
printf("%d\n", tsector);
printf("\n
Elige: ");
if (tsector==0) {
Elige: ");
do tsector=getch()-'0'; while ((tsector<0) || (tsector>8));
printf("\n
printf("%d\n", tsector);
scanf("%d", &t128);
if (tsector==0) {
}
printf("\n
Concreta el tamaño (1-128): ");
Concreta el tamaño (1-128): ");
scanf("%d", &t128);
printf("\nValor para el GAP (1/2 de el de formateo): ");
}
scanf("%d", &gap); printf("\nByte para inicializar sector: "); scanf("%d", &pokete);
for (r=0; r
/* "borrar" el buffer */
for (i=0; i
/* llenar sector */
} motor_on (unidad); motor_on (unidad); prepara_dma (0x4A, 128 << tsector, buffer); prepara_dma (0x46, 128 << tsector, buffer); outfdc (0x05 | densidad << 6);
/* comando para escribir */
outfdc (0x06 | densidad << 6);
/* comando para leer */
outfdc (cabezal << 2 | unidad);
/* byte 1 de dicho comando */
outfdc (cabezal << 2 | unidad);
/* byte 1 de dicho comando */
outfdc (cilindro);
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
outfdc (cabezal);
outfdc (tsector);
outfdc (sector);
outfdc (sectores);
outfdc (tsector);
outfdc (gap);
outfdc (sector);
outfdc (pokete);
/* byte de relleno */
outfdc (gap); outfdc (t128);
esperar_int();
/* tamaño si tsector=0 */
esperar_int();
/* esperar interrupción */
mostrar_resultados (&r);
/* esperar interrupción */
mostrar_resultados (&r);
motor_off();
motor_off();
if ((r & 0xC0)!=0) { printf ("Error al formatear. Pulsa una tecla."); getch();
if ((r & 0xC0)!=0) {
}
printf ("Error de escritura. Pulsa una tecla.");
else {
getch();
printf ("Formateo correcto. Pulsa una tecla.");
}
getch();
else {
}
printf ("Escritura correcta. Pulsa una tecla."); }
getch(); } }
void editar_tabla_fmt (unsigned char far *buffer, int numsect) { void formatear_pista (unidad, densidad, cabezal, cilindro, buffer)
int i, opcion, sector, dato;
unsigned char far *buffer; do {
{ int r, tsector, sectores, gap, pokete, i;
clrscr(); printf("Puntualizaciones sobre el formateo:\n\n");
clrscr();
printf("
printf("\n\nTamaño de sector:\n");
printf("bytes que hay que enviar al controlador, por cada uno\n");
printf("
0 ->
128 bytes\n");
printf("de los sectores de la pista, que están numerados:\n\n");
printf("
1 ->
256 bytes\n");
for (i=0; i
printf("
2 ->
512 bytes\n");
printf("\n\n
printf("
3 -> 1024 bytes\n");
printf(" 1
- Introducir tú los 4 bytes de un sector.\n");
printf("
4 -> 2048 bytes\n");
printf(" 2
- Modificar un cierto byte en todos los sectores.\n");
printf("
5 -> 4096 bytes\n");
printf("ESC - Dejar las cosas como están ahora.\n");
printf("\n
He establecido por defecto una tabla con los cuatro\n");
printf("\n
Elige: ");
Puedes elegir lo siguiente: \n\n");
Elige opción.");
do tsector=getch()-'0'; while ((tsector<0) || (tsector>8)); do {
printf("%d\n", tsector); printf("\nNúmero de sectores: "); scanf("%d", §ores); printf("\nValor para el GAP 3: "); scanf("%d", &gap);
opcion=getch(); if (!opcion) opcion=getch()<<8; } while (((opcion<'1') || (opcion>'3')) && (opcion!=27));
printf("\nByte para inicializar sectores: "); scanf("%d", &pokete); if (opcion=='1') { for (i=0; i
/* tabla propuesta para formatear */
do {
buffer[i*4]=cilindro;
printf("\n\nSector a alterar: "); scanf ("%d", §or);
buffer[i*4+1]=cabezal;
for (i=0; i
buffer[i*4+2]=i+1;
if (buffer[i*4+2]!=sector) printf("Ese sector no existe. No discutamos ");
buffer[i*4+3]=tsector;
else {
}
printf("Nº Cilindro (anterior=%d): ", buffer[i*4]); editar_tabla_fmt (buffer, sectores);
/* permitir su alteración */
scanf ("%d", &dato); buffer[i*4]=(char) dato; printf("Nº cabezal (anterior=%d): ", buffer[i*4+1]); scanf ("%d", &dato); buffer[i*4+1]=(char) dato;
motor_on (unidad);
printf("Nº sector (anterior=%d): ", buffer[i*4+2]); prepara_dma(0x4A, sectores<<2, buffer);
scanf ("%d", &dato); buffer[i*4+2]=(char) dato; printf("Tamaño sector (anterior=%d): ", buffer[i*4+3]);
outfdc (0x0D | densidad <<6);
/* comando para formatear */
scanf ("%d", &dato); buffer[i*4+3]=(char) dato;
outfdc (cabezal << 2 | unidad);
/* byte 1 de dicho comando */
}
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
if (cnth<9)
printf("¿De acuerdo (S/N)?");
tmp[i]=cnth*65535L + (65535-lectura);
} while ((getch() | 0x20)!='s');
else {
}
tmp[i]=0L;
else if (opcion=='2') {
/* error */
nec[i][0]=-1;
do {
/* no informar */
pokeb (0x40, 0x40, 0xFF);
printf("\n\nCaracterística a cambiar: \n");
}
printf(" (0) Nº Cilindro, (1) Nº cabezal,");
/* asegurar motor en marcha */
/* porque probablemente se está perdiendo mucho tiempo */
}
printf(" (2) Nº sector, (3) Tamaño de sector: "); opcion=getch();
outportb (0x61, inportb(0x61) & 0xFC);
} while ((opcion<'0') || (opcion>'3')); printf("\n Nuevo valor para todos los sectores: "); scanf ("%d", &dato);
clrscr();
for (i=0; i
printf("\r
}
printf(" Sector
Longitud (ms) Tamaño
");
Cilindro Cabeza
ST0
ST1
ST2 \n");
──────────────────- ");
} while (opcion!=27);
printf("
clrscr();
printf("────── ──────────── ──────── ────── ───── ───── ─────\n"); acu=0;
}
for (j=0; j<21; j++) {
/* rechazar primera muestra */
if (tmp[j+1] && tmp[j]) { void leer_id (unidad, densidad, cabezal)
acu+=tmp[j+1];
{
printf("
[%8.2f]%7.2f ", acu/1193.18, tmp[j+1]/1193.18);
}
unsigned long tmp[22], acu;
else
int nec[22][7];
printf("
unsigned i, j, lectura, antlectura, cnth;
N.D.
");
if (nec[j][0]>=0) { printf("
do {
%3d
", nec[j][5]);
clrscr();
printf("%5d (%3d)", nec[j][6]<9?128<
printf("\n\n\n\n\n\n\n\n\n\n\n\t\t\t\tLeyendo ID's...");
printf("
%4d
%4d
0x%02X
0x%02X
0x%02X\n", nec[j][3],
nec[j][4], nec[j][0], nec[j][1], nec[j][2]); motor_on (unidad);
}
/* asegurar que el motor está en marcha */
else { outportb (0x61, inportb(0x61) & 0xFD | 1);
/* inhibir sonido */
printf("
outportb (0x43, 0xB4);
/* contador 2 */
printf("
outportb (0x42, 0xFF); outportb (0x42, 0xFF);
??
?? ??
??
??"); ??
??\n");
}
/* cuenta 0xFFFF */ }
printf("\n\t\t
for (i=0; i<22; i++) {
Una tecla para leer más ID's [ESC=salir].");
} while (getch()!=27); outfdc (0x0A | densidad << 6);
/* comando 'Leer ID' */
outfdc (cabezal << 2 | unidad);
/* byte 1 del comando */
fin_ids:
motor_off();
} lectura=0xFFFF; cnth=0;
/* cuenta inicial */
do {
/* esperar interrupción */
void adios() {
antlectura=lectura; outportb (0x43, 0x80);
/* enclavamiento */
outportb (CONTROL, peekb(0x40, 0x8B) >> 6);
lectura=inportb(0x42);
/* parte baja de la cuenta */
clrscr(); printf("Fin de 765DEBUG\n");
lectura|=inportb(0x42) << 8;
/* parte alta de la cuenta */
if (lectura>antlectura) if (cnth++>8) break;
/* timeout */
exit (0); }
} while (!(peekb(0x40, 0x3E) & 0x80));
pokeb (0x40, 0x3E, peekb (0x40, 0x3E) & 0x7F); /* reset int. */
void mostrar_resultados (int *res) {
outportb (0x61, inportb(0x61) & 0xFE);
/* bajar GATE */
printf("\nResultado de la operación:\n\n");
outportb (0x61, inportb(0x61) | 1);
/* subir GATE */
*res=infdc(); if (*res>=0) {
if (kbhit()) if (getch()==27) goto fin_ids; /* tecla ESC */
printf("
[ST0=0x%02X] ", *res);
printf("[ST1=0x%02X] ", infdc()); for (j=0; j<7; j++) nec[i][j]=infdc();
printf("[ST2=0x%02X]\n", infdc()); printf("
[Cilindro %d] ", infdc());
/* velocidad normal */
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
int i;
printf("[Cabezal %d] ", infdc()); printf("[Sector %d] ", infdc());
/**** Evitar que la BIOS pare el motor (al menos en 14") ****/
printf("[Tamaño %d]\n\n", infdc()); }
pokeb(0x40,0x40,0xFF);
else { printf("
[ST0=??]
¡El FDC no responde!\n\n"); /**** Si no lo está, ponerlo en marcha y esperar 1 segundo ****/
} }
if (((i=peekb(0x40, 0x3F)) & (1 << unidad))==0) { outportb (ODIGITAL, 1<<(unidad+4) | 4+8 | unidad); void mostrar_sector (unsigned char far *buffer, int tamano, int tt)
pokeb (0x40, 0x3F, i | (1 << unidad));
{
delay (1000); pokeb(0x40,0x40,0xFF);
unsigned char far *p;
}
int vv, i, j, k, tecla; } vv = (1 << tamano) >> 1; if (!vv) vv++; if (tamano) tt=256;
void motor_off() {
i=0;
pokeb(0x40,0x40,55);
do {
/* la BIOS lo detendrá en 55/18.2 segundos */
}
p=&buffer[i*256]; clrscr(); printf("\n\n\n"); for (j=0; j
%04X:
", p-buffer);
for (k=0; k<8; k++) printf("%02X ", *p++);
void outfdc (unsigned char dato)
/* enviar byte al FDC */
{
/* no esperando más de 440 ms */ int t, i=0, rd;
printf("- "); for (k=8; k<16; k++) printf("%02X ", *p++); p-=16; printf("
do {
");
i++; t=peekb(0x40, 0x6C);
for (k=0; k<16; k++) {
while ((t==peekb(0x40, 0x6C)) && ((rd=inportb(FDCSTATUS)>>7)==0));
if (*p<' ') printf("."); else printf("%c", *p);
} while ((i<8) && !rd);
p++; }
if (rd) outportb (FDCDATA, dato);
printf("\n"); }
} printf("\n\t\t
Bytes %04d-%04d del sector (%d/%d)\n",
i*tt, (i+1)*tt-1, i+1, vv); printf("\t\t
Utiliza los cursores [ESC=salir]");
do tecla=getch(); while (tecla && (tecla!=27) && (tecla!=32) && (tecla!=13)); if ((tecla==32) || (tecla==13)) { i++; if (i>=vv) i=0; } if (!tecla) { tecla=getch(); if (tecla==0x48) i--;
/* cursor arriba */
if (tecla==0x50) i++;
/* cursor abajo */
if (tecla==0x47) i=0;
/* Inicio */
if (tecla==0x4f) i=vv-1;
/* Fin */
if (tecla==0x49) i-=2;
/* Re Pág */
if (tecla==0x51) i+=2;
/* Av pág */
if (i<0) i=0; if (i>=vv) i=vv-1; } } while (tecla!=27); }
void motor_on (unidad) {
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
int infdc (void)
/* leer byte del FDC */
{
/* no esperando más de 440 ms */ int t, i=0, rd;
do { i++; t=peekb(0x40, 0x6C); while ((t==peekb(0x40, 0x6C)) && ((rd=inportb(FDCSTATUS)>>7)==0)); } while ((i<8) && !rd);
if (rd) return (inportb (FDCDATA)); else return (-1);
/* fallo */
}
void esperar_int (void)
/* Esperar interrupción no más de 2 seg. */
{ int t, i=0;
do { i++; t=peekb(0x40, 0x6C); while ((t==peekb(0x40, 0x6C)) && (!(peekb(0x40, 0x3E) & 0x80))); } while ((i<37) && (!(peekb(0x40, 0x3E) & 0x80)));
pokeb (0x40, 0x3E, peekb (0x40, 0x3E) & 0x7F); }
void prepara_dma (rmodo, bytes, buffer) unsigned rmodo, bytes; unsigned char far *buffer; { unsigned long dir; unsigned dmapag, dmaoff;
dir = ((unsigned long) FP_SEG(buffer) <<4) + FP_OFF(buffer); dmapag = dir >> 16;
dmaoff = dir & 0xFFFF;
outportb (0x81, dmapag);
/* registro de página del canal 2 */
outportb (0xB, rmodo);
/* programar registro de modo */
outportb (0xC, 0);
/* clear first/last flip-flop */
outportb (4,dmaoff & 0xFF);
/* dirección base (parte baja) */
outportb (4,dmaoff >> 8);
/* dirección base (parte alta) */
outportb (5,(bytes-1) % 256); /* nº de bytes menos 1 (parte baja) */ outportb (5,(bytes-1) / 256); /* nº de bytes menos 1 (parte alta) */ outportb (0xA, 2);
/* habilitar canal 2 */
}
12.6.6 - LECTURA Y ESCRITURA DE SECTORES DE DISCO SIN DMA. Si bien lo normal es emplear el DMA para realizar los accesos a disco, ello no es estrictamente necesario (excepto en los auténticos PS/2): generalmente también se puede acceder enviando directamente los bytes al FDC, aunque sería más útil emplear el DMA (la CPU no tendría tiempos muertos de espera para mover los bytes). Realmente, bajo DOS da lo mismo acceder con el DMA que sin el, ya que aún cuando se emplea el DMA ¡la pobre CPU se queda esperando a que llegue la interrupción que indica el final de la operación!. La única ventaja real de utilizar el DMA, que motivó su uso por parte de los programadores de IBM, es que el contador de hora de la BIOS sigue avanzando (y el reloj no se atrasa), mientras que sin el DMA se pararía al tener que inhibir las interrupciones en el momento crítico de la transferencia del sector, con objeto de no perder datos. En otros sistemas operativos multitarea, el DMA permite a la CPU continuar trabajando (perdiendo sólo
EL HARDWARE DE APOYO AL MICROPROCESADOR
284
los ciclos estrictamente necesarios para la transferencia) a la par que es realizada la operación de disco: aunque el rendimiento global del sistema se degrada durante la operación, al menos no se detienen todos los procesos. El siguiente programa de ejemplo, realizado íntegramente en ensamblador, permite leer y escribir sectores de disco aislados en el formato MFM habitual. Soporta las unidades A: y B:, así como discos y disqueteras de todos los formatos y densidades -incluidos los no estándar-. Se preguntan todos y cada uno de los parámetros necesarios, dando algunas pautas para ayudar. Es importante responder correctamente, aunque el control de errores suele recuperar los fallos, sin dejar bloqueado el ordenador, en un plazo de tiempo razonable. Esta utilidad se basa en un menú principal donde se tiene acceso a las diversas opciones, que desembocan en las rutinas de bajo nivel que controlan el disco. No describiremos las rutinas encargadas de tomar datos del teclado ni tampoco las de impresión en pantalla, bastante obvias. Sin embargo, daremos un ligero repaso a las subrutinas encargadas de controlar el disco. El procedimiento init_drv enciende el motor de la disquetera y resetea el FDC a través de la subrutina reset_drv, esperando después a que el motor alcance un régimen de rotación adecuado. En reset_drv se selecciona además el modo NO DMA en el registro de salida digital, se espera por la interrupción que indica el fin del reset y se envía el comando specify al FDC; también se establece la velocidad de transferencia apropiada para el tipo de disquete a ser accedido. El procedimiento recalibrar ejecuta dicho comando del FDC hasta un máximo de dos veces en caso de fallo, entre otros motivos para prevenir que el cabezal estuviera inicialmente en una pista superior a la 77. Tanto en este procedimiento como en el seek_drv se detecta el inicio de la fase de resultados esperando la pertinente interrupción de disco (en la rutina espera_int). Debido a que las interrupciones no llegan cuando está activo el modo NO DMA en el registro de salida digital, por algún oscuro motivo que desconozco, es preciso establecer momentáneamente el modo DMA a través del bit 3 de dicho registro (rutina habilita_int) y volverlo a desactivar una vez que llega la interrupción; realmente, aún seleccionando esta modalidad, el DMA no será empleado ya que no se utiliza en los comandos de recalibración ni en el de posicionamiento del cabezal. En esta última rutina se tiene en cuenta el caso especial que supone un disquete de 40 pistas en una unidad de 80, multiplicándose entonces por 2 el número de cilindro antes de enviarlo al FDC. La rutina sector_io es la encargada de leer y escribir los sectores de disco. Tras enviar el comando al FDC, se espera que éste encuentre el sector y seguidamente se pasa a leer/escribir el mismo directamente, aunque en lugar de emplear las rutinas E/S habituales (fdc_read y fdc_write) se realiza el proceso de manera directa para acelerarlo. Más que para acelerarlo, para que no nos pille: la velocidad es aquí crítica (el proceso se realiza con las interrupciones apagadas) ya que cada 16-32 microsegundos hay que transferir un byte entre la CPU y el FDC y dormirse en los laureles supondría un error irrecuperable. Si se está escribiendo un sector y se produce un fallo, es fácil detectarlo (el FDC deja de recibir datos e intenta enviar los bytes de la fase de resultados) pero en la lectura de sectores serían leídos dichos resultados confundidos como datos del sector, aunque al terminar el comando (y bajar el bit CB del registro de estado) se detectaría afortunadamente el final de la operación y se podría suponer que los últimos 7 bytes leídos no eran del sector sino la fase de resultados. En general, si el usuario ha indicado bien todos los parámetros y el disquete no está defectuoso, no habrá problemas. Estas rutinas de lectura de sectores no están diseñadas de manera tolerante a fallos, ya que realizan saltos condicionales comprobando los bits del registro de estado, que en caso de quedarse congelados y no cambiar supondrían un cuelgue del sistema. Sin embargo, añadir controles de timeout alargaría los tiempos de ejecución y podría provocar, si no se tiene cuidado, que los PC/XT más lentos no fueran bastante potentes para acceder al disco con la suficiente rapidez. Además, la mejor técnica para controlar los timeout es, indiscutiblemente, la monitorización de los ciclos de refresco de la memoria dinámica de los AT (ese bit del puerto 61h que cambia 66287 veces por segundo): en los PC/XT sería más complicado... Por último, las rutinas fdc_read y fdc_write se encargan de la comunicación CPU-FDC en ambos sentidos, aunque aquí sí se han establecido unos rudimentarios controles de timeout, de esos que tardan más tiempo en recuperar el control en las máquinas más lentas. De ahí que estas subrutinas no sean empleadas desde sector_io, por razones de velocidad. Acceder a disco sin DMA es más incómodo y problemático que hacerlo a través del DMA, y no ofrece absolutamente ninguna ventaja adicional, a no ser que el 8237 esté averiado en el ordenador. De hecho, yo
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
personalmente dejé de utilizar durante algún tiempo el DMA en los accesos de disco (me hice un controlador especial que además me ayudó a subir nota en una asignatura), creyendo que los errores en la transferencia de datos en mis disqueteras se debían a este integrado. Sin embargo, finalmente averigué que la causa estaba en los SIPPs de memoria un tanto flojos (por fortuna, resulta que un amigo mío sí tenía estropeado el DMA de verdad en las operaciones de escritura, y ese driver le vino muy bien para poder escribir en sus disquetes). Anécdotas aparte, este programa es meramente educativo y no un modelo a seguir. ; ********************************************************************
CALL
init_drv
; *
*
CALL
recalibrar
Programa de demostración de acceso a
*
JC
fallo
bajo nivel al disquete sin emplear DMA.
*
CALL
seek_drv
*
JC
fallo
LEA
DI,buffer
CALL
sector_io
JC
fallo
CALL
imprime_sector
JMP
main
LEA
DX,cls_txt
CALL
print
LEA
DX,escritura_txt
CALL
print
LEA
DX,aviso_txt
CALL
print
CALL
pide_sector
; pedir pista, cabeza, ...
CALL
pide_relleno
; pedir byte de relleno
MOV
orden,F_WRITE
CALL
init_drv
CALL
recalibrar
JC
fallo
CALL
seek_drv
SEGMENT
JC
fallo
ASSUME CS:fdc_test, DS:fdc_test
LEA
DI,buffer
CALL
sector_io
JC
fallo
JMP
main
LEA
DX,fallo_txt
CALL
print
CALL
getch
JMP
main
; *
765NODMA.ASM 2.0
-
; * ; *
; ********************************************************************
; ************ Macros de propósito general.
XPUSH
MACRO regmem
; apilar lista de registros escribir:
IRP rm, PUSH rm ENDM ENDM
XPOP
MACRO regmem
; desapilar lista de registros
IRP rm, POP rm ENDM ENDM
; ************ Programa principal.
fdc_test
ORG
main
leer:
100h
fallo:
PROC CALL
menu
DEC
AL
JZ
leer
DEC
AL
JZ
escribir
LEA
DX,adios_txt
CALL
print
MOV
AX,40h
MOV
DS,AX
MOV
AL,DS:[8Bh]
MOV
CL,6
SHR
AL,CL
MOV
DX,3F7h
OUT
DX,AL
INT
; opciones
; opción de leer sector main
; cargar dicho sector
; mostrar su contenido
; limpiar pantalla
; mensaje inicial
; grabar dicho sector
; mensaje de error
ENDP
; opción de escribirlo ; ************ Subrutinas de apoyo ; opción de salir: menu
PROC LEA
DX,cls_txt
CALL
print
LEA
DX,opciones_txt
CALL
print
CALL
getch
CMP
AL,'1'
20h
JE
opc_ok
LEA
DX,cls_txt
CMP
AL,'2'
CALL
print
JE
opc_ok
LEA
DX,lectura_txt
CMP
AL,27
CALL
print
JE
opc3_ok
LEA
DX,aviso_txt
CMP
AX,2D00h
CALL
print
CALL
pide_sector
MOV
orden,F_READ
; velocidad previa al programa
; pasarla a bits 0..1 espera_opc: ; restaurar velocidad previa
; borrar pantalla
; mensaje inicial
; pedir pista, cabeza, ...
JNE
espera_opc
opc3_ok:
MOV
AL,'3'
opc_ok:
SUB
AL,'0'
; texto del menú
; elegida opción 1
; elegida opción 2
; ESC (opción '3')
; no es ALT-X
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
RET menu
otra_linea:
ENDP
; ------------ Solicitar información del sector a ser accedido.
pide_sector
computab:
pide_sector
pr_hexa:
PROC
MOV
CX,16
PUSH
CX
MOV
CX,16
MOV
AL,' '
CALL
printAL
MOV
AL,[BX]
INC
BX
CALL
print8hex
LEA
DX,unidad_txt
CALL
input_AL
MOV
unidad,AL
LOOP
pr_hexa
LEA
DX,vunidad_txt
MOV
AL,' '
CALL
input_AL
CALL
printAL
MOV
vunidad,AL
CALL
printAL
LEA
DX,tdisco_txt
SUB
BX,16
CALL
input_AL
MOV
CX,16
MOV
tunidad,AL
MOV
AL,[BX]
LEA
DX,tamano_txt
INC
BX
CALL
input_AL
CMP
AL,' '
MOV
tsector,AL
JAE
ascii_ok
LEA
DX,gap_rw_txt
MOV
AL,'.'
CALL
input_AL
CALL
printAL
MOV
gap,AL
LOOP
pr_ascii
LEA
DX,pista_txt
MOV
AL,13
CALL
input_AL
CALL
printAL
MOV
cilindro,AL
MOV
AL,10
LEA
DX,cabeza_txt
CALL
printAL
CALL
input_AL
POP
CX
MOV
cabezal,AL
LOOP
otra_linea
LEA
DX,sector_txt
LEA
DX,ptecla_txt
CALL
input_AL
CALL
print
MOV
sector_ini,AL
CALL
getch
MOV
sector_fin,AL
POP
CX
MOV
CL,tsector
LOOP
otra_mitad
MOV
CH,0
RET
INC
CX
MOV
AX,64
SHL
AX,1
LOOP
computab
MOV
bsector,AX
MOV
AL,cabezal
LEA
DX,relleno_txt
SHL
AL,1
CALL
input_AL
SHL
AL,1
LEA
DI,buffer
OR
AL,unidad
MOV
CX,bsector
MOV
byte1,AL
; pedir unidad
; seleccionar velocidad
; problema de 40/80 pistas pr_ascii:
; preguntar tamaño sector
; preguntar tamaño sector
ascii_ok:
; pedir pista
; pedir cabeza
; pedir sector
; CX: 1-128 bytes, 2-256, ...
; 16 líneas
; de 16 caracteres
imprime_sector ENDP
; ------------ Pedir byte para llenar el sector a grabar.
; bytes/sector
pide_relleno
PROC
; tamaño de sector en bytes
CLD
; byte 1 común a muchas órdenes
RET
REP
ENDP
RET pide_relleno
STOSB
ENDP
; ------------ Imprimir sector en hex/ASCII en bloques de 256 bytes. ; ------------ Imprimir cadena en DS:DX terminada en un '$'. imprime_sector PROC
otra_mitad:
print
PROC
LEA
BX,buffer
MOV
AX,bsector
PUSH
AX
MOV
CL,AH
MOV
AH,9
; función de impresión
MOV
CH,0
INT
21h
; llamar al sistema
AND
CX,CX
POP
AX
JNZ
otra_mitad
INC
CX
PUSH
CX
LEA
DX,cls_txt
CALL
print
; CX secciones de 256 bytes
RET ; al menos imprimir una vez
print
ENDP
; ------------ Imprimir carácter en AL
284
printAL
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
PUSH
DX
PUSH
AX
CALL
print
; función de impresión del DOS
MOV
AH,0Ah
DL,AL
; carácter a imprimir
LEA
DX,buffer
INT
21h
; llamar al sistema
MOV
BX,DX
POP
DX
MOV
WORD PTR [BX],4
; (inicializar dos variables)
POP
AX
; recuperar registros
INT
21h
; llamar al sistema
; retornar
MOV
CL,[BX+1]
XOR
CH,CH
POP
AX
POP
DX
PUSH
DX
PUSH
AX
PROC PUSH
AX
PUSH
DX
; registros usados preservados
MOV
AH,2
MOV
RET printAL
pedir_dato:
ENDP
; ------------ Imprimir carácter hexadecimal (AL).
print4hex
no_sup9:
print4hex
PROC PUSH
AX
; preservar AX
JCXZ
pedir_dato
ADD
AL,'0'
; pasar binario a ASCII
XOR
DX,DX
CMP
AL,'9'
MOV
AX,10
JBE
no_sup9
; no es letra
MUL
DX
ADD
AL,'A'-'9'-1
; lo es
MOV
DX,AX
CALL
printAL
; imprimir dígito hexadecimal
MOV
AL,[BX+2]
POP
AX
; restaurar AX
SUB
AL,'0'
RET
INC
BX
ENDP
XOR
AH,AH
ADD
DX,AX
LOOP
gen_num
POP
AX
gen_num:
; ------------ Imprimir byte hexadecimal en AL.
print8hex
MOV
AL,DL
PUSH
CX
POP
DX
PUSH
AX
POP
CX
MOV
CL,4
POP
BX
SHR
AL,CL
PROC
CALL
print4hex
imprimir
; número de caracteres pulsados
; se pulsó RETURN: reiterar
; conversión ASCII -> binario
; resultado
RET
; pasar bits 4..7 a 0..3 ;
; función de entrada (teclado)
nibble
más
input_AL
ENDP
significativo ; ------------ Encender motor y esperar a que tome cierta velocidad.
POP
AX
; restaurar AL
PUSH
AX
; y preservarlo de nuevo
AND
AL,1111b
;
dejar
nibble
menos
init_drv
CX
CALL
reset_drv
AX
MOV
CX,18
CX
CALL
retardo
POP
CX
CALL
print4hex
POP POP
; e imprimirlo
RET print8hex
PROC PUSH
significativo
; esperar aceleración disco
RET
ENDP init_drv
ENDP
; ------------ Esperar pulsación de tecla y devolverla en AX. ; ------------ Establecer modalidad de operación del controlador getch
getch
PROC
y asegurar que el motor está en marcha.
reset_drv
PROC
AH,1
; esperar carácter (algunos
INT
16h
; KEYB de XT se cuelgan
JZ
getch
; al usar directamente el
XPUSH
MOV
AH,0
; servicio 0).
PUSH
DS
INT
16h
MOV
BX,40h
; engañar al BIOS para
RET
MOV
DS,BX
; que no pare el motor al
ENDP
MOV
BYTE PTR DS:[BX],255
; menos durante 14 seg.
POP
DS
MOV
DX,3F2h
MOV
CL,unidad
; ------------ Leer nº decimal de hasta 3 dígitos y devolverlo en AL.
input_AL
;
MOV
ADD
CL,4
PUSH
BX
MOV
AL,1
PUSH
CX
SHL
AL,CL
PROC
; registro de salida digital
; colocar bit del motor
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
reset_drv
RET
OR
AL,unidad
; seleccionar unidad; NO DMA
OUT
DX,AL
; reset
OR
AL,00000100b
JMP
SHORT $+2
OUT
DX,AL
CALL
espera_int
MOV
AL,3
CALL
fdc_write
MOV
AL,0DFh
CALL
habilita_int
CALL
fdc_write
MOV
AL,0Fh
MOV
AL,3
; modo NO DMA
CALL
fdc_write
CALL
fdc_write
; head load y modo
JZ
fallo_seek
PUSH
DS
MOV
AL,byte1
MOV
BX,40h
CALL
fdc_write
MOV
DS,BX
MOV
AL,cilindro
MOV
CL,CS:unidad
CMP
tunidad,0
MOV
AL,1
JE
pista_ok
; es unidad de doble densidad
SHL
AL,CL
CMP
vunidad,1
; es de alta:
AND
BYTE PTR DS:[BX-1],11110000b
JNE
pista_ok
; no es disco 5¼-360
OR
DS:[BX-1],AL
SHL
AL,1
; cilindro=cilindro*2
POP
DS
CALL
fdc_write
; enviar cilindro
MOV
DX,3F7h
CALL
espera_int
; esperar interrupción
MOV
AL,vunidad
OUT
DX,AL
MOV
AL,8
XPOP
CALL
fdc_write
RET
JZ
fallo_seek
ENDP
CALL
fdc_read
; leer registro de estado 0
CALL
fdc_read
; leer cilindro actual
recalibrar
ENDP
; ------------ Llevar el cabezal a la pista indicada. ; fin del reset seek_drv
PROC XPUSH CLI
; Comando 'Specify':
; indicar motor ON pista_ok:
STI
;
MOV
CX,1
CALL
retardo
XPOP
para el caso de que deba moverse más de 77 pistas).
PROC
CLC
XPUSH
recalibra:
MOV
CX,2
CALL
habilita_int
MOV
AL,7
CALL
fdc_write
JZ
fallo_recal
MOV
AL,byte1
CALL
fdc_write
JZ
fallo_recal
CALL
espera_int
JZ
fallo_recal
MOV
AL,8
CALL
fdc_write
JZ
fallo_recal
CALL
fdc_read
JZ
; enviar HD, US1, US0
; comando 'leer estado int...'
; esperar asentamiento cabezal
; retornar con éxito
RET
; dos veces como mucho fallo_seek:
STI XPOP
STC
; comando de 'recalibrado'
; retornar indicando fallo
RET seek_drv
ENDP
; enviar HD, US1, US0 ; ------------ Habilitar interrupción disquete (y modo DMA). ; esperar interrupción habilita_int
PROC XPUSH MOV
CL,unidad
ADD
CL,4
MOV
AL,1
fallo_recal
SHL
AL,CL
; colocar bit del motor
MOV
AH,AL
OR
AL,unidad
; seleccionar unidad
CALL
fdc_read
; leer cilindro actual
OR
AL,00000100b
; no hacer reset
XOR
AH,00100000b
; bajar bit de 'seek end'
MOV
DX,3F2h
TEST
AH,11110000b
; comprobar resultado y ST0
OUT
DX,AL
JNZ
fallo_recal
; sin 'seek end' o sin TRK0
OR
AL,00001000b
XPOP
JMP
SHORT $+2
OUT
DX,AL
XPOP
CLC
; comando 'leer estado int...'
; leer registro de estado 0
; Ok.
RET fallo_recal:
; comando 'seek'
CLI
; velocidad de transferencia
; ------------ Recalibrar la unidad (si hay error se intenta otra vez
recalibrar
; usar interrupciones
LOOP
recalibra
XPOP
STC
RET
; reintentar comando habilita_int ; condición de fallo
ENDP
; modo DMA
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
; ------------ Esperar interrupción de disquete y volver de nuevo al
CALL
fdc_write
;
modo NO DMA (lo que inhibe interrupción disquete).
MOV
AL,128
CALL
fdc_write
espera_int
PROC
CLD
STI
MOV
AL,sector_fin
XPUSH
SUB
AL,sector_ini
XPUSH
INC
AL
POP
DS
XOR
AH,AH
MOV
AH,0FFh
MUL
bsector
CMP
AL,DS:[6Ch]
MOV
CX,AX
; bytes a leer/escribir
JE
mira_int
MOV
DX,3F4h
; registro de estado del FDC
MOV
AL,DS:[6Ch]
IN
AL,DX
INC
AH
TEST
AL,80h
CMP
AH,37
; no esperar más de 2 segundos
JZ
espera_exec
JA
fin_espera
; timeout
CMP
orden,F_WRITE
TEST
BYTE PTR DS:[3Eh],80h
JE
fdc_wr_sect
JZ
esperar_int
IN
AL,DX
AND
BYTE PTR DS:[3Eh],127
; resetear flag
TEST
AL,80h
POP
DS
; para futura interrupción
JZ
fdc_rd_sect
MOV
CL,unidad
TEST
AL,16
ADD
CL,4
JZ
sector_io_ko
; fallo en lectura
MOV
AL,1
INC
DX
; apuntar al registro de datos
SHL
AL,CL
; colocar bit del motor
IN
AL,DX
; leer byte del sector
OR
AL,unidad
; seleccionar unidad
DEC
DX
OR
AL,00000100b
; no hacer reset y no DMA
STOSB
MOV
DX,3F2h
LOOP
fdc_rd_sect
OUT
DX,AL
JMP
sect_io_fin
XPOP
esperar_int:
mira_int:
fin_espera:
espera_int
espera_exec:
fdc_rd_sect:
fdc_wr_sect:
; GAP de lectura/escritura
; tamaño sector si longitud=0
; AX = nº de sectores
; ¿alcanzada fase ejecución?
; ¿listo para E/S?
; ES:[DI++] <-- AL
IN
AL,DX
RET
TEST
AL,80h
ENDP
JZ
fdc_wr_sect
; repetir hasta fin sector(es)
; ¿listo para E/S?
TEST
AL,64
; ------------ Cargar o escribir CX sector(es) del disco en ES:DI,
JNZ
sector_io_ko
;
actualizando la dirección en ES:DI pero sin alterar
MOV
AL,ES:[DI]
;
ningún otro registro. Si hay error se devuelve CF=1 y
INC
DX
; apuntar al registro de datos
;
no se modifica ES:DI. En el momento crítico en que se
OUT
DX,AL
; escribir byte del sector
;
leen/escriben los sectores, no se llama a las
DEC
DX
;
subrutinas habituales por razones de velocidad, lo
INC
DI
;
que implica duplicar código y alargar el programa.
LOOP
fdc_wr_sect
sect_io_fin:
MOV
CX,7
sect_io_rx:
sector_io
CALL
fdc_read
XPUSH
LOOP
sect_io_rx
MOV
STI
PROC
AL,orden
POP
CLI
io_proc:
CALL
fdc_write
JNZ
io_proc
JMP
sector_io_ko
MOV
AL,byte1
CALL
fdc_write
MOV
AL,cilindro
CALL
fdc_write
MOV
AL,cabezal
CALL
fdc_write
MOV
AL,sector_ini
CALL
fdc_write
MOV
AL,tsector
CALL
fdc_write
MOV
AL,sector_fin
CALL
fdc_write
MOV
AL,gap
; «sacar» DI sin cambiarlo ; indicar éxito
sector_io_fin
sector_io_ko:
MOV
DX,3F4h
kill_info:
IN
AL,DX
TEST
AL,80h
JZ
kill_info
TEST
AL,64
JZ
info_killed
; el 765 no devuelve datos
INC
DX
; apuntar al registro de datos
IN
AL,DX
; leer byte de resultados
DEC
DX
JMP
kill_info
; enviar cilindro
; enviar cabezal
; enviar nº sector
info_killed:
; leer resultados del fallo
; ¿listo para E/S?
STI POP
; último sector
; leyendo resultados del éxito
JMP
; enviar HD, US1, US0
; longitud sector
; hasta acabar sector(es)
; ...fin de la fase crítica CX
CLC
; comando leer/escribir del 765
; fallo en escritura
DI
STC sector_io_fin: XPOP
; anular cambio de DI ; indicar fallo
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
sector_io
RET
POP
AX
ENDP
POP
DS
RET ; ------------ Recibir byte del FDC en AL. A la vuelta, ZF = 1 si ;
la operación fracasó (el FDC no estaba listo).
fdc_read
PROC
espera_rd:
PUSH
CX
PUSH
DX
MOV
DX,3F4h
; registro de estado del FDC
XOR
CX,CX
; evitar cuelgue total si falla
IN
AL,DX
; leer registro de estado
TEST
AL,80h
; ¿bit 7 inactivo?
LOOPZ espera_rd
; así es: el FDC está ocupado
INC
DX
; apuntar al registro de datos
IN
AL,DX
; leer byte del FDC
AND
CX,CX
; ZF = 1 si fallo al leer
POP
DX
POP
CX
RET fdc_read
ENDP
; ------------ Enviar byte AL al FDC. A la vuelta, ZF = 1 si ;
la operación fracasó (el FDC no estaba listo).
fdc_write
PROC
espera_wr:
PUSH
AX
PUSH
CX
PUSH
DX
MOV
DX,3F4h
; registro de estado del FDC
XCHG
AH,AL
; preservar AL en AH
XOR
CX,CX
; evitar cuelgue total si falla
IN
AL,DX
; leer registro de estado
TEST
AL,80h
; ¿bit 7 inactivo?
LOOPZ espera_wr
; así es: el FDC está ocupado
XCHG
AH,AL
; recuperar el dato de AL
INC
DX
; apuntar al registro de datos
OUT
DX,AL
; enviar byte al FDC
AND
CX,CX
; ZF = 1 si fallo al escribir
POP
DX
POP
CX
POP
AX
RET fdc_write
ENDP
; ------------ Esperar CX 1/18,2 avos de segundo.
retardo
PROC PUSH
DS
PUSH
AX
PUSH
CX
MOV
AX,40h
MOV
DS,AX
STI espera_tics:
MOV
AX,DS:[6Ch]
; esperar que el contador
espera_tic:
CMP
AX,DS:[6Ch]
; de hora del BIOS...
JE
espera_tic
LOOP
espera_tics
POP
CX
; ... cambie lo suficiente
retardo
ENDP
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
; ************ Mensajes
cls_txt DB
DB
10,10,10,10,10,10,10,10,10,10,10,10
relleno
DB
?
; byte de relleno (al escribir)
buffer
EQU
$
; para leer/escribir sector
fdc_test
ENDS
10,10,10,10,10,10,10,10,10,10,10,10,13,"$"
opciones_txt
DB
"765NODMA.ASM 2.0
DB
13,10," (c) 1991 Jesús Arias Alvarez."
DB
13,10," (c) 1992, 1993 Ciriaco García de Celis."
DB
13,10,10,9,"1.- Leer sector"
DB
13,10,9,"2.- Escribir sector"
DB
13,10,10,9,"
DB
13,10,10,"
lectura_txt
DB
13,10,"Lectura de sector.$"
escritura_txt
DB
13,10,"Escritura de sector.$"
aviso_txt
DB
13,10,"--------------------",13,10
DB
"Aviso: No se validan las entradas.",10,"$"
adios_txt
DB
13,"
ptecla_txt
DB
13,10,"- Estás viendo 256 bytes del sector."
DB
13,10,"- Pulsa una tecla para continuar.$"
fallo_txt
DB
13,10,10,"¡Fallo al acceder al disco!",7,"$"
unidad_txt
DB
13,10,"Unidad (0-A, 1-B): $"
vunidad_txt
DB
13,10,"Velocidad: "
DB
13,10,"
(0) 500 Kbaudios (5¼ HD y 3½ HD)"
DB
13,10,"
(1) 300 Kbaudios (5¼ DD)"
DB
13,10,"
(2) 250 Kbaudios (3½ DD)"
DB
13,10,"
Elige: $"
DB
13,10,"Disquete 40 pistas en unidad de 80: "
tdisco_txt
-
Acceso a disquete sin DMA."
ESC-Salir"
Elige una opción: $"
Hasta luego.
",13,10,"$"
DB
"(1) sí, (0) no: $"
tamano_txt
DB
13,10,"Tamaño de sector (2->512 bytes): $"
gap_rw_txt
DB
13,10,"Tamaño del GAP (41-DD, 27-HD):
pista_txt
DB
13,10,"Pista:
cabeza_txt
DB
13,10,"Cabezal: $"
sector_txt
DB
13,10,"Sector:
relleno_txt
DB
13,10,"Byte para inicializar sector:
$"
$"
$" $"
; ************ Datos
F_READ
EQU
01100110b
; orden de lectura del FDC
F_WRITE
EQU
01000101b
; orden de escritura del FDC
orden
DB
?
; orden a procesar
unidad
DB
?
vunidad
DB
?
; velocidad de transferencia
tunidad
DB
?
; control de salto de pista
cilindro
DB
?
; pista del disco a usar
cabezal
DB
?
; cabeza
sector_ini
DB
?
; sector inicial
sector_fin
DB
?
; sector final
tsector
DB
?
; tamaño de sector (logaritmo)
bsector
DW
?
; tamaño de sector (bytes)
gap
DB
?
; GAP para lectura/escritura
byte1
DB
?
; bits HD, US1, US0
END
main
EL HARDWARE DE APOYO AL MICROPROCESADOR
284
12.6.7 - PROGRAMACION AVANZADA DEL CONTROLADOR DE DISQUETES: 2M 3.0 Hasta ahora hemos descrito todo lo necesario para poder programar la controladora de disquetes. Ahora aplicaremos dicha información a un caso práctico real, con un programa. Ciertas aplicaciones comerciales de backup ya emplean formatos de disco de más capacidad para almacenar los datos, además de manera comprimida. Sin embargo, estos disquetes no pueden ser empleados directamente por el DOS. Por el contrario, la utilidad que desarrollaremos, 2M, es un programa residente que permite gestionar disquetes con sectores de más de 512 bytes e, incluso, con sectores de distinto tamaño en las pistas. Este último formato obtendrá algo más de capacidad, pero menos velocidad y fiabilidad. En 3½", los disquetes más comunes de 1.44M (1440K) se podrán formatear a 1804K y 1886K, respectivamente. Los de 720K alcanzarán los 984/1066K. En 5¼" los de 1.2M pasan a 1476/1558K y los de 360K a 820/902K. Los formatos de 1886K, 1066K y 1558K no pueden ser reproducidos por la versión de enero de 1992 del poderoso copión COPYWRITE; el de 902K sí es duplicado en algunos ordenadores, aunque a veces algunas pistas quedan mal. Esto no es problema para el usuario normal, que podrá hacer DISKCOPY (si 2M está instalado en memoria) hacia un disco destino ya formateado. Para formatear estos nuevos disquetes se empleará un pequeño programa escrito en C (2MF.C) que se limitará a llamar a las funciones de INT 13h reforzadas por 2M; dicho programa será descrito más adelante. Los programas que formatean los discos a mayor capacidad de la normal suelen limitarse a reducir el GAP 3 al formatear, colocando gracias a ello más sectores en las pistas. Sin embargo, la utilidad propuesta aquí rompe con el tamaño estándar de 512 bytes: al colocar sectores de mayor tamaño, existen menos sectores y también menos GAP de separación. El inconveniente de este método es que difícilmente sectores de 1024, 2048 ó más bytes pueden encajar aprovechando óptimamente la capacidad de la pista. Por ello se han adoptado dos soluciones diferentes que han originado 8 nuevos formatos de disco (2 por cada tipo de medio magnético): nEmpleo de sectores de 1 Kb. Pese a ser más grandes, se pueden colocar más o menos bien en los 4 tipos de disco (360-1.2-720-1.44) aprovechando más la capacidad de la pista, ya que al haber menos sectores también se derrocha menos espacio en GAPs sin necesidad de reducirlos excesivamente ni, por tanto, degradar la fiabilidad de los discos. Esta solución, si se tiene cuidado de optimizar el formateo de las pistas (con la numeración adecuada de los sectores en las mismas) permite obtener disquetes de mayor capacidad de la normal, tan fiables como los estándar del DOS y sensiblemente más rápidos que los creados por el FORMAT debido a dos motivos: en estos formatos el disco da sólo las vueltas necesarias para acceder a los datos y, además, se leen más datos en dichas vueltas. nLa otra solución alternativa consiste en emplear sectores aún de mayor tamaño, hasta 2 Kb (mayores no permitirían una ventaja significativa) y rellenar el hueco restante de la pista, donde no cabe otro sector de 2 Kb, con sectores menores. Esto implica colocar sectores de distinto tamaño en las pistas, lo cual escapa en teoría de las posibilidades del controlador de disquetes, si se repasa la documentación de las páginas anteriores. Sin embargo, sólo en teoría, ya que existen programas comerciales con protección anticopia que realizan esta tarea. La técnica ┌─────────────────────┐ que veremos permite realizar esto, pese a lo cual estos formatos de disco │ Parámetros /X e /Y │ no son recomendados: son poco seguros en cuanto a portabilidad │ de FDFORMAT para un │ disquetes creados en una máquina podrían tener problemas para ser │ formateo correcto. │ reconocidos en otro ordenador o incluso ser destruidos al escribir- y ├─────────────────────┤ aumentan poco la capacidad respecto a la 1ª solución; pese a todo han │ /X /Y │ sido calibrados de tal manera que se puede afirmar que en un ┌───────┼──────────┬──────────┤ elevadísimo porcentaje de veces el funcionamiento y la portabilidad │ 5¼-DD │ 1 │ 3 │ serán satisfactorios. │ 5¼-HD │ 2 │ 3 │ │ 3½-DD │
1
│
2
│
│ 3½-HD │
2
│
3
│
└───────┴──────────┴──────────┘
A lo largo de este apartado se hará alguna referencia al popular programa de formateo FDFORMAT creado por Christoph H. Hochstätter; esta utilidad permite formatear disquetes normales desplazando los
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
sectores de manera óptima (opciones /X e /Y) y también añadir más sectores (estrechando el GAP 3). Para superar las limitaciones de flexibilidad de la BIOS es preciso tener residente un pequeño programa de sólo 128 bytes de cara a soportar los formatos extendidos. Este programa, bastante superior al FORMAT en todos los aspectos, con el que además es compatible, está muy extendido en las principales BBS (su código fuente en Turbo Pascal viene incluido) y aborda desde otro punto de vista la ampliación de la capacidad normal de los disquetes, respetando los sectores de 512 bytes. No hay que olvidar que este programa permite crear, ┌────────────────────────────────────────────────────────────────────────┐ │ además de algunos formatos extendidos, disquetes │ │ totalmente estándar de 360K, 1.2M, 720K y 1.44M │ [1867/1867] B:\>dir │ │ que, por supuesto, no necesitan soporte residente y │ Volume in drive B is unlabeled Serial number is 2FE6:7632 │ son mucho más rápidos que los creados por el │ File not found "B:\*.*" │ FORMAT del DOS. Mientras el FORMAT del │ 0 bytes in 0 file(s) │ sistema operativo no corrija la numeración │ 1.912.320 bytes free │ incorrecta de sectores, que lleva practicando desde │ │ 1981, y a la espera de que David Astruga saque la │ │ próxima versión de su programa de copia y formateo │ [1867/1867] B:\>chkdsk │ (a finales del 94 o comienzos del 95); por el │ Número de serie de volumen es 2FE6-7632 │ momento, FDFORMAT y sus parámetros /X e /Y │ │ │ constituyen la única solución para los usuarios más │ 1912320 bytes de espacio total en disco │ entendidos (aquellos que usan 4DOS en vez de │ 1912320 bytes disponibles en disco │ COMMAND.COM, QEMM en lugar de EMM386, │ 512 bytes en cada unidad de asignación │ etc): emplear el FORMAT actual no es de │ │ 3735 total de unidades de asignación en el disco │ conservadores sino de no informados. 2M │ 3735 unidades de asignación disponibles en disco │ (abreviatura de 2 megas, aunque no se alcanza esa │ │ capacidad por disco) es un programa residente que │
655360 bytes de memoria total
│
│
649760 bytes libres
│
│
│
│
│
│
[1867/1867] B:\>testdisk
│
│
TD-Test Disco, Edición Estandar 4.50, (C) Copr 1984-88, Peter Norton
│
│
Traducción Castellano, Copyright (C) 1989 ANAYA Multimedia, S.A.
│
│
│
│
Verificar DISCO, ARCHIVO, o AMBOS
│
│
Pulse D, F, o A ... D
│
│
│
│
Puede pulsar BREAK (Ctrl-C) durante la
│
│
verificación para interrumpir Test Disco
│
│ │ │ │
│ Test leyendo el disco B:, zonas del sistema y de datos La zona del sistema consta de boot, FAT, y directorio Zona del sistema sin errores
│ │ │
│ │ │
La zona de datos consta de clusters numerados 2 - 3.736 Zona de datos sin errores
│
│ │ │
│ │
│
│ [1867/1867] B:\>_
│
└────────────────────────────────────────────────────────────────────────┘
EJEMPLO DE ACCESO A DISQUETE 2M DE 1.44 FORMATEADO A CASI 1.90
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
da soporte a los nuevos formatos de disco. Una vez instalado 2M en memoria, los nuevos disquetes serán reconocidos sin problemas: se podrá hacer DIR, COPY, CHKDSK,... e incluso DISKCOPY hacia un disco destino ya formateado. El código residente de 2M funciona también bajo WINDOWS 3.X; sin embargo, en OS/2 2.1 hay problemas, aunque se pueden arreglar, como veremos luego, usando el DOS de Microsoft (y no el que viene con el propio OS/2) desde un disquete o, mejor aún, creando una imagen en disco duro de ese disquete. De esta última manera, el usuario ni siquiera nota al diferencia entre estas ventanas de DOS y las normales. Tal vez alguien escriba algún día el driver oportuno para facilitar la operación en este sistema... de momento, 2M está diseñado sólo para los sistemas más extendidos. En WINDOWS NT, donde no ha sido probado, probablemente existirán problemas y limitaciones mayores de las que se producen bajo OS/2. Al momento de escribirse estas líneas, el autor de 2M tiene constancia de que hay intentos de portarlo al sistema operativo Linux por parte de Alain Knaff y David Niemi, si bien desconoce el grado de avance en esta materia. 2M añade un nuevo servicio a la INT 13h para poder formatear los nuevos disquetes. No es probable que gracias a ello la próxima versión de PC-TOOLS soporte los nuevos formatos, pero añadir rutinas de formateo apenas alargaba el código residente (sólo 0.75 Kb más hasta alcanzar los 5 Kb) y se trataba de la solución más elegante. Para formatear los nuevos disquetes se ha creado un programa en C de alto nivel, que sencillamente invoca la INT 13h sin verse obligado a realizar ni un solo acceso directo al hardware, pese a que el código residente de 2M accede siempre a disco a través del controlador de disquetes, sin una sola │
DB
│
│
Ensamblador
Comentario
│ Offset │
│ InfpX DB
NOP
│
DB
"2M-STV08"
; ID sistema
│
DW
512
; bytes/sector
│
DB
1
; sectores por cluster
13
│
│
DW
1
; sectores reservados al principio
14
│
│
DB
2
; nº copias de la FAT
16
│
│
DW
224
; entradas al directorio raíz
17
│
│
DW
3608
; nº total de sectores del disco
19
│
│ BootP ...
│
DB
0F0h
; byte descriptor de medio
21
│
arranque
│
DW
11
; sectores ocupados por la FAT
22
│
└────────────────────────────────────────────────────────────
│
DW
22
; sectores por pista
24
│
│
DW
2
; nº de cabezales
26
│
│
DD
0
; sectores especiales reservados
28
│
│
DD
0
; nº sectores (unidad 32 bit)
32
│
│
DB
0
; unidad física
36
│
│
DB
0
; reservado
37
│
│
DB
29h
; disco con número de serie
38
│
│
DD
8BC1AD20h
; número de serie provisional
39
│
│
DB
"NO NAME
; título del disco
43
│
│
DB
"FAT12
; tipo de FAT
54
│
│
DB
Flags
; bit 0 = 1 si FechaF/HoraF definido
62
│
│
DB
?
; checksum de la información vital
63
│
│
DB
7
; versión formato (>=7 si BOOT virtual) 64
│
│
DB
0
; a 1 si escribir al formatear
65
│
│
DB
0
; velocidad transferencia pista 0
66
│
│
DB
0
; velocidad transf. demás pistas
67
│
│
DW
BootP
; offset al programa de arranque
68
│
│
DW
Infp0
; T1: información para pista 0
70
│
│
DW
InfpX
; T2: información demás pistas
72
│
│
DW
InfTm
; T3: tabla tamaños demás pistas
74
│
│
DW
FechaF
; Fecha de formateo (2M 3.0+)
76
│
│
DW
HoraF
; Hora de formateo (2M 3.0+)
78
│
│ Infp0 DB
19, 70
; nº sectores / GAP de formateo
│
DB
1,2,3,4,5,6,7,8 ; sectores ordenados (20..22 no existen)
│
│
DB
9,10,11,12,13,14
│
"
│
2
│
3
│
11
│
│
JMP
│
"
0
; nº sectores / GAP de formateo
│
SHORT BootP
; 1 byte
11, 40 │
├─────────────────────────────┴──────────────────────────────────┴────────┤ ; 2 bytes
15,16,17,18,19
│
┌─────────────────────────────┬──────────────────────────────────┬────────┐
│
DB
3
;
tamaño
│ │
DB
1, 2
; desplazamiento numeración
│ │ InfTm DB
3,3,3,3,3,3
; tamaño sector 1, 2, 3,...
│ │
DB
3,3,3,3,3
│ ; programa del sector de │
─────────────┘
SECTOR DE ARRANQUE DE UN DISQUETE 2M DE 3½ A 1.80M
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
llamada al DOS/BIOS en ningún momento. La capacidad obtenida por 2M supera la conseguida por los programas comerciales de backup en los formatos especiales para almacenar sólo datos. Con la ayuda de un compresor de datos de dominio público líder (PKZIP, ARJ, etc) también superior en rendimiento a los programas de backup, se puede conseguir el método de backups que, indiscutiblemente, más aprovecha los disquetes, con una aplastante diferencia -y además el más barato-. Sin embargo, el usuario debería tener cuidado con el tipo de datos que almacena en estos discos, ya que no son tan portables como los estándar y sería problemático migrarlos después a otros entornos. Existen versiones de 2M tanto para sistemas AT como para PC/XT, con el único requisito de que la controladora y las unidades sean de alta densidad. 12.6.7.1 - FORMATO DE LA PRIMERA PISTA. La primera pista (cilindro y cabezal 0) de los nuevos disquetes tiene el formato normal de sectores de 512 bytes, conteniéndolos en cantidad también más o menos normal. Uno de los motivos es permitir que la FAT, zona del disco en la que a menudo cambia un sólo sector (y no varios consecutivos) tenga un acceso más ágil. En algunos formatos de disco, parte del directorio raíz también cabe en esta pista; en cualquier caso, esto no es demasiado importante porque sólo se accede al directorio raíz una vez por cada fichero. Debido al empleo en la primera pista de sectores físicos de 512 bytes, no se pueden emular todos los sectores virtuales. En 3½-HD por ejemplo, los nuevos formatos de disco contarán aparentemente con 22-23 sectores por pista. Realmente serán muchos menos y de más de 512 bytes, pero se engañará al DOS para hacerle creer que son la cantidad citada de sectores de 512 bytes, de cara a mantener la compatibilidad. En cualquier caso, esta cifra es muy superior a los 18 sectores habituales en este tipo de disco. Como la primera pista contiene sectores reales de 512 bytes, no se pueden meter tantos (no caben más de 21 y eso juntando excesivamente los sectores, como hace FDFORMAT en el formato 1.72M). Para arreglar este problema, el código residente de 2M se extralimita en sus funciones y, suponiendo que los discos se emplean bajo DOS, ignora las escrituras sobre la segunda copia de la FAT (que estaría sobre alguno de los sectores que no existen en la primera pista) devolviendo la primera copia de la FAT a quien quiera leer la segunda. Así se consigue además una pequeña velocidad extra, ya que la escritura sobre la segunda copia de la FAT que realiza el DOS al crear ficheros resulta ignorada. Realmente, es un poco innecesaria la presencia de 2 FAT en un disquete, máxime teniendo en cuenta que su adyacencia física propicia que en caso de daño se estropeen las dos (¿cuántas veces el lector ha tenido que echar mano de la segunda copia de la FAT para recuperar sus información adicional para describir el formato físico de disco datos?). El MS-DOS, incluso en la versión que se trate y así poder gestionarlo luego. De esta manera, se 6.0 no respeta sus propias especificaciones y sistematiza el soporte de los nuevos formatos y se simplifica el asume que los disquetes tienen 2 copias de la programa residente. Detrás de los primeros 62 bytes, donde va FAT: aunque se indique sólo una en el sector la información colocada por el FORMAT normal del DOS de arranque, hará caso omiso. Esta es, por un (incluyendo las últimas modas, como campos para etiqueta de lado, una buena manera de darle el corte de disco, número de serie, etc.) existen unos campos con mangas; por otro, un medio ideal para simular información adicional, que describiremos más adelante. Detras de este área está el más sectores en la primera pista física. El sector de arranque de los nuevos disquetes es en principio similar al de cualquier otro disco, pero contiene más
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
│
0
66
│
DB
0
; velocidad transf. demás pistas
67
│
┌─────────────────────────────┬──────────────────────────────
│
DW
BootP
; offset al programa de arranque
68
│
────┬────────┐
│
DW
Infp0
; T1: información para pista 0
70
│
│
DW
InfpX
; T2: información demás pistas
72
│
│
DW
InfTm
; T3: tabla tamaños demás pistas
74
│
├─────────────────────────────┴──────────────────────────────
│
DW
FechaF
; Fecha de formateo (2M 3.0+)
76
│
────┴────────┤
│
DW
HoraF
; Hora de formateo (2M 3.0+)
78
│
│ Infp0 DB
19, 70
; nº sectores / GAP de formateo
│
DB
1,2,3,4,5,6,7,8 ; sectores ordenados (20..23 no existen)
│
│
DB
9,10,11,12,13,14
│
│
DB
15,16,17,18,19
│
│
│
Ensamblador
Comentario
│ Offset │
│
JMP
│
│
DB
2
224
3772
DB
0F0h
DW
11
DW
23
; sectores por pista
2
; nº de cabezales
│ DW │
DD
0
reservados
; sectores especiales │
28
DD
0
│
; nº sectores (unidad 32 bit)
│ DB
36
│
37
│
│
0
DB
DB 38
0
29h
DB │
54
│
│
DB
DB
DB
"
"
; título del disco
;
tipo
de
FAT
; bit 0 = 1 si FechaF/HoraF
│ ?
63
; checksum de la información
│ 7
; versión formato (>=7 si BOOT
1
; a 1 si escribir al formatear
0
; velocidad transferencia pista
│
DB
DB
"FAT12
Flags 62
65
; número de serie (aleatorio)
"NO NAME
DB
virtual) 64
reservado
│
43
definido
;
; disco con número de serie
4B368A0Eh
│
vital
; unidad física
│
DD 39
│
; sectores ocupados por la FAT
│
26
│
; byte descriptor de medio
│
24
│
; nº total de sectores del
│
19
22
64, 3
; nº sectores / GAP de formateo
│
│
DB
7
; nº sectores a renumerar
│
│
DB
128+1,
4, 4
; tabla de renumeración formateo:
│
│
DB
128+12, 1, 4
; nº sector, nuevo número, tamaño
│
│
DB
128+23, 5, 4
│
│
DB
128+34, 2, 4
│
│
DB
128+45, 6, 3
│
│
DB
128+51, 3, 4
│
│
DB
128+62, 7, 2
│ BootP:...
4,4,4,4,4,3,2
│ ; tamaño sector 1, 2, 3,...
│
; programa del sector de arranque
│
└─────────────────────────────────────────────────────────────────────────┘
DW
21
│
; entradas al directorio raíz
│
disco
│
; nº copias de la FAT
│
│ InfpX DB
│ InfTm DB
DW 17
│
│
│
16
│
; sectores reservados al
14
32
sistema
; sectores por cluster
1
principio
│
ID
; bytes/sector
1
DW
│
byte
│
13
│
;
512
DB
│
1
│
11
│
"2M-STV04"
DW
│
bytes
│
3
│
;
DB
│
2
│
│
│
;
NOP 2
│
BootP
│
0
│
SHORT
│
SECTOR DE ARRANQUE DE UN DISQUETE 2M DE 3½ A 1.88M
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
programa de arranque del disquete, que en sus primeras versiones se limitaba a imprimir en pantalla un mensaje diciendo que el disco no es de arranque; actualmente arranca desde el disco duro si éste existe y, desde 2M 2.0, carga el código SuperBOOT almacenado en el disco si es de alta densidad. Los discos 2M de alta densidad utilizan 5 sectores libres de la segunda copia de la FAT (ubicados en la primera pista) para almacenar gran parte del código residente de 2M (todo, excepto las rutinas de formateo). De esta manera, desde 2M 2.0 es posible botar de un disco 2M de alta densidad, que puede crearse con un SYS ordinario. De hecho, el primer sector de la segunda copia de la FAT emula al auténtico sector de arranque, y los 5 restantes almacenan el código residente de 2M. Así, cuando 2M está instalado, el comando SYS y cualquier aplicación que acceda al sector de arranque estará accediendo realmente a un falso sector de arranque que está físicamente colocado en la FAT2. Y podrá modificarlo sin riesgo alguno para 2M, ya que el auténtico sector de arranque permanece inmutable; las versiones anteriores de 2M necesitaban proteger este sector restringiendo de alguna manera su acceso (para evitar que un simple SYS lo modificara y borrara la información vital que contiene). La denominación SuperBOOT para el código de 2M almacenado en la primera pista de los discos se debe exclusivamente a cuestiones de marketing. Debido a que se necesita un tamaño mínimo de FAT, modificar el tamaño de cluster en el sector de arranque no es conveniente, aunque está permitido y puede generar discos que no funcionen. Sin embargo, la utilidad estándar de formateo no deja cambiar el tamaño de cluster (por otra parte de sólo 512 bytes) y no hay muchos programas conocidos que alteren estos parámetros de los disquetes ya formateados. Cuando el sistema arranca de un disco 2M de alta densidad, el código SuperBOOT rebaja la memoria libre en 5 Kbytes (normalmente, de 640K a 635K) ubicándose al final de la memoria convencional y se instala en la INT 13h. Después, se carga el sector de arranque vía INT 13h (que en adelante será el falso sector de arranque emulado, al que pudo acceder el SYS) y se ejecuta, procediéndose al arranque normal del sistema, ya que la nueva BIOS soporta discos 2M... este sector de arranque ubicado en la FAT2 es denominado sector de arranque virtual en la documentación de 2M. Como puede observar el lector, dejar la primera pista con sectores de 512 bytes y emular la segunda copia de la FAT sobre la primera fue una idea primitiva que luego ha permitido muchas aplicaciones interesantes. Naturalmente, está previsto un mecanismo para poder acceder a los sectores físicos sin emulaciones: esto es útil además para permitir al programa de formateo grabar el código SuperBOOT y acceder al sector de arranque físico, ya que los programas normales no tienen motivos especiales para necesitar un acceso a dichas áreas. Cuando 2M está instalado, cualquier acceso al cabezal 128 ó 129 en lugar del 0 ó el 1 permite acceder al disco sin realizar ningún tipo de emulación; si bien esto sólo funciona con discos 2M (con un disco estándar en la unidad, aunque 2M esté instalado, el acceso a estos cabezales devuelve un error). En adelante nos referiremos al sector de arranque físico, no al virtual (que puede ser distinto si el disco es de sistema o ha sido alterado por alguna utilidad). El primer campo propio de 2M en el sector de arranque es una variable con flags, empleada sólo desde 2M 3.0 para indicar si se almacena la fecha y hora de formateo en el sector de arranque (bit 0 = 1 en caso afirmativo). Detrás hay un checksum o suma de comprobación de la zona vital del sector de arranque. El algoritmo empleado ha variado en las sucesivas versiones del programa. Desde la versión 6 del formateador (byte ubicado justo después del checksum) la zona total afectada por el checksum va desde el offset 64 hasta justo antes del programa de arranque del disco. Las versiones anteriores de 2M realizaban un checksum distinto, por lo que los discos formateados por ellas no están sujetos a la comprobación de checksum para evitar problemas. La suma total de este área (en número de 8 bits) debe dar un resultado 0. Por tanto, se permite modificar el programa de arranque e incluso los campos del principio. ┌───────────────────────────────────────┐ Cualquier otro cambio no │ GAPs y /X e /Y probados en 2MF /F │ permitido hará que 2M falle en ├─────────┬─────────┬─────────┬─────────┤ la comprobación del checksum │ 5¼-DD │ 5¼-HD │ 3½-DD │ 3½-HD │ la primera vez que el disco es ┌──────────────────────────────────────────────────┼─────────┼─────────┼─────────┼─────────┤ introducido en la unidad; en │ GAP mínimo de lectura soportado en las pruebas │ 1 │ 2 │ 1 │ 2 │ este caso INT 13h │ GAP mínimo de escritura soportado en las pruebas │
13
│
26
│
20
│
28
│
│ GAP máximo de escritura soportado en las pruebas │
197
│
76
│
187
│
49
│
│ GAP 3 de formateo adoptado finalmente
│
100
│
50
│
100
│
40
│
│ Valor óptimo obtenido experimentalmente para /X
│
1
│
1
│
1
│
1
│
│ Valor óptimo obtenido experimentalmente para /Y
│
1
│
2
│
1
│
2
│
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
└───────────────────────────────────── ─────────────┴─────────┴─────────┴──── ─────┴─────────┘ 2MF ES EL FORMATEADOR PARA 2M. CON /F SE CREAN DISCOS NORMALES Y /M INDICA MÁXIMA CAPACIDAD.
devuelve un Seek Error poco habitual para señalizar la circunstancia. Sin embargo, un cambio en el campo ID (bytes 3 al 10) podría acarrear que 2M no reconociera el disco como suyo. Quizá el lector opine que hubiera sido mejor ser más tolerantes, pero yo opino que no: si el sector de arranque está corrompido, el código residente de 2M, que no valida nada de dicho sector, podría estrellarse si se fía de la información del mismo. Así nadie podrá decir: «se me cuelga al hacer DIR A:», como mucho: «me dice Seek Error y no me deja acceder al disco». En realidad, es difícil que se produzcan estos errores porque nadie que intente alterar el sector de arranque físico lo podrá conseguir con 2M en memoria, sin saber como hacerlo o sin acceder directamente a la controladora. Tras el checksum hay un byte que indica la versión del formateador, de cara a permitir que futuras versiones de 2M sepan con qué formato de disco se enfrentan para respetar los viejos formatos (en caso de que surjan otros nuevos). El siguiente byte indica si es necesaria una escritura tras el formateo: en los formatos de más capacidad, trasformatear la pista hay que escribirla para evitar que una lectura posterior produzca errores de CRC, como luego veremos y explicaremos. En los formatos normales este byte estará a 0, y a 1 en los de más capacidad. Los siguientes 2 bytes indican la velocidad de transferencia a emplear en la primera pista (cilindro y cabezal 0) y en las demás; el dato no está, por supuesto, en Kbit/seg sino que se trata del valor que hay que enviar al registro de salida digital. En los disquetes de 3½-DD se utilizará la velocidad de 250 Kbit/seg en la primera pista y 300 Kbit/seg en las demás. El motivo es que las primeras versiones de 2M delegaban parte del trabajo de reconocer la densidad de disco a la BIOS, la cual sólo soporta 250 Kbit/seg en estas unidades. Actualmente no sería necesario, ya que 2M detecta la densidad de los discos (y de hecho, sustituye a la BIOS original en esta tarea), pero se ha mantenido por compatibilidad con los primeros formatos de disco de 2M. Tras estos campos hay unos punteros a diversas áreas interesantes: el primero apunta al programa de arranque y será empleado por dicho programa para conocer con comodidad su propia ubicación; después hay un puntero a una tabla con información sobre la estructura de la primera pista del disco, otro puntero apunta a una tabla con información de las demás pistas y, finalmente, un último puntero referencia una tabla de tamaños de los sectores de las pistas (excepto la primera). Los últimos campos sólo se emplean desde 2M 3.0 y almacenan la fecha y hora de formateo. La primera tabla contiene un byte que indica el número real de sectores de la primera pista, seguido de otro byte con el valor de GAP 3 empleado al formatear. Después vienen los números de sectores, uno tras otro, lo que permite elegir líbremente el interleave. Las últimas versiones de 2M acceden de manera eficiente a la primera pista (y a todas las demás) soportando perfectamente un interleave 1:1, si bien los primeros disquetes 2M fueron formateados con un factor 1:2. En los formatos de 1.80/1.88M la FAT ocupa 11 sectores, y otro el sector de arranque físico. Los sectores que van del 1 al 12 están, por lo tanto, necesariamente ocupados; pero del 13 al 19 hay sitio para 7 sectores que pueden contener el BOOT virtual (1 sector) y el código SuperBOOT (5 sectores). El sector restante se debe a que en discos de 1.88M con 84 pistas la FAT1 ocuparía un sector más. ┌────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────┐ │ Capacidad bruta real antes de
│
│ formatear (con 82 pistas y en
├─────────────────────┬─────────────────────┬─────────────────────┬─────────────────────┤
│
Bytes netos obtenidos por los principales formateadores
│ controladora de alta densidad) │ FORMAT (40/80p) (*) │ FDFORMAT (82p) (**) │
2MF 3.0 /F (82p)
│
2MF 3.0 /M (82p)
│
┌───────┼────────────────────────────────┼─────────────────────┼─────────────────────┼─────────────────────┼─────────────────────┤ │ 5¼-DD │
1.025.000 bytes
(0,98 Mb)
│
│ 5¼-HD │
1.708.224 bytes
(1,63 Mb)
│
│ 3½-DD │
1.230.000 bytes
(1,17 Mb)
│
│ 3½-HD │
2.050.000 bytes
(1,96 Mb)
│
(360K)
│
1.228.800 (1200K)
│
368.640
(720K)
│
1.474.560 (1440K)
│
737.280
(820K)
│
(820K)
│
(902K)
│
1.511.424 (1476K)
│
1.511.424 (1476K)
│
1.595.392 (1558K)
│
(820K)
│
1.007.616
(984K)
│
1.091.584 (1066K)
│
1.763.328 (1722K)
│
1.847.296 (1804K)
│
1.931.264 (1886K)
│
839.680
839.680
839.680
923.648
└───────┴────────────────────────────────┴─────────────────────┴─────────────────────┴─────────────────────┴─────────────────────┘ (*) También FDFORMAT cuando se emplean los formatos estándar del DOS.
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
(**) Formatos de máxima capacidad soportados (820-1.48-1.72).
La segunda tabla contiene información de las demás pistas del disco. El contenido y el formato de esta tabla varía según el tipo de disco: los formatos normales (como el caso de 1.80M) poseen 5 bytes: el primero indica el número de sectores de la pista, el siguiente el GAP 3 al formatear, otro byte indica el tamaño de sector empleado (siempre 3, esto es, 1024 bytes) y los dos últimos bytes son equivalentes a los parámetros /X e /Y de FDFORMAT para desplazar de manera óptima la numeración de los sectores en las pistas consecutivas. Estos valores de /X e /Y son sensiblemente menores que los de FDFORMAT, pero no hay que olvidar que aquí los sectores son dos veces más grandes. En los formatos de disco de máxima capacidad (como en 1.88M) esta tabla cambia radicalmente de estructura: el primer byte sigue siendo el número de sectores, pero ahora son sectores de 128 bytes. Esto se debe a que en estos formatos, las pistas son preformateadas (en una primera pasada) con sectores de 128 bytes. El siguiente byte es el GAP 3, que como se puede observar es muy pequeño (de 3 a 5 bytes). Finalmente, viene el número de sectores a renumerar. La razón es que, durante el formateo, se asignan números a partir de 129 a la mayoría de los sectores; sin embargo, algunos de ellos no se llevan el que les correspondería sino que siguen otra numeración más baja a partir de 1. En estos sectores, además, al ser enviada su información al FDC durante el formateo, se indicará un tamaño distinto de 128 (512, 1024 ó 2048). Así, por ejemplo, en 1.88M la pista queda formateada con nada menos que 64 sectores de 128 bytes numerados desde 129, habiendo sin embargo algunos de ellos con números más bajos (1, 2,..., 7) y definidos con mayor tamaño. Al ser escritos dichos sectores (segunda fase del formateo) se machacarán los sectores de 128 bytes que les siguen y quedarán sólo ellos en la pista. Esto permite colocar sectores de distinto tamaño en la pista. El GAP 3 definitivo será mayor (13 bytes en el peor de los casos). Ahora comprenderá el lector por qué había que escribir la pista, después del formateo, en estos formatos de disco... Por último, señalar que en esta tabla se elige un factor de interleave adecuado, que si se echa un vistazo resulta ser de 1:2, ya que los sectores están demasiado próximos para numerarlos consecutivamente (por razones de velocidad, si bien al ser accedidos uno a uno la controladora no tendría problemas para encontrarlos). En el caso del formato 1.88M, por ej., quedan numerados: 4,1,5,2,6,3,7. La última tabla es la única que realmente emplea 2M para acceder a todas las pistas, con excepción de la primera. Se trata de una lista ordenada de los tamaños de los sectores. En los formatos de disco normales es una lista de treses, ya que todos los sectores son iguales y de 1024 bytes. En los formatos de máxima capacidad, como 1.88M, se puede comprobar que la lista es más variada. Las otras dos tablas vistas con anterioridad sólo son empleadas durante el formateo del disco. 12.6.7.2 - PUNTUALIZACIONES SOBRE EL FORMATO DE MAXIMA CAPACIDAD. El formateo de disquetes 2M se realiza con un programa que veremos más adelante, 2MF.EXE, que permite elegir entre formatos normales (2MF sin parámetros o con la opción /F) y formatos de máxima capacidad (2MF /M). Como se vio en la descripción del sector de arranque, el formato de máxima capacidad logra introducir sectores de distinto tamaño en la misma pista. Seguramente la descripción dada en el apartado anterior no ha quedado muy clara, por lo que ahora puntualizaremos un poco más. Uno de los principales objetivos al realizar 2M fue conseguir un nivel de compatibilidad lo suficientemente alto, incluso en los formatos menos seguros como el que se describirá a continuación, al menos en comparación con los ya estudiados de sectores de 1 Kb. Hay disqueteras de 1.44M que soportan el formateo de 3 sectores de 4096 bytes en una pista, lo que permitiría obtener 1968K (en 82 cilindros, soportados por prácticamente todas las unidades). Sin embargo, hay muchos ordenadores en que esto no es posible, por tanto esta solución fue descartada. En los casos en que es posible, lo es además a costa de rebasar con creces los mínimos niveles de seguridad (machacando no sólo el GAP ubicado al final de la pista, sino también el del principio e incluso el IAM; resulta increíble que algunas controladoras de disquete continúen reconociendo los sectores). Además, se trataría de una solución exclusiva para disquetes de 1.44M. El truco explicado con anterioridad consiste en formatear los discos con sectores muy pequeños de 128 bytes, pero definiéndoles con tamaños de 512, 1024 y 2048 bytes al enviar la información de cada sector al controlador, de cara a agruparles posteriormente para obtener sectores de mayor tamaño. Echando cuentas, con un GAP 3 provisional de sólo 3 bytes (podríamos denominarlo GAP virtual) cada sector ocupa 128+62+3 = 193
EL HARDWARE DE APOYO AL MICROPROCESADOR
284
bytes. Agrupando 11 de estos sectores se obtienen 193*11=2123 bytes, suficientes para contener un sector de 2048 bytes, los 60 bytes añadidos al principio del primer sector de 128 bytes por el FDC, los 2 bytes añadidos al final del último sector por el FDC y otros 13 bytes de GAP 3. Agrupando 6 sectores se obtienen 1158 bytes, suficientes para contener un sector de 1024 bytes con un GAP 3 de 72 bytes. Finalmente, agrupando 3 se consiguen 579 bytes, en los que cabe un último sector de 512 bytes con un GAP 3 de 5 bytes. Así, en un disquete estándar de 1.44M, con 12500 bytes por pista, donde caben bastante holgadamente 64 sectores de 128 bytes de las características mencionadas, se pueden colocar 5 grupos de 11, 1 de 6 y otro de 3. En total: 11,5 Kb en cada pista (1886 en todo el disco, a 82 cilindros). Una vez formateada la pista, es conveniente escribir todos los sectores (la primera lectura daría error de CRC en caso contrario), de paso se asegura de esta manera, en una posterior lectura, que la escritura no ha provocado que ningún sector pise a otro, asegurando la fiabilidad del método. Una vez que el disco ha sido formateado, la verificación realizada durante el formateo garantiza que es seguro; la separación o GAP 3 medio menor es de 13 bytes y puede considerarse bastante razonable (el sector de 512 bytes con un GAP 3 de sólo 5 es colocado siempre al final de la pista); en los disquetes de doble densidad es además superior, al emplearse un GAP 3 virtual en la primera fase de 4 ó 5 bytes en vez de 3. El formateo es relativamente lento, ya que requiere tres fases: formateo, escritura y lectura para verificar; cada una de ellas, dada la proximidad de los sectores, requiere de dos vueltas del disco (los sectores estarán numerados alternamente con un razonable interleave 1:2); en total, 6 vueltas en un disco de 1.44M por cada pista, lo que equivale a 1,2 segundos por pista y 3:17 minutos en el conjunto del disquete (2 caras y 82 cilindros). Este es el precio que hay que pagar para obtener 1.912.320 bytes libres netos (los que aparecen al hacer un DIR) frente a los 1.457.664 conseguidos por el FORMAT del DOS. Un último detalle a tener en cuenta es que, en este tipo de formato, al escribir el cabezal 1 del cilindro 0, el código de 2M se saltará el acceso al primer sector de la pista (al estar la FAT2 en él, por regla general, y debido a las emulaciones). Por tanto, en este caso, es necesario escribir en el cabezal 129 para asegurar que realmente se escribe la pista y el disco queda correctamente inicializado. Por comodidad, se puede escribir en el cabezal 128/129 de todas las pistas (salvo la primera, que no tiene realmente tantos sectores como las demás y que además tampoco es necesario escribir tras el formateo). 12.6.7.3 - DESCRIPCION DE FUNCIONAMIENTO DEL SOPORTE RESIDENTE (2M). 2M es un programa residente ordinario que desvía la INT 13h/40h. En las máquinas AT con disco duro de tipo IDE (los más extendidos actualmente) o con una controladora de disco duro ordinaria de AT, la BIOS desvía a INT 40h los servicios de disquete, siendo invocada esta interrupción desde la INT 13h para atender las funciones de disquete. Sin embargo, si el ordenador no tiene disco duro o incorpora una controladora de disco duro de XT, es la INT 13h quien podría controlar los disquetes. La versión 1.0 de 2M desviaba la INT 40h en lugar de la INT 13h, por el motivo que ahora analizaremos (ayuda en la cuestión del DMA); sin embargo, ésto hacia que el programa no funcionara en algunas máquinas AT sin disco duro o con controladora de XT. Por ello, en la versión 1.1 se volvió a trabajar con INT 13h. Pero desde 2M 2.0+, aunque ahora más por razones de seguridad que de comodidad, se utiliza una técnica mixta: si el ordenador emplea la INT 40h, 2M se instala desde esta interrupción; en caso contrario, lo hace desde INT 13h (actuándo desde INT 40h el programa toma el control de los discos antes que otros TSR instalados después). Y volvamos sobre la cuestión del DMA, que motivó el uso de INT 40h en 2M 1.0. Como el lector recordará, a la hora de transferir con la disquetera hay que tener cuidado con las fronteras de DMA. Sin embargo, resultaría muy engorroso tener que tener esto en cuenta en los programas de alto nivel. El propio DOS considera que es un auténtico fastidio tener que comprobar esto cada vez que se accede al disco. Por ello, cuando el sistema operativo se carga en el ordenador desvía la INT 13h y la modifica para arreglar de un plumazo los problemas con el DMA: a partir de ese momento, la INT 13h es realmente controlada por el DOS, aunque se trate de una interrupción BIOS. Las nuevas rutinas de la INT 13h colocadas por el DOS se limitan a llamar a la vieja INT 13h (nadie ha hablado aún de INT 40h) y, cuando se produce un error de frontera de DMA, la operación de disco que lo había provocado es segmentada probablemente en tres fases: los sectores que estaban antes de la frontera, los que quedan por detrás y el que cae justo en medio; este sector es probablemente transferido a través de un buffer intermedio del sistema. ┌───────────────────────────────────────────────────────────────────────────────────┐ │ Porcentaje de disco aprovechado (perdido) tras el formateo │
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
├────────────────────┬────────────────────┬────────────────────┬────────────────────┤ │ FORMAT │ FDFORMAT 1.8 │ 2MF 3.0 /F │ 2MF 3.0 /M │ ┌───────┼────────────────────┼────────────────────┼────────────────────┼────────────────────┤ │ 5¼-DD │ 35,96% (64,04%) │ 81,92% (18,08%) │ 81,92% (18,08%) │ 90,11% ( 9,89%) │ │ 5¼-HD │ 71,93% (28,07%) │ 88,48% (11,52%) │ 88,48% (11,52%) │ 93,39% ( 6,61%) │ │ 3½-DD │ 59,94% (40,06%) │ 68,27% (31,73%) │ 81,92% (18,08%) │ 88,75% (11,25%) │ │ 3½-HD │ 71,93% (28,07%) │ 86,02% (13,98%) │ 90,11% ( 9,89%) │ 94,21% ( 5,79%) │ ├───────┼────────────────────┼────────────────────┼────────────────────┼────────────────────┤ │ Media │ 59,94% (40,06%) │ 81,17% (18,83%) │ 85,60% (14,40%) │ 91,62% ( 8,38%) │ └───────┴────────────────────┴────────────────────┴────────────────────┴────────────────────┘
Si 2M se instala colgando de INT 13h, al introducir un disquete de tipo 2M (cuyo control evidentemente corre a cargo de 2M) todas las llamadas del DOS a la INT 13h serían llamadas a 2M, que ha sido instalado después de que el DOS arregle la INT 13h. Por tanto, 2M debe en ese caso ocuparse de la engorrosa gestión de errores de DMA, ya que el DOS no espera nunca este tipo de error de una llamada a la INT 13h. En la práctica, 2M a partir de la versión 1.1, en las operaciones que afectan a varios sectores de disco consecutivos, se ve obligado a detectar con antelación el futuro cruce de una frontera de DMA: en caso de que se vaya a producir, el sector problemático es transferido a través del buffer intermedio del programa. La versión 1.0 de 2M desviaba INT 40h en vez de INT 13h y se limitaba a devolver la condición de error cuando se iba a producir, para que el propio DOS en INT 13h llamara de nuevo con más cuidado. 2M podría haber sido creado como controlador de dispositivo que definiera nuevas letras de unidad para soportar los nuevos disquetes; sin embargo resulta más intuitivo para el usuario continuar empleando las unidades A: y B: habituales. Esto se consigue, como hemos visto, modificando la INT 13h de la BIOS, lo que además permite el funcionamiento de ciertas utilidades de bajo nivel en los nuevos disquetes; realmente, en el mundo del PC no hay casi programas de utilidad a bajo nivel con el disco. Salvo los copiones, la mayoría de los llamados programas de bajo nivel en materia de disquetes se limitan a llamar a la BIOS. La técnica de ampliar la funcionalidad de la INT 13h de la BIOS es, por tanto, la más eficiente. El listado que comentaremos es sólo la parte importante del programa. Desde 2M 3.0 ya no hay listados con partes repetidas: un único fichero 2M.ASM produce 2M.COM (sistemas AT) y 2MX.COM (en PC/XT) por medio del ensamblaje condicional. Para ello se apoya en 2MKERNEL.INC, núcleo principal con todo el código de acceso a la controladora para soportar los discos 2M, y también empleado para generar 2M.SYS (versión driver para AT) y 2MFBOOT.BIN (con código SuperBOOT para el formateador). También se utiliza 2MUTIL.INC para englobar ciertas rutinas de utilidad comunes a más programas de la aplicación. Aquí nos limitaremos a comentar 2MKERNEL.INC, ya que lo restante no está relacionado con la controladora de discos. 2M puede controlar las unidades de disco A: y B: si son de alta densidad (de lo contrario se limita a invocar a la INT 13h original). Por ello, además de un juego de variables globales, hay una estructura que define las variables propias de una unidad que se emplea para crear dos áreas de datos particulares, una para │
[ 0x04
┌──────────────────────────────────────────────────────────────────────────────────┐ │ │
Longitud (ms)
Sector
Tamaño
Cilindro Cabeza
ST0
ST1
ST2
──────────────────- ────── ──────────── ──────── ────── ───── ───── ─────
│
│ │
│
[
19.58]
19.58
10
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
37.44]
17.86
11
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
55.31]
17.87
1
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
73.18]
17.87
2
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
91.05]
17.87
3
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
108.91]
17.86
4
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
126.79]
17.87
5
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
144.65]
17.86
6
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
162.52]
17.87
7
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
180.39]
17.87
8
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
198.26]
17.87
9
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
217.85]
19.59
10
1024 (
3)
0
1
0x04
0x00
0x00
│
[ 0x04
│
[ 0x04
│
[ 0x04
│
[ 0x04
│
[ 0x04
│
[ 0x04
│
[ 0x04
│
[
235.71] 0x00
253.71] 0x00
289.44] 0x00
307.43] 0x00
325.43] 0x00
343.42] 0x00
361.28] 0x00
379.16]
17.87
1024 (
3)
0
1
2
1024 (
3)
0
1
3
1024 (
3)
0
1
4
1024 (
3)
0
1
5
1024 (
3)
0
1
6
1024 (
3)
0
1
7
1024 (
3)
0
1
8
1024 (
3)
0
1
│
17.87
0x00
1
│
17.99
0x00
1
│
17.99
0x00
0
│
17.99
0x00
3)
│
17.87
0x00
1024 (
│
17.86
0x00
11 │
18.00
0x00
271.57] 0x00
17.86
0x00
│
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
0x04
0x00
│
0x00
│
│
│
│
Una tecla para leer más ID's [ESC=salir].
└──────────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────────┐ │ │
Longitud (ms)
Sector
Tamaño
Cilindro Cabeza
ST0
ST1
ST2
──────────────────- ────── ──────────── ──────── ────── ───── ───── ─────
│ │
│
[
33.95]
33.95
3
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
45.32]
11.37
7
512 (
2)
0
1
0x04
0x00
0x00
│
│
[
79.14]
33.82
4
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
112.94]
33.80
1
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
146.76]
33.82
5
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
180.58]
33.82
2
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
198.97]
18.39
6
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
232.78]
33.82
3
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
244.16]
11.37
7
512 (
2)
0
1
0x04
0x00
0x00
│
│
[
277.97]
33.81
4
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
311.78]
33.81
1
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
345.60]
33.81
5
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
379.42]
33.82
2
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
397.80]
18.38
6
1024 (
3)
0
1
0x04
0x00
0x00
│
│
[
431.62]
33.82
3
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
443.00]
11.38
7
512 (
2)
0
1
0x04
0x00
0x00
│
│
[
476.95]
33.95
4
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
510.75]
33.81
1
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
544.57]
33.82
5
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
578.40]
33.83
2
2048 (
4)
0
1
0x04
0x00
0x00
│
│
[
596.79]
18.38
6
1024 (
3)
0
1
0x04
0x00
0x00
│
│ │
cada disquetera. A lo largo de la mayoría del código residente, el registro SI estará apuntando a esa zona de variables locales de la disquetera que se trate. Al principio del programa está la rutina que controla la interrupción 2Fh, empleada para gestionar la autodetección en memoria del programa residente y permitir su posible futura desinstalación. La rutina que controla la INT 13h ó INT 40h es más importante. Su labor consiste en pasar el control de las funciones 2 (lectura), 3 (escritura), 4 (verificación) y 5 (formateo) a 2M (si el disquete introducido es de este tipo) o a la interrupción original (si el disquete introducido no es de tipo 2M). Existe una variable por cada unidad que indica en todo momento si el disquete introducido es de tipo 2M (control2m_flag=ON) o no. Otro cometido consiste en detectar los cambios de disco, para actualizar dicha variable en consecuencia. Ante el primer cambio de disco detectado se retorna con un error 6 (porque así lo hace la BIOS original).
│ Una tecla para leer más ID's [ESC=salir].
│
└──────────────────────────────────────────────────────────────────────────────────┘
LECTURA DE ID's EN 3½-HD (FORMATO NORMAL Y DE MAXIMA CAPACIDAD)
En el caso de la función de formateo (no implementada en el código SuperBOOT por falta de espacio), se mira si quien la invoca solicita un formateo normal o si se trata de una petición de formateo de disquete 2M. Esto es debido a que 2M aumenta la funcionalidad de la función 5 original de la BIOS para soportar los nuevos disquetes. En la función de la BIOS, se indica en AL el número de sectores de la pista, en CH la pista, en DH el cabezal, en DL la unidad y en ES:BX se apunta a un buffer con información para formatear. Cuando está 2M residente y se invoca la función 5 con el registro SI=324Dh (SI="2M") y con AL=7Fh, se le indica a 2M que no llame a la función de formateo original de la BIOS y que formatee él la pista en la unidad y cabezal indicados. En este caso AL es ignorado, ya que en ES:BX lo que se le pasa a la BIOS (es decir, a 2M) no es la dirección de tabla alguna sino el sector de arranque del futuro disquete, que contiene toda la información necesaria sobre la estructura del disco para poder clonarlo. No hay que crear tablas ni emplear otras funciones BIOS para seleccionar densidad ni nada por el estilo. Tampoco hay que considerar la complejidad de los formatos 2M (en los que difiere la primera pista de las restantes): de todo se ocupa el código residente del propio 2M. La rutina format_2m invocada desde ges_int13 se encarga del formateo. Primero se llama a la INT 13h original (previa a 2M) para solicitar un formateo en el cabezal 2, inexistente, con objeto de que retorne rápidamente ante el error. Así, se avisa a todos los demás programas residentes de que el disco va a ser formateado: el propio DOS invalida los buffers asociados al viejo disquete; si 2M no tomara esta medida, al hacer DIR sobre el disco recién formateado aparecería aún, falsamente, su contenido previo. A continuación realiza las siguientes tareas: toma nota de los parámetros del futuro disco, pone en marcha el motor, lleva el cabezal a la pista, crea la tabla con información para el formateo, formatea la pista y retorna con el código de error o éxito correspondiente. En los formatos de máxima capacidad, recuérdese que había que escribir la pista tras el formateo, para evitar que la primera lectura diera error y para completar realmente el proceso. Sin embargo, el código residente de 2M no
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
escribe nada tras el formateo. Esto permite en este caso a los programas de copia de disquetes poder ir escribiendo el disco destino a la vez que formatean; lo contrario sería una pérdida de tiempo con una escritura muerta. En el caso de programas que sólo formateen, tendrán además que escribir; esto implica que esos programas deben estar diseñados para formatear disquetes 2M (nadie ha dicho que el FORMAT del DOS pudiera hacerlo por sí solo). El procedimiento detecta_cambio determina si se ha producido un cambio de disco. En caso de que se haya producido (o la primera vez absoluta que se ejecuta la rutina tras haber instalado 2M en memoria) se intenta leer el sector de arranque del mismo para determinar la densidad del mismo y averiguar si es de tipo 2M. Primero se intenta bajar la línea de cambio de disco: si no fuera posible, es que la unidad está sin disquete introducido. El acceso se intenta tres veces, con todas las densidades posibles (500, 300, 250 Kbit/seg y finalmente 1 Mbps). Si no se pudiera leer el sector de arranque, podría deberse a que es un disco sin formatear, o tratarse de otro medio físico, por lo que se le devuelve el control a la INT 13h original hasta un futuro nuevo cambio de disco. Esto mismo puede suceder si se consigue leer el sector de arranque y la rutina set_info comprueba que el disco es estándar del DOS. Cuando no hay disco en la unidad y se falla al bajar la línea de cambio, se delega el control a la BIOS pero si ésta logra bajarla
┌──────────────────────────────────────────────────────────────────────────────────┐ │ │
Longitud (ms)
Sector
Tamaño
Cilindro Cabeza
ST0
ST1
ST2
──────────────────- ────── ──────────── ──────── ────── ───── ───── ─────
│ │
│
[
31.72]
31.72
2
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
63.27]
31.55
3
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
103.25]
39.98
4
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
134.76]
31.51
5
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
166.35]
31.59
1
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
197.98]
31.63
2
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
229.53]
31.55
3
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
269.51]
39.98
4
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
301.01]
31.50
5
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
332.61]
31.60
1
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
364.24]
31.63
2
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
395.79]
31.55
3
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
435.77]
39.98
4
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
467.27]
31.50
5
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
498.86]
31.59
1
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
530.59]
31.72
2
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
562.13]
31.54
3
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
602.12]
39.99
4
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
633.62]
31.50
5
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
665.22]
31.60
1
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
696.85]
31.63
2
1024 (
3)
0
1
0x05
0x00
0x00
│
│
│
│
│
Una tecla para leer más ID's [ESC=salir].
└──────────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────────┐ │ │
Longitud (ms)
Sector
Tamaño
Cilindro Cabeza
ST0
ST1
ST2
──────────────────- ────── ──────────── ──────── ────── ───── ───── ─────
│ │
│
[
56.44]
56.44
3
2048 (
4)
0
1
0x05
0x00
0x00
│
│
[
112.90]
56.46
1
2048 (
4)
0
1
0x05
0x00
0x00
│
│
[
143.63]
30.73
4
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
158.92]
15.29
2
512 (
2)
0
1
0x05
0x00
0x00
│
│
[
165.85]
6.93
0
128 (
0)
0
1
0x05
0x00
0x00
│
│
[
222.30]
56.45
3
2048 (
4)
0
1
0x05
0x00
0x00
│
│
[
278.75]
56.45
1
2048 (
4)
0
1
0x05
0x00
0x00
│
│
[
309.49]
30.73
4
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
324.78]
15.29
2
512 (
2)
0
1
0x05
0x00
0x00
│
│
[
331.70]
6.92
0
128 (
0)
0
1
0x05
0x00
0x00
│
│
[
388.16]
56.46
3
2048 (
4)
0
1
0x05
0x00
0x00
│
│
[
444.61]
56.45
1
2048 (
4)
0
1
0x05
0x00
0x00
│
│
[
475.34]
30.73
4
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
490.63]
15.29
2
512 (
2)
0
1
0x05
0x00
0x00
│
│
[
497.55]
6.92
0
128 (
0)
0
1
0x05
0x00
0x00
│
│
[
554.01]
56.45
3
2048 (
4)
0
1
0x05
0x00
0x00
│
│
[
610.46]
56.45
1
2048 (
4)
0
1
0x05
0x00
0x00
│
│
[
641.19]
30.73
4
1024 (
3)
0
1
0x05
0x00
0x00
│
│
[
656.48]
15.29
2
512 (
2)
0
1
0x05
0x00
0x00
│
│
[
663.41]
6.93
0
128 (
0)
0
1
0x05
0x00
0x00
│
│
[
719.86]
56.45
3
2048 (
4)
0
1
0x05
0x00
0x00
│
│
│
EL HARDWARE DE APOYO AL MICROPROCESADOR
│
284
Una tecla para leer más ID's
[ESC=salir].
│
└────────────────────────────────────────────────────────── ────────────────────────┘
LECTURA DE ID's EN 5¼-DD (FORMATO NORMAL Y DE MAXIMA CAPACIDAD)
(¿controladora no compatible?) se le vuelve a robar el control al siguiente acceso. Esta artimaña permitió a versiones antiguas de 2M funcionar en máquinas 486 (cuando no se tomaba la precaución de hacer un retardo al resetear la controladora y ésta quedaba en ocasiones atontada, hasta que la BIOS del sistema la reseteaba bien). En caso de ser un disco 2M se anotan las características del mismo, teniendo en cuenta que lo que acabamos de leer es precisamente su sector de arranque... Como 2M es el encargado de detectar la densidad del disco, es necesario que ajuste las variables de la BIOS indicando dicha densidad, ya que ella será la encargada de controlar los disquetes normales. En realidad, la densidad sólo se ajusta en el primer acceso al disco, existiendo dos variables en el área de datos de la BIOS, en el segmento 40h, que indican la densidad a emplear en cada disquetera: si dichas variables no están correctamente inicializadas, al conmutar de una unidad a otra la BIOS no seleccionaría la velocidad correcta y se produciría un error. Como al introducir un disco nuevo en la unidad lo primero que hace el DOS es consultar su sector de arranque, las primeras versiones de 2M dejaban la tarea de detectar la densidad del disco a la propia BIOS (espiando las lecturas del sector de arranque que ésta realizaba para determinar el tipo de disco y decidir si robar el control o no). Sin embargo, ciertas BIOS de prestigiosa marca italiana (yo sólo conozco una) hacían cosas muy raras para determinar la densidad de los discos (como ir leyendo varias pistas consecutivas) y tropezaban con los disquetes 2M. Esto es un botón de muestra de lo que pasa cuando los fabricantes europeos modifican mal las BIOS de los taiwaneses, para no copiarlas del todo. De ahí que la versión definitiva del programa reemplace en esta tarea a la BIOS. Sin embargo, en caso de que 2M no pueda determinar la densidad de la unidad sique delegando el control a la BIOS: el motivo es mantener la compatibilidad con otros soportes extraños. Este es también el motivo por el que 2M no sustituye totalmente el código BIOS de INT 13h, que hubiera dado menos problemas a la hora de programar (aunque el programa resultante ocuparía también algo más de memoria). COPY DE 21 FICHEROS Y 1.457.664 BYTES ┌──────────────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ │ Formato │ 1.44 │ 1.44 │ 1.64 │ 1.72 │ 1.80 │ 1.88 │ ├──────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ │ Formateador │ FORMAT │FDFORMAT │FDFORMAT │FDFORMAT │ 2MF │ 2MF │ ├──────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ │ Tiempo escritura │ 1:17.27 │ 1:06.72 │ 1:00.74 │ 1:27.05 │ 1:15.30 │ 1:23.93 │ ├──────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ │ Tiempo lectura │ 0:59.82 │ 0:48.50 │ 0:44.11 │ 1:05.69 │ 0:43.78 │ 0:54.16 │ ├──────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ │ Espacio libre │ 0 │ 0 │ 203,776 │ 287,744 │ 370,688 │ 454,656 │ ├──────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ │ Escritura (Kb/s) │ 18.42 │ 21.34 │ 23.44 │ 16.35 │ 18.90 │ 16.96 │ ├──────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ │ Lectura (Kb/s) │ 23.80 │ 29.35 │ 32.27 │ 21.67 │ 32.51 │ 26.28 │ ├──────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ │ Promedio (Kb/s) │ 21.11 │ 25.35 │ 27.86 │ 19.01 │ 25.71 │ 21.62 │ │ Indice relativo │ 100.00 │ 120.09 │ 131.98 │ 90.05 │ 121.79 │ 102.42 │ └──────────────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ Notas: Ficheros: 2 de 256K, 3 de 128K, 4 de 64K, 5 de 32K, 6 de 16K y 1 de 15.5K. Prueba bajo DOS 6.2 y con solo 2M y FDREAD instalados. La prueba de escritura consistía en COPY C:\TEST\*.* B: y la de lectura consistía en COPY /B *.* NUL Al leer del disco duro se perdieron 5.5 segundos que han sido ya descontados; el disco ya estaba girando. Con FDFORMAT se emplearon siempre los parámetros /X:2 e /Y:3 para lograr la mayor velocidad posible.
La rutina calc_chk es quien realmente realiza el checksum del sector de arranque, comprobando además si el disco es de tipo 2M. La rutina set_err, invocada al final del formateo y desde la rutina que accede
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
directamente a los sectores de disco, analiza el código de error devuelto por el controlador de disquetes y lo convierte a la notación de errores de la BIOS. Set_bios_err copia el resultado del acceso a disco a las variables propias de la BIOS por razones de compatibilidad con el software de disco de bajo nivel. En el procedimiento control_2m se realiza la gestión a alto nivel del acceso a disco: es aquí donde se emula la existencia de la segunda copia de la FAT apoyándose en la primera, así como el sector de arranque virtual ubicado en el primer sector físico de la FAT2. Como 2M 2.0 apareció cuando ya estaba bastante extendida la versión anterior, se hizo necesario (y lo sigue siendo en 2M 3.0) continuar soportando los discos antiguos. En ellos, se sigue leyendo el sector de arranque físico en lugar del virtual, que no existe, y se permite su escritura si es correcto (si no se intentan tocar partes sensibles del mismo). Así mismo se tiene en cuenta el acceso al cabezal 128 ó 129 para acceder en ese caso al 0 ó al 1 sin emulaciones. Las coordenadas de la BIOS, en la forma cilindro-cabezal-sector son traducidas momentáneamente a las del DOS para simplificar el proceso. También se comprueba si el checksum (o suma de comprobación) del sector de arranque, realizado con anterioridad en set_info, es correcto. Es difícil que no lo sea, porque el código de 2M no deja a cualquiera escribir sobre el sector de arranque físico. Pero si no lo fuera, se devuelve un seek error al programa que llama a la INT 13h, habiéndose elegido este código porque no había otro más descriptivo en la lista de errores de disco de la BIOS. Si al ejecutar un comando DIR sobre un disquete 2M aparecen errores de seek ya sabrá el lector por qué... El procedimiento ejecuta_io es llamado repetidamente desde control_2m para realizar la lectura o escritura de un conjunto de sectores. Este procedimiento es el más complicado de todo el programa, pero la tarea que realiza es relativamente sencilla. Primero, vuelve a pasar las coordenadas del disco del formato DOS al formato físico propio de la BIOS. Hay que tener en cuenta que un sector físico en un disquete 2M puede ser de 512 bytes, pero también de 1024 ó 2048. Por tanto, introducimos aquí el concepto de sección para hacer referencia a 512 bytes, que a fin de cuentas es la unidad de medida a emplear. En el caso de los formatos de mayor capacidad (2MF /M) se accede de sector en sector físico, ya que las operaciones de lectura/escritura de varios sectores en bloque sólo tienen sentido cuando éstos están lo suficientemente separados pero sin pasarse. En nuestro caso están excesivamente separados, ya que la numeración es discontinua (interleave 1:2) y entre dos sectores de número consecutivo hay otro; por tanto, no se ganaría rendimiento en un acceso multisector; por otro lado, algunos formatos de disco tienen un número par de sectores en las pistas y dos de ellos tienen que tener forzosamente el número consecutivo, con lo que fallaría el acceso multisector debido a la excesiva proximidad en este caso; además, no está muy claro si se podrán acceder de esta manera sectores que no sean del mismo tamaño (no me molesté en probarlo). La lectura es la operación más sencilla: se extrae del disco el sector físico donde está incluida la sección que toca leer y después se copia a la dirección de memoria definitiva. No se puede leer el sector directamente en el buffer requerido por el programa que invoca la INT 13h, ya que éste podría requerir sólo 512 bytes (o un múltiplo impar de esta cifra) y los sectores físicos podrían exceder este tamaño, afectando a zonas no permitidas de la memoria ubicadas tras el buffer. Por tanto se utiliza un buffer intermedio (definido con un tamaño de 2 Kb para acomodar el mayor sector posible). El movimiento de la sección a su ubicación definitiva no es una tarea muy costosa, ya que en un ordenador medio se ejecuta unas cien veces más rápido │ Entorno
015C-0174
400
0176
MAPAMEM
│
│ Programa 0176-01C9
1.344
0176
MAPAMEM
│
│
┌──────────────────────────────────────────────────┐
│ Libre
01CB-9FFE 648.000
0000
│ MAPAMEM 2.1
│
│ Sistema
A000-D3B4 211.792
0008
│
│
│
│ Sistema
D3B6-D3C2
208
D3B6
│
│
│ Sistema
D3C4-D50D
5.280
D3C4
│
│
│ Sistema
D50F-E437
62.096
0008
│
│ -------- --------- ------- ----- --------------- │
│ Sistema
E439-E49C
1.600
E439
│
│ Sistema
0000-003F
1.024
Interrupciones │
│ Sistema
E49E-E4AD
256
E49E
│
│ Sistema
0040-004F
256
Datos del BIOS │
│ Sistema
E4AF-E4CE
512
E4AF
│
│ Sistema
0050-0105
2.912
Sistema Operat.│
│ Sistema
E4D0-E55E
2.288
E4D0
│
│ Sistema
0107-0143
976
0008
│
│ Sistema
E560-E568
144
E560
│ Sistema
0145-0144
0
0008
│
│ Datos
E56A-E631
3.200
014B
4DOS
│
│ Sistema
0146-0149
64
0008
│
│ Entorno
E633-E672
1.024
014B
4DOS
│
│ Programa 014B-015A
256
014B
│
│ Libre
E674-E68C
400
0000
│
- Información sobre la memoria del sistema.
│ │ Tipo
Ubicación Tamaño
PID
Propietario
4DOS
│
EL HARDWARE DE APOYO AL MICROPROCESADOR
284
que lo que ha tardado la lectura desde el disco. Este proceso de lectura se repite tantas veces como secciones haya que │ Entorno E97C-E996 432 E998 VIDRAM │ transferir. En todo momento, unas variables indican qué sector │ Programa E998-EA04 1.744 E998 VIDRAM │ físico (y de qué cilindro, cabezal y unidad) está en el buffer. De │ Entorno EA06-EA1F 416 EA21 UNIVESA │ este modo, por ejemplo, cuando se lee un sector de 2 Kb para │ Programa EA21-EBF1 7.440 EA21 UNIVESA │ transferir su primera sección, se traen a la memoria 4 secciones │ Programa EBF3-EC1D 688 EBF3 KEYBSP │ de golpe y ya no serán necesarios más accesos a disco si hubiera │ Programa EC1F-EC77 1.424 EC1F RCLOCK │ que transferir también las 3 restantes, porque el sector en que │ Programa EC79-EDBB 5.168 EC79 2M │ están ya se encuentra en el buffer. La escritura es algo más │ Programa EDBD-EDD8 448 EDBD DISKLED │ compleja, y hay que distinguir dos casos: por un lado, cuando │ Libre EDDA-EDF3 416 0000 │ hay que volcar a disco un número de secciones consecutivas │ Programa EDF5-F281 18.640 EDF5 DATAPLUS │ suficientes para completar un sector físico; por otro, cuando hay │ Programa F283-F34D 3.248 F283 HBREAK │ que escribir una o varias secciones que no completan un sector │ Programa F34F-F354 96 F34F TDSK(D) │ │ Datos F356-FB55 32.768 F34F TDSK(D) │ físico. En el primer caso, se escribe sin más; en el segundo caso │ Libre FB57-FFA5 17.648 0000 │ es necesario leer el sector al buffer, modificar sólo la(s) └──────────────────────────────────────────────────┘ seccion(es) afectada(s) y escribirlo en el disco. Este último caso MEMORIA OCUPADA POR 2M supone una fuerte degradación de la velocidad, ya que tras leer un sector del disco habrá que volver a escribirlo, hecho que no ocurrirá hasta la siguiente vuelta del mismo. Por fortuna, cuando se hace un COPY el DOS envía grandes bloques, lo que en la mayoría de los casos (no en todos) provoca escrituras de pistas completas, tarea en la que no se pierde un ápice de rendimiento. No obstante, esta arquitectura de los disquetes 2M provoca que sean notablemente más lentos escribiendo que leyendo. │ Programa E68E-E810
6.192
E68E
SHARE
│
│ Programa E812-E97A
5.776
E812
PRINT
│
En los formatos normales (2MF /F) todos los sectores de la pista son del mismo tamaño, lo que también sucede en la primera pista de los formatos de más capacidad. Están suficientemente separados y numerados consecutivamente. Por tanto, una acceso multisector es posible y más que interesante. Aquí no sólo no se emplea el buffer intermedio sino que además no se puede, porque el acceso multisector puede superar los 2 Kb de capacidad del buffer. La transferencia se hace directamente sobre la dirección deseada por el programa que invoca la INT 13h. Sólo hay un par de excepciones: cuando la primera sección a transferir es la segunda mitad de un sector (recordemos que son de 1 Kb) y cuando la última sección es la primera mitad de un sector. En ambos casos se emplea el buffer intermedio por el mismo motivo de siempre: evitar la alteración de zonas de memoria que vayan detrás del buffer suministrado por el programa que llama a la INT 13h. Sobre la escritura se podrían hacer las mismas consideraciones que hacíamos con los formatos de máxima capacidad. En la operación de acceso multisector hay que considerar también el posible cruce del buffer suministrado por el programa principal con una frontera de DMA: la rutina acceso_multi se encarga, llegado el momento, de transferir el sector crítico a través del buffer intermedio, segmentando la operación en tres fases (los sectores anteriores, el sector que cruza la frontera y los restantes). No controlar los problemas con el DMA provoca que el ordenador se cuelgue al hacer COPY de un fichero mediano (o que lo copie mal en cualquier caso). Obviamente, el buffer intermedio se inicializa para que nunca cruce una frontera de DMA. El único caso en que acceso_multi no necesita tomar precauciones con el DMA es en el código SuperBOOT: aunque se instale desde la INT 13h, lo hace antes de la carga del sistema operativo (que será el encargado de arreglar los problemas con el DMA). Por tanto, en ejecuta_io es donde se toman todas las complicadas decisiones sobre cómo y dónde cargar/grabar de disco. He de agradecer aquí a Edgar Swank su colaboración en detectar y corregir errores en esta compleja rutina, proponiéndome además las modificaciones en el listado: antes de 2M 2.0, los discos 2M no soportaban realmente la escritura con verificación (VERIFY ON a nivel DOS). La variable sector_fin está a 0 para indicar el acceso a un solo sector (sector_ini) o es distinta de cero para indicar el último sector involucrado en el caso de accesos multisector (junto a sector_ini). Dentro de este procedimiento, la subrutina acceso_secc se encarga de la transferencia de una sola sección. El procedimiento trans_secc realiza las transferencias entre el buffer interno y la dirección suministrada por el programa que llama a la INT 13h. La rutina leido? comprueba si el próximo sector físico a
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
ser accedido esta ya en el buffer, para evitar una segunda lectura innecesaria. El procedimiento acceso_sector se encarga de hacer ciertas tareas como determinar la longitud del sector a ser leído (para poder programar luego correctamente el FDC), llevar el cabezal a la pista adecuada, cargar los registros convenientemente según haya que emplear el buffer intermedio o no, llamar a la rutina que accede realmente al disco y tomar nota de qué sector ha sido recién leído (para evitar futuras lecturas innecesarias). En num_secciones se calcula el número de secciones o bloques de 512 bytes del sector físico en curso, apoyándose en la información del sector de arranque del disquete que fue anotada cuando se le reconoció por vez primera. La rutina motor_ok arranca el motor de la unidad si aún no estaba en marcha. En caso de estar parado, o de llevar poco tiempo encendido a causa de una reciente lectura de la línea de cambio
┌───────────────────────────────────────────────────────────────────┐ │ │
│ │
[14464/109040] C:\>dir b:
│ │ │ │ │
│ Volume in drive B is unlabeled
Serial number is EA82:3F1B
File not found "B:\*.*" 0 bytes in 0 file(s) 1.912.320 bytes free
│ │
│ │
│ │
Inserte el disquete de ORIGEN en la unidad B:
│ │
│
│ [14464/109040] C:\>diskcopy b: b:
│ │
│
│ │
Presione cualquier tecla para continuar . . .
│
│ │
│
Copiando 82 pistas
│
│
23 sectores por pista, 2 cara(s)
│
│ │
│ Inserte el disquete de DESTINO en la unidad B:
│ │
│ │
Presione cualquier tecla para continuar . . .
│
│ │
└───────────────────────────────────────────────────────────────────┘
LA COMPATIBILIDAD DE 2M ES PRACTICAMENTE DEL 100%
de disco (el contador de tiempo que resta para su detención es aún muy alto) se hace la pausa pertinente para que alcance el régimen de rotación adecuado. Esta rutina es invocada en varias ocasiones; entre otras, desde ejecuta_io. En reset_drv se inicializa el FDC enviándole el comando Specify; la situacion de reset es mantenida durante unos microsegundos, pausa que también realizan las BIOS modernas, ya que en algunas versiones de 2M anteriores a la 1.3 se comprobó que no lograban resetear la controladora en algunas máquinas 486 (en estos casos no se detectaba el tipo del nuevo disco introducido en la disquetera y, al delegar el control a la BIOS, ésta generaba errores de sector no encontrado y anomalía general con los disquetes 2M). La rutina seek_drv posiciona el cabezal seleccionado sobre el cilindro adecuado: si ya estaba sobre él (por haber accedido con anterioridad a la otra cara del disco) no es necesario esperar a que el cabezal deje de vibrar; en caso de que haya que hacer esta pausa se establecen 1 ms para el caso de la lectura (no es muy peligroso que se produzca un error, ya que la operación se reintentaría) y 15 ms para la escritura, asegurando en este último caso el éxito de la operación, ya que escribir con el cabezal no asentado podría dañar la información del disco. El disco está formateado (salvo en los los formatos de máxima capacidad, que son un mundo aparte) con ciertos deslizamientos en la numeración de los sectores al conmutar de cilindro y cabezal (opciones /X e /Y del formateador) de tal manera que el acceso en escritura es factible en una sola vuelta del disco para todas las pistas a las que se acceda consecutivamente. Rebajar a 1 ms en el caso de la lectura tiene por objeto asegurar esto mucho más todavía. Así, algún ordenador muy extraño que pinchara en los índices de rendimiento a la hora de escribir probablemente no lo haría, al menos, al leer. Como un posicionamiento del cabezal precede siempre a las operaciones de lectura o escritura (seek_drv), se selecciona aquí la velocidad de transferencia a emplear, acorde con la densidad de la pista a ser accedida (set_rate). En caso de que la unidad precisara recalibración (debido a algún reset anterior) se llama desde aquí al procedimiento recalibrar.
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
El procedimiento sector_io es quien finalmente se encarga de hacer la lectura o escritura del sector o sectores necesarios, programando el FDC. Se calcula el tamaño en bytes del bloque a transferir, se programa el DMA por medio de las rutinas calc_dir_DMA y prepara_DMA y se envía el comando adecuado al FDC (lectura/escritura). Al final, se anotan los resultados. La subrutina calc_dir_DMA traduce la dirección segmentada al formato necesario para programar el DMA; en el código SuperBOOT tiene que devolver además un posible error de cruce de frontera de DMA, ya que el código de 2M no evita las llamadas ilegales en este caso. En genera_info se construye la tabla de información a enviar al DMA para formatear la pista solicitada en la función de formateo de 2M. Esta información se obtiene a partir del sector de arranque del futuro disco, suministrado por el programa que intenta formatear. Conociendo cómo esta estructurado dicho sector, la arquitectura de los disquetes 2M y qué necesita el comando del FDC para formatear se puede entender cómo funciona la rutina, por lo que no nos detendremos en analizarla. Es formatea_pista el procedimiento que formatea la pista a partir de la tabla creada por la rutina anterior. La subrutina espera_int espera durante no más de 2 segundos la llegada de una interrupción de disquete que señalice el final de una operación con el FDC. Conviene no esperar indefinidamente porque si la unidad no está preparada podría tardar muchísimo en devolver la interrupción. Así, se detecta en un tiempo razonable la circunstancia y posteriormente se reseteará la controladora (ante el error) para arreglar el problema de la interrupción pendiente (y del FDC que no respondía). Fdc_read y fdc_write se encargan de recibir y enviar bytes al FDC, típicamente órdenes y resultados. Ambas rutinas también tienen control timeout, en este caso de 2 milisegundos; al principio de las mismas se realiza una brevísima pausa al igual que hacen las BIOS AMI de 486 (que para algo servirá). Finalmente, las subrutinas fdc_respiro y retardo efectúan una pausa de 60 µs y AX milisegundos, respectivamente, apoyándose repetitivamente en la macro pmicro, que pierde unos 15,09 microsegundos muestreando los ciclos de refresco de memoria del AT. Pmicro no es una subrutina (salvo en el caso del código SuperBOOT, por razones de espacio) porque el CALL y RET asociados podrían ralentizar la monitorización de los ciclos de refresco de manera excesiva en los ordenadores más lentos, deparando un retardo efectivo superior. Finalmente, initcode será invocada sólo desde el sector de arranque físico durante el arranque desde disquete, con objeto de inicializar ciertas variables y activar el código SuperBOOT. Una precaución importante es que, ensamblando para obtener código SuperBOOT, éste tiene que ocupar exactamente 2560 bytes (5 sectores). Ciertamente, entra muy justo... pero cabe, con alguna que otra artimaña (excluir rutinas de formateo, utilizar subrutinas en vez de macros, simplificar la gestión de las fronteras de DMA, etc) aunque los 5 sectores que ocupa impiden ubicarlo en discos de doble densidad. Pero, ¿quién va a querer hacer botable un disco 2M de doble densidad, cuando uno estándar de alta tiene más capacidad?. ;│
│
;│
█ █▀▀▀▀ █
│
;│
█ █
█
│
;└───────────────────────────────────────────────────────────────────┘
█ █ █ █████ █
│
;│ ;│
█████ █
█ █
█
█▀▀▀▀ █▀▀▄
;│
█ ██ ██ █ █
█
;│
█████ █ █ █ ██▄
█████ ████
;│
█
█
█ █
█
;│
█████ █
█ █
█
█
█
█ ██
█
█ █
█ █▄▄▄▄ █
█ █
██ █
█
█ █▄▄▄▄ █████
│ │
;│ ;│
│
2MKERNEL
-
(C) Ciriaco García de Celis.
│
;│
│
;│
NUCLEO RESIDENTE DE 2M UTILIZADO POR SUS PRINCIPALES EJECUTABLES │ │
;│ ;│
Los siguientes símbolos se utilizan
│
;│
para el ensamblaje condicional:
│ │
;│ ;│
XT -> Indica que el código ejecutable es para PC/XT y no posee
│
;│
instrucciones de 286 ni utiliza recursos hardware de AT.
│
;│
│
SUPERBOOT -> Indica que el código ejecutable se ensambla para
│
;┌───────────────────────────────────────────────────────────────────┐
ocupar 2560 bytes exactamente (para autoarranque).
│ │
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
; --- Interpretación BIOS de los bits de ST1
; ------------ Códigos de modos y órdenes del DMA y del FDC.
F_READ
EQU
46h
; modo DMA para lectura
DB
4
F_WRITE
EQU
4Ah
; modo DMA para escritura
lista_errs
DB
0
F_VERIFY
EQU
42h
; modo DMA para verificación
DB
10h
; 'bad CRC'
F_FORMAT
EQU
01001101b
; orden de formateo del FDC
DB
8
; 'DMA overrun'
DB
0
DB
4
; 'sector not found'
DB
3
; 'write-protect error'
DB
2
; 'address mark not found'
DB
20h
; en otro caso: 'bad NEC'
; ------------ Estructura de datos con información para cada unidad.
; 'sector not found'
info_drv
STRUC
maxs
EQU
13
; máximo 13 sectores físicos/pista
tipo_drv
DB
?
; tipo de la disquetera (0 = no hay)
control2m_flag DB
OFF
; a ON si 2M controla la unidad
info_A
info_drv <>
; datos de A:
cambio
DB
ON
; a ON indica cambio de soporte
info_B
info_drv <>
; datos de B:
version_fmt
DB
?
; versión del formato de disco 2M
multi_io
DB
?
; a 0 si posible acceso multi-sector
; ***************************************
chk
DB
?
; a 0 si checksum del sector 0 Ok
; *
vunidad
EQU
THIS WORD
vunidad0
DB
?
; velocidad pista 0
; *
vunidadx
DB
?
; velocidad demás pistas
; ***************************************
gap
DB
?
; GAP entre sectores (leer/escribir)
sectpista
DB
?
; sectores lógicos por pista
tabla_tsect
DB
maxs DUP (?) ; tamaños de sectores 1, 2, ..., N
tam_fat
DB
?
; *
* C O D I G O
R E S I D E N T E
* *
; ------------ Rutina de gestión de INT 2Fh.
IFNDEF SUPERBOOT
; sectores/FAT en la unidad
; Código SuperBOOT no soporta INT 2Fh
ENDS ges_int2F
info_ptr
DW
info_A
DW
info_B
; punteros a datos de las unidades
preguntan:
IFDEF SUPERBOOT DB
"30"
; Versión 2MFBOOT 3.0
ENDIF id_sistema
DB
"2M-STV"
; identificación de disco 2M
IFDEF XT tbase
DW
?
; base de tiempos para retardos
PROC
FAR
STI
; ------------ Variables del programa.
ENDIF
CMP
AH,CS:multiplex_id
JE
preguntan
JMP
CS:ant_int2F
CMP
DI,1992h
JNE
ret_no_info
MOV
AX,ES
CMP
AX,1492h
JNE
ret_no_info
PUSH
CS
POP
ES
LEA
DI,autor_nom_ver
MOV
AX,0FFFFh
; saltar al gestor de INT 2Fh
; no llama alguien del convenio
; no llama alguien del convenio
; sí llama: darle información
unidad
DB
?
; unidad física de disco en curso
numsect
DW
?
; sectores a transferir
sectini
DW
?
; primer sector DOS a transferir
cilindro
DB
?
; cilindro del disco a acceder
cabezal
DB
?
; cabezal a emplear
sector
DB
?
; número de sector físico
sector_ini
DB
?
; número de sector físico inicial
sector_fin
DB
?
; número de sector físico final
; ------------ Nueva rutina de gestión de INT 13h. Llama a la INT 13h
seccion
DB
?
; parte del sector físico en curso
;
original o a una nueva rutina de control para la
secciones
DB
?
; sectores lógicos a transferir
;
lectura (AH=2), escritura (AH=3) y verificación (AH=4)
tsector
DB
?
; LOG2 (tamaño de sector) - 7
;
según el tipo de disco introducido. Ante una función de
buffer
DW
buffer_io
; puntero al buffer intermedio
;
formateo (AH=5) se entrega el control a la INT 13h
buf_unidad
DB
?
; unidad del sector en el buffer
;
original. Se detecta un posible cambio de disco y se
buf_cilcab
DW
?
; cilindro/cabezal de sector buffer
;
retorna en ese caso con el correspondiente error. En el
buf_sector
DB
?
; número de sector en el buffer
;
código SuperBOOT no hay soporte para formatear.
status
DB
?
; resultado de los accesos a disco
fdc_result
DB
7 DUP (?)
; bytes de resultados del FDC
orden
DB
?
; operación F_READ/F_WRITE/F_VERIFY
tab_ordenes
DB
F_READ
DB
F_WRITE
DB
F_VERIFY
ret_no_info:
ges_int2F
ENDP
ENDIF
IFNDEF SUPERBOOT
ges_int13
PROC STI
; órdenes 2, 3 y 4
; "entrada multiplex en uso"
IRET
CLD PUSHF
FAR
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
ges_2m:
MOV
unidad,DL
CALL
set_SI_drv
SI
MOV
cilindro,CH
CALL
set_SI_drv
MOV
cabezal,DH
CMP
CS:[SI].tipo_drv,2
OR
CH,DH
JE
ges_2m
JNZ
format_trx
CMP
CS:[SI].tipo_drv,4
INC
CL
POP
SI
JNZ
no_f_chg
JC
ges13bios
MOV
[SI].cambio,ON
CMP
AH,2
JMP
fmt_exit
JB
ges13bios
CMP
AH,5
MOV
AL,1
JA
ges13bios
; no Read/Write/Verify/Format
MOV
CH,0
CALL
detecta_cambio
; ¿cambio de disco?
MOV
DH,2
; en cabezal 2 (incorrecto)
JNC
sin_cambio
ant_int13
; avisar al DOS del nuevo disco
CMP
DL,2
JAE
ges13bios
PUSH
; no es disquetera A: ó B:
; ¿unidad 1.2M?
; ¿unidad 1.44/2.88M?
; no es unidad de alta densidad
; no Read/Write/Verify/Format
no_f_chg:
sin_cambio:
format_bios:
dilucida:
CLD
AX,600h
RET
2
CMP
AH,5
JNE
dilucida
CALL
leer_lin_camb
JNZ
format_bios
CMP
AL,7Fh
JNE
format_bios
CMP
SI,"2M"
JE
format_2m
; es orden de formateo de 2M
CALL
set_flag_STV
; CF = 0 -> indicar no 2M
PUSH
SI
CALL
set_SI_drv
CMP
CS:[SI].control2m_flag,OFF
POP
SI
JE
ges13bios
CALL
; retornar con error
2
; no es orden de formateo format_trx: ; no hay disquete en la unidad
; no es orden formateo de 2M
CALL
set_info
; características nuevo soporte
CALL
genera_info
; tabla de información formateo
CALL
motor_ok
; asegurar que está en marcha
CALL
seek_drv
CALL
formatea_pista
CLC
; SI -> variables de la unidad fmt_exit:
; la unidad la controla la BIOS
; la controla 2M
ges_int13
CALL
set_err
CALL
set_bios_err
XPOPA
; cuenta normal detención motor
; no altera flags ; **
MOV
AH,status
POP
DS
RET
2
; *
ENDP
ELSE CS:ant_int13
motor_off_cnt
POPF
; El código SuperBOOT no formatea
; saltar al gestor de INT 13h ges_int13
format_2m:
; asegurar aceleración motor
PUSHF
POPF JMP
reset_drv
CALL
control2m
; mantener DF=0
XPOPA
CLC
RET
; formatear (AH=5)
STC
MOV
POPF
ges13bios:
XPUSHA
CALL ; hubo cambio:
CALL
; simular cambio de disco
PUSHF
POPF STC
; no es cilindro 0 y cabezal 0
PROC
FAR
; --- Función de formateo implementada por 2M. En los
STI
;
disquetes creados con /M todas las pistas salvo
CLD
;
la 0 deberían ser formateadas invocando INT 13h
PUSHF
;
de manera directa (con CALL) para evitar que se
PUSH
SI
;
ejecute cierto código de WINDOWS en el modo
CMP
DL,2
;
protegido que provoca errores al formatear. Antes
JAE
ges13bios
;
de formatear la primera pista física del disco se
CALL
set_SI_drv
;
invoca la función de formateo de la INT 13h
CMP
CS:[SI].tipo_drv,2
;
original (con un error para provocar un rápido
JE
ges_2m
;
retorno) con objeto de informar al DOS y a todos
CMP
CS:[SI].tipo_drv,4
;
los TSR previos del cambio de soporte.
JC
ges13bios
;
El intento de formateo en la pista/cabezal 0 con
CMP
AH,2
;
CL=255 sirve para simular un cambio de disco.
JB
ges13bios
CMP
AH,5
ges_2m:
JA
ges13bios
; *
JNE
no_format
; **
CALL
set_flag_STV
JMP
ges13bios
CALL
detecta_cambio
POPF PUSH
DS
XPUSHA PUSH
CS
POP
DS
no_format:
; no es disquetera A: ó B:
; ¿unidad 1.2M?
; ¿unidad 1.44/2.88M?
; no es unidad de alta densidad
; no Read/Write/Verify/Format
; no Read/Write/Verify/Format
; CF = 0 -> "disco no 2M"
; ¿cambio de disco?
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
JNC
dilucida
;
inactiva. A la entrada, DL contiene la unidad. El
POP
SI
;
motor es puesto en marcha y, si no lo estaba ya, la
;
variable que indica lo que resta para detenerlo
;
es llevada a su valor normal, por lo que el disco no
;
tardará mucho en detenerse (incluso sin quizá haber
;
acelerado aún). En la práctica, invocando esta rutina
POPF STC
dilucida:
; hubo cambio:
MOV
AX,600h
RET
2
CMP
CS:[SI].control2m_flag,OFF
;
desde INT 13h nunca será necesario arrancar el motor
JE
ges13bios
;
ya que el DOS ejecuta antes la función equivalente,
POP
SI
;
la 16h, que lo pone en marcha. Es simplemente una
;
medida de seguridad contra las BIOS «de marca».
leer_lin_camb
PROC
; retornar con error
; la unidad la controla la BIOS
POPF
ges13bios:
CALL
control2m
RET
2
POP
SI
; la controla 2M
XPUSHA PUSH
POPF JMP
CS:ant_int13
; *
DS
DDS
; saltar al gestor de INT 13h
MOV
AL,1
MOV
CL,DL
SHL
AL,CL
TEST
DS:[3Fh],AL
; ------------ A la entrada en DL se indica la unidad y a la salida se
JNZ
rodando
; el motor ya está girando
;
devuelve SI apuntando sus variables sin alterar flags.
CLC CALL
motor_off_cnt
; cuenta normal detención motor
set_SI_drv
PROC
MOV
AH,DL
ges_int13
ENDP
ENDIF
rodando:
XSHL
AH,4
PUSH
BX
OR
AH,AL
MOV
BL,DL
XSHL
AL,4
MOV
BH,0
OR
AL,00001100b
; modo DMA, no hacer reset
SHL
BX,1
OR
AL,DL
; AL para reg. salida digital
MOV
SI,CS:[BX+OFFSET info_ptr]
MOV
DX,3F2h
POP
BX
CLI
PUSHF
set_SI_drv
; bit de motor en 0..3
; AH = byte BIOS
POPF
MOV
DS:[3Fh],AH
; actualizar variable BIOS
RET
OUT
DX,AL
; arrancado motor en la unidad
ENDP
ADD
DX,5
DELAY ; ------------ Si CF=1, indicar disquete 2M presente. A la
IN
;
entrada, DL indica la unidad de disco.
STI
set_flag_STV
PROC
tipo_stv_ok:
set_flag_STV
pista0?
; leer línea de cambio de disco
TEST
AL,80h
; ZF=0 -> cambio de disco
POP
DS
XPUSHA
XPOPA
CALL
set_SI_drv
RET
MOV
AL,ON
JC
tipo_stv_ok
MOV
AL,OFF
MOV
CS:[SI].control2m_flag,AL
; *
; indicar 2M
leer_lin_camb
; indicar no 2M
; ------------ Determinar si ha habido cambio de disco y, en ese caso,
ENDP
;
si el nuevo disquete es de tipo 2M o no. El cambio de
XPOPA
;
disco se detecta leyendo la línea de cambio de disco o
RET
;
chequeando la variable que indica si ha habido cambio
ENDP
;
o no (esta variable está a ON tras instalar 2M para
;
forzar la detección del tipo de disco introducido; se
;
pone en ON también si no se logra bajar la línea de
;
cambio de disco por si fuera un soporte raro y la BIOS
;
sí lo lograra -forzando así una detección posterior-).
; ------------ Devolver ZF=1 si cilindro y cabezal 0.
pista0?
AL,DX
PROC PUSH
AX
MOV
AL,cabezal
OR
AL,cilindro
XPUSHA
; *
POP
AX
CALL
set_SI_drv
; SI -> variables de la unidad
RET
CMP
CS:[SI].cambio,ON ; ¿cambio de soporte?
ENDP
MOV
CS:[SI].cambio,OFF
JE
hubo_cambio
CALL
leer_lin_camb
; ------------ Devolver ZF=1 si la línea de cambio de disco está
detecta_cambio PROC
; leer línea de cambio de disco
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
JNZ
hubo_cambio
XPOPA
otro_intento
MOV
[SI].vunidad0,AL
CLC
JMP
intenta_io
MOV
[SI].vunidad0,0
POP
CX
LOOP
intenta_io0
; no hay cambio de disco
set_flag_STV
XPUSH MOV
BX,90h
ADD
BL,DL
; CF = 0 -> supuesto no 2M
AND
otro_intento:
; **
fin_detecta_c: STC fin_detecta:
DDS
XPOP
MOV
unidad,DL
STC
; próxima velocidad
; probar otra velocidad
; reintento ; indicar cambio de disco
; ** ; *
RET detecta_cambio ENDP
; asegurar motor en marcha
; ------------ Anotar la información del disquete si es de tipo 2M.
CALL
reset_drv
;
A la entrada, DS:SI apunta a las variables de la unidad
MOV
cilindro,1
;
y ES:BX al sector de arranque del disco. Se actualiza
MOV
cabezal,0
;
también la variable BIOS de tipo de densidad (la BIOS
CALL
seek_drv
;
no se da cuenta del cambio de disco y conviene ayudar).
DEC
cilindro
CALL
seek_drv
set_info
PROC
; bajar línea cambio de disco
XPUSHA
CLC CALL
motor_off_cnt
; cuenta normal detención motor
CALL
calc_chk
CALL
leer_lin_camb
; ¿bajada línea cambio disco?
JC
set_info_exit
JZ
disco_dentro
; se pudo: hay disco dentro
MOV
[SI].chk,AL
MOV
[SI].cambio,ON
; futura detección tipo disco
MOV
[SI].version_fmt,CL ; y versión del formato
; NO indicar cambio de disco...
MOV
DL,unidad
; ...para pasar control a BIOS
STC
CLC JMP
fin_detecta
PUSH
DS
DDS ; error 'media changed'
; no es disco 2M ; anotar checksum
CALL
set_flag_STV
; CF = 1 -> indicar disco 2M
MOV
AL,ES:[BX+22]
; tamaño de FAT
MOV
[SI].tam_fat,AL
MOV
BYTE PTR DS:[41h],6
POP
DS
MOV
CL,ES:[BX+65]
IFNDEF SUPERBOOT
MOV
[SI].multi_io,CL
CMP
AH,5
; ¿función de formateo?
MOV
AX,ES:[BX+66]
JE
fin_detecta_c
; no perder el tiempo
MOV
[SI].vunidad,AX
MOV
AL,ES:[BX+24]
ENDIF
intenta_io:
XPOP XPOPA
BYTE PTR [BX],255-16 ; densidad no determinada
XPUSH
intenta_io0:
AL,3
JA
CALL
disco_dentro:
AX
CMP
RET
CLC
hubo_cambio:
INC
; CL a 0 si acceso multi-sector
; velocidad pista 0 / demás
MOV
buf_unidad,-1
; invalidar buffer
MOV
[SI].sectpista,AL ; sectores/pista
MOV
[SI].gap,20
; GAP provisional
MOV
DI,ES:[BX+72]
MOV
CX,3
; 3 intentos
MOV
AL,ES:[BX+DI+1]
PUSH
CX
MOV
AH,AL
CMP
CX,2
AND
CL,CL
; CL a 0 si acceso multi-sector
CALL
reset_drv
JZ
gap_rw_ok
; GAP R/W para /F
MOV
[SI].vunidad0,0
ADD
AH,190
MOV
AL,0
MOV
AL,11
MOV
cilindro,AL
MUL
AH
MOV
cabezal,AL
SUB
AX,2048+62
MOV
sector,1
SHR
AL,1
MOV
seccion,AL
MOV
[SI].gap,AL
MOV
secciones,1
MOV
CX,maxs
MOV
orden,F_READ
MOV
DI,ES:[BX+74]
MOV
DI,buffer
ADD
DI,BX
CALL
direct_acceso
LEA
BX,[SI].tabla_tsect
JNE
otra_densidad
MOV
AL,ES:[DI]
POP
CX
MOV
[BX],AL
MOV
BX,buffer
INC
BX
CALL
set_info
INC
DI
LOOP
genera_ts
; CF=1 la 3ª vez (a 0 si CX<>1)
; empezar con 500 Kbit/seg.
; sector de arranque
; es otra densidad de disco
; características nuevo soporte
CLC JMP otra_densidad: MOV
fin_detecta_c AL,[SI].vunidad0
; indicar cambio de disco
gap_rw_ok:
genera_ts:
set_info_exit: MOV IFDEF
AL,[SI].vunidad0 XT
; GAP de formateo
; AX = (190+GAP)*11
; GAP R/W para /M
; información estructura pistas
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
MOV
CL,6
AND
AL,10110111b
SHL
AL,CL
LEA
BX,lista_errs
MOV
CX,9
MOV
AH,[BX]
SHL
AL,1
JC
err_ok
INC
BX
ELSE XSHL
AL,6
; velocidad en bits 7:6
busca_err:
ENDIF
modo_ok:
OR
AL,00010111b
CMP
[SI].tipo_drv,2
JA
modo_ok
AND
AL,11111000b
OR
AL,00000101b
TEST
AL,01000000b
JZ
modo_ok
XOR
AL,00100001b
PUSH
DS
MOV
BX,90h
ADD
BL,unidad
; establecido otro medio físico
LOOP
busca_err
err_ok:
OR
status,AH
err_retc:
STC
err_ret:
XPOPA
; es unidad de 3½
; 1.2 en 1.2
; aislar condiciones de test
; código de error BIOS
; es ese error
; buscar otro error
; condición de error
RET ; 360 en 1.2 y seek * 2
set_err
ENDP
; ------------ Actualizar variables de error de la BIOS.
set_bios_err
DDS
PROC
; respetar bit de 2.88M
PUSHF
DS:[BX],AL
; actualizar variable BIOS
XPUSHA
; **
DS
; con el tipo de densidad
PUSH
ES
; ***
AND
BYTE PTR DS:[BX],8
OR POP
; *
XPOPA
DES
RET
MOV
DI,41h
; bytes de resultados del 765
ENDP
LEA
SI,status
; variable BIOS de status y 7
MOV
CX,4
; bytes: 4 palabras
; ------------ Calcular el checksum de la zona vital del sector de
REP
MOVSW
;
arranque. A la entrada, ES:BX -> sector de arranque.
POP
ES
;
A la salida, CF=1 si el disco no es 2M; de otro modo
XPOPA
; **
;
checksum en AL y versión del formato de disco en CL.
POPF
; *
set_info
; ***
RET calc_chk
set_bios_err
PROC
ENDP
XPUSH ; DI=BX+3
DI,[BX+3]
LEA
SI,id_sistema
;
que sustituye el código de la BIOS para poder soportar
MOV
CX,6
;
los formatos 2M. La operación puede quedar dividida en
REP
CMPSB
;
tres fases: el fragmento anterior a la FAT2, la zona
;
correspondiente a la FAT2 (se ignora la escritura y se
;
simula su lectura leyendo la FAT1) y un último bloque
;
ubicado tras la FAT2. El sector de arranque es emulado
;
empleando el primer sector físico de la FAT2 (aunque en
;
los discos de versión de formato anterior a la 7 se usa
;
el sector de arranque verdadero -permitiendo escribirlo
; comparar identificación
STC
chk_sum:
JNE
chk_ret
XOR
AX,AX
MOV
CL,ES:[BX+64]
CMP
CL,6
JB
chk_ok
MOV
DI,ES:[BX+68]
;
sólo si es válido-). En cualquier caso, si el número de
DEC
DI
;
cabezal tiene el bit 7 activo, se sobreentiende que el
ADD
AL,ES:[BX+DI]
;
programa que llama soporta disquetes 2M y no se emula
CMP
DI,63
;
la FAT2 ni el sector de arranque, para permitirle
JA
chk_sum
;
acceder al código SuperBOOT. Las coordenadas de la BIOS
;
se traducen a las unidades del DOS por mayor comodidad.
control2m
PROC
chk_ok:
CLC
chk_ret:
XPOP
; el disco no es 2M
; versión del formateador
; no usaba este checksum
RET calc_chk
; ------------ Realizar lecturas, escrituras y verificaciones: rutina
LEA
PUSH
ENDP
DS
XPUSHA ; ------------ Determinar el tipo de error producido en el acceso.
set_err
PUSH
CS
POP
DS
PROC
MOV
unidad,DL
XPUSHA
CALL
set_SI_drv
; * ; **
; SI -> variables de la unidad
JNC
err_ret
; no hay error
CMP
[SI].chk,0
CMP
status,0
; ¿'status' ya asignado?
JE
chk_valido
; checksum correcto en sector 0
JNE
err_retc
; no cambiarlo si es así
MOV
status,40h
; devolver 'Seek Error' al DOS
MOV
AL,BYTE PTR fdc_result+1
JMP
exit_2m_ctrl
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
chk_valido:
boot_fin_op:
boot_real:
si_skip:
io_emula:
en_fat2?:
PUSH
AX
MOV
AH,0
; ***
MOV
numsect,AX
; nº sectores
MOV
AL,CH
; cilindro
SHL
CMP
orden,F_WRITE
JNE
emula_fat1
IFDEF
XT
XCHG
CH,CL
AL,1
SHL
CH,1
MOV
DL,DH
ELSE
AND
DH,01111111b
ADD
AL,DH
MUL
[SI].sectpista
ADD
AL,CL
ADC
AH,0
DEC
AX
; AX = nº sector DOS
MOV
sectini,AX
MOV
XSHL
CX,9
; CX = CX * 512
ADD
DI,CX
; ES:DI actualizado
JMP
acceso_final
MOV
DL,[SI].tam_fat
MOV
DH,0
; 0FFFFh si sector 0 (error)
SUB
AX,DX
; leer de FAT1 y no de la FAT2
DI,BX
; ES:DI -> dirección
CALL
ejecuta_io
; CX sectores desde AX
POP
BX
; ***
JNE
fin_ctrl
; error
MOV
BL,BH
CMP
numsect,0
MOV
BH,0
JE
fin_ctrl
MOV
CL,[BX+OFFSET tab_ordenes-2]
MOV
AX,sectini
MOV
orden,CL
MOV
CX,numsect
SHL
DL,1
CALL
ejecuta_io
JC
acceso_final
; cabezal >= 128: no emular
AND
AX,AX
; ¿comienza en sector 0?
CALL
motor_off_cnt
; cuenta normal detención motor
JNZ
io_emula
; no
CALL
set_bios_err
; actualizar variables BIOS
CMP
[SI].version_fmt,7
JB
boot_real
; no soportado BOOT virtual
MOV
AH,status
MOV
AL,[SI].tam_fat
; AH = 0
POP
DS
INC
AX
AND
AH,AH
MOV
CX,1
; sector BOOT emulado en
JZ
st_ok
CALL
ejecuta_io
; el primer sector FAT2
STC
JNE
fin_ctrl
DEC
numsect
st_ok:
RET
INC
sectini
calc_iop:
SUB
CX,AX
MOV
AX,sectini
INC
CX
JMP
io_emula
CMP
CX,numsect
CMP
orden,F_WRITE
JBE
nsect_ok
JNE
io_emula
MOV
CX,numsect
MOV
BX,DI
SUB
numsect,CX
CALL
calc_chk
ADD
sectini,CX
JC
si_skip
AND
AL,AL
JZ
io_emula
ADD
DI,512
JMP
boot_fin_op
MOV
CL,[SI].tam_fat
MOV
CH,0
CMP
AX,CX
JA
en_fat2?
CALL
ENDIF
; cabezal físico
; sector emula_fat1:
acceso_final:
fin_ctrl:
exit_2m_ctrl:
CLC
XPOPA
MOV
; BOOT de 2M 1.3 y anteriores
nsect_ok:
; fin de la transferencia
; **
; *
; resultado correcto (CF=0) ; error
AL,0
; 0 sectores movidos
; CX sectores
; sólo quedan CX
RET
; no es de tipo 2M control2m
ENDP
; lo es y con checksum correcto ; ------------ A la entrada, AX indica el sector inicial (coordenadas ;
del DOS) y CX el número de sectores a procesar.
;
* Definiciones: «Sector físico» es un sector del disco
;
de 512, 1024 ó 2048 bytes (números de sector del 1 al N
;
en la pista). Este sector físico está dividido en
; ¿la operación afecta a FAT2?
;
«secciones» de 512 bytes, constando por tanto de 1, 2 ó
calc_iop
; calcular sectores antes FAT2
;
4 secciones. «Sector virtual» es el número de sector
CALL
ejecuta_io
; CX sectores desde AX
;
del programa que llama a INT 13h, comprendido entre 1 y
JNE
fin_ctrl
; error
;
M. Esta estructura de N sectores por pista de distintos
CMP
numsect,0
;
tamaños, se verifica en todo el disco con excepción del
JE
fin_ctrl
;
cabezal y cilindro 0 (con un formato más convencional
MOV
AX,sectini
;
de sectores de 512 bytes numerados de 1 a J, aunque no
MOV
CL,[SI].tam_fat
;
existen algunos de los intermedios que corresponden a
MOV
CH,0
;
la segunda copia de la FAT).
SHL
CX,1
;
* Primero se convierte el sector virtual (1..M) en su
CMP
AX,CX
;
correspondiente físico (1..J en la pista 0 y 1..N en
JA
acceso_final
; la operación es tras la FAT2
;
las demás), deduciendo qué porción de 512 bytes (o
CALL
calc_iop
; sectores hasta fin de FAT2
;
sección) es afectada. Un sector virtual (512 bytes)
; impedir estropicio de BOOT
; CX = primer sector FAT2 - 1
; fin de la transferencia
; CX = último sector FAT2
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
;
simulado suele ser parte de un sector físico de 2048
MOV
CH,1
;
bytes en muchos casos. Si dicho sector físico ya había
SHL
CH,CL
;
sido leído al buffer en anteriores accesos, se extrae
SUB
AL,CH
;
la sección necesaria. Si no, se carga del disco y se
JNC
resta_secc
;
extrae dicho fragmento. El número de sectores virtuales
ADD
AL,CH
;
que se solicitan (=secciones) permite realizar un bucle
XCHG
AH,AL
;
hasta completar la transferencia; el interleave 1:2 de
MOV
sector,AL
; sector lógico convertido a
;
los sectores físicos en /M permite acceder sector a
MOV
seccion,AH
; sector y sección físicas
;
sector sin pérdida de rendimiento. En el caso de la
motor_ok
; asegurar que está en marcha
;
escritura, se estudia primero si hay varios sectores
MOV
AH,0
;
virtuales consecutivos que escribir, completando entre
MOV
sector_fin,AH
; no acceder a más de 1 sector
;
todos un sector físico: en ese caso, se prepara el
CALL
pista0?
; (al menos de momento)
;
mismo y se escribe sin más. En caso de que haya que
JNZ
decide_multi
; no es pista 0
;
modificar sólo una única sección de un sector físico,
MOV
AL,secciones
;
salvo si éste es de 512 bytes, no hay más remedio que
MOV
secciones,AH
;
cargarlo al buffer (realizar una prelectura),
JMP
multi_proc
;
actualizar la sección correspondiente y volverlo a
CMP
[SI].multi_io,AH
; AH = 0
;
escribir.
JNE
io_pasos
; acceso sector a sector
;
* En el formato /F se realiza una operación multisector
CMP
seccion,AH
;
si es posible y sin emplear el buffer intermedio (si
JE
multi_acc
;
bien podría ser preciso emplearlo con la primera y
CALL
acceso_secc
;
última sección); en los dos formatos de disco se hace
JC
fin_io
;
la operación multisector en la primera pista. Las
CMP
secciones,AH
;
operaciones multisector puede que sea preciso
JE
fin_io
;
dividirlas en tres fases: los sectores antes de una
CALL
num_secciones
;
frontera de DMA, el que la cruza (que es transferido
MOV
CL,AL
;
a través del buffer intermedio) y los que están detrás.
MOV
AL,secciones
DIV
CL
AND
AL,AL
JZ
io_pasos
; no quedan sectores enteros
MOV
secciones,AH
; las que restan
CALL
acceso_multi
; de AL sectores
JC
fin_io
ejecuta_io
no_cabe:
si_cabe:
direct_acceso: CALL
decide_multi:
multi_acc:
PROC ; AX = sector DOS inicial
; las que restan (AH = 0)
; no acceso a inicio sector
; AH = 0
; AH = 0
MOV
BX,AX
CMP
AH,0FFh
JE
no_cabe
; (acceso a sector BIOS 0)
MOV
secciones,CL
; CX sectores (CL realmente)
DIV
[SI].sectpista
CMP
secciones,0
INC
AH
; numerado desde 1...
JE
fin_io
MOV
sector,AH
; ...el resto es el sector
CALL
acceso_secc
SHR
AL,1
JNC
io_pasos
MOV
cilindro,AL
CMP
status,0
RCL
AL,1
AND
AL,1
MOV
cabezal,AL
PUSH
AX
MOV
AL,sector
CMP
orden,F_WRITE
ADD
AL,secciones
JE
escritura
JC
no_cabe
; sector+secciones > 255
CMP
orden,F_VERIFY
DEC
AX
; DEC AX = DEC AL
JE
verificacion
CMP
AL,[SI].sectpista
CALL
leido?
; realizar lectura...
JBE
si_cabe
JNC
ya_leido
; sector ya en el buffer
MOV
status,4
CALL
acceso_sector
; efectuar E/S
JMP
fin_io
JC
acc_ret
; ha habido fallo
MOV
AL,AH
CALL
trans_secc
; buffer -> memoria
JMP
acc_ret
CMP
seccion,0
JNE
prelectura
CBW
resta_secc:
s_xx:
; en las demás pistas
multi_proc:
io_pasos:
; cilindro
fin_io:
; no restan secciones finales
; ZF = 1 -> operación correcta
RET
; cabezal
; 'sector no encontrado'
; sector en AL
acceso_secc:
hay_que_leer:
ya_leido:
; sección 0 (AH = 0) escritura:
; acabar transferencia sector
CALL
pista0?
JZ
s_xx
LEA
BX,[SI].tabla_tsect-1
CALL
num_secciones
DEC
AX
CMP
secciones,AL
INC
BX
JAE
escribir
; Todo el sector físico cambia
INC
AH
CALL
leido?
; Leer el sector físico para
MOV
CL,[BX]
JNC
escribir
; cambiar sólo una parte de él
SUB
CL,2
MOV
orden,F_READ
; de momento leer...
; sector físico en pista/cara 0
; AH = 0
prelectura:
; sólo parte del sector cambia
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
escribir:
verificacion:
dec_sec_veri:
verifica:
acc_ret:
CALL
acceso_sector
; efectuar E/S
JZ
acc_mult3
MOV
orden,F_WRITE
; ... restaurar orden original
MOV
AH,sector
JC
acc_ret
; ha habido fallo
MOV
sector_ini,AH
CALL
trans_secc
; memoria -> buffer
ADD
AH,CL
CALL
acceso_sector
; volcar buffer al disco
DEC
AH
JMP
acc_ret
MOV
sector_fin,AH
PUSH
BX
INC
AH
MOV
BL,seccion
SUB
AL,CL
CALL
num_secciones
CALL
acceso_sector
DEC
secciones
MOV
sector,AH
JZ
verifica
JC
acc_mult_fin
INC
BX
AND
AL,AL
CMP
BL,AL
JZ
acc_mult_fin
JB
dec_sec_veri
ADD
secciones,BL
; compensar futuro decremento
POP
BX
CALL
acceso_secc
; a través del buffer auxiliar
CALL
acceso_sector
; leer para forzar verificación
JC
acc_mult_fin
DEC
AL
INC
sector
; preparado para otro sector
JMP
acc_mult1
MOV
seccion,0
; desde su primera sección
acc_mult3:
PUSHF
acc_mult_fin:
; primer sector problemático
; sectores no problemáticos
; ahora el sector problemático
; sectores que restan
RET
POPF POP
ENDIF
AX
RET ejecuta_io IFDEF
SUPERBOOT
ENDP
; SuperBOOT: válido el cruce del DMA ; ------------ Mover secciones desde el buffer hacia la memoria (con
acceso_multi:
acc_mult_fin:
PUSH
AX
;
orden F_READ) después de la lectura o de la memoria al
AND
AL,AL
;
buffer (orden F_WRITE) antes de la escritura. En la
JZ
acc_mult_fin
;
verificación (orden F_VERIFY) no se mueve nada porque
MOV
AH,sector
;
esta subrutina no es invocada.
MOV
sector_ini,AH
ADD
AL,AH
trans_secc
PROC
DEC
AX
XPUSH
; *
MOV
sector_fin,AL
MOV
BL,seccion
; desde esta sección
INC
AL
CALL
num_secciones
; nº secciones del sector
CALL
acceso_sector
PUSH
BX
MOV
sector,AL
IFDEF
XT
POP
AX
; AL = sectores a transferir
; sectores no problemáticos
otra_secci:
RET
MOV
BH,BL
SHL
BH,1
MOV
BL,0
; No es SuperBOOT: Evitar cruce frontera DMA
ELSE
PUSH
AX
; AL = sectores a transferir
ENDIF
MOV
BX,ES
;
desde 'sector' teniendo
XSHL
BX,4
;
cuidado con el DMA
ADD
BX,DI
NEG
BX
CALL
num_secciones
MOV
CH,AL
MOV
CL,0
SHL
CX,1
XCHG
AX,BX
XOR DIV MOV
CL,AL
; CL = sectores que caben
POP
AX
; AL = sectores a transferir
CMP
AL,CL
ELSE
XSHL acceso_multi:
BX,9
; sección * 512
ADD
BX,buffer
; dirección
MOV
SI,BX
MOV
CX,256
; tamaño sección (palabras)
CALL
swap_reg
; ¿intercambiar origen-destino?
REP
MOVSW
; copiar 512 bytes
CALL
swap_reg
; ¿intercambiar origen-destino?
POP
BX
; CX = bytes por sector
DEC
secciones
; BL = secciones por sector
JZ
fin_secc
DX,DX
INC
BX
; otra sección del sector
CX
CMP
BL,AL
; ¿sector agotado?
JB
otra_secci
; aún no
XPOP
; *
; BX = bytes hasta frontera DMA
; AL secciones de 512 bytes
fin_secc:
RET
JA
acc_mult2
CMP
CS:orden,F_WRITE
acc_mult1:
MOV
CL,AL
JE
interc
acc_mult2:
AND
CL,CL
CLC
; no hay problemas con el DMA
swap_reg:
; una menos
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
MOV
RET interc:
XCHG
SI,DI
XPUSH XPOP
trans_secc
; en escritura, invertir el
PUSHF
; sentido de la operación
JMP
; en el futuro (por defecto)
PUSH
CS
ENDP
POP
ES
MOV
DI,buffer
; acceso con buffer auxiliar
MOV
AL,sector
; mismo sector inicial/final
MOV
sector_ini,AL
MOV
sector_fin,AL
PUSH
AX
CALL
sector_io
MOV
AL,buf_unidad
MOV
sector_fin,0
CMP
AL,unidad
XPOP
JNE
no_leido
MOV
AL,cilindro
MOV
AL,-1
; invalidar contenido buffer
MOV
AH,cabezal
JC
acceso_anota
; si hay error
CMP
AX,buf_cilcab
CMP
orden,F_VERIFY
JNE
no_leido
JE
acceso_rep
MOV
AL,buf_sector
MOV
AL,unidad
CMP
AL,sector
MOV
buf_unidad,AL
JNE
no_leido
MOV
AL,cilindro
POP
AX
MOV
AH,cabezal
MOV
buf_cilcab,AX
MOV
AL,sector
MOV
buf_sector,AL
PROC
PUSHF
; es en otra unidad
; es en otro cilindro/cabezal
acceso_anota: ; es otro sector
; está en el buffer
STC POP
AX
RET leido?
; **1 acceso_rep
RET
RET no_leido:
; no acceder a más de 1 sector
acceso_buffer: XPUSH
; ------------ Comprobar si el sector ya está en el buffer.
leido?
sector_fin,0
acceso_rep:
; sector no leído
ENDP acceso_fin:
POPF
; nada leído físicamente
; anotado el sector en buffer ; ** mucho cuidado con la pila
CALL
set_err
XPOP
; ajustar variable «status»
RET
; ------------ Leer o escribir sector(es). Se selecciona el tamaño de acceso_sector
ENDP
;
sector correcto antes de llamar a sector_io.
;
rutina se actualiza la variable «status» en función de
;
los posibles errores de acceso.
;
distinto de 0 se accede a los sectores indicados, si es
;
0 se accede sólo al sector «sector» a través del buffer
;
intermedio y al final se anota el sector cargado ó
CALL
pista0?
;
escrito para evitar futuras lecturas innecesarias, a
MOV
AL,1
;
modo de mini-caché que dispara la velocidad de acceso a
JZ
num_secc_ok
;
sectores lógicos consecutivos.
XPUSH LEA
BX,[SI].tabla_tsect
acceso_sector
PROC
ADD
BL,sector
XPUSH
ADC
BH,0
MOV
CL,[BX-1]
SUB
CL,2
MOV
AL,1
SHL
AL,CL
XPOP
acc_fin_err:
en_pista:
tam_acc_ok:
CALL
seek_drv
JNC
en_pista
CMP
status,0
JNE
acc_fin_err
OR
status,40h
En esta
; **2
Si sector_fin es
; ------------ Devolver el número de secciones del sector en curso.
num_secciones
; posicionar el cabezal
; ¿error ya determinado?
; no: pues 'seek error'
STC
PROC
num_secc_ok:
RET
num_secciones
ENDP
; sectores 512 en cil./cab. 0
; resultado en AL
JMP
acceso_fin
CALL
pista0?
MOV
AL,2
JZ
tam_acc_ok
LEA
BX,[SI].tabla_tsect
ADD
BL,sector
XPUSHA
; *
ADC
BH,0
PUSH
DS
; **
MOV
AL,[BX-1]
MOV
BX,40h
MOV
tsector,AL
PUSH
BX
CMP
sector_fin,0
POP
DS
JE
acceso_buffer
MOV
CH,255-18
CALL
sector_io
CLI
; ------------ Asegurar que el motor está en marcha. ; sectores 512 en cil./cab. 0
; ¿usar buffer intermedio?
motor_ok
PROC
; CH = 255 - 1 segundo
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
arrancarlo:
MOV
CX,50
LOOP
respiro
MOV
CL,CS:unidad
MOV
AL,1
SHL
AL,CL
TEST
[BX-1],AL
; ¿motor en marcha?
JZ
arrancarlo
; arrancarlo
CMP
[BX],CH
; Si encendido y acelerado...
OR
AL,00000100b
JBE
ok_motor
; ...seguir
OUT
DX,AL
; fin de señal de reset
MOV
AH,CL
CALL
espera_int
; rehabilitará interrupciones
MOV
CL,4
MOV
AL,8
SHL
AH,CL
CALL
fdc_write
OR
AL,AH
CALL
fdc_read
MOV
[BX-1],AL
CALL
fdc_read
MOV
BYTE PTR [BX],255 ; asegurar que no se pare
CALL
envia_specify
MOV
DX,3F2h
XPOPA
ADD
CL,CS:unidad
MOV
AL,1
SHL
AL,CL
; colocar bit del motor
OR
AL,CS:unidad
; seleccionar unidad
; ------------ Enviar comando specify a la controladora. El step-rate
OR
AL,00001100b
; modo DMA, no hacer reset
;
se selecciona según la densidad, para evitar un sonido
OUT
DX,AL
; poner en marcha el motor
;
extraño al posicionar o recalibrar el cabezal.
envia_specify
PROC
respiro:
ELSE CALL
fdc_respiro
; tiempo reconocer reset en 486
ENDIF
; unidad << 4
; nuevo estado motores
; registro de salida digital
; comando 'leer estado int...'
; comando 'specify' adecuado
RET reset_drv
ENDP
STI MOV
AX,90FDh
PUSH
AX
INT
15h
; permitir multitarea
PUSH
DS
JC
ok_motor
; timeout
DDS
MOV
AX,1000
; 1 segundo aceleración
MOV
AH,DS:[8Bh]
CALL
retardo
; esperar aceleración disco
POP
DS
MOV
[BX],CH
; cuenta máxima detención motor
MOV
AL,3
; sin forzar futura aceleración
CALL
fdc_write
; **
MOV
AL,0BFh
; *
AND
AH,11000000b
RET
JZ
spec1_ok
ENDP
MOV
AL,0AFh
CMP
AH,11000000b
; ------------ Establecer modalidad de operación del controlador
JE
spec1_ok
;
y poner el motor en marcha. Si CF=1 se le da tiempo
MOV
AL,0DFh
;
además a la unidad para que acelere.
CALL
fdc_write
MOV
AL,2
reset_drv
PROC
CALL
fdc_write
XPUSHA
POP
AX
CLC
ok_motor:
STI POP
DS
XPOPA
motor_ok
spec1_ok:
; comando 'specify'
; step rate para 500 kbps
; step rate para 1 Mbps
; step rate para 250/300 Kbps
; head load y modo DMA
RET
CALL
motor_off_cnt
MOV
CL,unidad
; cuenta detención motor
MOV
AL,CL
XSHL
AL,4
; unidad seleccionada
; ------------ Recargar cuenta para la detención del motor. Si CF=1 al
MOV
AH,1
; bit de motor
;
entrar, se establece la mayor cuenta posible; en caso
SHL
AH,CL
; colocar dicho bit
;
contrario, se pone el valor normal de la tabla base.
OR
AL,AH
PUSH
DS
; *
motor_off_cnt
PROC
envia_specify
ENDP
XPUSHA
DDS
PUSH
DS
MOV
DS:[3Fh],AL
MOV
AL,0FFh
AND
BYTE PTR DS:[3Eh],70h ; bit IRQ=0 y recalibrar
JC
motor_off_ok
POP
DS
; *
XSHL
AL,4
; bits motor en nibble alto
XOR
BX,BX
OR
AL,CL
; seleccionar unidad
MOV
DS,BX
OR
AL,00001000b
; interrupciones+DMA y reset
MOV
DX,3F2h
; registro de salida digital
PUSH
0
OUT
DX,AL
; señal de reset
POP
DS
CLI
IFDEF
XT
IFDEF
XT
ELSE
ENDIF
; valor máximo
284
motor_off_ok:
motor_off_cnt
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
LDS
BX,DWORD PTR DS:[1Eh*4] ; DS:BX -> INT 1Eh
MOV
AH,AL
MOV
AL,[BX+2]
CALL
fdc_read
; leer cilindro actual
TEST
AH,11000000b
; comprobar ST0
JNZ
fallo_seek
; byte 2 tabla base disco
DDS MOV
BYTE PTR DS:[40h],AL
POP
DS
; cuenta parada motor
MOV
AL,15
XPOPA
CMP
orden,F_WRITE
RET
JE
rseek_ok
ENDP
MOV
AL,1
rseek_ok:
CBW CALL
; ------------ Llevar el cabezal a la pista indicada, recalibrando si seek_ok:
; estabilización para lectura ; AH = 0
retardo
; esperar asentamiento cabezal
XPOPA
;
hubo un reset (se invocó la función 0 de la INT 13h o
;
se ejecutó reset_drv) antes de esta operación. Primero
CLC
;
se selecciona la velocidad de transferencia y se borra
RET
;
el resultado de cualquier operación anterior, para que
;
todo quede listo para el próximo acceso a disco.
fallo_seek:
; estabilización para escritura
; retornar con éxito
XPOPA STC
; retornar indicando fallo
RET seek_drv
seek_drv
PROC
ENDP
XPUSHA CALL
set_rate
; velocidad / borrar resultados
; ------------ Establecer velocidad de transferencia correcta si aún
CALL
envia_specify
; comando 'specify' adecuado
;
no ha sido seleccionada y borrar el resultado de otra
MOV
AH,1
;
operación previa.
MOV
CL,unidad
SHL
AH,CL
set_rate
PROC
PUSH
DS
; AH = 1 (A:) ó 2 (B:)
XPUSHA CALL
pista0?
TEST
AH,DS:[3Eh]
MOV
AX,[SI].vunidad
POP
DS
JZ
vel_ok
JNZ
do_seek
MOV
AL,AH
CALL
recalibrar
PUSH
DS
JC
fallo_seek
MOV
BX,94h
ADD
BL,unidad
MOV
AL,cilindro
PUSH
DS
DDS
do_seek:
; la unidad ya fue recalibrada vel_ok:
; *
DDS
; fallo al recalibrar
MOV IFDEF
; *
AH,DS:[8Bh] XT
MOV
CL,6
SHR
AH,CL
ELSE
DDS
hacer_seek:
; velocidad pista 0 / demás
OR
DS:[3Eh],AH
; unidad ya recalibrada
SHR
MOV
AH,DS:[41h]
; código de error previo
ENDIF
CMP
AL,[BX]
CMP
AL,AH
MOV
[BX],AL
JE
vel_set
POP
DS
; *
MOV
DX,3F7h
JNE
hacer_seek
; seek necesario
OUT
DX,AL
CMP
AH,40h
; ¿error de seek previo?
XSHL
AL,6
JNE
seek_ok
; no, evitar seek innecesario
AND
BYTE PTR DS:[8Bh],00111111b
MOV
AL,0Fh
OR
DS:[8Bh],AL
CALL
fdc_write
POP
DS
JC
fallo_seek
LEA
DI,status
MOV
AL,cabezal
MOV
CX,8
XSHL
AL,2
MOV
[DI],CH
OR
AL,unidad
INC
DI
CALL
fdc_write
LOOP
borra_status
MOV
AL,cilindro
CALL
fdc_write
; enviar cilindro
CALL
espera_int
; esperar interrupción
JC
fallo_seek
MOV
AL,8
CALL
fdc_write
JC
fallo_seek
CALL
fdc_read
JC
fallo_seek
; comando 'seek'
vel_set:
borra_status:
; enviar HD, US1, US0
AH,6
; aislar bits de velocidad
; velocidad ya seleccionada
; seleccionarla
; *
; borrar información de estado
XPOPA RET set_rate
ENDP
; ------------ Recalibrar la unidad (si hay error se intenta otra vez ; comando 'leer estado int...'
;
para el caso de que deba moverse más de 77 pistas).
; leer registro de estado 0
recalibrar
PROC XPUSHA
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
MOV
BX,94h
DEC
CX
ADD
BL,unidad
MOV
AX,ES
PUSH
DS
CALL
calc_dir_DMA
; *
IFDEF
DDS
recalibra:
fallo_recal:
; chequear cruce frontera DMA
MOV
AL,orden
; modo DMA necesario
CALL
prepara_DMA
CMP
AL,F_WRITE
MOV
AL,11000101b
JE
orden_io_ok
MOV
AL,11100110b
; comando leer (verif.) del FDC
CALL
fdc_write
; comando leer/escribir del FDC
JC
sector_io_ko
MOV
AL,cabezal
XSHL
AL,2
fallo_recal
OR
AL,unidad
MOV
AL,8
CALL
fdc_write
CALL
fdc_write
MOV
AL,cilindro
JC
fallo_recal
CALL
fdc_write
CALL
fdc_read
MOV
AL,cabezal
JC
fallo_recal
CALL
fdc_write
MOV
AH,AL
MOV
AL,sector_ini
CALL
fdc_read
; leer cilindro actual
CALL
fdc_write
XOR
AH,00100000b
; bajar bit de 'seek end'
MOV
AL,tsector
TEST
AH,11110000b
; comprobar resultado y ST0
CALL
fdc_write
JNZ
fallo_recal
; sin 'seek end' o TRK0
MOV
AL,sector_fin
MOV
AX,1
; pausa de 1 ms
CALL
fdc_write
CALL
retardo
MOV
AL,[SI].gap
JMP
recal_ret
LOOP
recalibra
[BX],BH
; pista actual = 0
POP
DS
; *
MOV
CX,2
; dos veces como mucho
MOV
AL,7
CALL
fdc_write
JC
fallo_recal
MOV
AL,cabezal
XSHL
AL,2
OR
AL,unidad
CALL
fdc_write
JC
fallo_recal
CALL
espera_int
JC
recalibrar
ENDIF
; comando de 'recalibrado'
orden_io_ok: ; enviar HD, US1, US0
; esperar interrupción
; comando 'leer estado int...'
; leer registro de estado 0
CALL
fdc_write
; reintentar comando
MOV
AL,128
; condición de fallo
CALL
fdc_write
XPOPA
CALL
espera_int
RET
PUSHF
ENDP
LEA
BX,fdc_result
MOV
CX,7
CALL
fdc_read
STC recal_ret:
; AX:DI -> base BX y página AH
SUPERBOOT sector_io_ko
MOV
JC
; bytes totales - 1
; ------------ Cargar o escribir sector(es) del disco en ES:DI,
sect_io_res:
; comando de escritura del FDC
; byte 1 de la orden
; enviar cilindro
; enviar cabezal
; enviar nº sector
; longitud sector
; último sector
; GAP de lectura/escritura
; tamaño sector si longitud=0
; *
; leyendo resultados
;
actualizando la dirección en ES:DI pero sin alterar
MOV
[BX],AL
;
ningún otro registro. Si hay error se devuelve CF=1 y
INC
BX
;
no se modifica ES:DI. A partir de fdc_result se dejan
LOOP
sect_io_res
;
los 7 bytes que devuelve el FDC al final del acceso.
POPF
;
En caso de verificación (F_VERIFY) se programa el DMA
JC
sector_io_ko
;
para que no realice transferencia física (convenio de
TEST
fdc_result,11000000b
;
las BIOS con fecha 15/11/85 y posterior).
JNZ
sector_io_ko
ADD
DI,DX
sector_io
PROC
CLC
XPUSH
JMP
; *
; actualizar dirección ; Ok
sector_io_fin
MOV
CL,tsector
sector_io_ko:
MOV
CH,0
sector_io_fin: XPOP
STC
; indicar fallo
RCL
CH,CL
sector_io
MOV
CL,0
MOV
AL,sector_fin
; ------------ Devolver en AH la página de DMA y en BX la base. A la
SUB
AL,sector_ini
;
entrada, AX:DI -> dirección de memoria y CX = bytes-1.
INC
AX
;
Se supone que el buffer no cruza una frontera de DMA,
; AX sectores (AH = 0)
;
aunque el código SuperBOOT devuelve error en ese caso.
; bytes totales
calc_dir_DMA
PROC
RET
STC
CBW MUL
CX
MOV
DX,AX
MOV
CX,AX
ENDP
; nº de bytes por sector
PUSH
DX
284
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
MOV
BX,16
MOV
AH,0
; byte relleno /F
MUL
BX
MOV
[SI+2],AX
; GAP / byte de relleno
ADD
AX,DI
PUSH
DX
ADC
DX,0
; DX:AX = dirección 20 bits
MOV
AX,ES:[DI+3]
MOV
BX,AX
; base en BX
PUSH
AX
MOV
AH,DL
; página
ADD
AL,AH
MUL
cilindro
MOV
DX,AX
IFDEF
SUPERBOOT
MOV
DX,CX
; comprobar cruce en SuperBOOT
ADD
DX,BX
POP
AX
JNC
dir_DMA_ok
MUL
cabezal
MOV
status,9
ADD
AX,DX
XOR
DX,DX
MOV
BL,ES:[DI]
RET
MOV
BH,0
ENDP
DIV
BX
SUB
DL,ES:[DI]
; ------------ Crear tabla con información para formatear. En ES:BX
NEG
DL
;
MOV
AL,DL
POP
DX
; restaurar tamaño en DH
MOV
DL,AL
; primer sector de la pista - 1
MOV
BL,ES:[DI]
; nº sectores en la pista
ADD
SI,4
INC
DX
CMP
DL,BL
; error de frontera de DMA
ENDIF dir_DMA_ok:
calc_dir_DMA
POP
DX
está el futuro sector de arranque del disquete.
IFNDEF SUPERBOOT
genera_info
; en SuperBOOT no se formatea
genera_pn:
PROC XPUSHA
genera_0:
; invalidar contenido buffer
MOV
buf_unidad,-1
MOV
SI,buffer
JBE
ns_ok
MOV
DI,BX
MOV
DL,1
CALL
pista0?
MOV
AL,cilindro
JNZ
no_cilcab0
; no es cilindro/cabezal 0
MOV
AH,cabezal
ADD
DI,ES:[BX+70]
; DI -> datos pista 0
MOV
[SI],AX
; datos para cada sector
MOV
CL,ES:[DI]
MOV
[SI+2],DX
; nº sector / LOG2 (tamaño)-7
MOV
CH,0
LOOP
genera_pn
INC
DI
MOV
AL,ES:[DI]
; GAP para pista 0
MOV
AH,0
; byte de relleno
INC
DI
MOV
BYTE PTR [SI],2
MOV
ns_ok:
; CX sectores en pista 0
; empezar desde el 1
XPOPA RET MOV
CH,ES:[DI]
; nº sectores
MOV
CL,0
; CL:CH sectores
; tamaño de sector
MOV
[SI],CX
; tamaño (CL=0) y número
BYTE PTR [SI+1],CL
; número de sectores
XCHG
CH,CL
; CX sectores
MOV
[SI+2],AX
; GAP / byte de relleno
MOV
AL,ES:[DI+1]
; GAP para formatear
ADD
SI,4
MOV
AH,4Eh
; byte de relleno /M
MOV
AL,cilindro
MOV
[SI+2],AX
; GAP / byte de relleno
MOV
AH,cabezal
MOV
DL,128
MOV
[SI],AX
ADD
SI,4
MOV
AL,ES:[DI]
INC
DX
MOV
AH,2
MOV
AL,cilindro
INC
DI
MOV
AH,cabezal
MOV
[SI+2],AX
MOV
[SI],AX
LOOP
genera_0
; datos para cada sector
info_stv:
genera_otro:
; LOG2 (tamaño)-7
; nº de sector / tamaño
XPOPA busca_num:
RET no_cilcab0:
; DL = módulo
; datos para cada sector
XPUSH
; *
MOV
CL,ES:[DI+2]
; CH está a 0
ADD
DI,3
ADD
DI,ES:[BX+72]
CMP
DL,ES:[DI]
CMP
BYTE PTR ES:[BX+65],1
MOV
AX,ES:[DI+1]
; número de sector / tamaño
JE
info_stv
JE
hallado
; es sector a cambiar número
MOV
DL,ES:[DI+2]
; tamaño /F
LOOP
busca_num
MOV
DH,ES:[DI]
; nº sectores
MOV
AL,DL
; no cambiar número
MOV
[SI],DX
MOV
AH,0
; e indicar tamaño 128
XCHG
DH,DL
XPOP
; *
MOV
CL,DL
MOV
[SI+2],AX
; nº sector / LOG2 (tamaño)-7
MOV
CH,0
; CX sectores
LOOP
genera_otro
MOV
AL,ES:[DI+1]
; GAP para formatear
XPOPA
; tamaño en DH
hallado:
284
EL HARDWARE DE APOYO AL MICROPROCESADOR
genera_info
RET
PUSH
ENDP
DDS MOV
INT
15h
MOV
DX,0280h
XPUSHA
MOV
BX,3Eh
MOV
BX,buffer
JC
timeout_int
MOV
DI,BX
esp_int_1s:
XOR
CX,CX
MOV
CL,[BX+1]
esp_int:
TEST
[BX],DL
MOV
CH,0
JNZ
fin_espera
XSHL
CX,2
DEC
CX
MOV
AX,DS
CALL
calc_dir_DMA
; AX:DI -> base BX y página AH
MOV
AL,4Ah
; modo DMA para escribir
ADD
BX,4
; saltar primeros 4 bytes
CALL
prepara_DMA
MOV
BX,buffer
AND
MOV
AL,F_FORMAT
POPF
CALL
fdc_write
POP
JC
fallo_fmt
XPOPA
MOV
AL,cabezal
XSHL
AL,2
OR
AL,unidad
CALL
fdc_write
JC
fallo_fmt
MOV
CX,4
MOV
AL,[BX]
STI
CALL
fdc_write
XPUSHA
INC
BX
XPUSH