Está en la página 1de 610

EL UNIVERSO

DIGITAL
DEL IBM PC, AT Y PS/2
Edición 4.0 (4ª edición)

Versión impresa del original electrónico ubicado en:

http://www.gui.uva.es/udigital
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.
ÍNDICE 5

ÍNDICE
PRÓLOGO DE LA EDICIÓN 4.0 ELECTRÓNICA....................................................................... 11
PRÓLOGO DE LA TERCERA EDICIÓN (1994).......................................................................... 17
1 - INTRODUCCIÓN .......................................................................................................................... 21
1.1 - Números binarios, octales y hexadecimales ................................................................. 21
1.2 - Cambio de base ............................................................................................................. 22
1.3 - Estructura elemental de la memoria .............................................................................. 22
1.4 - Operaciones aritméticas sencillas en binario ................................................................ 23
1.5 - Complemento a dos ....................................................................................................... 23
1.6 - Agrupaciones de bytes .................................................................................................. 23
1.7 - Representación de datos en memoria........................................................................... 23
1.8 - Operaciones lógicas en binario ..................................................................................... 24
2 - ARQUITECTURA E HISTORIA DE LOS MICROORDENADORES........................................... 25
2.1 - Arquitectura Von Neuman.............................................................................................. 25
2.2 - El microprocesador ........................................................................................................ 26
2.3 - Breve historia del ordenador personal y el DOS ............................................................ 27
3 - MICROPROCESADORES 8086/88, 286, 386, 486 y Pentium .................................................. 31
3.1 - Características generales .............................................................................................. 31
3.2 - Registros del 8086 y del 286 ......................................................................................... 33
3.3 - Registros del 386 y procesadores superiores ............................................................... 36
3.4 - Modos de direccionamiento ........................................................................................... 36
3.5 - La pila ............................................................................................................................. 38
3.6 - Un programa de ejemplo ............................................................................................... 39
4 - JUEGO DE INSTRUCCIONES 80x86 ......................................................................................... 41
4.1 - Descripción completa de las instrucciones.................................................................... 41
4.1.1 - De carga de registros y direcciones ............................................................... 41
4.1.2 - De manipulación del registro de estado ......................................................... 43
4.1.3 - De manejo de la pila ....................................................................................... 45
4.1.4 - De transferencia de control............................................................................. 46
4.1.5 - De entrada/salida............................................................................................ 49
4.1.6 - Aritméticas ...................................................................................................... 49
Suma............................................................................................................. 49
Resta............................................................................................................. 51
Multiplicación................................................................................................. 53
División.......................................................................................................... 54
Conversiones ................................................................................................ 55
4.1.7 - Manipulación de cadenas............................................................................... 55
4.1.8 - Operaciones lógicas a nivel de bit.................................................................. 58
4.1.9 - De control del procesador............................................................................... 59
4.1.10 - De rotación y desplazamiento ...................................................................... 60
4.2 - Resumen alfabético de las instrucciones y banderines. Índice..................................... 63
4.3 - Instrucciones específicas del 286, 386 y 486 en modo real ......................................... 64
4.3.1 - Diferencias en el comportamiento global respecto al 8086 ........................... 64
4.3.2 - Instrucciones específicas del 286................................................................... 65
4.3.3 - Instrucciones propias del 386 y 486............................................................... 66
4.3.4 - Detección de un sistema AT o superior ......................................................... 68
4.3.5 - Evaluación exacta del microprocesador instalado ......................................... 68
4.3.6 - Modo plano (flat) del 386 y superiores ........................................................... 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 Mapa de memoria ...................................................................................................... 381
II Tabla de interrupciones del sistema .......................................................................... 383
III Tabla de variables de la BIOS ................................................................................... 385
IV Puertos de E/S ........................................................................................................... 389
V Códigos de rastreo del teclado .................................................................................. 391
VI Tamaños y tiempos de ejecución de las instrucciones ............................................. 393
VII Señales del slot de expansión ISA ............................................................................ 399
VIII Funciones del sistema, la BIOS y el DOS aludidas en este libro.............................. 401
IX Especificaciones XMS y EMS: Todas sus funciones ................................................ 423
X Juego de caracteres ASCII extendido ....................................................................... 427
XI Bibliografía ................................................................................................................. 429
5 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
PRÓLOGO DE LA EDICIÓN 4.0 ELECTRÓNICA 10

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.
INTRODUCCIÓN 21

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 │ 16
└─────────
114 77 │ 16 1234d = 4D2h
2 └─────────
13 4

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 ║
INTRODUCCIÓN 21

║ 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:

Podemos observar que la suma se desa-


1010 1010b rrolla de la forma tradicional; es decir:
+ 0011 1100b sumamos normalmente, salvo en el caso de
────────────── 1 + 1 = 102 , en cuyo caso tenemos un aca-
1110 0110b rreo 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 Como en un byte solo tenemos dos nibbles, es


+ 01h decir, dos dígitos hexadecimales, el resultado es
────── 0 (observar cómo el 1 más significativo subrayado
100h 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.


INTRODUCCIÓN 21

Se realizan a nivel de bit y pueden ser de uno o dos operandos:

╔═════╤═════════╗ ╔════════════╤══════════════╤═════════════╤════════════╗
║ x │ NOT (x) ║ ║ x y │ x AND y │ x OR y │ x XOR y ║
╟─────┼─────────╢ ╟────────────┼──────────────┼─────────────┼────────────╢
║ 0 │ 1 ║ ║ 0 0 │ 0 │ 0 │ 0 ║
║ 1 │ 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 interrupciones-
significa 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
MICROPROCESADORES 8086/88, 286, 386 Y 486 31

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 │ │ SP │ │ CS │ │ IP │
├────────────┤ ├────────────┤ ├────────────┤ ├────────────┤
│ BX │ │ BP │ │ DS │ │ flags │
├────────────┤ ├────────────┤ ├────────────┤ └────────────┘
│ CX │ │ SI │ │ SS │ Registro
├────────────┤ ├────────────┤ ├────────────┤ puntero de
│ DX │ │ DI │ │ ES │ instrucciones
└────────────┘ └────────────┘ └────────────┘ y flags
Registros Registros Registros
de punteros de 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.

┌──────┬──────┐ ┌──────┐ ┌──────┐ ┌──────┐ Se amplía el tamaño de los registros


│ │ AX │ │ SP │ │ CS │ │ IP │ de datos (que pueden ser accedidos en
└──────┴──────┘ └──────┘ └──────┘ └──────┘ fragmentos de 8, 16 ó 32 bits) y se añaden dos
EAX nuevos registros de segmento multipropósito
(FS y GS). Algunos de los registros aquí
┌──────┬──────┐ ┌──────┬──────┐ ┌──────┐ ┌──────┐
mostrados son realmente de 32 bits (como
│ │ BX │ │ │ BP │ │ DS │ │ flags│
EIP en vez de IP), pero bajo sistema operativo
└──────┴──────┘ └──────┴──────┘ └──────┘ └──────┘
EBX EBP
DOS no pueden ser empleados de manera
directa, por lo que no les consideraremos.
┌──────┬──────┐ ┌──────┬──────┐ ┌──────┐ ┌──────┐
│ │ CX │ │ │ SI │ │ ES │ │ FS │
└──────┴──────┘ └──────┴──────┘ └──────┘ └──────┘
ECX ESI

┌──────┬──────┐ ┌──────┬──────┐ ┌──────┐ ┌──────┐


│ │ DX │ │ │ DI │ │ SS │ │ GS │
└──────┴──────┘ └──────┴──────┘ └──────┘ └──────┘
EDX EDI

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


MICROPROCESADORES 8086/88, 286, 386 Y 486 31

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 ; símbolo constante


MOV AX,dato

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 ; ahora es una variable


MOV AX,OFFSET dato ; 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] ; AX = [SS*16+BP]


MOV ES:[DI],AX ; [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] ó MOV AX,desp[DI]


ADD [SI+DESP],BX ó 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 AX,ES:desp[BX][DI]


MOV CS:[BX+SI+DESP],CX ó 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:

CS SS DS ES
╔═══════════════╦═══════════════╦═══════════════╦═══════════════╗
IP ║ Sí ║ No ║ No ║ No ║
╠═══════════════╬═══════════════╬═══════════════╬═══════════════╣
SP ║ No ║ Sí ║ No ║ No ║
╠═══════════════╬═══════════════╬═══════════════╬═══════════════╣
BP ║ con prefijo ║ por defecto ║ con prefijo ║ con prefijo ║
╠═══════════════╬═══════════════╬═══════════════╬═══════════════╣
BX ║ con prefijo ║ con prefijo ║ por defecto ║ con prefijo ║
╠═══════════════╬═══════════════╬═══════════════╬═══════════════╣
SI ║ con prefijo ║ con prefijo ║ por defecto ║ con prefijo ║
╠═══════════════╬═══════════════╬═══════════════╬═══════════════╣
DI ║ 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
MICROPROCESADORES 8086/88, 286, 386 Y 486 31

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 SS:SP Memoria SS:SP Memoria SS:SP


├───────────┤ ├───────────┤ ├───────────┤
│ 66h │ │ 66h │ │ 66h │
├───────────┤ ├───────────┤ ├───────────┤
│ 91h │ <-- 14C0:D022 │ 91h │ │ 91h │ <-- 14C0:D022
├───────────┤ ├───────────┤ ├───────────┤
│ F3h │ │ 12h │ │ 12h │
├───────────┤ ├───────────┤ ├───────────┤
│ 21h │ │ 34h │ <-- 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 C6 07 20 MOV BYTE PTR [BX],32 ; se pone BYTE PTR para indicar que 32 es de 8 bits
14D3:7A1E 43 INC BX ; 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.
JUEGO DE INSTRUCCIONES 80x86 41

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 ds,ax


mov bx,es:[si]
mov 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 bl,ch


xchg 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 bx,offset tabla


mov al,4
xlat

„ 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 dx,offset datos


add 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


JUEGO DE INSTRUCCIONES 80x86 41

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 12345678h


lds 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 ZF AF PF CF
- - - - x x x x 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 PF CF
- - - - - - - - 0

Borra el indicador de acarreo (CF) sin afectar a ninguno otro.

„ CLD (baja el indicador de dirección)

Sintaxis: CLD
41 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

Indicadores: OF DF IF TF SF ZF AF PF CF
- 0 - - - - - - -

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 TF SF ZF AF PF CF
- - 0 - - - - - -

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

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 PF CF
- - - - - - - - 1

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 IF TF SF ZF AF PF CF
- 1 - - - - - - -

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.
JUEGO DE INSTRUCCIONES 80x86 41

„ STI (pone a uno el indicador de interrupción)

Sintaxis: STI

Indicadores: OF DF IF TF SF ZF AF PF CF
- - 1 - - - - - -

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 ax
pop 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 DF IF TF SF ZF AF PF CF
x x x x x x x x 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: call proc1

dir dd 0f000e987h
call 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
JUEGO DE INSTRUCCIONES 80x86 41

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 etiqueta


jmp 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 cx,10
bucle: .......
.......
loop 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 TF SF ZF AF PF CF
- - 0 0 - - - - -

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 TF SF ZF AF PF CF
- - 0 0 - - - - -

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
JUEGO DE INSTRUCCIONES 80x86 41

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 ax,0fh
in 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 12h,ax


out 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 PF CF
? - - - ? ? x ? 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 DF IF TF SF ZF AF PF CF
x - - - x x x x 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 DF IF TF SF ZF AF PF CF
x - - - x x x x 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 ax,bx


add cl,dh

„ DAA (ajuste decimal para la suma)

Sintaxis: DAA

Indicadores: OF DF IF TF SF ZF AF PF CF
? - - - x x x x 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 al,cl


daa

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)
JUEGO DE INSTRUCCIONES 80x86 41

Sintaxis: INC destino

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x -

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 al
inc es:[di]
inc ss:[bp+4]
inc word ptr cs:[bx+di+7]

*** RESTA ***

„ AAS (ajuste ASCII para la resta)

Sintaxis: AAS

Indicadores: OF DF IF TF SF ZF AF PF CF
? - - - ? ? x ? 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 al,bl


aas

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 DF IF TF SF ZF AF PF CF
x - - - x x x x 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 bx, mem_pal


cmp ch,cl

„ DAS (ajuste decimal para la resta)

Sintaxis: DAS

Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - x x x x 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 al,bl


das

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 DF IF TF SF ZF AF PF CF
x - - - x x x x -

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 ax
dec mem_byte

„ NEG (negación)

Sintaxis: NEG destino

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x 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 DF IF TF SF ZF AF PF CF
x - - - x x x x 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 ax,ax


sbb ch,dh
JUEGO DE INSTRUCCIONES 80x86 41

„ SUB (resta)

Sintaxis: SUB destino, origen

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x 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 al,bl


sub dx,dx

*** MULTIPLICACION ***

„ AAM (ajuste ASCII para la multiplicación)

Sintaxis: AAM

Indicadores: OF DF IF TF SF ZF AF PF CF
? - - - x x ? x ?

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 bl
aam

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 DF IF TF SF ZF AF PF CF
x - - - ? ? ? ? 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: imul bx
imul ch

„ MUL (multiplicación sin signo)


41 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

Sintaxis: MUL origen (origen no puede ser operando inmediato)

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - ? ? ? ? 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 byte ptr ds:[di]


mul dx
mul cl

*** DIVISION ***

„ AAD (ajuste ASCII para la división)

Sintaxis: AAD

Indicadores: OF DF IF TF SF ZF AF PF CF
? - - - x x ? x ?

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: div bl
div mem_pal

„ IDIV (división entera)


JUEGO DE INSTRUCCIONES 80x86 41

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 bl
idiv 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: CMPS cadena_destino, cadena_origen


CMPSB (bytes)
CMPSW (palabras)

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x 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 si,origen
lodsb

„ 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
JUEGO DE INSTRUCCIONES 80x86 41

„ SCAS/SCASB/SCASW (explorar cadena)

Sintaxis: SCAS cadena_destino


SCASB (bytes)
SCASW (palabras)

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x 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 di,destino


mov al,50
scasb

„ 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 di,destino


mov ax,1991
stosw

„ 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 Función Instrucciones


----------- ------------------------------- ----------------
REP Repetir CX veces MOVS, STOS
REPE/REPZ Repetir CX veces mientras ZF=1 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 ; hacia adelante
REP 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 DF IF TF SF ZF AF PF CF
0 - - - x x ? x 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 ax,bx


and bl,byte ptr es:[si+10h]

„ NOT (no lógico)

Sintaxis: NOT destino

Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
JUEGO DE INSTRUCCIONES 80x86 41

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 DF IF TF SF ZF AF PF CF
0 - - - x x ? x 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 DF IF TF SF ZF AF PF CF
0 - - - x x ? x 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 DF IF TF SF ZF AF PF CF
0 - - - x x ? x 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.


JUEGO DE INSTRUCCIONES 80x86 41

„ RCL (rotación a la izquierda con acarreo)

Sintaxis: RCL destino, contador

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - - - - - 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 ax,1


rcl al,cl
rcl di,1

„ RCR (rotación a la derecha con acarreo)

Sintaxis: RCR destino, contador

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - - - - - 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 bx,cl


rcr bx,1

„ ROL (rotación a la izquierda)

Sintaxis: ROL destino, contador

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - - - - - 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 dx,cl


rol ah,1

„ ROR (rotación a la derecha)

Sintaxis: ROR destino, contador

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - - - - - 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 cl,1


ror ax,cl

„ SAL/SHL (desplazamiento aritmético a la izquierda)

Sintaxis: SAL/SHL destino, contador

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x ? x 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 SAL/SHL
└────┘ └─────────────────────────┘

Ejemplos: shl dx,1


sal bx,cl

„ SAR (desplazamiento aritmético a la derecha)

Sintaxis: SAR destino, contador

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x ? x 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.

┌────┐
│ ┌─À───────────────────────┐ ┌────┐
JUEGO DE INSTRUCCIONES 80x86 41

└──┤ alto ──¾ bajo ├─────¾│ CF │ SAR


└─────────────────────────┘ └────┘

Ejemplos: sar ax,cl


sar bp,1

„ SHR (desplazamiento lógico a la derecha)

Sintaxis: SHR destino, contador

Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x ? x 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 ax,cl


shr 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 Efecto sobre los flags pág.


──────────────── ───────────────────── ────────────────────────── ────
OF DF IF TF SF ZF AF PF CF
AAA AAA ? - - - ? ? x ? x 49
AAD AAD ? - - - x x ? x ? 54
AAM AAM ? - - - x x ? x ? 53
AAS AAS ? - - - ? ? x ? x 51
ADC dst,fnt ADC dst,fnt x - - - x x x x x 50
ADD dst,fnt ADD dst,fnt x - - - x x x x x 50
AND dst,fnt AND dst,fnt 0 - - - x x ? x 0 58
CALL dsp CALL dsp - - - - - - - - - 46
CBW CBW - - - - - - - - - 55
CLC CLC - - - - - - - - 0 43
CLD CLD - 0 - - - - - - - 43
CLI CLI - - 0 - - - - - - 44
CMC CMC - - - - - - - - x 44
CMP dst,fnt CMP dst,fnt x - - - x x x x x 51
CMPS/CMPSB
CMPSW cdst,cfnt CMPS cdst,cfnt x - - - x x x x x 55
CWD CWD - - - - - - - - - 55
DAA DAA ? - - - x x x x x 50
DAS DAS - - - - x x x x x 51
DEC dst DEC dst x - - - x x x x - 52
DIV fnt DIV dst ? - - - ? ? ? ? ? 54
ESC opcode,fnt ESC opcode,fnt - - - - - - - - - 59
HLT HLT - - - - - - - - - 59
IDIV fnt IDIV fnt ? - - - ? ? ? ? ? 54
IMUL fnt IMUL fnt x - - - ? ? ? ? x 53
IN acum,port IN acum,port - - - - - - - - - 49
INC dst INC dst x - - - x x x x - 50
INT interrup INT interrup - - 0 0 - - - - - 48
INTO INTO - - 0 0 - - - - - 48
IRET IRET x x x x x x x x x 48
Jcc (JA, JBE...) Jcc dsp - - - - - - - - - 47
JMP JMP dsp - - - - - - - - - 46
JCXZ dsp JCXZ dsp - - - - - - - - - 47
LAHF LAHF - - - - - - - - - 43
LDS dst,fnt LDS dst,fnt - - - - - - - - - 42
LEA dst,fnt LEA dst,fnt - - - - - - - - - 42
LES dst,fnt LES dst,fnt - - - - - - - - - 43
LOCK LOCK - - - - - - - - - 60
LODS/LODSB/
LODSW cfnt LODS mem - - - - - - - - - 56
LOOP LOOP dsp - - - - - - - - - 47
LOOPcc (LOOPE...) LOOPcc dsp - - - - - - - - - 48
MOV dst,fnt MOV dst,fnt - - - - - - - - - 41
MOVS/MOVSB/
MOVSW cdst,cfnt MOVS cdst,cfnt - - - - - - - - - 56
MUL fnt MUL fnt x - - - ? ? ? ? x 53
NEG dst NEG fnt x - - - x x x x x 52
JUEGO DE INSTRUCCIONES 80x86 41

NOP NOP - - - - - - - - - 59
NOT dst NOT dst - - - - - - - - - 58
OR dst,fnt OR dst,fnt 0 - - - x x ? x 0 58
OUT port,acum OUT port,acum - - - - - - - - - 49
POP dst POP dst - - - - - - - - - 45
POPF POPF x x x x x x x x x 45
PUSH dst PUSH dst - - - - - - - - - 45
PUSHF PUSHF - - - - - - - - - 45

Instrucción Sintaxis Efecto sobre los flags pág.


──────────────── ───────────────────── ────────────────────────── ────
OF DF IF TF SF ZF AF PF CF
RCL dst,cnt RCL dst,cnt x - - - - - - - x 60
RCR dst,cnt RCR dst,cnt x - - - - - - - x 61
REP/REPE/REPZ/
REPNE/REPNZ REP - - - - - - - - - 57
RET [val] RET [val] - - - - - - - - - 47
RETF [val] RETF [val] - - - - - - - - - 47
ROL dst,cnt ROL dst,cnt x - - - - - - - x 61
ROR dst,cnt ROR dst,cnt x - - - - - - - x 61
SAHF SAHF - - - - x x x x x 43
SAL/SHL dst,cnt SAL dst,cnt x - - - x x ? x x 62
SAR dst,cnt SAR dst,cnt x - - - x x ? x x 62
SBB dst,fnt SBB dst,fnt x - - - x x x x x 52
SCAS/SCASB/
SCASW cdst SCAS cdst x - - - x x x x x 56
SHR dst,cnt SHR dst,cnt x - - - x x ? x x 62
STC STC - - - - - - - - 1 44
STD STD - 1 - - - - - - - 44
STI STI - - 1 - - - - - - 45
STOS/STOSB/
STOSW cdst STOS cdst - - - - - - - - - 57
SUB dst,fnt SUB dst,fnt x - - - x x x x x 52
TEST dst,fnt TEST dst,fnt 0 - - - x x ? x 0 59
WAIT WAIT - - - - - - - - - 60
XCHG dst,fnt XCHG dst,fnt - - - - - - - - - 41
XLAT tfnt XLAT tfnt - - - - - - - - - 42
XOR dst,fnt XOR dst,fnt 0 - - - x x ? x 0 59

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
JUEGO DE INSTRUCCIONES 80x86 41

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 ó BSF reg, [memoria]


BSR reg, reg ó 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
JUEGO DE INSTRUCCIONES 80x86 41

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 AX ; AX = flags
AND AH,0Fh ; borrar nibble más significativo
PUSH AX
POPF ; intentar poner a 0 los 4 bits más significativos de los flags
PUSHF
POP AX
AND AH,0F0h ; seguirán valiendo 1 excepto en un 80286 o superior
CMP AH,0F0h
JE no_es_AT
JMP 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 ; ¿procesador del equipo?

; * CPU v2.2 (c) Septiembre 1992 CiriSOFT * JNE no_es_este

; * (c) Grupo Universitario de Informática - Valladolid * LEA DX,apuntador_txt ; sí lo es: indicarlo

; * * CALL print

; * Este programa determina el tipo de microprocesador del equipo * no_es_este: LEA DX,separador_txt

; * y devuelve un código ERRORLEVEL indicándolo: * CALL print

; * * CMP CX,7 ; 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 ; últimos caracteres

; * * CALL print

; * Aviso: Utilizar TASM 2.0 o compatible exclusivamente. * MOV AH,4Ch ; retornar código errorlevel AL

; * * INT 21h ; fin de programa

; ********************************************************************

procesador? PROC ; devolver el tipo de microprocesador en AX

cpu SEGMENT PUSHF

ASSUME CS:cpu, DS:cpu PUSH DS

PUSH ES

.386 PUSH CX

PUSH DX

ORG 100h PUSH DI

inicio: PUSH SI

LEA DX,texto_ini ; texto de saludo MOV AX,CS

MOV AH,9 MOV 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

otro_proc: INC CX PUSH AX

ADD BX,2 POPF ; intentar poner a 0 los 4 bits más

MOV DX,[BX] ; nombre del primer procesador PUSHF ; significativos de los flags
JUEGO DE INSTRUCCIONES 80x86 41

POP AX NOP ; en un 8086/80186/V30 (y no en un

AND AX,0F000h ; seguirán valiendo 1 excepto en INC CX ; 8088/80188/V20) porque está en la

CMP AX,0F000h ; un 80286 o superior tipo_bus_byte: STI ; cola de lectura adelantada.

JE ni286ni_super tipo_bus_dest: STI

PUSHF ; es 286 o superior JCXZ cpu_hallada ; el bus ya era supuesto de 8 bits

POP AX INC DL ; resulta que es de 16

OR AX,7000h ; intentar activar bit 12, 13 ó 14 cpu_hallada: MOV AL,DL

PUSH AX XOR AH,AH

POPF POP SI

PUSHF POP DI

POP AX POP DX

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 POPF

CLI ; momento crítico RET ; AX = CPU: 0/1-8088/86, 2/3-NEC V20/V30

MOV EDX,ESP ; preservar ESP en EDX procesador? ENDP ; 4/5-80188/186, 6-286, 7-386, 8-486

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) print PROC

POP EAX ; recuperar flags en EAX PUSH AX

MOV ECX,EAX PUSH BX

XOR EAX,40000h ; conmutar bit 18 PUSH CX

PUSH EAX MOV AH,9

POPFD ; intentar cambiar este bit INT 21h

PUSHFD POP CX

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

POPFD ; restaurar bit 18 de los flags cpus_indice DW i88,i86,v20,v30,i188,i186,i286,i386,i486

MOV ESP,EDX ; restaurar ESP i88 DB "Intel 8088 $"

STI ; permitir interrupciones de nuevo i86 DB "Intel 8086 $"

POP DX ; recuperar tipo de CPU en DL v20 DB " NEC V20 $"

CMP AX,0 v30 DB " NEC V30 $"

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$"

ni286ni_super: MOV DL,4 ; supuesto un 80188 ... i386 DB "Intel 80386$"

MOV AX,0FFFFh i486 DB "Intel 80486$"

MOV CL,33

SHL AX,CL ; (80188/80186 toman CL mod 32) apuntador_txt DB " <───$"

JNZ tipo_bus_proc ; ... lo es, calcular bus (188/186)

MOV DL,2 ; no lo es, supuesto un V20 ... texto_ini LABEL BYTE

MOV CX,0FFFFh DB 13,10,"CPU Test v2.2 "

STI DB "(c) Septiembre 1992 Ciriaco García de Celis."

DB 0F3h,26h,0ACh ; opcode de REPZ LODSB ES: DB 13,10," El microprocesador de este "

JCXZ tipo_bus_proc ; ... lo es, calcular bus (V20/V30) DB "equipo es compatible:",10

XOR DL,DL ; ya sólo puede ser un 8088/8086 separador_txt DB 13,10,9,9,9,"$"

tipo_bus_proc: STD ; transferencias hacia arriba texto_fin DB 13,10,"$"

LEA DI,tipo_bus_dest

MOV AL,BYTE PTR DS:tipo_bus_byte ; opcode de STI cpu ENDS

MOV CX,3 END inicio

CLI

REP STOSB ; transferir tres bytes

CLD

NOP ; el INC CX (1 byte) será machacado

NOP ; con STOSB pero aún se ejecutará


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 │ flat386 PROC

; │ a 4 Gb en modo real). │ PUSH DS

; │ │ PUSH ES

; │ TASM flat386 /m5 │ PUSH EAX

; │ TLINK flat386 /t /32 │ PUSH BX

; └──────────────────────────────────────────────────────────────────┘ PUSH CX

MOV CX,SS

.386p ; sólo para 386 o superior XOR EAX,EAX

MOV AX,CS

segmento 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

ORG 100h CLI

prueba: LGDT CS:[gdtr] ; cargar tabla global de descriptores

CALL flat386 ; activar modo flat MOV EAX,CR0

XOR AX,AX OR AL,1 ; bit de modo protegido

MOV DS,AX MOV CR0,EAX ; pasar a modo protegido

MOV EBX,0B8000h ; dirección de vídeo absoluta JMP SHORT $+2 ; borrar cola de prebúsqueda

MOV CX,2000 MOV BX,gcodl ; índice de descriptor en BX

llena_pant: 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 ; fin de programa AND AL,11111110b

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 (necesario hacerlo directamente STI

; o a través de algún servicio XMS antes de acceder a POP CX

; las áreas de memoria extendida afectadas). POP BX


JUEGO DE INSTRUCCIONES 80x86 41

POP EAX

POP ES

POP DS

RET

gdtr LABEL QWORD ; datos para cargar en GDTR

gd1 DW gdtl-1

gd2 DD ?

gdt DB 0,0,0,0,0,0,0,0 ; GDT

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
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 destino


es:[di] 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


EL LENGUAJE ENSAMBLADOR DEL 80x86 71

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 100 ; «dato» vale 100
MOV AL,dato GE 10 ; AL = 0FFh (cierto)
MOV AH,dato EQ 99 ; 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


también es válido:
MOV AX,OFFSET DS:variable

„ 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 76
MOV 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 100 DUP (12345)


MOV AX,SIZE matriz ; AX = 200
MOV BX,LENGTH matriz ; 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:
EL LENGUAJE ENSAMBLADOR DEL 80x86 71

frase DB "simpático"
longitud EQU $-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 1025


MOV AL,LOW dato ; AL = 1
MOV AH,HIGH dato ; 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 DW 1991
mes DB 12
numerazo DD 12345678h
texto DB "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 29.72
espanoles91 DQ 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 DB 100 DUP (0)


basura DW 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 [BX+DI+8]


MOV 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
EL LENGUAJE ENSAMBLADOR DEL 80x86 71

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 SEGMENT AT 40h


p_serie0 DW ?
vars_bios ENDS

De esta manera, la dirección del primer puerto serie puede obtenerse de esta manera (por ejemplo):

MOV AX,variables_bios ; segmento


MOV ES,AX ; inicializar ES
MOV AX,ES:p_serie0

- 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 PROC
...
cls 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:

PUBLIC proc1, var_x


proc1 PROC FAR
⋅⋅⋅
proc1 ENDP
var_x 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
EL LENGUAJE ENSAMBLADOR DEL 80x86 71

codigo SEGMENT
⋅⋅⋅
codigo ENDS

datos SEGMENT
dato DW 1234
datos ENDS

pila SEGMENT STACK 'STACK'


DB 128 DUP (?)
pila ENDS

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 AX,dato ; ¡incorrecto!


MOV AX,supersegmento:dato ; 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 LABEL WORD


byte_bajo DB 0
byte_alto DB 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 STRUC
mote DB '0123456789' ; modificable
edadaltura DB 20,175 ; no modificable
peso DB 0 ; modificable
otros DB 10 DUP(0) ; no modificable
telefono DD ? ; modificable
alumno ENDS

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 AX,OFFSET felipe.telefono


LEA BX,felipe
MOV 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 ; AL = 5 (desplazamiento del bit


; menos significativo de A)
MOV AL, MASK A ; AL = 01100000b (máscara de A)
MOV AL, WIDTH 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 [símbolo/exp./arg.] ; xxx es la condición


...
ELSE ; el ELSE es opcional
...
ENDIF

IF expresion (expresión distinta de cero)


EL LENGUAJE ENSAMBLADOR DEL 80x86 71

IFE expresión (expresión igual a cero)


IF1 (pasada 1 del ensamblador)
IF2 (pasada 2 del ensamblador)
IFDEF símbolo (símbolo definido o declarado como externo)
IFNDEF símbolo (símbolo ni definido ni declarado como externo)
IFB <argumento> (argumento en blanco en macros -incluir '<' y '>'-
)
IFNB <argumento> (lo contrario, también es obligado poner '<' y
'>')
IFIDN <arg1>, <arg2> (arg1 idéntico a arg2, requiere '<' y '>')
IFDIF <arg1>, <arg2> (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
EL LENGUAJE ENSAMBLADOR DEL 80x86 71

(PUSHA y POPA). Quien esté acostumbrado a emplearlas, puede crear unas macros que simulen estas
instrucciones en los 8086:

SUPERPUSH MACRO
PUSH AX
PUSH CX
PUSH DX
PUSH BX
PUSH SP
PUSH BP
PUSH SI
PUSH DI
ENDM

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 AX
MOV AX,positivos
ADD AX,negativos
MOV total,AX
POP 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 MACRO dato1, dato2, resultado


LOCAL ya_esta
MOV AX,dato1
CMP AX,dato2 ; ¿es dato1 el menor?
JB ya_esta ; sí
MOV AX,dato2 ; no, es dato2
ya_esta: MOV resultado,AX
ENDM

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 c
MOV AL,"&c"
etiqueta&c: CALL imprimir
ENDM

Al ejecutar SALUDO A se producirá la siguiente expansión:

MOV AL,"A"
etiquetaA: CALL imprimir

Si no se hubiera colocado el & se hubiera expandido como MOV AL,"c"


EL LENGUAJE ENSAMBLADOR DEL 80x86 71

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 MACRO x
IRP i, <1, 2>
x&i DB i
ENDM
ENDM

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 MACRO x
IRP i, <1, 2>
x&&i DB i
ENDM
ENDM

Lo que con MEMORIA ET generará correctamente las líneas:

ET1 DB 1
ET2 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 mensaje, suma


%OUT * mensaje, suma *
ENDM

(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 EQU 120


SIM2 EQU 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 2
OUT DX,AL
ENDM

Esta secuencia se transformará, al ensamblar, en lo siguiente:

OUT DX,AL
OUT 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 ; fin de REPT
ENDM ; fin de macro

La sentencia SUCESION 3 provocará la siguiente expansión:

DB 0
DB 1
DB 2

„ IRP simbolo_control, <arg1, arg2, ..., arg_n> ... 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 i, <1,2,3>
DB 0, i, i*i
ENDM

Al expansionarse, este conjunto de instrucciones se convierte en lo siguiente:

DB 0, 1, 1
DB 0, 2, 4
EL LENGUAJE ENSAMBLADOR DEL 80x86 71

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 p1, p2, p3, p4, valor


PUSH AX
PUSH DX
MOV AL,valor
IRP cn, <p1, p2, p3, p4>
MOV DX, cn
OUT DX, AL
ENDM ; fin de IRP
POP DX
POP AX
ENDM ; fin de macro

Al ejecutar TETRAOUT 318h, 1C9h, 2D1h, 1A4h, 17 se obtendrá:

PUSH AX
PUSH DX
MOV AL, 17
MOV DX, 318h
OUT DX, AL
MOV DX, 1C9h
OUT DX, AL
MOV DX, 2D1h
OUT DX, AL
MOV DX, 1A4h
OUT DX,AL
POP DX
POP 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 lista, p


IRP i, <lista>
INC i
ENDM ; fin de IRP
DEC p
ENDM ; fin de macro

Se comprende la necesidad de utilizar los ángulos:

INCD AX, BX, CX, DX se expandirá:

INC AX
DEC BX ; CX y DX se ignoran (4 parámetros)

INCD <AX, BX, CX>, DX se expandirá:

INC AX
INC BX
71 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

INC CX
DEC DX ; (2 parámetros)

„ IRPC simbolo_control, <c1c2 ... cn> ... 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 i, <813>
DB i
ENDM

El bloque anterior generará al expandirse:

DB 8
DB 1
DB 3

Ejemplo de utilización dentro de una macro (en combinación con el operador &):

INICIALIZA MACRO a, b, c, d
IRPC iter, <&a&b&c&d>
DB iter
ENDM ; fin de IRPC
ENDM ; fin de macro

Al ejecutar INICIALIZA 7, 1, 4, 0 se produce la siguiente expansión:

DB 7
DB 1
DB 4
DB 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
EL LENGUAJE ENSAMBLADOR DEL 80x86 71

interrupciones:

XPUSH MACRO R1,R2,R3,R4,R5,R6,R7,R8,R9,R10


IRP reg, <R1,R2,R3,R4,R5,R6,R7,R8,R9,R10>
IFNB <reg>
PUSH reg
ENDIF
ENDM ; fin de IRP
ENDM ; fin de XPUSH

Por ejemplo, la instrucción:


XPUSH AX,BX,DS,ES,VAR1
Se expandirá en:
PUSH AX
PUSH AX
PUSH DS
PUSH ES
PUSH VAR1

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, <lista>
PUSH i
ENDM
ENDM

XPOP MACRO lista


IRP i, <lista>
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 <AX, BX, CX>
XPOP <CX, BX, AX>
Que al expandirse queda:
PUSH AX
PUSH BX
PUSH CX
POP CX
POP BX
POP AX

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 ; apilar parámetros
PUSH datoH
CALL moduloA ; llamada
ADD SP,4 ; equilibrar pila
⋅⋅⋅
EL LENGUAJE ENSAMBLADOR DEL 80x86 71

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 91

/X Lista las condiciones falsas (ensamblaje condicional).


/z Visualiza la línea del error y no sólo el número de la misma.
/Zi Genera información simbólica para los depuradores de código.
/Zd 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
<simbolo>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 [<dirección> [numbytes]] (dump): visualiza el contenido de la memoria. SYMDEB permite además


visualizarla en palabras (DW), dobles palabras (DD), coma flotante ...

„ A [<dirección>] (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 CS: ; mal ensamblado con DEBUG (no así con SYMDEB)
MOV WORD PTR ES:[100],1234 ; error en DEBUG (sí vale con SYMDEB)
CS: ; bien emsamblado con ambos
XLAT
ES: ; y esto también
MOV WORD PTR [100],1234

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 <dirección> [<lista>] (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 <lista>, 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 [<direccion> [<rango>]] (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 [<registro>] (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) │
│ Acarreo auxiliar │ AC │ NA │
│ Paridad │ PE (par) │ PO (impar) │
│ Acarreo │ CY │ NC │
└──────────────────┴──────────┴────────────┘

„ G [=<dirección> [,<dirección>,...]] (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 [<veces>] (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 [<veces>] (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 <especificacion_fichero> (name): se asigna un nombre al programa que está siendo creado o modificado.
Se puede indicar la trayectoria de directorios.

„ L [<dirección>] (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 <dirección> <unidad> <primer_sector> <num_sectores> (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 [<dirección>] (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 <dirección> <unidad> <primer_sector> <num_sectores> (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 <rango> <lista> (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 <rango> <lista> (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 <rango> <dirección> (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 <rango> <dirección> (move): Más que mover, copia una zona de memoria en otra de manera inteligente
(controlando los posibles solapamientos de los bloques).

„ I <puerto> (input): visualiza la lectura del puerto de E/S indicado.

„ O <puerto> <valor> (output): envia un valor a un puerto de E/S.

„ H <valor1> <valor2> (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).
EL ENSAMBLADOR EN ENTORNO DOS 91

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
EL ENSAMBLADOR EN ENTORNO DOS 91

*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 ; CS:DX nueva rutina de gestión
CLI ; evitar posible interrupción
MOV [BX],DX ; cambiar vector (offset)
MOV [BX+2],CS ; cambiar vector (segmento)
STI ; permitir interrupciones
POP 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

ORG 100h ; programa .COM ordinario SHR AX,1 ; desplazamiento / 8

inicio: SHR AX,1 ; desplazamiento / 16 (párrafos)

MOV AH,15 ; función para obtener modo de vídeo ADD BX,AX ; segmento de vídeo efectivo

INT 10h ; llamar a la BIOS

MOV BX,0B000h ; segmento de pantalla monocroma datos_ok: MOV DS,BX ; DS = segmento de pantalla

MOV CX,2000 ; tamaño (caracteres) de la pantalla XOR BX,BX ; BX = 0 (primer carácter)

CMP AL,7 ; ¿es realmente modo monocromo? otra_letra: CMP BYTE PTR [BX],'a'; ¿código ASCII menor que 'a'?

JE datos_ok ; en efecto JB no_minuscula ; luego no puede ser minúscula

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 ; luego no puede ser minúscula

JE pant_color ; en efecto AND BYTE PTR [BX],0DFh ; poner en mayúsculas

CMP AL,2 ; ¿es modo de texto de 80 columnas? no_minuscula: 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 final: MOV AH,4Ch

MOV AL,1 ; pantalla gráfica o desconocida: INT 21h

JMP final ; fin de programa (errorlevel=1)

mays ENDS

pant_color: MOV AX,40h ; considerar página activa<>0 END inicio

MOV DS,AX ; DS = 40h (variables de la BIOS)

MOV AX,DS:[4Eh] ; desplazamiento de la página activa

7.4 - LA PANTALLA EN MODO GRÁFICO.


ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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 Texto Resolución Colores Segmento Tarjeta


──── ───── ────────── ─────── ──────── ─────────────────────
04h 40x25 320x200 4 B800 CGA, EGA, MCGA, VGA
05h 40x25 320x200 4 grises B800 CGA, EGA
05h 40x25 320x200 4 B800 CGA, VGA
06h 80x25 640x200 2 B800 CGA, EGA, MCGA, VGA
0Dh 40x25 320x200 16 A000 EGA, VGA
0Eh 80x25 640x200 16 A000 EGA, VGA
0Fh 80x25 640x350 2 A000 EGA, VGA
10h 80x25 640x350 4 A000 EGA con 64K
10h 80x25 640x350 16 A000 EGA con 256K, VGA
11h 80x30 640x480 2 A000 VGA, MCGA
12h 80x30 640x480 16/256k A000 VGA
13h 40x25 320x200 256/256k A000 VGA, MCGA

27h 720x512 16 Genoa


29h 800x600 16 A000 Genoa
2Dh 640x350 256/256k A000 Genoa
2Eh 640x480 256/256k A000 Genoa
2Fh 720x512 256 Genoa
30h 800x600 256/256k A000 Genoa
37h 1024x768 16 A000 Genoa
58h 100x75 800x600 16/256k A000 Paradise VGA
59h 100x75 800x600 2 A000 Paradise VGA
5Bh 100x75 800x600 16/256k A000 Trident TVGA 8800, 8900
5Bh 640x350 256 Genoa 6400
5Ch 80x25 640x400 256 A000 Trident TVGA 8800
5Ch 640x480 256 Genoa 6400
5Dh 80x30 640x480 256 A000 Trident TVGA 8800 (512K)
5Eh 80x25 640x400 256 Paradise VGA
5Eh 800x600 256 Trident 8900
5Eh 800x600 256 Genoa 6400
5Fh 80x30 640x480 256 Paradise VGA (512K)
5Fh 1024x768 16/256k A000 Trident TVGA 8800 (512K)
5Fh 1024x768 16 Genoa 6400
61h 96x64 768x1024 16/256k A000 Trident TVGA 8800 (512K)
62h 1024x768 256 Trident TVGA 8900
6Ah 800x600 16 Genoa 6400
7Ch 512x512 16 Genoa
7Dh 512x512 256 Genoa

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 DS
MOV AX,1A00h
INT 10h ; solicitar información VGA a la BIOS
CMP AL,1Ah ; BL = tipo de tarjeta
JE tarjeta_ok ; función soportada (hay VGA)
MOV AX,40h
MOV DS,AX
MOV BL,10h
MOV AH,12h
INT 10h ; solicitar información EGA a la BIOS
CMP BL,10h
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

JE no_ega ; de momento, no es EGA


MOV BL,1 ; supuesto MDA
TEST BYTE PTR DS:[87h],8 ; estado del control de vídeo
JNZ tarjeta_ok ; es MDA
MOV BL,4 ; supuesto EGA color
OR BH,BH
JZ tarjeta_ok ; así es
INC BL ; es EGA mono
JMP tarjeta_ok
no_ega: MOV BL,2 ; supuesto CGA
CMP WORD PTR DS:[63h],3D4h ; base del CRT
JE tarjeta_ok ; así es
DEC BL ; es MDA
tarjeta_ok: POP DS
RET
tipo_tarjeta ENDP

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.

┌────────────┬────────────────┬──────────┬──────────┬───────────────┬───────────┐ La organización de
│ Modo (hex) │ Resolución │ Colores │ Segmento │ Organización │ Adaptador │ la memoria (entrelazado,
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ planos de bit o lineal) es la
│ 4 y 5 │ 320 x 200 │ 4 │ B800 │ entrelazado │ CGA │ manera en que se direcciona
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤
la memoria de vídeo por
│ │ │ │ │ │ │
6 640 x 200 2 B800 entrelazado CGA
parte de la CPU. Por
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤
ejemplo, en el modo 6, cada
│ 0Dh │ 320 x 200 │ 16 │ A000 │ planos de bit │ EGA │
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤
pixel de la pantalla está
│ 0Eh │ 640 x 200 │ 16 │ A000 │ planos de bit │ EGA │
asociado a un bit (8 pixels
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ por byte) a partir de la
│ 0Fh │ 640 x 350 │ 2 │ A000 │ planos de bit │ EGA │ dirección B800:0000; sin
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ embargo, cuando se recorren
│ 10h │ 640 x 350 │ 4 │ A000 │ planos de bit │ EGA │ 80 bytes en la memoria (640
├────────────┼────────────────┼──────────┼──────────┼───────────────┼───────────┤ bits o pixels, primera línea
│ 10h │ 640 x 350 │ 16 │ A000 │ 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 <dos.h>

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 192-
255): 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
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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).

┌────────────────────┐

│ │

├────────────────────┤

│ │

├────────────────────┤

│ │ ┌─ 0..63 ┐

├────────────────────┤ CASO 4 X 64 │ │

│ │ ├─ 64..127 │

├────────────────────┤ ┌── valor 0..63 ─────¾ elemento del DAC ───┤ │¾ página (0..3)

│ │ │ ├─ 128..191 │ seleccionable

├────────────────────┤ │ │ │ (0 por defecto)

┌───¾│ │ ─────────────┤ └─ 192..255 ┘

│ ├────────────────────┤ │

color │ │ │ │ ┌─ 0..15 ┐

en pantalla (0..15) ────────┘ ├────────────────────┤ └── valor 0..15 ─────¾ elemento del DAC ───┼─ 16..31 │

│ │ ├─ 32..47 │

├────────────────────┤ CASO 16 x 16 : │¾ página (0..15)

│ │ : │ seleccionable

├────────────────────┤ ├─ 224..239 │

│ │ └─ 240..255 ┘

├────────────────────┤

│ │ 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 */

#include <dos.h> for (i=4; i<17; i++) paleta[i]=0; /* resto colores y borde negros

#include <graphics.h> */

void main() r.r_es=FP_SEG(paleta); r.r_dx=FP_OFF(paleta);

{ r.r_ax=0x1002; intr (0x10, &r); /* establecer paleta y borde */

struct REGPACK r;

int gdrv, gmodo, coderr, i, x, color, pixel; getch(); closegraph();

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<getmaxx()/16; pixel++, x++) {

setcolor (color); line (x, 0, x, getmaxy());

}
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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 <dos.h> for (x=color=0; color<16; color++)

#include <graphics.h> for (pixel=0; pixel<getmaxx()/16; pixel++, x++) {

setcolor (color); line (x, 0, x, getmaxy());

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


for (i=0; i<16; i++) paleta[i]=i; partir del anterior, que requiere ya un auténtico
paleta[16]=0; /* color del borde */ adaptador VGA. Lo primero que se hace es
seleccionar el modo de 16 páginas en el DAC,
r.r_es=FP_SEG(paleta); r.r_dx=FP_OFF(paleta);
estableciendo la página 2 como activa
r.r_ax=0x1002; intr (0x10, &r); /* establecer paleta y borde */
(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
/* LLENAR ELEMENTOS 32..47 DEL DAC DE AMARILLOS CRECIENTES */

for (i=32; i<48; i++) {


hubieran sido los elementos 16..31 y así
dac[i][0]=i*4; /* valores crecientes 0..60 de rojo */
sucesivamente). Los registros de paleta, simples
dac[i][1]=i*4; /* valores crecientes 0..60 de verde */
índices en el DAC, toman los valores 0,1,...,15
dac[i][2]=0; /* sin componente azul */ (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
r.r_bx=32; /* primer elemento del DAC */ colores deseados, entre los 262.144 posibles. Como
r.r_cx=16; /* número de elementos a definir */ cada componente puede variar entre 0 y 63,
r.r_es=FP_SEG(dac[32]); r.r_dx=FP_OFF(dac[32]);
elegimos 16 valores espaciados proporcionalmente
r.r_ax=0x1012; intr (0x10, &r); /* programar elementos del DAC */
(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,
getch();

closegraph();

}
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).

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 <dos.h>

/*********************************************************************

* 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 103

register i, j; 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
for (i=0; i<256; i++) { /* anotar la paleta activa */ tres tipos de arquitectura de pantalla (modos CGA,
disable();
16 colores y 256 colores). El fallo reside,
outportb (0x3C7, i);
simplemente, en que han sido desarrollados sin
dac [i][0] = inportb (0x3C9); /* R */
pensar en la velocidad. Por ejemplo, la BIOS emplea
el algoritmo más lento posible que existe para trazar
dac [i][1] = inportb (0x3C9); /* G */

dac [i][2] = inportb (0x3C9); /* B */

enable();
puntos en los modos de 16 colores. Lo más
}
conveniente es utilizar los recursos del lenguaje de
/* claridad descendente desde el
programación o, mejor aún, acceder directamente a
64/64-avo al 0/64-avo de intensidad */ la memoria de pantalla con subrutinas en
for (i=64; i>=0; i--) { ensamblador. Este es el procedimiento seguido por
while (!((inportb(0x3DA) & 8)==8)); /* esperar retrazo vertical */ la mayoría de las aplicaciones comerciales. Sin
while (!((inportb(0x3DA) & 8)==0)); /* esperar su fin */ embargo, la BIOS tiene la ventaja de que permite
for (j=0; j<256; j++) { normalizar el acceso a la pantalla. Así, un programa
disable();
puede fácilmente trazar un punto en el modo
outportb (0x3C8, j);
1024x768x256 de una SuperVGA (y nunca mejor
outportb (0x3C9, dac[j][0]*i >> 6);
dicho, porque como sean muchos más de uno...).
Para trazar un punto se coloca en CX la coordenada
outportb (0x3C9, dac[j][1]*i >> 6);

outportb (0x3C9, dac[j][2]*i >> 6);

enable();
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 <dos.h>

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
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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 ; segunda

; * RED.ASM - Demostración de gráfica en CGA utilizando BIOS * MOV CX,BP

; * * MOV DX,0

; ******************************************************************** MOV SI,0

MOV DI,max_y-1

modo EQU 6 ; modo de vídeo SUB DI,BX

max_x EQU 640 CALL recta ; tercera

max_y EQU 200 MOV CX,max_x-1

max_color EQU 2 SUB CX,BP

MOV SI,max_x-1

red SEGMENT CALL recta ; cuarta

ASSUME CS:red, DS:red ADD BX,6

ADD BP,14

ORG 100h CMP BX,max_y

inicio: JB otras_cuatro

MOV AX,modo MOV AH,0

INT 10h ; modo de pantalla INT 16h ; esperar pulsación de tecla

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

otras_cuatro: MOV CX,0

MOV DX,BX recta PROC

MOV SI,BP PUSH AX ; de (CX,DX) a (SI,DI) color AL

MOV DI,max_y-1 PUSH BX

CALL recta ; primera recta PUSH CX

MOV CX,max_x-1 PUSH DX

MOV SI,max_x-1 PUSH SI


103 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

PUSH DI DEC AX ; «dx»--

PUSH BP JNZ penmay1

MOV color,AL fin: POP BP

MOV AX,SI POP DI

SUB AX,CX ; AX = X2-X1 POP SI

JNC absx2x1 POP DX

NEG AX POP CX

XCHG CX,SI POP BX

XCHG DX,DI POP AX

absx2x1: MOV BX,DI ; AX = ABS(X2-X1) = «dx» RET

SUB BX,DX color DB 0

MOV BP,1 ; BP = 1 = «yincr» si Y2>Y1 recta ENDP

JNC absy2y1

NEG BP ; BP = -1 = «yincr» si Y2<=Y1 punto PROC

NEG BX PUSH BX ; preservar registros (salvo AX)

absy2y1: CMP AX,BX ; BX = ABS(Y2-Y1) = «dy» PUSH CX

PUSHF PUSH DX

JA noswap ; ABS(pendiente) menor de 1 PUSH BP

XCHG AX,BX PUSH SI

noswap: SHL BX,1 ; BX = «dy» * 2 PUSH DI

MOV SI,BX MOV AH,0Ch ; trazar punto usando BIOS

SUB SI,AX ; SI = «dy» * 2 - «dx» = «d» XOR BX,BX

MOV DI,BX INT 10h

SUB DI,AX POP DI

SUB DI,AX ; DI = «dy»*2-«dx»*2 = «incr2» POP SI

POPF POP BP

JBE penmay1 ; pendiente mayor de 1 POP DX

penmen1: PUSH AX POP CX

MOV AL,color POP BX

CALL punto ; en (CX, DX) = («x», «y») RET

POP AX punto ENDP

INC CX ; «x»++

AND SI,SI ; (SI>0) ? -> «d» > 0 ? red ENDS

JS noincy END inicio

ADD SI,DI ; «d» > 0 : «d» = «d» + «incr2»

ADD DX,BP ; «y» = «y» + «yincr»

DEC AX ; «dx»--

JNZ penmen1

JMP fin

noincy: ADD SI,BX ; «d» < 0 : «d» = «d» + «incr1»

DEC AX

JNZ penmen1

JMP fin

penmay1: PUSH AX

MOV AL,color

CALL punto ; en (CX, DX) = («x», «y»)

POP AX

ADD DX,BP ; «y» = «y» + «yincr»

AND SI,SI ; (SI>0) ? -> «d» > 0 ?

JS noincx

ADD SI,DI ; «d» > 0 : «d» = «d» + «incr2»

INC CX ; «x»++

DEC AX ; «dx»--

JNZ penmay1

JMP fin

noincx: ADD SI,BX ; «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
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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.

punto640x200_C PROC ; en (CX, DX) de color AL (CGA 640x200) SHL DX,1

PUSH DS ; sólo se corrompe AX SHL DX,1 ; DX = («cy» / 2) * 64

PUSH 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

no_add: INC CL ; CL = 4 POP DS

SHL DX,CL ; DX = («cy» / 2) * 16 RET

ADD BX,DX ; BX = BX + («cy» / 2) * 16 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 ; modo de vídeo

; * * 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
103 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

ASSUME CS:oviseg, DS:oviseg SUB DX,DI

CALL circunferencia ; en (x-SI, y-DI)

ORG 100h INC AL

inicio: ADD CX,SI

MOV AX,modo ADD CX,SI

INT 10h CALL circunferencia ; en (x+SI, y-DI)

CALL paleta_verde INC AL

MOV CX,max_x SUB CX,SI

SHR CX,1 ; CX = max_x / 2 ADD DX,DI

MOV DX,max_y ADD CX,DI

SHR DX,1 ; DX = max_y / 2 ADD DX,SI

MOV BX,DX CALL circunferencia ; en (x+DI, y+SI)

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 ; esperar pulsación de tecla CALL circunferencia ; en (x-DI, y+SI)

MOV AX,3 INC AL

INT 10h ; volver a modo texto SUB DX,SI

INT 20h ; fin de programa SUB DX,SI

CALL circunferencia ; en (x-DI, y-SI)

paleta_verde PROC INC AL

MOV CX,256 ; los 256 registros ADD CX,DI

MOV DX,3C8h ADD CX,DI

otro_reg: MOV AL,CL CALL circunferencia ; en (x+DI, y-SI)

OUT DX,AL ; registro a programar INC AL

INC DX SUB CX,DI

XOR AL,AL ADD DX,SI ; CX = x, DX = y

OUT DX,AL ; componente roja CMP BP,0

MOV AL,CL JG ovillo_decx

REPT max_x/320 ADD BP,DI

SHR AL,1 ADD BP,DI

ENDM ADD BP,DI

OUT DX,AL ; componente verde ADD BP,DI

XOR AL,AL ADD BP,6

OUT DX,AL ; componente azul JMP ovillo_incy

DEC DX

LOOP otro_reg

RET

paleta_verde ENDP

ovillo PROC ; circunferencia de circunferencias

MOV BP,BX ; en (CX, DX) con radio BX y color AL

MOV AL,0

MOV SI,BX

XOR DI,DI

SHL BP,1

SUB BP,3

NEG BP ; BP = 3 - 2 * BX

ovillo_acaba: CMP DI,SI

JG ovillo_ok ; ovillo completado

ADD CX,SI

ADD DX,DI

CALL circunferencia ; en (x+SI, y+DI)

INC AL

SUB CX,SI

SUB CX,SI

CALL circunferencia ; en (x-SI, y+DI)

INC AL

SUB DX,DI
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

ovillo_decx: 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 circunf_decx: DEC SI

SHL AX,1 PUSH AX

ADD BP,AX MOV AX,DI

POP AX SUB AX,SI

ADD BP,10 SHL AX,1

ovillo_incy: INC DI SHL AX,1

JMP ovillo_acaba ADD BX,AX

ovillo_ok: RET POP AX

ovillo ENDP ADD BX,10

circunf_incy: INC DI

circunferencia PROC ; en (CX,DX) con radio BX y color AL JMP circunf_acaba

PUSH BX circunf_ok: POP DI

PUSH CX POP SI

PUSH DX POP DX

PUSH SI POP CX

PUSH DI POP BX

MOV SI,BX RET

XOR DI,DI circunferencia ENDP

SHL BX,1

SUB BX,3 punto PROC ; trazar punto en 320x200 con 256 col.

NEG BX ; BX = 3 - 2 * BX PUSH DS ; en (CX, DX) con color AL

circunf_acaba: CMP DI,SI PUSH CX

JG circunf_ok ; circunferencia completada PUSH DX

ADD CX,SI XCHG DH,DL ; DX = «cy» * 256

ADD DX,DI ADD CX,DX ; CX = «cy» * 256 + «cx»

CALL punto ; en (x+SI, y+DI) SHR DX,1

SUB CX,SI SHR DX,1 ; DX = «cy» * 64

SUB CX,SI ADD CX,DX ; CX = «cy» * 320 + «cx»

CALL punto ; en (x-SI, y+DI) 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 ; en (x-SI, y-DI) 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 ; en (x+SI, y-DI) POP CX

SUB CX,SI POP DS

ADD DX,DI RET

ADD CX,DI punto ENDP

ADD DX,SI

CALL punto ; en (x+DI, y+SI) oviseg ENDS

SUB CX,DI END inicio

SUB CX,DI

CALL punto ; en (x-DI, y+SI)

SUB DX,SI

SUB DX,SI

CALL punto ; en (x-DI, y-SI)

ADD CX,DI

ADD CX,DI

CALL punto ; en (x+DI, y-SI)

SUB CX,DI

ADD DX,SI ; CX = x, DX = y

CMP BX,0

JG circunf_decx

ADD BX,DI

ADD BX,DI
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
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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, /* dirección del segmento de vídeo */

* * far *pantalla,

* ESTANDAR GRAFICO VESA: EJEMPLO DE USO DEL MEJOR MODO DE 256 * i, modo, max_x, max_y, vram, bancos, banco, limite;

* COLORES EN CUALQUIER SUPERVGA. *

* *

*********************************************************************/ if (!testvesa()) {

printf ("\nNecesario soporte VESA para este programa.\n");

exit (1);

#include <dos.h> }

#include <alloc.h>

#include <stdio.h> modo = getbest256();

#include <stdlib.h> setmode (modo, &ConmutaBanco, &video_seg);

#include <string.h> getinfo (modo, &max_x, &max_y, &vram, &bancos);

for (banco=0; banco<bancos; banco++) {

#define M640x400x256 0x100 /* modos VESA normales de 256c */ setbank (ConmutaBanco, banco); /* direccionar banco */

#define M640x480x256 0x101 pantalla=MK_FP(video_seg, 0); /* normalmente 0xA000:0 */

#define M800x600x256 0x103

#define M1024x768x256 0x105 if (banco!=bancos-1)

#define M1280x1024x256 0x107 limite=32768; /* 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; /* pintar */

existe_modo (unsigned), /* Comprobar si un modo es soportado */ }

getbest256 (void); /* Obtener mejor modo de 256c */

void setbank (ConmutaBanco, 0);

setbank (long, unsigned), /* Conmutar banco de memoria */ printf ("Modo de %dx%dx256 con %d Kb\n\n", max_x, max_y, vram);

setmode (unsigned, long *, /* Establecer modo VESA */ }

unsigned *),

getinfo (unsigned, /* Obtener información del modo */

unsigned *, /* COMPROBAR QUE EXISTE SOPORTE VESA */

unsigned *, unsigned *, unsigned *);

unsigned testvesa(void)

/* DEMOSTRACION */ struct REGPACK r;

char far *mem;

void main() unsigned vesa;

struct REGPACK r; mem = farmalloc (256L);

long r.r_es = FP_SEG (mem); r.r_di = FP_OFF (mem);

ConmutaBanco; /* dirección FAR del conmutador de banco */ r.r_ax = 0x4F00; intr (0x10, &r);

unsigned 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 103

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 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.


ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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:

MOV BX,40h ; meter carácter AX en el buffer del teclado


MOV DS,BX
CLI ; evitar conflictos con interrupciones
MOV BX,DS:[1Ch] ; puntero a la cola del buffer
MOV CX,BX
ADD CX,2 ; apuntar CX al siguiente dato
CMP CX,DS:[82h] ; más allá del fin del buffer
JB no_desb
MOV CX,DS:[80h] ; inicio de la cola circular
no_desb: CMP CX,DS:[1Ah] ; puntero al inicio del buffer
JE fin_rutina ; ZF = 1 --> buffer lleno
MOV DS:[BX],AX ; introducir carácter ASCII (AL) en el buffer
MOV DS:[1Ch],CX ; actualizar puntero al final del buffer
CMP SP,0 ; ZF=0 (SP siempre <> 0) --> buffer no lleno
fin_rutina: STI

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 ; permitir interrupción periódica


PUSH AX ; preservar registros modificados
IN AL,60h ; código de la tecla pulsada
PUSHF ; preparar la pila para IRET
CALL CS:anterior_int9 ; llamar a la INT 9 original
⋅ ⋅ ⋅ ; hacer algo con esa tecla
POP AX ; restaurar registros modificados
IRET ; 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: STI
PUSH AX
IN AL,60h ; código de la tecla pulsada
CMP AL,tecla ; ¿es nuestra tecla?
JNE fin ; no
PUSH AX ; vamos a «manchar» AX
IN AL,61h
OR AL,10000000b
OUT 61h,AL
AND AL,01111111b
OUT 61h,AL ; señal de reconocimiento enviada
POP AX ; AL = tecla pulsada
⋅ ⋅ ⋅ ; gestionarla
MOV AL,20h
OUT 20h,AL ; EOI al 8259
POP AX ; AX del programa principal
IRET ; volver al programa principal
fin: POP AX ; AX del programa principal
JMP 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 ; breve ventana para interrupciones


PUSH AX
CALL espera
MOV AL,0ADh
OUT 60h,AL ; inhibir teclado
CALL espera
IN AL,60h ; ¿tecla?
STI ; permitir rápidamente interrupciones
... ; procesar tecla y enviar EOI al 8259
CALL espera
MOV AL,0AEh
OUT 60h,AL ; desinhibir teclado
POP AX
IRET ; no merece la pena hacer STI

espera: PUSH AX
PUSH CX
MOV CX,995 ; constante para 15 ms
CLI
testref: IN AL,61h
AND AL,10h ; método válido solo en AT
CMP AL,AH
JZ testref
MOV AH,AL
IN AL,64h ; registro de estado del 8042
TEST AL,2 ; ¿buffer de entrada lleno?
LOOPNZ testref ; así es
POP CX
POP AX
RET

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 AX,1200h
INT 16h ; invocar función teclado expandido
CMP AX,1200h
JE no_expandido ; función no soportada
JMP 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
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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 │ byte 0: 0 para partición inactiva, 80h en la de arranque. │

offset 1BEh del sector (al principio │ byte 1: cabeza donde comienza la partición. │

está el código ejecutable); cada │ byte 2: bits 0 al 5: sector de inicio de la partición; 6, 7: parte alta del │

partición de las 4 posibles ocupa 16 │ número de cilindro. │


│ byte 3: parte baja del número de cilindro de inicio de la partición. │
bytes; al final de las cuatro está la
│ byte 4: tipo de partición, las más comunes son 0: No usada; 1: DOS-12 (FAT │
marca 0AA55h, ubicada en el offset
│ 12 bits); 4: DOS-16 (FAT 16 bits); 5: DOS Extendida; 6:BIGDOS (más │
1FEh, que indica que la tabla es │ de 32Mb); 7: OS/2 HPFS ó WinNT NTFS; 0Ah: OS/2 Boot Manager; 0Bh: │
válida. Los 16 bytes que la forman │ 32-bit FAT Win95 (0Ch con LBA); 0Eh y 0Fh (como 06 y 05 pero con │
se interpretan como indica el cuadro │ LBA); 81h Linux; 82h Linux swap; 83h: Linux native; 0A5h: FreeBSD │
de la derecha: │ o BSD/386; 0F2h: partición secundaria (no estudiada en este libro). │
│ 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 3 (8 bytes): Identificación del sistema (ej., "IBM 3.3") │
│ 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 │
│ 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). El valor de este campo │
│ depende de la posición relativa que ocupe la partición dentro del disco duro (será 0 en los │
│ disquetes), este valor ha de sumarse al del número de sector del DOS antes de traducirlo a │
│ un número de sector de la BIOS. │
│ offset 32 (2 palabras): 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 ", aunque tanto │
│ 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 " o "FAT16 ". │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Formato del SECTOR DE ARRANQUE


ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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 Valor Interpretación


0 FFD El disco es de tipo 0FDh (despreciar restantes
bits)
1 FFF Entrada no utilizada
2 003 El siguiente cluster del fichero A es el 3
3 005 El siguiente cluster del fichero A es el 5
4 FF7 Cluster defectuoso
5 006 El siguiente cluster del fichero A es el 6
6 FFF Este es el último cluster del fichero A
7 013 El siguiente cluster del fichero B es 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.

; ************ Escribir un elemento en una FAT de 12 bits POPF

; Entrada: AX = posición de dicho elemento JC poke_fat_imp

; DS:BX = FAT completamente cargada en memoria AND AX,1111000000000000b ; preservar la otra entrada

; DX = nuevo valor de dicho elemento JMP poke_fat_ok

poke_fat_imp: AND AX,0000000000001111b ; preservar la otra entrada

poke_fat PROC PUSH CX

PUSH AX ; preservar registros MOV CL,4

PUSH BX SHL DX,CL ; colocarlo: 4 bits a la izda

PUSH DX POP CX

ADD BX,AX ; BX = BX + cluster poke_fat_ok: OR AX,DX ; «mezclar»

SHR AX,1 ; AX = cluster / 2 MOV [BX],AX ; nuevo valor en la FAT

PUSHF ; CF = 1 si impar POP DX

ADD BX,AX ; BX = BX + cluster * 1,5 POP BX

MOV AX,[BX] ; AX = palabra con dato 12 bits POP AX


ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

RET ; retorno sin alterar registros ; ************ Leer un elemento de una FAT de 12 bits

poke_fat ENDP ; Entrada: AX = posición de dicho elemento

; DS:BX = FAT completamente cargada en memoria

; Salida: DX = valor de dicho elemento

peek_fat PROC

PUSH AX ; preservar registros

PUSH BX

ADD BX,AX ; BX = BX + cluster

SHR AX,1 ; AX = cluster / 2

PUSHF ; CF = 0 si par

ADD BX,AX ; BX = BX + cluster * 1,5

MOV DX,[BX]

POPF

JNC peek_fat_par

PUSH CX

MOV CL,4

SHR DX,CL ; DX=DX/16: si DX=xyz0, DX=0xyz

POP CX

peek_fat_par: AND DH,00001111b ; borrar posible dígito izdo

POP BX

POP AX

RET ; retornar sólo DX modificado

peek_fat 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 │ │ la etiqueta de volumen │
│ offset 24 (2 bytes): (año-1980)*512 + mes*32 + día │ │ 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).
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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):

┌───────────────────────────────────────────────────────────────────────────┐ El DOS convierte


│ offset 0 DW bytes_por_sector │ internamente el BPB en DPB (Drive
│ offset 2 DB sectores_por_cluster │ Parameter Block), una estructura
│ offset 3 DW sectores_reservados_al_comienzo_del_disco │ similar con más información útil. Para
│ offset 5 DB número_de_FATs │ obtener el DPB de una unidad
│ offset 6 DW número_de_entradas_en_el_directorio_raíz │ determinada, puede utilizarse la
│ offset 8 DW número_total_de_sectores (0 con nº de sector de 32 bits) │ función 32h del DOS, Get Drive
│ offset 10 DB byte_descriptor_de_medio │ Parameter Block (indocumentada);
│ offset 11 DW numero_de_sectores_por_FAT │ la cadena de DPBs del DOS puede
│ -- A partir del DOS 3.0: │ recorrerse a partir del primer DPB
│ offset 13 DW sectores_por_pista │ (obtenido con la función 52h del
│ offset 15 DW número_de_cabezas │ DOS, Get List of Lists, también
│ offset 17 DD número_de_sectores_ocultos │ indocumentada).
│ -- A partir del DOS 4.0 (más bien DOS 3.31) │
│ offset 21 DD número_de_sectores (unidades con direccionamiento de │
│ sector de 32 bits) │
│ offset 25 DB 6 DUP (?) (6 bytes no documentados) │
│ 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, byte 4:Sectores por pista.
que indica el step rate (el tiempo de acceso cilindro- byte 5:Longitud del GAP entre sectores (normalmente 2Ah en
cilindro, a menudo es 0Dh = 3 ó 6 ms) y el head unload unidades de 5¼ y 1Bh en las de 3½).
time (normalmente, 0Fh = 240 ó 480 ms). byte 6:Longitud de sector (ignorado si el byte 3 no es 0).
byte 1:Es el byte 2 del comando 'Specify': los bits 7..1 indican el byte 7:Longitud del GAP 3 al formatear (80 en 5¼ y 3½-DD, 84 en
head load time (normalmente 01h = 2 ó 4 ms) y el bit 0 5¼-HD y 108 en 3½-HD).
suele estar a 0 para indicar modo DMA. byte 8:Byte de relleno al formatear (normalmente 0F6h).
byte 2:Tics de reloj (pulsos de la interrupción 8) que transcurren tras byte 9:Tiempo de estabilización del cabezal en ms.
el acceso hasta que se para el motor. byte 10:Tiempo de aceleración del motor (en unidades de 1/8 de
byte 3:Bytes por sector (0=128, 1=256, 2=512, 3=1024). 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
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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

; * MIRA.ASM - Utilidad para visualizar ficheros de texto. * LEA DX,fichnom ; dirección para el «input»

; * * MOV BYTE PTR [fichnom],60 ; no más de 60 caracteres

; ******************************************************************** MOV AH,10 ; función de entrada de teclado

INT 21h ; llamar al DOS

mira 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

inicio:

LEA DX,input_txt ; mensaje LEA DX,fichnom+2 ; offset a cadena ASCIIZ nombre


103 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 imprime: MOV DL,[BX] ; carácter a carácter

MOV AH,2 ; ir llamando al servicio 2 del

trocito: 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

cerrar: 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

error: 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

; ------------ datos y variables

handle DW 0 ; handle de control del fichero

input_txt DB 13,10,"Nombre del fichero: $"

fallo_txt DB 13,10,"*** Error ***",13,10,10,"$"

fichnom DB 80 DUP (0) ; buffer para leer desde el teclado

buffer DB 2048 DUP (0) ; " " " " el disco

mira ENDS

END 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 -
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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» desactivada. if (!strcmp(strupr(argv[argc-1]),"/I")) cb++; /* parámetro /I */

*/

/* sp^=cb;

*/ if (argc>cb+1)

/******************************************************************** unidad=(*argv[1] | 0x20)-'a';

/ else

unidad=getdisk();

#include <string.h> preservar_pantalla (&scrbuf,&modo,&pag,&cur_x,&cur_y,&scr_ok,&cb);

#include <dos.h> if (!existe_vga()) salida_error (1);

#include <dir.h> if ((boot=farmalloc(2048L))==NULL) salida_error (2);

#include <conio.h> if (leesect(unidad, 1, 0L, boot)!=0) salida_error (3);

#include <alloc.h> if (!info_disco (boot, &numsect, &numclusters, &tamcluster,

&inifat, &sectfat, &tamfat, &tsect)) salida_error(5);

#define C_PACIENCIA 78 /* colores */ if ((fat=farmalloc(tamfat))==NULL) salida_error (2);

#define C_PACIENCIAM 9 if ((bitfat=farmalloc((long)numclusters))==NULL) salida_error (2);

#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 */ analiza_fat (bitfat, numclusters, &clusters_datos,

#define C_LEYENDA 4 /* VGA azul claro */ &clusters_malos);

#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 */ clusters_datos, clusters_malos);

leyendas (numclusters, clusters_datos, clusters_malos);

#define MODO 0x12 /* modo de vídeo */ prepara_punto();

#define MIN_X 2 marco();

#define MAX_X 637 /* ventana de dibujo de FAT */ while (kbhit()) getch();

#define MIN_Y 152 pinta_fat (bitfat, numclusters);

#define MAX_Y 477 if (!getch()) getch();

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,

informe_disco(), leyendas(), marco(), int *cx, int *cy, int *scr_ok, int

pinta_fat(), prepara_punto(), punto(), prepara_paleta(); *colorbits)


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];

movedata(0xb000,0,FP_SEG(*scrbuf),FP_OFF(*scrbuf),4096); static unsigned char dac[][3] = {

else /* R G B */

movedata(0xb800,peek(0x40,0x4e), { 0, 0, 0}, /* VGA negro */

FP_SEG(*scrbuf),FP_OFF(*scrbuf),4096); {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 */

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)

movedata(FP_SEG(scrbuf),FP_OFF(scrbuf),0xb000,0,4096); r.r_es=FP_SEG(paleta); r.r_dx=FP_OFF(paleta);

else r.r_ax=0x1002; intr (0x10, &r); /* establecer paleta y borde */

movedata(FP_SEG(scrbuf),FP_OFF(scrbuf),

0xb800,peek(0x40,0x4e),4096); 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);

} 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() /* devolver condición cierta si hay VGA */ if (modo>1) cx=25; else cx=4;

{ escribir (cx, 12, C_PACIENCIA,"╔══════════════════════════════╗");

struct REGPACK r; escribir (cx, 13, C_PACIENCIA,

sp?"║ ANALIZANDO AREAS DEL SISTEMA ║":

r.r_ax=0x1A00; intr (0x10, &r); "║ PROCESSING SYSTEM AREAS ║");

return ((r.r_ax & 0xFF)==0x1A); escribir (cx+32, 13, C_PACIENCIAM, "█");

} 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,

poke (0x40, 0x10, peek (0x40, 0x10) & 0xFFCF | 0x20); int sectfat, int tsect)

/* establecer modo 640x480x16 */ {

r.r_ax=MODO; intr (0x10, &r); int parte1, parte2, parte3;

}
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

parte1=(sectfat+2)/3; nclus = nsect / boot[0x0D];

parte2=(sectfat-parte1)/2; if (nclus>65535L) salida_error (4);

parte3=sectfat-parte1-parte2; /* la FAT se carga de tres veces */ *numclusters = nclus;

if (parte1) *tamcluster = (*tamsect) * boot[0x0D];

if (leesect(unidad, parte1, inifat, fat)!=0) salida_error (3); *bytesfat=(long) (*sectfat) * (*tamsect);

if (parte2) return (1); /* 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) {

void escribir (int cx, int cy, int color, unsigned char *cadena) case 1: printf (sp?"\n Este programa requiere adaptador VGA.\n":

{ "\n This program requires VGA adaptor.\n");

struct REGPACK r; break;

unsigned char *p, pagina; case 2: printf (sp?"\n Memoria insuficiente.\n":

unsigned char far *cursor_x; "\n Insufficient memory.\n");

break;

pagina = peekb(0x40, 0x62); case 3: printf (sp?"\n Unidad incorrecta, no preparada, HPFS o

r.r_ax=0x200; r.r_bx = (pagina << 8); r.r_dx=0xFF00; de red.\n":

"\n Incorrect, not ready, HPFS or network

intr (0x10, &r); /* eliminar cursor de la pantalla */ drive.\n");

break;

cursor_x = MK_FP (0x40, 0x50 + (pagina <<1) ); case 4: printf (sp?"\n Sólo soportados sistemas FAT12/FAT16.\n":

poke (0x40, 0x50 + (pagina << 1), (cy << 8) + cx); "\n Only supported FAT12/FAT16 filesystems.\n");

break;

p=cadena; case 5: printf (sp?"\n Sector de arranque dañado, imposible

while (*p) { informar.\n":

r.r_ax=0x900 | *p; r.r_bx = (pagina << 8) | color; r.r_cx=1; "\n Boot record damaged, impossible to analyze

intr (0x10, &r); drive.\n");

(*cursor_x)++; break;

p++; }

} exit (error);

} }

int info_disco (unsigned char *boot, unsigned long *numsect, void dec2str (char *cadena, unsigned long num, int longitud)

unsigned *numclusters, int *tamcluster, {

unsigned long *inifat, int *sectfat, unsigned long div;

unsigned long *bytesfat, int *tamsect) int i, coma;

unsigned long nclus, nsect; switch (longitud) {

*tamsect = boot[0x0B] | ((int) boot[0x0C] << 8); case 13: coma=1; div=1000000000L; break;

*numsect = boot[0x13] | ((unsigned long) boot[0x14] << 8); case 6: coma=2; div=10000L; break;

if (!*numsect) *numsect=(long) boot[0x20] | (long) boot[0x21]<<8 }

| (long) boot[0x22]<<16 | (long) boot[0x23]<<24; for (i=0; i<longitud; i++, div/=10L) {

if (i==coma) {

*sectfat=boot[0x16] | (int) boot[0x17] << 8; cadena[i]='.'; coma+=4; i++;

*inifat=boot[0x0E] | (int) boot[0x0F] << 8; }

cadena[i]=num/div+'0'; num%=div;

if ((*tamsect<32) || (numsect==0) || (boot[0x0D]==0) || }

(*sectfat==0)) cadena[i]=0;

return (0); /* retorno con error */ while (((*cadena=='0') || (*cadena=='.')) && (*(cadena+1)))

else { *cadena++=' ';

nsect=*numsect - (*inifat) - (*sectfat) * boot[0x10] - }

(boot[0x11] | (int) boot[0x12] << 8) * 32 /

*tamsect;
103 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

void porc2str (char *cadena, int num) else

{ if (elemento == C_ERRONEA) (*clusters_malos)++;

cadena[0]=num/10000 | '0'; num%=10000; *clusters_datos=numclusters-libres-(*clusters_malos);

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 | '0'; void informe_disco (int unidad, unsigned char *boot,

unsigned long numsect,

if (cadena[0]=='0') { unsigned datos, unsigned malos)

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, numcaras = boot[0x1A] | (int) boot[0x1B] << 8;

unsigned char huge *bitfat, unsigned numclusters) sectfat = boot[0x16] | (int) boot[0x17] << 8;

{ sectcluster = boot[0x0D];

unsigned int fat16=0, elemento, pos;

unsigned i; escribir (0, 0, C_CABECERA, sp?

"───────── DMAP 2.1 (c) Julio 1994 CiriSOFT ──────── Informe

if (numclusters>4084) fat16++; unidad A: ─────────":

"───────── DMAP 2.1 (c) July 1994 CiriSOFT ─────────── Drive A:

for (i=2; i<numclusters+2; i++) report ─────────");

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 */

else escribir (0, 1, C_TITULOS, sp?"ID sistema: ":"System ID: ");

if (elemento == 0xFFF7) for (i=3; i<11; i++) id[i-3]=boot[i]; id[8]=0;

bitfat[i-2]=C_ERRONEA; /* cluster defectuoso */ escribir (15, 1, C_INFO, id);

else escribir (0, 2, C_TITULOS, sp?"Byte de Medio: ":"Media byte: ");

bitfat[i-2]=C_OCUPADA; /* 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;

else /* FAT12 */ { escribir (19, 2, C_INFO, id);

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);

else escribir (0, 4, C_TITULOS, sp?"Cilindros: ":"Cylinders: ");

elemento = fat[pos] | ((fat[pos+1L] & 0x0F) << 8); dec2str (id, (numsect/sectpista/numcaras*256+255) >> 8, 6);

if (!elemento) escribir (15, 4, C_INFO, id);

bitfat[i-2]=C_LIBRE; /* cluster libre */ escribir (0, 5, C_TITULOS, sp?"Caras: ":"Sides: ");

else dec2str (id, numcaras, 6);

if (elemento == 0xFF7) escribir (15, 5, C_INFO, id);

bitfat[i-2]=C_ERRONEA; /* cluster defectuoso */ escribir (0, 6, C_TITULOS, sp?"Pistas: ":"Tracks: ");

else dec2str (id, numsect/sectpista, 6);

bitfat[i-2]=C_OCUPADA; /* 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, escribir (26, 2, C_TITULOS,

unsigned *clusters_datos, unsigned *clusters_malos) sp?"Sectores/cluster:":"Sectors/cluster: ");

{ dec2str (id, sectcluster, 6);

unsigned i, elemento, libres=0; escribir (43, 2, C_INFO, id);

escribir (26, 3, C_TITULOS, sp?"Sectores/FAT: ":"Sectors/FAT:

for (i=0; i<numclusters; i++) ");

if ((elemento=bitfat[i])==C_LIBRE) dec2str (id, sectfat, 6);

libres++; escribir (43, 3, C_INFO, id);


ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

escribir (26, 4, C_TITULOS, sp?"Número de FATs:":"Number of escribir (55, 8, C_LEYENDA, sp?"Area defectuosa (":"Damaged area

FATs:"); (");

dec2str (id, boot[0x10], 6);

escribir (43, 4, C_INFO, id); porc=malos*10000L/numclusters+5;

escribir (26, 5, C_TITULOS, sp?"Sectores reserv.:":"Reserved porc2str (cad, porc); escribir (sp?72:69, 8, C_LEYENDA, cad);

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:"); {

dec2str (id, boot[0x11] | (int) boot[0x12] << 8, 6); int x, y;

escribir (43, 6, C_INFO, id);

escribir (52, 1, C_TITULOS, sp?"Sectores: ":"Sectors: for (y=MIN_Y; y<=MAX_Y; y++) {

"); 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:");

dec2str (id, (long)numclusters*tamsect*sectcluster, 13); void pinta_fat (unsigned char huge *bitfat, unsigned numclusters)

escribir (67, 3, C_INFO, id); {

escribir (52, 4, C_TITULOS, sp?"Bytes libres:":"Bytes free: "); unsigned long factor;

dec2str (id, (((long)numsect/sectcluster-datos-malos) unsigned x, y,

*tamsect*sectcluster), 13); ant_pixel_l=0, ant_pixel_h=0, coord_x=2, coord_y=MIN_Y*80;

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 {

escribir (52, 6, C_TITULOS, sp?"Bytes erróneos:":"Bytes damaged: 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 } /* SI --> posición del primer cluster */

strcpy (id, "────────────────"); proc_fat: asm {

for (i=0; i<5; i++) mov al,es:[bx] }

escribir (i<<4, 7, C_CABECERA, id); cuenta: asm {

} inc bx

cmp al,es:[bx]

loope cuenta

void leyendas (unsigned numclusters, unsigned datos, unsigned malos) mov di,bx

{ sub di,si /* DI --> número de cluster hasta donde avanzar

int porc; */

char *cad="100,0%)"; 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 /* DI:SI producto parcial */

porc2str (cad, porc); escribir (sp?19:18, 8, C_LEYENDA, cad); mul word ptr [factor+2] /* DX:AX segundo producto parcial

escribir (28, 8, C_LIBRE, "██"); */

escribir (31, 8, C_LEYENDA, sp?"Area libre (":"Free area ("); add ax,di

adc dx,0 /* DX:AX:SI producto */

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


103 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

shl si,1 dec_msb: asm {

rcl ax,1 pop si

rcl dx,1 /* DX:AX = DX:AX:SI / 16384 = pixel 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 /* SI:DI = nº de pixels a pintar pop bx

*/ mov ax,bp

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 fin_proc: asm {

mov cl,bl /* BX = cx, BP = cy*80 */ pop es; pop di; pop si; pop dx; pop cx; pop bx; pop ax;

and cl,7 }

mov ah,80h }

shr ah,cl /* AH = bit a pintar en su sitio */

push bx

mov cl,3 void prepara_punto()

shr bx,cl {

add bx,bp /* BX = cy*80+cx/8 */ asm {

push si push ax; push dx /* preparar la VGA para punto() */

mov si,80 mov dx,3CEh

out dx,ax } mov ax,205h /* registro de modo (5): escr. 2 lect. 0 */

pinta_mas: asm { out dx,ax

mov cl,[bx] /* acceso en lectura */ mov ax,3 /* cambiar AH para hacer OR/XOR/AND */

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 /* siguiente pixel en el eje X */ asm { /* rutina rápida sólo para modos de 640x???x16 */

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 /* BX = cx, DX = cy */

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 /* DX = cy * 16 */

shr bx,cl mov ax,dx

add bx,bp /* BX = cy*80+cx/8 */ shl ax,1

push ax shl ax,1 /* CX = cy * 64 */

mov ah,1 add dx,ax /* DX = cy * 80 */

int 16h mov al,bl

pop ax dec cl

jz pinta_mas shr bx,cl /* CL = 3 */

pop si; pop bx; pop bp; pop ds; pop cx; pop bx; pop si; add bx,dx /* BX = cy * 80 + cx / 8 */

jmp fin_proc } /* tecla pulsada */ and al,7


ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

mov cl,al push bp; push ds;

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] /* acceso en lectura */

mov ax,color

mov [bx],al

pop dx; pop cx; pop bx; pop ax;

pop ds

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 si; push di;

push bp; push ds;

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 */

pushf

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

else /* unidades con menos de 65536 sectores */

asm {

push ax; push bx; push cx; push dx; push si; push di;
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 */

pushf

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

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:
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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)
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS 103

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 DB 55h, 0AAh


DB 32 ; 16 Kb de ROM
JMP inicio
...
...
fin_bios ... ; 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 55h, 0AAh


DB 64 ; 32 Kb de ROM-BASIC
JMP inicio
DB 5 ; longitud del siguiente comando
DB "BASIC"
JMP basic ; salto al comienzo del BASIC
DB 6 ; longitud del siguiente comando
103 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

DB "BASICA"
JMP basic ; salto al comienzo (el mismo del BASIC)
DB 0 ; no más comandos
basic ...
...
fin_bios ... ; 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 DB 55h, 0AAh


DB 48 ; 24 Kb de contabilidad
RETF ; nada que hacer
DB 0AAh, 55h ; esto es un listado BASIC
... ; aquí, el programa
fin_bios ... ; 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 4D 5A 40 00 03 00 01 00-20 00 00 00 FF FF 04 00 MZ@..... .......


0010 00 02 00 00 00 00 02 00-3E 00 00 00 01 00 FB 30 ........>.....{0
0020 6A 72 00 00 00 00 00 00-00 00 00 00 00 00 00 00 jr..............
0030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 05 00 ................
0040 02 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
. . . . . . . . . . . . . . . . .
01F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0200 0D 0A 54 65 78 74 6F 20-61 20 69 6D 70 72 69 6D ..Texto a imprim
0210 69 72 0D 0A 24 00 00 00-00 00 00 00 00 00 00 00 ir..$...........
0220 1E 33 C0 50 B8 00 00 8E-D8 BA 00 00 B4 09 CD 21 .3@P8...X:..4.M!
0230 CB 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 K...............
0240 70 69 6C 61 70 69 6C 61-70 69 6C 61 70 69 6C 61 pilapilapilapila
. . . . . . . . . . . . . . . . .
0430 70 69 6C 61 70 69 6C 61-70 69 6C 61 70 69 6C 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 al
tamaño del mismo. En el ejemplo es 0 (el programa se conforma con lo que ocupa en disco).

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 AH,62h
INT 21 ; obtener dirección del PSP en BX
MOV ES,BX
MOV ES,ES:[2Ch] ; dirección del espacio de entorno
MOV AH,49h ; función para liberar bloque
INT 21h ; 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 AX,ES:[2Ch] ; dirección del espacio de entorno


DEC AX ; apuntar a su MCB
MOV ES,AX
MOV WORD PTR ES:[1],0 ; 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 MS-
DOS 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. - Información sobre la memoria del sistema.

Tipo Ubicación Tamaño PID 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 Operat.
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 <Nadie>
Programa 035A-03EE 2.384 035A MATAGAME Entorno 0515-0544 768 0466 COMMAND
Entorno 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 <Nadie>
Programa 0439-0492 1.440 0439 MAPAMEM Sistema A000-D800 229.392 0008
Libre 0494-9FFE 636.592 0000 <Nadie> Sistema D802-E159 38.272 0008
Sistema A000-DEFF 258.048 0007 Libre E15B-E17F 592 0000 <Nadie>
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 <Nadie>
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 <Nadie>
Sistema F000-F5FF 24.576 0007
Sistema F601-F6FF 4.080 0008

; ********************************************************************

; * * ORG 100h ; programa tipo COM

; * MAPAMEM 2.2 - Utilidad para listar los bloques de memoria. *

; * * mapa PROC

; ******************************************************************** MOV BX,tam_mapmem ; tamaño de este programa

MOV AH,4Ah ; modificar memoria asignada

INT 21h ; ejecutar función del DOS

mapamem SEGMENT LEA DX,cabecera_txt

ASSUME CS:mapamem; DS:mapamem CALL print


LA GESTIÓN DE MEMORIA DEL DOS 143

MOV AH,52h ; función "Get List of Lists" MOV BX,64

INT 21h MUL BX

MOV AX,ES:[BX-2] ; segmento del primer M.C.B. DEC AX

MOV ES,AX MOV BX,AX

DEC AX POP AX

CALL print16hex ; imprimir dónde acaba el DOS

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 ; pasar párrafos a bytes INT 21h ; fin del programa

MOV CL,8+16 mapa ENDP

CALL print_32 ; imprimir tamaño zona del DOS

LEA DX,cabx_txt imprime_tipo PROC

CALL print LEA SI,tabla_tipos

otro_mcb: MOV BX,WORD PTR ES:[1] ; P.I.D. (Process ID) MOV AL,tipo

MOV DL,0 ; supuesta zona libre (tipo DL) XOR AH,AH

CMP BX,0 SHL AX,1 ; AX = tipo * 2

JE tipo_ok ; lo es (PID = 0) ADD SI,AX

MOV DL,1 ; supuesto bloque XMS de DR-DOS MOV DX,[SI] ; dirección del mensaje

CMP BX,6 CALL print ; imprimirlo

JE tipo_ok ; lo es (PID = 6) RET

MOV DL,2 ; supuesta zona del sistema imprime_tipo ENDP

PUSH DS

MOV DS,BX imprime_rango PROC

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 ; imprimir inicio del bloque

CMP AX,20CDh MOV AL,'-'

JE no_tipo_sys ; es un PSP CALL printAL ; imprimir guión

CMP AX,27CDh MOV AX,ES

JNE tipo_ok ; no es un PSP ADD AX,ES:[3]

no_tipo_sys: MOV DL,3 ; supuesta zona de programa CALL print16hex ; imprimir final del bloque

MOV AX,ES MOV AX,ES:[3]

INC AX MOV DX,16

CMP BX,AX ; ¿PID=MCB+1? MUL DX ; pasar bytes a párrafos

JE tipo_ok ; lo es MOV CL,8+16

MOV DL,4 ; supuesta zona de entorno CALL print_32 ; imprimir tamaño del bloque

CMP CX,AX RET

JE tipo_ok imprime_rango ENDP

INC DL ; por eliminación zona de datos

tipo_ok: MOV pid,BX imprime_pid PROC

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 ; retorno de carro MOV AL,' '

CALL printAL CALL printAL

MOV AL,10 ; salto de línea CALL printAL

CALL printAL RET

MOV AX,ES ; MCB ya tratado imprime_pid ENDP

ADD AX,ES:[3] ; tamaño del bloque

INC AX ; apuntar al siguiente MCB imprime_nombre PROC

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 ; no, no era el último CMP tipo,0 ; ¿bloque libre?

JNE no_libre ; no

PUSH AX CALL print ; imprimirlo

INT 12h JMP nombre_ok


143 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

no_libre: 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

nombre_listo: MOV BX,7 ; nombre de ES:BX+1 a ES:BX+9 RET

MOV CX,8 ; máximo tamaño del nombre print8hex ENDP

otra_letra: INC BX

MOV AL,ES:[BX] ; carácter del nombre print16hex PROC ; imprimir palabra hexadecimal (AX)

AND AL,AL PUSH AX

JZ nombre_ok ; es cero: fin del nombre MOV AL,AH

CMP AL,' ' CALL print8hex ; imprimir parte alta

JAE cod_normal POP AX

MOV AL,'?' ; evitar códigos raros en DOS < 4.0 CALL print8hex ; imprimir parte baja

cod_normal: CALL printAL ; imprimirlo RET

LOOPNZ otra_letra ; a por otro (8 como máximo) print16hex ENDP

nombre_ok: POP ES

RET ; -------------------------- PRINT-32 v3.1 --------------------------

imprime_nombre ENDP ;

; Subrutina para imprimir nº decimal de 32 bits en DXAX formateado.

print PROC ; imprimir cadena en DS:DX con ;

PUSH AX ; el final delimitado por un '$' ; 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 = 1 --> se imprimirán signos separadores de millar

POP AX ; bits 0-3 = nº total de dígitos (incluyendo separadores de

RET ; millar y parte fraccional)

print ENDP ; bits 5-7 = nº de dígitos de la parte fraccional (cuantos

; dígitos de DXAX, empezando por la derecha, se

printAL PROC ; imprimir carácter en AL ; consideran parte fraccional, e irán precedidos

PUSH AX ; del correspondiente separador)

PUSH DX ; registros usados preservados ;

MOV AH,2 ; función de impresión del DOS ; Salidas:

MOV DL,AL ; carácter a imprimir ; nº impreso, ningún registro modificado.

INT 21h ; llamar al sistema ;

POP DX ; * Ejemplo, si DXAX=9384320 y CL=010 1 1011

POP AX ; recuperar registros ; se imprimirá ( '_' representa un espacio en blanco ): __93.843,20

RET ; retornar ;

printAL ENDP ; 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 PROC ; 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 PROC

ADD AL,'A'-'9'-1 ; lo es PUSHF

no_sup9: CALL printAL ; imprimir dígito hexadecimal PUSH AX ; preservar registros

POP AX ; restaurar AX PUSH BX

RET PUSH CX

print4hex ENDP PUSH DX

PUSH SI

print8hex PROC ; imprimir byte hexadecimal en AL PUSH DI

PUSH CX PUSH DS

PUSH AX PUSH ES

MOV CL,4 MOV BX,CS

SHR AL,CL ; pasar bits 4..7 a 0..3 MOV DS,BX


LA GESTIÓN DE MEMORIA DEL DOS 143

MOV ES,BX SUB CX,ent_frac_pr32

MOV formato_pr32,CL ; byte del formato de impresión ADD CX,3

MOV BX,OFFSET tabla_pr32 MOV SI,final_pr32

MOV CX,10 MOV DI,SI

digit_pr32: PUSH CX INC DI

PUSH AX REP MOVSB ; cadena arriba (hacer hueco)

PUSH DX MOV AL,millares_pr32

XOR DI,DI MOV [DI],AL ; poner separador de millares

MOV SI,1 ; DISI = 1 INC final_pr32

DEC CX ; CX - 1 MOV ent_frac_pr32,SI ; usar la variable como puntero

JCXZ hecho_pr32 SUB SI,OFFSET tabla_pr32

factor_pr32: SAL SI,1 CMP SI,3

RCL DI,1 ; DISI * 2 JAE entera_pr32 ; próximo separador

MOV DX,DI poner_pr32: MOV BX,final_pr32

MOV AX,SI MOV BYTE PTR [BX+1],"$" ; delimitador fin de cadena

SAL SI,1 MOV BX,OFFSET tabla_pr32

RCL DI,1 MOV principio_pr32,BX ; inicio de cadena

SAL SI,1 limpiar_pr32: MOV AL,[BX]

RCL DI,1 ; DISI * 8 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

hecho_pr32: POP DX CMP AL,fracc_pr32

POP AX ; CX se recuperará más tarde JNE acabar_pr32

MOV CL,0FFh MOV BYTE PTR [BX-1],'0' ; reponer 0 antes de la coma

rep_sub_pr32: INC CL DEC principio_pr32

SUB AX,SI acabar_pr32: MOV AL,formato_pr32 ; imprimir

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 ; DX = offset 'principio'

MOV [BX],CL AND AX,AX

POP CX ; CX se recupera ahora JNZ format_pr32 ; longitud solicitada

INC BX MOV DX,principio_pr32 ; longitud obtenida del número

LOOP digit_pr32 ; próximo dígito del número format_pr32: CALL print ; imprimir cadena en DS:DX

STD ; transferencias hacia atrás POP ES

DEC BX ; BX apunta al último dígito POP DS ; restaurar todos los registros

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 ; AL = nº de decimales POP BX

AND AL,AL POP AX

JZ no_frac_pr32 ; ninguno POPF

MOV CL,AL RET ; salida del procedimiento

XOR CH,CH blanco_pr32: MOV BYTE PTR [BX],' ' ; quitar 0 / separador millares

MOV SI,final_pr32 INC BX ; sustituyendo por espacios

MOV DI,SI INC principio_pr32

INC DI CMP BX,final_pr32

REP MOVSB ; cadena arriba (hacer hueco) JB limpiar_pr32

INC final_pr32 MOV DX,BX ; es el número 0.000.000.00X

MOV AL,fracc_pr32 JMP SHORT acabar_pr32 ; imprimir

MOV [DI],AL ; separador de parte fraccional formato_pr32 DB 0

MOV ent_frac_pr32,SI ; indicar nueva frontera DB 5 DUP (' ') ; área de trabajo

no_frac_pr32: MOV AL,formato_pr32 tabla_pr32 DT 0

TEST AL,16 ; interpretar el formato DW 0,0

JZ poner_pr32 ; imprimir como tal millares_pr32 EQU '.' ; separador de millares

entera_pr32: MOV CX,final_pr32 ; añadir separadores de millar fracc_pr32 EQU ',' ; " parte fraccional
143 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

final_pr32 DW 0 ; offset último byte a imprimir ; ------------ Datos

principio_pr32 DW 0 ; " " primer " " "

ent_frac_pr32 DW 0 ; offset frontera entero-fracc. cabecera_txt LABEL BYTE

print_32 ENDP DB 13,10,"MAPAMEM 2.2"

DB 13,10," - Información sobre la memoria del sistema.",13,10,10

DB "Tipo Ubicación Tamaño PID Propietario",13,10

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-$"

cabx_txt DB " Sistema Operat.",13,10,"$"

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 "<Nadie>$"

tipo DB 0

pid DW 0

tam_mapmem EQU ($-OFFSET mapamem)/16+1 ; tamaño de MAPAMEM

mapamem ENDS

END 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
LA GESTIÓN DE MEMORIA DEL DOS 143

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 AX,352Fh ; obtener vector de INT 2Fh en ES:BX


INT 21h
MOV AX,ES
CMP AX,0
JE no_hay_XMS ; en DOS 2.x la INT 2Fh está indefinida
MOV AX,4300h ; chequear presencia de XMS
INT 2Fh ; interrupción Multiplex
CMP AL,80h
JE hay_XMS
JNE no_hay_XMS

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 AX,4310h ; preguntar dirección del controlador


INT 2Fh
MOV XMS_seg,ES ; almacenarla
MOV XMS_off,BX

donde XMS_seg y XMS_off es una estructura del tipo:

gestor_XMS LABEL DWORD


XMS_off DW 0
XMS_seg 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.

│ │
─ ─ ─ DFFFF ├─────────────┤ ┌─────────────┐
16 Kb │ 3 │ ½─ ─ ┐ │ ├─┐
─ ─ ─ DC000 ├─ ─ ─ ─ ─ ─ ─┤ │ ┌ ½─ ─ ─ ─ A└─┬───────────┘ ├─┐
│ 2 │ ½─ ─ ┤ │ ½─ ─ ─ ─ ─ B└─┬───────────┘ ├─┐
D8000 ├─ ─ ─ ─ ─ ─ ─┤ │½──────────────┤ ½─ ─ ─ ─ ─ ─ C└─┬───────────┘ ├─┐
│ 1 │ ½─ ─ ┤ │ ½─ ─ ─ ─ ─ ─ ─ D└─┬───────────┘ ├─┐
D4000 ├─ ─ ─ ─ ─ ─ ─┤ │ │ ½─ ─ ─ ─ ─ ─ ─ ─ E└─┬───────────┘ ├─┐
│ 0 │ ½─ ─ ┘ │ ½─ ─ ─ ─ ─ ─ ─ ─ ─ F└─┬───────────┘ │
D0000 ├─────────────┤ └ ½─ ─ ─ ─ ─ ─ ─ ─ ─ ─ G└─────────────┘

MARCO DE PÁGINA DE MEMORIA EXPANDIDA PÁGINAS DE MEMORIA EXPANDIDA ASIGNABLES


(PÁGINAS FÍSICAS) (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 compatibilidad-
las 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á programada-
pasa 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 MS-
DOS. 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 28h
IRET

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 ; FCB 0

; * * MOV WORD PTR [BX+8],CS

; * SHELL.ASM 1.0 - Demostración de carga de subprograma. * MOV WORD PTR [BX+0Ah],6Ch ; FCB 1

; * * MOV WORD PTR [BX+0Ch],CS

; ******************************************************************** LEA DX,nombre

MOV AX,4B00h

TAMTOT EQU 1024 ; este programa y su pila caben en 1 Kb. INT 21h ; cargar y ejecutar programa

PUSH CS

shell SEGMENT POP DS ; DS = CS

ASSUME CS:shell, DS:shell LEA DX,adios_txt

MOV AH,9

ORG 100h INT 21h ; mensaje de despedida

inicio: MOV AX,4C00h

MOV SP,TAMTOT ; redefinir la pila INT 21h ; terminar

MOV AH,4Ah

MOV BX,TAMTOT/16 nombre DB "C:\DOS\COMMAND.COM",0 ; programa a ejecutar

INT 21h ; redimensionar bloque memoria exec_info DB 22 DUP (0)

LEA DX,hola_txt hola_txt DB 13,10

MOV AH,9 DB "Estás dentro de SHELL.COM ...",13,10,"$"

INT 21h ; mensaje de bienvenida adios_txt DB 13,10

LEA BX,exec_info DB "... Acabas de abandonar SHELL.COM",13,10,"$"

MOV WORD PTR [BX],0

MOV WORD PTR [BX+2],80h ; PSP shell ENDS

MOV WORD PTR [BX+4],CS 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 Operat.
Sistema 0B5B-0CF1 6.512 0008
Programa 0CF3-0E1C 4.768 0CF3 COMMAND
Libre 0E1E-0E21 64 0000 <Nadie>
SUBPROCESOS, RECUBRIMIENTOS Y FILTROS 157

Entorno 0E23-0E52 768 0CF3 COMMAND


Entorno 0E54-0E6D 416 0E6F SHELL
Programa 0E6F-0EAE 1.024 0E6F SHELL
Datos 0EB0-0EC8 400 0ECA COMMAND
Programa 0ECA-0F72 2.704 0ECA COMMAND
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 <Nadie>
Sistema A000-D800 229.392 0008
Sistema D802-E159 38.272 0008
Libre E15B-E179 496 0000 <Nadie>
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 <Nadie>

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
SUBPROCESOS, RECUBRIMIENTOS Y FILTROS 157

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 <stdio.h>

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 21h ; escribir

; * * RET

; * MIN.ASM 1.0 - Filtro para poner en minúsculas ASCII Español. * escribe_salida ENDP

; * *

; ******************************************************************** pon_minusculas PROC

PUSH CX

segmento SEGMENT LEA BX,buffer

ASSUME CS:segmento, DS:segmento procesa_car: MOV AL,[BX]

CMP AL,'A'

STDIN EQU 0 JB car_ok

STDOUT EQU 1 CMP AL,128

JAE car8

ORG 100h CMP AL,'Z'

inicio: JA car_ok

CALL lee_entrada ; leer de STDIN OR AL,32

JCXZ fin_filtro ; en CX, bytes leídos car_ok: MOV [BX],AL

PUSHF INC BX

CALL pon_minusculas LOOP procesa_car

CALL escribe_salida ; escribir en STDOUT POP CX

POPF RET

JNC inicio car8: MOV AH,'ñ'

fin_filtro: MOV AX,4C00h ; CF = 1 si fin de fichero CMP AL,'Ñ'

INT 21h JE trad_ok

MOV AH,'ç'

lee_entrada PROC 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 ; leer MOV AH,'é'

MOV CX,AX CMP AL,'É'

RET JE trad_ok

lee_entrada ENDP MOV AH,AL

trad_ok: MOV AL,AH

escribe_salida PROC JMP car_ok

LEA DX,buffer pon_minusculas ENDP

MOV BX,STDOUT

MOV AH,40h buffer DB 512 DUP (?)


157 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

segmento ENDS

END inicio
PROGRAMAS RESIDENTES 161

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.
PROGRAMAS RESIDENTES 161

demores SEGMENT main: PUSH ES

ASSUME CS:demores, DS:demores MOV AX,3508h

INT 21h ; obtener vector de INT 8

ORG 100h MOV ant_int08_seg,ES

inicio: MOV ant_int08_off,BX

JMP main MOV AX,3510h

INT 21h ; obtener vector de INT 10h

controla_int08 PROC MOV ant_int10_seg,ES

PUSHF MOV ant_int10_off,BX

CALL CS:ant_int08 ; llamar al gestor normal de INT 8 POP ES

STI

CMP CS:in10,0 LEA DX,controla_int08

JNE fin_int08 ; estamos dentro de INT 10h MOV AX,2508h

INT 21h ; nueva rutina de INT 8

; Colocar aquí el proceso a ejecutar 18,2 veces/seg. LEA DX,controla_int10

; que puede invocar funciones de INT 10h MOV AX,2510h

fin_int08: INT 21h ; nueva rutina de INT 10h

IRET

controla_int08 ENDP PUSH ES

MOV ES,DS:[2Ch] ; dirección del entorno

controla_int10 PROC MOV AH,49h

INC CS:in10 ; indicar entrada en INT 10h INT 21h ; liberar espacio de entorno

PUSHF POP ES

CALL CS:ant_int10

DEC CS:in10 ; fin de la INT 10h 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

in10 DB 0 ; mayor de 0 si hay INT 10h MOV AX,3100h ; terminar residente

ant_int08 LABEL DWORD INT 21h

ant_int08_off DW ?

ant_int08_seg DW ? demores ENDS

ant_int10 LABEL DWORD END inicio

ant_int10_off DW ?

ant_int10_seg DW ?

; 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 Tamaño Descripción


-16 WORD segmento donde realmente comienza el código del TSR (CS en programas
con PSP, segmento de memoria superior XMS si instalado como UMB...)
-14 WORD offset donde realmente comienza el código del TSR (frecuentemente 100h
en programas *.COM y 0 en TSR's en memoria superior).
-12 WORD 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).
-10 BYTE 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
-9 BYTE 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.
-8 WORD offset a la tabla area_vectores (se verá después)
-6 WORD offset a la tabla area_extra (ver bit 7 en offset -10)
-4 4 BYTEs "*##*" (asegurar que el TSR verifica el convenio)
00h ??? "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 de la tabla area_vectores:


Offset Tamaño Descripción
-1 BYTE número de vectores interceptados por el TSR
00h BYTE número del primer vector
01h DWORD puntero al primer vector antes de instalar el TSR
05h BYTE número del segundo vector
06h 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.
PROGRAMAS RESIDENTES 161

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 de la tabla area_extra (opcional):


Offset Tamaño Descripción
00h WORD offset a la tabla control_externo (0 si no soportada)
02h 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. mx_no_hueco: STC

; A la salida, CF=1 si no hay hueco (ya hay 64 programas RET

; residentes instalados con esta técnica). Si CF=0, se mx_si_hueco: CLC

; devuelve en AH un valor de entrada libre en la INT 2Fh. RET

mx_get_handle ENDP

mx_get_handle PROC

MOV AH,0C0h ; ------------ Buscar un TSR por la interrupción Multiplex. A la

mx_busca_hndl: PUSH 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 AH,0C0h


161 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

mx_rep_find: 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

PUSH DI

REP CMPSB ; comparar identificación

POP DI

JE mx_tsr_found ; programa buscado hallado

mx_skip_hndl: POP DI

POP ES

POP DS

POP SI

POP CX

POP AX

INC AH

JNZ mx_rep_find

STC

RET

mx_tsr_found: ADD SP,4 ; «sacar» ES y DI de la pila

POP DS

POP SI

POP CX

POP AX

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 auto-
desinstale 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.

; ------------ Eliminar TSR del convenio si es posible. A la entrada, RET

; 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 ; BP=entrada Multiplex del TSR

; al desinstalar, AL devuelve el vector «culpable». MOV CX,2

mx_ul_pasada: PUSH CX ; siguiente pasada

mx_unload PROC LEA SI,tabla_vectores

PUSH ES MOV CL,ES:[SI-1]

CALL mx_ul_tsrcv? MOV CH,0 ; CX = nº vectores

JNC mx_ul_able mx_ul_masvect: POP AX

POP ES PUSH AX ; pasada en curso


PROGRAMAS RESIDENTES 161

DEC AL POP AX

PUSH CX JNE mx_ul_chain ; no

mx_ul_2f: MOV AL,ES:[SI] ; vector en curso POP ES ; sí: ¡posible reponer vector!

JNZ mx_ul_pasok POP CX

CMP CX,1 ; ¿último vector? POP BX

JNE mx_ul_noult PUSH BX

MOV AL,2Fh PUSH CX

LEA SI,tabla_vectores PUSH ES

mx_ul_busca2f: CMP ES:[SI],AL ; ¿INT 2Fh? DEC BX

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

mx_ul_noult: CMP AL,2Fh ; ¿restaurar INT 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

mx_ul_pasok: 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 DS

MOV CS:mx_ul_tsroff,AX mx_ul_norest: POP ES

MOV CS:mx_ul_tsrseg,0 ; apuntar a tabla vectores POP CX

POP AX ADD SI,5 ; siguiente vector

PUSH AX DEC CX

MOV AH,35h JZ mx_unloadable ; no más, ¡desinstal-ar/ado!

INT 21h ; vector en ES:BX JMP mx_ul_masvect

POP AX mx_ul_chain: 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 ; INT xx en DX (aprox.) SHR DX,CL

MOV AH,0C0h

mx_ul_masmx: CALL mx_ul_tsrcv?

JNC mx_ul_tsrcv

JMP mx_ul_otro

mx_ul_tsrcv: PUSH ES:[DI-16] ; ...TSR del convenio en ES:DI

PUSH ES:[DI-12]

MOV DI,ES:[DI-8] ; offset a la tabla de vectores

MOV CL,ES:[DI-1]

MOV CH,0 ; número de vectores en CX

mx_ul_buscav: CMP AL,ES:[DI]

JE mx_ul_usavect ; este TSR usa vector analizado

ADD DI,5

LOOP mx_ul_buscav

ADD SP,4 ; no lo usa

JMP mx_ul_otro

mx_ul_usavect: POP CX ; tamaño del TSR

POP BX ; segmento del TSR

CMP DX,BX

JB mx_ul_otro ; la INT xx no le apunta

ADD BX,CX

CMP DX,BX

JA mx_ul_otro ; la INT xx le apunta

PUSH AX

XOR AL,AL

XCHG AH,AL

CMP AX,BP ; ¿es el propio TSR?


161 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

MOV CX,ES:[DI+3]

ADD DX,CX ; INT xx en DX (aprox.)

MOV AH,0BFh

mx_ul_otro: INC AH ; a por otro TSR

JZ mx_ul_exitnok ; ¡se acabaron!

JMP mx_ul_masmx

mx_ul_exitnok: ADD SP,6 ; equilibrar pila

POP 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ª

mx_ul_exitok: TEST ES:info_extra,111b ; ¿tipo de instalación?

MOV ES,ES:segmento_real ; segmento real del bloque

JZ mx_ul_freeml ; cargado en RAM convencional

CMP xms_ins,1

JNE mx_ul_freeml ; no hay controlador XMS (¿?)

MOV DX,ES

MOV AH,11h

CALL gestor_XMS ; liberar memoria superior

POP ES

CLC

RET

mx_ul_freeml: MOV AH,49h

INT 21h ; liberar bloque de memoria ES:

POP ES

CLC

RET

mx_ul_tsrcv?: PUSH AX ; ¿es TSR del convenio?...

PUSH ES

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 ; CF=0

POP AX

RET

mx_ul_ncvexit: POP DI ; ...no es TSR del convenio

POP ES

POP AX

STC ; 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
PROGRAMAS RESIDENTES 161

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 <dos.h> printf("\n- ID de programas residentes que incumplen convenio: ");

#include <string.h> for (entrada=0; entrada<64; entrada++)

if (tsr_raro[entrada]) printf("%2d; ", entrada+0xc0);

if (vect) printf("\n");

void cabecera(), }

listar_tsr(), if (!vect) printf("\n- Ejecute con /V para listado de vectores.\n");

obtener_item(); }

void main (int argc, char *argv[]) int hay_tsr (int entrada) /* función booleana: 1 si hay TSR */

{ {

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 */ intr (0x2f, &r);

raro=0; /* a 1 si detectado TSR no del convenio */ return ((r.r_ax & 0xff)==0xff);

char tsr_raro[64]; /* flags de TSRs que no respetan el convenio */ }

if ((argc>1) && (!strcmp(strupr(argv[1]),"/V"))) vect=1;

int tsr_convenio (int entrada)

printf("\nTSRLIST 1.3 (c) Febrero 1994 CiriSOFT.\n"); {

printf(" Listado de tareas residentes normalizadas:\n\n"); struct REGPACK r;

for (entrada=0xc0; entrada<=0xff; entrada++) { r.r_ax=entrada << 8;

tsr_raro[entrada-0xc0]=0; r.r_es=0x1492; r.r_di=0x1992;

if (hay_tsr(entrada)) { intr (0x2f, &r);

if (tsr_convenio (entrada)) { return ((r.r_ax==0xFFFF) &&

if (primera_vez) cabecera(vect); /* encabezamiento */ (peek(r.r_es,r.r_di-4)==9002) && (peek(r.r_es,r.r_di-2)==10787));

listar_tsr (entrada, vect); /* informar del TSR */ }


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. */

/* */

void listar_tsr (int entrada, int vect) /********************************************************************/

struct REGPACK r;

char cad[40]; #include <dos.h>

unsigned int base, cont; #include <string.h>

char huge *info; #include <stdio.h>

#include <stdlib.h>

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 {

obtener_item (1, 8, info, cad); /* elemento 1: nombre */ unsigned segmento_real;

printf("%-8s", cad); unsigned offset_real;

obtener_item (2, 3, info, cad); /* elemento 2: versión */ unsigned ltsr;

printf(" %-4s %04X:%04X ", unsigned char info_extra;

cad, peek(r.r_es, r.r_di-16), peek(r.r_es, r.r_di-14)); unsigned char multiplex_id;

printf("%6u %03u ", unsigned vectores_id;

peek(r.r_es, r.r_di-12)*16, peekb(r.r_es, r.r_di-9) & 0xff); unsigned extension_id;

unsigned long validacion;

if (vect) /* listado de vectores */ { char autor_nom_ver[80];

base=peek(r.r_es, r.r_di-8); };

for (cont=0; cont<peekb(r.r_es, base-1); cont++) {

if (!(cont % 12) && cont) /* excesivos vectores: otra línea */

printf ("\n "); int tsr_convenio(),

printf("%02X ", peekb(r.r_es, base+cont*5)); mx_unload(),

} existe_xms();

} void liberar_umb(),

else /* imprimir autor */ { desinstalar();

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, if ((((mxid=atoi(argv[1]))<0xc0) || (mxid>0xFF)) && (mxid!=-1)) {

char huge *info, char *cad) printf (" - Indicar número Mx. ID (TSRLIST) entre 192 y 255");

{ printf (" (-1 todos los TSR).\n");

int i; exit (1); }

for (i=0; i<posicion; i++) while ((*info++)!=':'); if (mxid==-1) {

i=0; while ((*info!=':') && (*info)) cad[i++]=*info++; for (mxid=0xc0; mxid<=0xFF; mxid++)

cad[i]=cad[max_long]=0; /* fin de cadena y controlar tamaño */ if (tsr_convenio(mxid, &tsr)) desinstalar (mxid);

} }

else
PROGRAMAS RESIDENTES 161

desinstalar (mxid); nofincadena=1; mx=0xC0;

} 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; && i) i--;

char far *nombre, *p, if (i && (intptr>=iniciotsr)&&(intptr<=iniciotsr+tsrx->ltsr))

cadena [80], cadaux[80]; if (mx==mxid) nofincadena=0;

else {

correcto=mx_unload (mxid, &vector, &nombre); tablaptr[vx][0]=FP_SEG(tsrx);

tablaptr[vx][1]=tsrx->vectores_id+5*(i-1)+1;

if (correcto || (vector<0x100)) { intptr=peek(tablaptr[vx][0],tablaptr[vx][1]+2) +

strcpy (cadaux, nombre); p=cadaux; ((unsigned) peek(tablaptr[vx][0],tablaptr[vx][1]) >>4);

while (*p) if ((*p++)==':') *(p-1)=0; p=cadaux; mx=0xBF; /* compensar incremento posterior */

while (*p++); strcpy (cadena, p); /* nombre programa */ }

strcat (cadena, " "); }

while (*p++); strcat (cadena, p); /* versión */ if (mx==0xFF) posible=0; else mx++;

strcat (cadena, " de "); }

strcat (cadena, cadaux); /* autor */ }

*interrupción = vector;

if (correcto) *tsrnombre = tsr->autor_nom_ver;

printf(" - Desinstalado el %s\n", cadena);

else { if (strstr(*tsrnombre, "HBREAK")!=NULL) {

if (vector==0x100) posible=0; *interrupción=0x101; }

printf (" - No hay TSR %u o no es del convenio.\n", mxid);

else if (vector==0x101) if (strstr(*tsrnombre, "2MGUI")!=NULL) {

printf (" - HBREAK es «demasiado fuerte» para TSRKILL.\n"); posible=0; *interrupción=0x102; }

else if (vector==0x102)

printf (" - 2MGUI es «demasiado fuerte» para TSRKILL.\n"); if (posible) {

else { for (i=0; i<numvect; i++) {

printf (" - El %s no se puede desinstalar: ", cadena); vector = peekb(FP_SEG(tsr), tsr->vectores_id+5*i);

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);

int mx_unload (int mxid, int *interrupción, char far **tsrnombre) else {

{ asm cli

int mx, posible, vx, vector, i, nofincadena; poke (tablaptr[i][0], tablaptr[i][1], ofs);

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) {

if (!tsr_convenio (mxid, &tsr)) { case 0: r.r_es=tsr->segmento_real; r.r_ax=0x4900;

*interrupción=0x100; intr (0x21, &r); break;

return (0); case 1: if (existe_xms()) liberar_umb (tsr->segmento_real);

} break;

numvect = peekb(FP_SEG(tsr), tsr->vectores_id-1); }

for (i=0; i<256; i++) tablaptr[i][0]=tablaptr[i][1]=0; return (posible);

for (posible=1, vx=0; posible && (vx<numvect); vx++) {

vector = peekb(FP_SEG(tsr), tsr->vectores_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_es=0x1492; r.r_di=0x1992; r.r_ax=0x4300; intr (0x2F, &r); return ((r.r_ax & 0xFF)==0x80);

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 auto-
relocaliza (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 ; preservar estado UMB

; devuelve el código de error del controlador XMS). MOV AX,5803h

MOV BX,1

UMB_alloc PROC INT 21h ; 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 ; restaurar estrategia

POP DX ; ok MOV AX,5803h

POP CX MOV BL,umb_state

POP BX XOR BH,BH

CLC INT 21h ; restaurar estado cadena UMB

RET POP AX

no_umb_disp: MOV AX,0 POPF

XMS_fallo: POP DX JC UPPER_fin ; hubo fallo

POP CX PUSH DS

POP BX DEC AX

STC MOV DS,AX

RET INC AX

UMB_alloc 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

UPPER_alloc PROC DEC CX

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 ; copiar nombre de programa

STC POP ES

JMP UPPER_fin ; necesario DOS 5.0 mínimo POP DS

UPPER_existe: PUSH AX ; preservar párrafos... CLC

MOV AX,5800h UPPER_fin: RET

INT 21h UPPER_alloc ENDP

MOV alloc_strat,AX ; 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;
PROGRAMAS RESIDENTES 161

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 ; función exclusiva del DR-DOS


INT 21h
JC no_es_drdos ; probablemente es MS-DOS
CMP AX,1063h
JE drdos341
CMP AX,1065h
JE drdos5
CMP AX,1067h
JE drdos6
JA drdos_futuro

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

;* RCLOCK v2.3 (c) Septiembre 1992 CiriSOFT * ; 011: *.SYS formato EXE

;* (c) Grupo Universitario de Informática - Valladolid * ; bit 7 a 1: «extension_id» definida

;* * multiplex_id DB 0 ; número Multiplex de este TSR

;* »»» Utilidad de reloj-alarma residente ««« * vectores_id DW tabla_vectores

;* * extension_id DW tabla_extra

;********************************************************************* DB "*##*"

autor_nom_ver DB "CiriSOFT:RCLOCK:2.3",0

; ------------ Macros de propósito general

DB 4 ; número de vectores de interrupción usados

XPUSH MACRO RM tabla_vectores EQU $

IRP reg, <RM> DB 8 ; INT 8

PUSH reg ant_int08 LABEL DWORD ; dirección original

ENDM ant_int08_off DW 0

ENDM ant_int08_seg DW 0

DB 9 ; INT 9

XPOP MACRO RM ant_int09 LABEL DWORD ; dirección original

IRP reg, <RM> ant_int09_off DW 0

POP reg ant_int09_seg DW 0

ENDM DB 10h ; INT 10h

ENDM ant_int10 LABEL DWORD ; dirección original

ant_int10_off DW 0

; ------------ Programa ant_int10_seg DW 0

DB 2Fh ; INT 2Fh

rclock SEGMENT ant_int2F LABEL DWORD ; dirección original

ASSUME CS:rclock, DS:rclock ant_int2F_off DW 0

ant_int2F_seg DW 0

ORG 100h

tabla_extra LABEL BYTE

ini_residente EQU $ DW ctrl_exterior ; permitido control exterior

DW 0 ; campo reservado

; ****************************************

; * * ctrl_exterior LABEL BYTE

; * D A T O S R E S I D E N T E S * reubicabilidad DB 1 ; programa 100% reubicable

; * * activacion DW visibilidad

; ****************************************

; ------------ 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».

program_id LABEL BYTE ; Los datos (para notas mayores de 6) se han calculado con la fórmula:

segmento_real DW 0 ; segmento real donde será cargado ;

offset_real DW 0 ; offset real " " " ; 1193180/(36.8*(2^(1/12))^(nota-6))

longitud_total DW 0 ; zona de memoria ocupada (párrafos) ;

info_extra DB 80h ; bits 0, 1 y 2-> 000: normal, con PSP ;


PROGRAMAS RESIDENTES 161

; 41 43 46 48 50 53 55 58 60 62 ; ------------ Parámetros básicos del reloj

; ─┬──▄▄▄─▄▄▄──┬──▄▄▄─▄▄▄─▄▄▄──┬──▄▄▄─▄▄▄──┬──▄▄▄─▄▄▄─▄▄▄──┬─

; . . │ ███ ███ │ ███ ███ ███ │ ███ ███ │ ███ ███ ███ │ . . alarm_enable DB 0 ; por defecto, alarma OFF

; │ ███ ███ │ ███ ███ ███ │ ███ ███ │ ███ ███ ███ │ hora_alarma LABEL BYTE

; . . │ 40│ 42│ 44│ 45│ 47│ 49│ 51│ 52│ 54│ 56│ 57│ 59│ 61│ 63│ . . alarm_h DW "0 "

; ─┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴─ DB ":"

; alarm_m DW "00"

DB ":"

tabla_periodos LABEL WORD alarm_s DW "00"

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 ; 4 -> a los cuartos; 5 -> cada 5 min.,

DW 11463,10820,10212,9639,9098,8587,8105,7650 ; 0 -> sin señal

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 ; ------------ Variables de control general

DW 451,426,402,379,358,338,319,301

DW 284 in10 DW 0 ; flag contador de entradas en INT 10h

cont_refresco DB 1 ; contador de INT's 8 a «saltar»

; ------------ Sonido pagina DB 0 ; página de vídeo activa

modo_video DB 255 ; modo de vídeo activo (valor imposible

; formato de la música: ; para provocar inicialización)

; número de nota (0-88), duración (en 1/18,2 seg.) operacion DB 0 ; 8/9 para preservar/restaurar la zona

; ; de pantalla ocupada por el reloj

; Las primeras 7 notas son inaudibles y sirven para 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

; que va a sonar

; fragmento del preludio 924 de Bach: contador_nota DB 0 ; INT's 8 que le quedan por sonar a la

; nota que está en curso

musica_alarma DB 47,2,52,2,56,3,1,1,47,2,52,2,56,3,1,1 turno_blanco DB 0 ; a 1 si se procesa la nota separadora

DB 47,2,52,2,54,3,1,1,51,2,54,2,59,3,1,1 ; de notas

DB 49,2,54,2,59,3,1,1,49,2,54,2,57,3,1,1 parando DB 0 ; contador para detener el sonido

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 ; ------------ Cadenas para imprimir

DB 51,2,54,2,57,3,1,1

DB 255 hora_actual LABEL BYTE

horasH DB 0

; típica música de las iglesias: horasL DB 0

DB ":"

musica_horas 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 ":"

; tres pitidos descendentes segundosH DB 0

segundosL DB 0

musica_medias DB 47,7,54,7,56,7,52,7,255 DB 0

; tres pitidos ascendentes: restaurar DB 8 DUP (' ') ; para almacenar el contenido previo

DB 8 DUP (7) ; de la pantalla (sólo modo texto)

musica_cuartos DB 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

; *************************************** MOV alarm_enable,AH ; desactivar alarma

CALL chiton ; silenciar altavoz

; ------------ Rutina de gestión de INT 2Fh JMP ret_int09

no_sonando: XOR visibilidad,AH ; invertir visibilidad reloj

ges_int2F PROC FAR MOV cont_refresco,AH ; acelerar presencia/ausencia

STI ret_int09: XPUSH <BX, CX, BP>

CMP AH,CS:multiplex_id MOV AH,1

JE preguntan INT 16h ; consultar estado del buffer

JMP CS:ant_int2F ; saltar al gestor de INT 2Fh JZ no_hay_alt_r ; no se colocó Alt-R en buffer

preguntan: CMP DI,1992h MOV AH,0 ; este KEYB es más compatible:

JNE ret_no_info ; no llama alguien del convenio INT 16h ; sacar código Alt-R del buffer

MOV AX,ES no_hay_alt_r: XPOP <BP, CX, BX>

CMP AX,1492h fin_int09ds: POP DS

JNE ret_no_info ; no llama alguien del convenio fin_int09: POP AX

PUSH CS IRET

POP ES ; sí llama: darle información ges_int09 ENDP

LEA DI,autor_nom_ver

ret_no_info: MOV AX,0FFFFh ; "entrada multiplex en uso" ; ------------ Rutina de gestión de INT 8

IRET

ges_int2F ENDP ges_int08 PROC FAR

PUSHF

; ------------ Rutina de control INT 10h. No se imprimirá en pantalla CALL CS:ant_int08 ; llamar al controlador previo

; cuando se ejecute una INT 10h para no reentrar al BIOS. STI

XPUSH <AX, BX, CX, DX, SI, DI, BP, DS, ES>

ges_int10 PROC FAR MOV AX,CS

INC CS:in10 ; indicar entrada en INT 10h MOV DS,AX

PUSHF MOV ES,AX

CALL CS:ant_int10 CALL avisos_sonoros ; darlos si es necesario

DEC CS:in10 ; fin de la INT 10h DEC cont_refresco ; contador de INTs 8 a «saltar»

IRET JNZ fin_int08 ; no han pasado las suficientes

ges_int10 ENDP MOV AL,refresco

MOV cont_refresco,AL ; recargar cuenta

; ------------ Rutina de gestión de INT 9 CMP CS:in10,0

JNE fin_int08 ; estamos dentro de INT 10h

ges_int09 PROC FAR CALL obtiene_hora ; crear cadena con la hora

PUSH AX CMP visibilidad,1 ; ¿reloj visible?

IN AL,60h ; espiar código de rastreo JNE restaurar? ; no

PUSHF CMP visible,1 ; sí, ¿acaba de aparecer?

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 scr_getted: 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 ; invertir bits de Ctrl y Alt restaurar?: CMP visible,1 ; reloj oculto ¿recientemente?

TEST AL,12 JNE fin_int08 ; no, ya había desaparecido

JZ ctrl_alt ; pulsado Ctrl-Alt MOV visible,0 ; sí:

TEST BYTE PTR DS:[96h],8 MOV operacion,9

JZ fin_int09ds ; no pulsado AltGr CALL bios_scr_proc ; reponer contenido de pantalla

ctrl_alt: STI fin_int08: XPOP <ES, DS, BP, DI, SI, DX, CX, BX, AX>

PUSH CS IRET

POP DS ges_int08 ENDP

MOV AH,1

CMP musica_sonando,AH ; ------------ Controlar la generación de señales sonoras

JNE no_sonando ; no hay sonido

DEC AH avisos_sonoros PROC

MOV parando,19 ; en 1 segundo, no más notas CMP parando,0 ; ¿"callar" durante 1 segundo?

MOV musica_sonando,AH ; parar música JE avisos_on ; no


PROGRAMAS RESIDENTES 161

DEC parando ; sí JNE cuarto?

JMP fin_avisos LEA AX,musica_medias-2 ; 30 minutos exactos

avisos_on: CMP musica_sonando,1 CMP CL,2 ; ¿avisar a las medias?

JNE no_mas_notas ; no hay sonido en curso JAE fin_avisando ; en efecto

DEC contador_nota cuarto?: 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 cuar_quiza?: 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 ; 15 ó 45 minutos exactos

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 ; puntero a la siguiente nota cinco_min?: 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 cinc_quiza?: 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 fin_avisando: 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: RET

MOV alarm_enable,0 ; desactivar alarma avisos_sonoros ENDP

CALL chiton ; acallar altavoz

JMP no_mas_notas ; ------------ Detener sonido por el altavoz

sonar: INC BH

MOV contador_nota,BH ; INT's 8 que dura esa nota chiton PROC

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] ; período del sonido JMP SHORT $+2

CALL programar_8253 JMP SHORT $+2

JMP fin_avisos OUT 61h,AL ; altavoz silenciado

no_mas_notas: CMP alarm_enable,0 RET

JE no_alarma ; alarma desactivada chiton ENDP

LEA SI,hora_actual

LEA DI,hora_alarma ; ------------ Preparar la producción de sonido

MOV CX,8

CLD programar_8253 PROC

REP CMPSB ; ¿hora actual = hora alarma? PUSH AX

JNE no_alarma ; no es la hora de la alarma MOV AL,182 ; preparar canal 2

LEA AX,musica_alarma-2 ; sí lo es OUT 43h,AL

JMP fin_avisando POP AX

no_alarma: 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" ; ¿hora en punto? MOV AL,AH

JNE media? JMP SHORT $+2

CMP DI,"00" JMP SHORT $+2

JNE media? OUT 42h,AL ; canal #2 del 8253 programado

LEA AX,musica_horas-2 ; hora en punto JMP SHORT $+2

CMP CL,1 ; ¿avisar a las horas? JMP SHORT $+2

JAE fin_avisando ; en efecto IN AL,61h

media?: CMP SI,"03" ; ¿30 minutos exactos? OR AL,3

JNE cuarto? JMP SHORT $+2

CMP DI,"00" JMP SHORT $+2


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

gestiona_fondo PROC MOV SI,DS:[46Ch]

MOV AH,15 MOV DI,DS:[46Eh] ; contador de hora del BIOS

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 ; DXDISI = DISI * 1080

CMP BH,pagina ; ¿ha cambiado la página? MOV AX,19663

JNE clr_fondo? ; así es CALL divi48x15 ; DXDISI = DXDISI / 19663

RET ; no ha cambiado nada PUSH DI

clr_fondo?: MOV modo_video,AL ; actualizar nuevos parámetros PUSH SI ; DISI = tics/18,2065 = seg.

MOV pagina,BH MOV AX,3600

MOV BL,c_x ; coordenada X teórica CALL divi48x15

CMP BL,72 ; ¿es la 72? MOV AX,SI ; AX = SI = horas

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 ; a la derecha posible OR AX,"00" ; pasar BCD a ASCII

dejar_c_x: 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,' ' ; evitar cero a la izda en hora

CMP AL,7 ; ¿modo de texto monocromo? no_cero_izda: 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 ; DXAX = horas*3600

fondo_clr_ar: MOV BYTE PTR DS:[BX],' ' POP SI

MOV BYTE PTR DS:[BX+8],7 ; y atributos blancos POP DI

INC BX SUB SI,AX

LOOP fondo_clr_ar ; acabar buffer SBB DI,DX ; DISI = segundos+minutos*60

RET MOV AX,SI

get_fondo: MOV operacion,8 ; preservar zona de la pantalla MOV CL,60

CALL bios_scr_proc DIV CL ; AL = minutos

RET PUSH AX

gestiona_fondo ENDP MOV AH,0

MOV CL,10

; ------------ Imprimir reloj en pantalla DIV CL ; pasar binario a BCD

OR AX,"00" ; pasar BCD a ASCII

print_reloj PROC MOV minutosH,AL

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 ; SI = segundos restantes

MOV DL,c_xx MOV AX,SI

MOV DH,c_y ; coordenadas del reloj 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 ENDP

MOV AH,2

INT 10h ; restaurar posición del cursor ; ------------ Imprimir en color usando BIOS; sería más rápido acceder

RET ; a la memoria de vídeo, pero así también funciona en los


PROGRAMAS RESIDENTES 161

; modos gráficos y en cualquier tarjeta (incluído SVGA). MOV [SI+8],AH ; y su atributo

; La cadena ASCIIZ se entrega en DS:BX. opcont: CALL cursor_derecha ; siguiente posición

INC SI ; próximo carácter

bios_print PROC POP CX

MOV AL,[BX] ; primer carácter a imprimir LOOP proximo_car ; acabar caracteres

INC BX POP DX ; recuperar coordenadas

AND AL,AL MOV BH,pagina

JZ fin_print ; byte 0 -> fin de cadena MOV AH,2

PUSH BX INT 10h ; y reponer posición del cursor

MOV AH,9 ; función de impresión RET

MOV BH,pagina bios_scr_proc ENDP

MOV BL,color

MOV CX,1 ; número de caracteres ; ------------ Rutina para multiplicar números de 32 por números de 16

INT 10h ; bits generando resultado de 48 bits: DISI * AX = DXDISI

CALL cursor_derecha ; avanzar cursor

POP BX mult32x16 PROC

JMP bios_print ; siguiente carácter 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

; ------------ Avanzar cursor a la derecha PUSH AX

MOV AX,DI

cursor_derecha PROC MUL 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 RET

INT 10h ; actualizar posición cursor mult32x16 ENDP

RET

cursor_derecha ENDP ; ------------ Rutina para dividir números de 48 por números de 15

; 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

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 ; rotar 49 veces

MOV AH,2 divi48_15_cmp: CMP AX,BX

MOV DL,c_xx JA divi48_nosub

MOV DH,c_y ; coordenadas del reloj SUB BX,AX

MOV BH,pagina STC

INT 10h ; mover cursor divi48_nosub: RCL SI,1

LEA SI,restaurar ; dirección del buffer RCL DI,1

MOV CX,8 ; 8 caracteres RCL DX,1

proximo_car: PUSH CX PUSHF

MOV AH,operacion ; 8 ->preservar, 9 ->restaurar CMP CX,1

MOV BH,pagina JE divi48_resto ; ¡no rotar el resto al final!

MOV BL,[SI+8] ; preparar BL por si AH=9 POPF

MOV AL,[SI] ; preparar AL por si AH=9 RCL BX,1

MOV CX,1 ; preparar CX por si AH=9 PUSHF

INT 10h ; leer/escribir carácter divi48_resto: POPF

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


161 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

POP BX JNE instalar_ml ; en efecto

RET MOV AX,parrafos_resid ; párrafos de memoria precisos

divi48x15 ENDP CALL UMB_alloc ; pedir memoria superior XMS

JNC instalar_umb ; hay la suficiente

fin_residente EQU $ ; fin del área residente MOV AX,parrafos_resid

CALL UPPER_alloc ; pedir memoria superior DOS 5

bytes_resid EQU fin_residente-ini_residente JC instalar_ml ; no hay la suficiente

STC ; indicar que usa memoria DOS

parrafos_resid EQU (bytes_resid+15)/16 instalar_umb: 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

; * I N S T A L A C I O N * JMP fin_noresid ; programa instalado «arriba»

; * * instalar_ml: STC ; indicar que usa memoria DOS

; ***************************** MOV DI,60h ; instalación mem. convencional

CALL inicializa_id ; inicializar identificación

main PROC CALL reubicar_prog ; reubicar programa a ES:DI

LEA DX,rclock_txt ; nombre del programa CALL activar_ints ; interceptar vectores

CALL print CALL free_environ ; liberar espacio de entorno

CALL obtener_param ; analizar posibles parámetros MOV DX,parrafos_resid ; tamaño zona residente, desde

JNC params_ok ; son correctos ADD DX,6 ; PSP:60h bytes (6 párrafos)

CALL print_err ; no: informar del error/ayuda MOV AX,3100h

JMP fin_noresid INT 21h ; terminar residente

params_ok: CALL inic_XMS ; considerar presencia de XMS fin_noresid: MOV AX,4C00h

CALL residente? ; ¿programa ya residente? INT 21h ; terminar no residente

JC no_residente ; todavía no main ENDP

CMP param_u,1 ; sí: ¿solicitan desinstalarlo?

JE desinst ; así es

CALL adaptar_param ; parámetros en copia residente ;*********************************************************

JMP fin_noresid ;* *

desinst: MOV ES,tsr_seg ;* SUBRUTINAS DE PROPOSITO GENERAL PARA LA INSTALACION *

CALL rclock_off ;* *

MOV AH,ES:multiplex_id ;*********************************************************

CALL mx_unload ; desinstalarlo:

LEA DX,des_ok_txt ; ------------ Admitir posibles parámetros en la línea de comandos

JNC mens_ok ; ha sido posible

LEA DX,des_no_ok_txt ; es imposible obtener_param PROC

mens_ok: CALL print MOV BX,81h ; apuntar a zona de parámetros

JMP fin_noresid otro_pmt_mas: CALL saltar_esp ; saltar delimitadores

no_residente: CMP AX,0 ; ¿reside una versión distinta? JNC otro_pmt ; quedan más parámetros

JE instalable ; no: se admite instalación JMP fin_proc_pmt ; no más parámetros

CALL error_version ; error de versión incompatible otro_pmt: CMP AL,'/'

JMP fin_noresid JE pmt_barrado ; parámetro precedido por '/'

instalable: CMP param_u,1 ; no residente: ¿desinstalar? CMP AL,'?'

JNE instalar ; no lo piden MOV DH,128 ; código de «error» para ayuda

LEA DX,imp_desins_txt ; lo piden, ¡serán despistados! JNE pmt_nobarrado

CALL print JMP mal_proc_pmt ; «error» de solicitud de ayuda

JMP fin_noresid pmt_nobarrado: OR WORD PTR [BX]," " ; pasar a minúsculas

instalar: CALL mx_get_handle ; obtener entrada Multiplex CMP WORD PTR [BX],"no" ; ¿parámetro ON?

JNC handle_ok JNE pmt_off?

LEA DX,nocabe_txt ; no quedan entradas MOV visibilidad,1

CALL print MOV visible,1

JMP fin_noresid MOV param_onoff,1

handle_ok: MOV multiplex_id,AH ; entrada multiplex para RCLOCK ADD BX,2

LEA DX,instalado_txt ; mensaje de instalación JMP otro_pmt_mas

CALL print pmt_off?: CMP WORD PTR [BX],"fo" ; ¿parámetro OFx?

CALL preservar_ints ; tomar nota de vectores MOV DH,0 ; código de error

CMP param_ml,0 ; ¿se indicó parámetro /ML? JNE mal_proc_pmt


PROGRAMAS RESIDENTES 161

OR BYTE PTR [BX+2],' ' ; pasar a minúsculas JMP otro_pmt_mas

CMP BYTE PTR [BX+2],'f' ; ¿parámetro OFF? pmt_X: MOV param_x,1

JNE mal_proc_pmt MOV c_x,AL

MOV visibilidad,0 CMP AX,124 ; admitir hasta 132 columnas

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 pmt_Y: MOV param_y,1

pmt_barrado: INC BX MOV c_y,AL ; y hasta 60 líneas

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 ; falta parámetro JMP otro_pmt_mas

CMP AL,'?' pmt_C: MOV param_c,1

MOV DH,128 ; código de «error» para ayuda MOV color,AL

JE mal_proc_pmt CMP AX,255

OR AL,' ' ; poner en minúsculas MOV DH,6

CMP AL,'h' JA mal_proc_pmt

JE mal_proc_pmt JMP otro_pmt_mas

CMP AL,'a' pmt_A: PUSH BX

JNE pmt_no_A CALL get_num

JMP pmt_A ; parámetro /A=hh:mm:ss|ON|OFF JNC bien_pmt_A

pmt_no_A: CMP AL,'u' POP BX

JE pmt_U ADD BX,2

MOV SI,[BX] ; ¿parámetro de dos caracteres? OR WORD PTR [BX]," " ; pasar a minúsculas

OR SI," " ; mayusculizar CMP WORD PTR [BX],"no" ; ¿parámetro ON?

CMP SI,"lm" ; ¿parámetro /ML? JNE pmt_A_off?

JNE no_ml MOV alarm_enable,1

MOV param_ml,1 ; en efecto MOV param_a_onoff,1

ADD BX,2 ADD BX,2

JMP otro_pmt_mas JMP otro_pmt_mas

no_ml: PUSH AX pmt_A_off?: 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 bien_pmt_A: MOV param_a,1

CMP CL,'c' ADD SP,2 ; «sacar» BX de la pila

MOV DH,2 ; código de error CMP AX,23

JE pmt_C JA mal_pmtA

mal_proc_pmt: STC ; error en parámetro(s) MOV CL,10

RET DIV CL ; pasar binario a BCD

fin_proc_pmt: CLC ; parámetros procesados ADD AX,"00" ; pasar BCD a ASCII

RET CMP AL,'0'

pmt_U: MOV param_u,1 JNE no_cero_izda2

INC BX MOV AL,' ' ; evitar cero a la izda. hora

JMP otro_pmt_mas no_cero_izda2: MOV BYTE PTR alarm_h,AL

pmt_T: 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


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 ; fin número

MOV BYTE PTR alarm_m,AL JE fin_num

MOV BYTE PTR alarm_m+1,AH CMP AL,9 ; fin número

DEC BX JE fin_num

CALL get_num CMP AL,'/' ; fin número (otro parámetro)

JC mal_pmtA JE fin_num

CMP AX,59 CMP AL,':' ; fin número (otro dato)

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 fin_num: 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 otro_car: 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 ; delimitador: fin de número

; ------------ Saltar espacios, tabuladores, ... buscando un parámetro CMP CL,':'

JE ok_num ; delimitador: fin de número

saltar_esp: MOV AL,[BX] CMP CL,'.'

INC BX JNE no_millar ; saltar los puntos de millar

CMP AL,9 ; carácter tabulador CMP AX,1000

JE saltar_esp JE otro_car

CMP AL,32 ; espacio en blanco JMP mal_num ; separador millar descolocado

JE saltar_esp no_millar: CMP CL,'0'

CMP AL,0Dh ; fin de zona de parámetros JB mal_num

JE fin_param CMP CL,'9'

DEC BX ; puntero al primer carácter JA mal_num

CLC ; hay parámetro SUB CL,'0' ; pasar ASCII a binario

RET MOV CH,0 ; CX = 0 .. 9

fin_param: STC ; no hay parámetro PUSH AX ; AX = 10 elevado a la N

RET AND AX,AX

JNZ multiplica

; ------------ Obtener número chequeando delimitadores /= y /: AND CL,CL

JNZ mal_num_pop ; a la izda sólo permitir ceros

get_num: INC BX multiplica: PUSH DX ; tras completar 5º dígito

MOV AL,[BX] MUL CX

INC BX POP DX

CMP AL,'=' JC mal_num_pop

JE delimit_ok ADD DX,AX ; DX = DX + digito (CX) * 10 ^ N (AX)

CMP AL,':' JC mal_num_pop

JE delimit_ok POP AX

err_sintax: STC ; sintaxis incorrecta CMP AX,10000

RET JNE potencia ; AX*10 no se desbordará

delimit_ok: 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 potencia: MOV DI,10

INC BX PUSH DX ; no manchar DX al multiplicar

RET MUL DI ; AX = AX elevado a la (N+1)

POP DX

; ------------ Extraer nº de 16 bits y depositarlo en AX; al final, el 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

STC ; condición de error

obtener_num PROC RET

CMP AL,0Dh ; fin zona parámetros y número ok_num: MOV BX,SI ; número correcto
PROGRAMAS RESIDENTES 161

MOV AX,DX ; resultado RET

CLC ; condición de Ok. error_version ENDP

RET

obtener_num ENDP ; ------------ Considerar presencia de controlador XMS

; ------------ Imprimir errores en los parámetros inic_XMS PROC

MOV AX,4300h

print_err PROC INT 2Fh ; chequear presencia XMS

CMP DH,128 ; error: DH código de error CMP AL,80h

JNE no_ayuda JNE XMS_ausente ; no instalado

LEA DX,ayuda_txt PUSH ES

JMP pr_ret MOV AX,4310h

no_ayuda: MOV AH,DH INT 2Fh ; sí: obtener su dirección

MOV AL,CL ; CL=parámetro en errores 1..6 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 ; tabla de mensajes de error POP ES

PUSH AX RET

MOV AL,AH XMS_ausente: MOV xms_ins,0

SHL AL,1 ; AL = AL * 2 RET

XOR AH,AH ; AX = AL inic_XMS ENDP

ADD BX,AX

MOV DX,[BX] ; dirección del texto ; ------------ Comprobar si el programa ya reside en memoria. A la

CALL print ; salida, CF=0 si programa ya reside, con «tsr_seg» y

POP AX ; recuperar código y parámetro ; «tsr_off» inicializadas apuntando a la cadena de

CMP AH,1 ; identificación de la copia residente. Si CF=1, el

JBE no_pr_pmt ; error 0 ó 1 ; programa no reside aún (AX=0) o reside pero en otra

MOV DL,AL ; versión distinta (AX=1).

MOV AH,2

INT 21h ; imprimir letra del parámetro residente? PROC

no_pr_pmt: LEA DX,fin_err_txt PUSH CX

pr_ret: CALL print PUSH SI

RET PUSH DI

print_err ENDP PUSH ES

PUSH AX

; ------------ Ya está instalada otra versión distinta del programa LEA DI,autor_nom_ver ; identificación del programa

MOV SI,DI

error_version PROC 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 ; tamaño autor+programa+versión

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 DL,ES:[DI] ; número de versión MOV tsr_off,DI ; anotar la dirección programa

MOV AH,2 MOV tsr_seg,ES ; por si estaba instalado

INT 21h POP AX

MOV DL,'.' JNC resid_ok ; CF=0 -> programa ya residente

MOV AH,2 POP ES

INT 21h PUSH ES

MOV DL,ES:[DI+2] ; revisión 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


161 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

SUB DI,SI CALL espera_reloj ; esperar a que se vaya

MOV CX,DI ; tamaño autor+programa MOV AH,c_y

MOV AX,1492h MOV ES:c_y,AH ; actualizar coordenada Y

MOV ES,AX MOV ES:visibilidad,AL ; restaurar visibilidad

MOV DI,1992h ; ES:DI protocolo de búsqueda param_c?: CMP param_c,1

CALL mx_find_tsr ; buscar si está en memoria JNE param_adapted

MOV tsr_off,DI ; anotar dirección del programa MOV AL,color ; parámetro /C:

MOV tsr_seg,ES ; por si instalada otra versión MOV ES:color,AL ; actualizar byte de atributos

MOV AX,0 param_adapted: RET

JC resid_ok ; CF=1, AX=0 -> no residente adaptar_param ENDP

MOV AX,1

STC ; CF=1, AX=1 -> sí: otra vers. ; ------------ Eliminar el RCLOCK de la pantalla

resid_ok: POP ES

POP DI rclock_off PROC

POP SI MOV ES:visibilidad,0

POP CX CALL espera_reloj ; eliminarlo de la pantalla

RET MOV ES:musica_sonando,0

residente? ENDP IN AL,61h ; parar posible sonido

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

RET

adaptar_param PROC rclock_off ENDP

LEA 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 ; parámetros ON u OFF: PUSH DS

MOV ES:visibilidad,AL ; adaptar visibilidad del reloj PUSH AX

param_a?: 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

CLD MOV DS,AX

REP MOVSB STI

param_aonoff?: CMP param_a_onoff,1 espera_tics: MOV AX,DS:[6Ch]

JNE param_t? espera_tic: CMP AX,DS:[6Ch]

MOV AL,alarm_enable ; parámetro /A=ON o /A=OFF: JE espera_tic

MOV ES:alarm_enable,AL ; actualizar estado alarma LOOP espera_tics

param_t?: 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

param_x?: CMP param_x,1 espera_reloj ENDP

JNE param_y?

MOV AL,ES:visibilidad ; parámetro /X: ; ------------ Preservar vectores de interrupción previos

MOV ES:visibilidad,0 ; eliminar reloj de pantalla

CALL espera_reloj ; esperar a que se vaya preservar_INTs PROC

MOV AH,c_x PUSH ES

MOV ES:c_x,AH ; actualizar coordenada X PUSH DI

MOV ES:c_xx,AH LEA DI,tabla_vectores

MOV ES:visibilidad,AL ; restaurar visibilidad MOV CL,[DI-1]

param_y?: CMP param_y,1 MOV CH,0 ; CX vectores interceptados

JNE param_c? otro_vector: PUSH CX

MOV AL,ES:visibilidad ; parámetro /Y: PUSH DI

MOV ES:visibilidad,0 ; eliminar reloj de pantalla MOV AH,35h


PROGRAMAS RESIDENTES 161

MOV AL,[DI] PUSH AX

INT 21h ; obtener vector de INT xx MOV AH,30h

POP DI INT 21h

POP CX CMP AL,5

MOV [DI+1],BX ; anotar donde apunta POP AX

MOV [DI+3],ES JAE UPPER_existe

ADD DI,5 STC

LOOP otro_vector ; repetir con los restantes JMP UPPER_fin ; necesario DOS 5.0 mínimo

POP DI UPPER_existe: PUSH AX ; preservar párrafos...

POP ES MOV AX,5800h

RET INT 21h

preservar_INTs ENDP MOV alloc_strat,AX ; preservar estrategia

MOV AX,5802h

; ------------ Liberar espacio de entorno INT 21h

MOV umb_state,AL ; preservar estado UMB

free_environ PROC MOV AX,5803h

PUSH ES MOV BX,1

MOV ES,DS:[2Ch] ; dirección del entorno INT 21h ; conectar cadena UMB's

MOV AH,49h MOV AX,5801h

INT 21h ; liberar espacio de entorno MOV BX,41h

POP ES INT 21h ; High Memory best fit

RET POP BX ; ...párrafos requeridos

free_environ ENDP MOV AH,48h

INT 21h ; asignar memoria

; ------------ Reservar bloque de memoria superior del nº párrafos AX, PUSHF

; devolviendo en AX el segmento donde está. CF=1 si no PUSH AX ; guardado el resultado

; 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 ; restaurar estrategia

UMB_alloc PROC MOV AX,5803h

PUSH BX MOV BL,umb_state

PUSH CX XOR BH,BH

PUSH DX INT 21h ; restaurar estado cadena UMB

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 ; hubo fallo

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

no_umb_disp: 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

UMB_alloc ENDP REP MOVSB ; copiar nombre de programa

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_fin: RET

UPPER_alloc ENDP

UPPER_alloc PROC
161 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

; ------------ Inicializar área «program_id» del programa residente. INT 21h ; desviar INT xx a DS:DX

; 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

inicializa_id PROC POP CX

PUSHF RET

MOV segmento_real,ES ; anotar segmento del bloque activar_INTs ENDP

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

POPF ; CF=0: usar memoria UMB XMS MOV AH,0C0h

JNC info_ok mx_busca_hndl: PUSH AX

DEC AL ; usar memoria convencional MOV AL,0

info_ok: OR info_extra,AL INT 2Fh

RET CMP AL,0FFh

inicializa_id ENDP POP AX

JNE mx_si_hueco

; ------------ Reubicar programa residente a su dirección definitiva. INC AH

JNZ mx_busca_hndl

reubicar_prog PROC mx_no_hueco: STC

PUSH DI RET

LEA SI,ini_residente mx_si_hueco: CLC

MOV CX,bytes_resid RET

CLD mx_get_handle ENDP

ADD SI,2 ; no copiar primera palabra

ADD DI,2 ; respetar primera palabra ; ------------ Buscar un TSR por la interrupción Multiplex. A la

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

reubicar_prog ENDP ; mismo. Si no, CF=1 y ningún registro alterado.

; ------------ Desviar vectores de interrupción a las nuevas rutinas. mx_find_tsr PROC

; Se tendrá en cuenta que está ensambladas para correr en MOV AH,0C0h

; un offset inicial (100h) y que el offset real en que mx_rep_find: 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

activar_INTs PROC PUSH DI

PUSH CX MOV AL,0

PUSH DS ; preservar DS para el retorno PUSH CX

MOV AX,100h INT 2Fh

SUB AX,DI ; AX = 100h-DI POP CX

MOV CL,4 CMP AL,0FFh

SHR AX,CL ; AX = (100h-DI)/16 JNE mx_skip_hndl ; no hay TSR ahí

MOV CX,ES CLD

SUB CX,AX PUSH DI

MOV DS,CX REP CMPSB ; comparar identificación

LEA SI,offsets_ints POP DI

MOV CX,CS:[SI] ; CX vectores a desviar JE mx_tsr_found ; programa buscado hallado

ADD SI,2 mx_skip_hndl: POP DI

desvia_otro: 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


PROGRAMAS RESIDENTES 161

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 ; vector en ES:BX

mx_tsr_found: ADD SP,4 ; «sacar» ES y DI de la pila POP AX

POP DS MOV CL,4

POP SI SHR BX,CL

POP CX MOV DX,ES

POP AX ADD DX,BX ; INT xx en DX (aprox.)

CLC MOV AH,0C0h

RET mx_ul_masmx: CALL mx_ul_tsrcv?

mx_find_tsr ENDP JNC mx_ul_tsrcv

JMP mx_ul_otro

; ------------ Eliminar TSR del convenio si es posible. A la entrada, mx_ul_tsrcv: PUSH ES:[DI-16] ; ...TSR del convenio en ES:DI

; 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] ; offset a la tabla de vectores

; 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 ; número de vectores en CX

mx_ul_buscav: CMP AL,ES:[DI]

mx_unload PROC JE mx_ul_usavect ; este TSR usa vector analizado

PUSH ES ADD DI,5

CALL mx_ul_tsrcv? LOOP mx_ul_buscav

JNC mx_ul_able ADD SP,4 ; no lo usa

POP ES JMP mx_ul_otro

RET mx_ul_usavect: POP CX ; tamaño del TSR

mx_ul_able: XOR AL,AL POP BX ; segmento del TSR

XCHG AH,AL CMP DX,BX

MOV BP,AX ; BP=entrada Multiplex del TSR JB mx_ul_otro ; la INT xx no le apunta

MOV CX,2 ADD BX,CX

mx_ul_pasada: PUSH CX ; siguiente pasada CMP DX,BX

LEA SI,tabla_vectores JA mx_ul_otro ; la INT xx le apunta

MOV CL,ES:[SI-1] PUSH AX

MOV CH,0 ; CX = nº vectores XOR AL,AL

mx_ul_masvect: POP AX XCHG AH,AL

PUSH AX ; pasada en curso CMP AX,BP ; ¿es el propio TSR?

DEC AL POP AX

PUSH CX JNE mx_ul_chain ; no

mx_ul_2f: MOV AL,ES:[SI] ; vector en curso POP ES ; sí: ¡posible reponer vector!

JNZ mx_ul_pasok POP CX

CMP CX,1 ; ¿último vector? POP BX

JNE mx_ul_noult PUSH BX

MOV AL,2Fh PUSH CX

LEA SI,tabla_vectores PUSH ES

mx_ul_busca2f: CMP ES:[SI],AL ; ¿INT 2Fh? DEC BX

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

mx_ul_noult: CMP AL,2Fh ; ¿restaurar INT 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

mx_ul_pasok: 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 DS
161 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

mx_ul_norest: POP ES POP AX

POP CX STC ; CF=1

ADD SI,5 ; siguiente vector RET

DEC CX mx_ul_tsroff DW 0

JZ mx_unloadable ; no más, ¡desinstal-ar/ado! mx_ul_tsrseg DW 0

JMP mx_ul_masvect mx_unload ENDP

mx_ul_chain: MOV CS:mx_ul_tsroff,DI ; ES:DI almacena la dirección

MOV CS:mx_ul_tsrseg,ES ; de la variable vector ; ------------ imprimir cadena en DS:DX delimitada por un '$'

MOV DX,ES:[DI+1]

MOV CL,4 print PROC

SHR DX,CL PUSH AX

MOV CX,ES:[DI+3] MOV AH,9

ADD DX,CX ; INT xx en DX (aprox.) INT 21h

MOV AH,0BFh POP AX

mx_ul_otro: INC AH ; a por otro TSR RET

JZ mx_ul_exitnok ; ¡se acabaron! print ENDP

JMP mx_ul_masmx

mx_ul_exitnok: ADD SP,6 ; equilibrar pila

POP 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ª xms_ins DB 0 ; a 1 si presente controlador XMS

mx_ul_exitok: TEST ES:info_extra,111b ; ¿tipo de instalación? gestor_XMS LABEL DWORD ; dirección del controlador XMS

MOV ES,ES:segmento_real ; segmento real del bloque XMS_off DW 0

JZ mx_ul_freeml ; cargado en RAM convencional XMS_seg DW 0

CMP xms_ins,1

JNE mx_ul_freeml ; no hay controlador XMS (¿?) 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 ; liberar memoria superior tsr_dir LABEL DWORD ; dirección de la copia residente

POP ES tsr_off DW 0

CLC tsr_seg DW 0

RET

mx_ul_freeml: MOV AH,49h offsets_ints DW 4 ; número de vectores interceptados

INT 21h ; liberar bloque de memoria ES: DB 8 ; tabla de offsets de los vectores

POP ES DW ges_int08 ; de interrupción interceptados

CLC

RET

mx_ul_tsrcv?: PUSH AX ; ¿es TSR del convenio?...

PUSH ES

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 ; CF=0

POP AX

RET

mx_ul_ncvexit: POP DI ; ...no es TSR del convenio

POP ES
PROGRAMAS RESIDENTES 161

DB 9 DB " (c) 1992 CiriSOFT, (c) Grupo Universitario de Informática - "

DW ges_int09 DB "Valladolid.",13,10,10

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 " /A Indica una hora de alarma y activa la misma; con /A=ON o "

DW ges_int2F DB "/A=OFF se puede",13,10

DB " controlar a posteriori la habilitación de la alarma. Tras "

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 " desactivada (hasta un posterior /A=ON o bien /A=hh:mm:ss). "

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 " celar siempre el sonido pulsando Ctrl-Alt-R o AltGr-R "

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 " ON y OFF Controlan la aparición del reloj en pantalla. "

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 " AltGr-R ó Ctrl-Alt-R con el reloj ya instalado y sin "

param_c DB 0 ; a 1 si se indicó /C DB "sonido en curso.",13,10

DB " /T Indica el nivel de avisos sonoros del reloj: 0 ninguno; 1 "

rclock_txt DB 13,10," RCLOCK v2.3$" DB "señal horaria;",13,10

DB " 2, a las medias; 4 a los cuartos y 5 cada cinco minutos. "

instalado_txt DB " instalado.",13,10,"$" DB "Cada uno de los",13,10

DB " niveles incluye a su vez a los anteriores. Por defecto, "

ya_install_txt DB " ya instalado.",13,10 DB "/T=1.",13,10

DB " - Parámetros indicados actualizados." DB " /X e /Y Indican las coordenadas de pantalla donde se "

DB 13,10,"$" DB "imprimirá el reloj; su",13,10

DB " valor varía según el modo de pantalla. Las coordenadas son "

tabla_err DW err0_txt, err1_txt, err2_txt, err3_txt DB "siempre refe-",13,10

DW err4_txt,err5_txt, err6_txt, err7_txt DB " ridas al modo texto, aunque la pantalla esté en modo "

ini_err_txt DB 13,10," - Error: $" DB "gráfico. Para /X=72",13,10

err0_txt DB "sintaxis incorrecta$" DB " (valor por defecto) el reloj no se imprimirá realmente en "

err1_txt DB "hora de alarma incorrecta$" DB "la columna 72,",13,10

err2_txt DB "parámetro no admitido: /$" DB " sino lo más a la derecha posible según el modo de vídeo "

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 " /C Indica los atributos de color en que aparece el reloj."

err5_txt DB "parámetro fuera del rango 0..59: /$" DB 13,10

err6_txt DB "parámetro fuera del rango 0..255: /$" DB " /U Permite desinstalar el programa de la memoria si ello es "

err7_txt DB "necesario numéro en el parámetro /$" DB "posible.",13,10

fin_err_txt DB 13,10 DB " /ML Fuerza la instalación en memoria convencional -por defecto "

DB " Ejecute RCLOCK /? para obtener ayuda." DB "se cargará en",13,10

DB 13,10,7,"$" DB " memoria superior XMS o en su ausencia en la administrada "

DB "por el DOS 5.0-",13,10,"$"

mal_ver_txt1 DB " - Error: ya está instalada la versión $"

mal_ver_txt2 DB " de este programa.",13,10,7,"$" rclock ENDS

END inicio

des_ok_txt DB " desinstalado.",13,10,"$"

des_no_ok_txt DB 13,10," - Desinstalación imposible (se ha "

DB "instalado después un programa"

DB 13,10," que no respeta el convenio y tiene "

DB "alguna interrupción común).",13,10,7,"$"

imp_desins_txt DB 13,10," - Programa aún no instalado: "

DB "imposible desinstalarlo.",13,10,"$"

nocabe_txt DB ": Instalación imposible.",13,10

DB " Ya hay 64 programas residentes con la "

DB "misma técnica.",13,10,"$"

ayuda_txt LABEL BYTE

DB 13,9,9,"RCLOCK v2.3 - Utilidad de reloj-alarma residente.",13,10


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.
PROGRAMAS RESIDENTES 161

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.

; ******************************************************************** ; 011: *.SYS formato EXE

; * * ; bit 7 a 1: «extension_id» definida

; * SCRCAP 1.0 * multiplex_id DB 0 ; número Multiplex de este TSR

; * * vectores_id DW tabla_vectores

; * Utilidad residente de captura de pantallas de texto. * extension_id DW tabla_extra

; * * DB "*##*"

; ******************************************************************** autor_nom_ver DB "CiriSOFT:SCRCAP:1.0",0

; ------------ Macros de propósito general DB 6 ; vectores de interrupción interceptados

tabla_vectores EQU $

XPUSH MACRO RM DB 8 ; INT 8

IRP reg, <RM> ant_int08 LABEL DWORD ; dirección original

PUSH reg ant_int08_off DW 0

ENDM ant_int08_seg DW 0

ENDM DB 9 ; INT 9

ant_int09 LABEL DWORD ; dirección original

XPOP MACRO RM ant_int09_off DW 0

IRP reg, <RM> ant_int09_seg DW 0

POP reg DB 13h ; INT 13h

ENDM ant_int13 LABEL DWORD ; dirección original

ENDM ant_int13_off DW 0

ant_int13_seg DW 0

; ------------ Programa DB 21h ; INT 21h

ant_int21 LABEL DWORD ; dirección original

scrcap SEGMENT ant_int21_off DW 0

ASSUME CS:scrcap, DS:scrcap ant_int21_seg DW 0

DB 28h ; INT 28h

ORG 100h ant_int28 LABEL DWORD ; dirección original

ant_int28_off DW 0

ini_residente EQU $ ant_int28_seg DW 0

DB 2Fh ; INT 2Fh

inicio: JMP main ant_int2F LABEL DWORD ; dirección original

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 ; campo reservado

longitud_total DW 0 ; zona de memoria ocupada (párrafos)

info_extra DB 80h ; bits 0, 1 y 2-> 000: normal, con PSP ctrl_exterior LABEL BYTE

; 001: bloque UMB XMS reubicabilidad DB 1 ; programa 100% reubicable

; 010: *.SYS activacion DW act


161 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

act DW 1 STI

CMP AH,CS:multiplex_id

; ------------ Variables internas JE preguntan

JMP CS:ant_int2F ; saltar al gestor de INT 2Fh

dosver DW ? ; versión del DOS preguntan: CMP DI,1992h

ega DB ON ; a ON si EGA o superior JNE ret_no_info ; no llama alguien del convenio

activo DB OFF MOV AX,ES

inminente DB OFF CMP AX,1492h

marcas DB 8 ; Por defecto, Alt... JNE ret_no_info ; no llama alguien del convenio

cod_rastreo DB 54h ; ...SysReq (PetSys) PUSH CS

in13 DW 0 POP ES ; sí llama: darle información

in28 DW 0 LEA DI,autor_nom_ver

indos LABEL DWORD ret_no_info: MOV AX,0FFFFh ; "entrada multiplex en uso"

indos_off DW ? IRET

indos_seg DW ? ges_int2F ENDP

crit_err LABEL DWORD

crit_err_off DW ? ; ------------ Rutina de gestión de INT 8

crit_err_seg DW ?

ant_pila_off DW ? ges_int08 PROC

ant_pila_seg DW ? PUSHF

mainpsp DW ? ; PSP del programa principal CALL CS:ant_int08

maindta LABEL DWORD ; DTA del programa principal STI

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 exit_08: IRET

errinfo_bx DW ? ges_int08 ENDP

errinfo_cx DW ?

DW 8 DUP (0) ; DX, SI, DI, DS, ES, etc. ; ------------ Rutina de gestión de INT 9

ret_off DW ?

ret_seg DW ? ges_int09 PROC

ret_flags DW ? STI

PUSH AX

DB 192 DUP ("PILA") ; 0,75 Kb de pila IN AL,60h

pila_ini EQU $ PUSHF

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

local_ints DW 3 PUSH DS

DB 1Bh ; INT 1Bh MOV DS,AX

DW ges_int1B ; nueva dirección MOV AL,DS:[17h]

ant_int1B LABEL DWORD ; dirección original POP DS

ant_int1B_off DW 0 AND AL,15

ant_int1B_seg DW 0 CMP AL,CS:marcas ; ¿marcas de activación?

DB 23h ; INT 23h JNE fin_09

DW ges_int23 ; nueva dirección CALL proceso_tsr ; ejecutar TSR si es posible

ant_int23 LABEL DWORD ; dirección original fin_09: POP AX

ant_int23_off DW 0 IRET

ant_int23_seg DW 0 ges_int09 ENDP

DB 24h ; INT 24h

DW ges_int24 ; nueva dirección ; ------------ Rutina de gestión de INT 13h

ant_int24 LABEL DWORD ; dirección original

ant_int24_off DW 0 ges_int13 PROC FAR ; gestionar INT 13h

ant_int24_seg DW 0 STI

PUSHF

; ------------ Rutina de gestión de INT 2Fh INC CS:in13 ; indicar entrada en INT 13h

CALL CS:ant_int13

ges_int2F PROC FAR PUSHF ; mucho cuidado con los flags


PROGRAMAS RESIDENTES 161

DEC CS:in13 ; salida de INT 13h INC CS:in28 ; dentro de INT 28h

POPF CALL proceso_tsr ; ejecutar código del TSR

RET 2 ; retornar sin tocar flags DEC CS:in28 ; fuera de INT 28h

ges_int13 ENDP exit_28: JMP CS:ant_int28

ges_int28 ENDP

; ------------ Rutinas de gestión de INT 1Bh, 23h y 24h.

; ------------ Rutina de control de ejecución del TSR

ges_int1B EQU THIS BYTE ; gestionar INTs 1Bh/23h

ges_int23 PROC proceso_tsr PROC ; ejecutar TSR si se puede

IRET ; ignorar Ctrl-C y Ctrl-Break CMP CS:in28,0

ges_int23 ENDP JNE proceder ; dentro de INT 28h

CMP CS:in13,0

ges_int24 PROC ; gestionar INT 24h JA no_proceder ; INT 13h en curso

STI XPUSH <DS, BX, AX>

MOV AX,3 ; función de fallo LDS BX,CS:crit_err

CMP CS:dosver,300h MOV AL,[BX]

JAE ret_int24 LDS BX,CS:indos

XOR AX,AX ; 0 en DOS 2.x OR AL,[BX] ; crit_err OR indos

ret_int24: IRET AND AL,AL

ges_int24 ENDP XPOP <AX, BX, DS>

JZ proceder ; se cumple que ambos a 0

; ------------ Rutina de gestión de INT 21h no_proceder: MOV CS:inminente,ON ; esperar próxima INT 8/28h

RET

ges_int21 PROC FAR proceder: CLI ; a comprobar semáforo...

POP 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 STI ; ...semáforo comprobado

PUSH CS:ret_off ; dejar sólo segmento:offset MOV CS:inminente,OFF ; ya atendida la petición

PUSH CS:ret_flags MOV CS:ant_pila_off,SP

CALL CS:ant_int21 MOV CS:ant_pila_seg,SS ; preservar pila

PUSHF 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

exit_21: POPF STI

RET ; retornar sin alterar flags XPUSH <AX, BX, CX, DX, SI, DI, BP, DS, ES>

ges_int21 ENDP XPUSH <CS, CS>

XPOP <DS, ES> ; DS y ES apuntan al TSR

; ------------ Rutina de gestión de INT 28h CALL pushset_ints

CALL pushset_psp

ges_int28 PROC ; gestionar INT 28h CALL pushset_dta

STI CALL push_crit_err

CMP CS:activo,ON CALL kbuff_limp

JE exit_28 ; TSR ya activo CALL tarea_TSR ; ejecutar proceso residente

CMP CS:inminente,ON CALL pop_crit_err

JNE exit_28 ; no hay que activarlo CALL pop_dta

CMP CS:in13,0 CALL pop_psp

JA exit_28 ; INT 13h en curso CALL pop_ints

XPUSH <DS, BX> XPOP <ES, DS, BP, DI, SI, DX, CX, BX, AX>

LDS BX,CS:crit_err CLI

CMP BYTE PTR [BX],0 ; ¿error crítico? MOV SP,CS:ant_pila_seg

XPOP <BX, DS> MOV SS,SP

JNE exit_28 MOV SP,CS:ant_pila_off ; pila restaurada

XPUSH <DS, BX> MOV CS:activo,OFF

LDS BX,CS:indos exit_proceso: STI

CMP BYTE PTR [BX],1 ; ¿Indos>1? RET

XPOP <BX, DS> proceso_tsr ENDP

JA exit_28
161 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

; ------------ Subrutinas de apoyo PUSH BX

MOV AH,50h

pushset_ints PROC ; interceptar INT 1Bh/23h/24h MOV BX,segmento_real

PUSH ES INT 21h ; activar nuevo PSP

LEA SI,local_ints POP BX

MOV CX,[SI] psp_ok: MOV mainpsp,BX

phst_otro: PUSH CX RET

MOV AL,[SI+2] pushset_psp ENDP

MOV AH,35h

INT 21h pop_psp PROC ; restaurar PSP programa principal

MOV [SI+5],BX PUSH DS

MOV [SI+7],ES ; INT xx preservada MOV AX,dosver

MOV DX,[SI+3] CMP AH,2

MOV AL,[SI+2] JA setpsp3

MOV AH,25h LDS BX,crit_err ; en DOS 2.x ...

INT 21h ; INT xx desviada 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 ; restaurar PSP

RET POP BX

pushset_ints ENDP MOV BYTE PTR [BX],0 ; anular error crítico

JMP psp_poped

pop_ints PROC ; restaurar vectores INT 1Bh/23h/24h setpsp3: MOV AH,50h ; DOS 3+

PUSH DS MOV BX,mainpsp

LEA SI,local_ints INT 21h ; restaurar PSP

MOV CX,[SI] psp_poped: POP DS

pop_otro: PUSH CX RET

MOV AL,CS:[SI+2] pop_psp ENDP

MOV AH,25h

MOV DX,CS:[SI+5] pushset_dta PROC

MOV DS,CS:[SI+7] XPUSH <DS, ES>

INT 21h ; INT xx restaurada MOV AH,2Fh

ADD SI,7 INT 21h

POP CX MOV maindta_off,BX

LOOP pop_otro MOV maindta_seg,ES ; almacenar DTA activo

POP DS MOV AH,1Ah

RET MOV DX,80h

pop_ints ENDP MOV DS,segmento_real

INT 21h ; establecer nuevo DTA

pushset_psp PROC ; preservar PSP y activar el nuevo XPOP <ES, DS>

MOV AX,dosver RET

CMP AH,2 pushset_dta ENDP

JA getpsp3

PUSH DS ; en DOS 2.x ... pop_dta PROC

LDS DI,crit_err PUSH DS

MOV BYTE PTR [DI],0FFh ; forzar error crítico MOV AH,1Ah

MOV AH,51h MOV DX,maindta_off

INT 21h ; BX = PSP activo (DOS 2.x) MOV DS,maindta_seg

PUSH BX INT 21h ; restaurar DTA

MOV AH,50h POP DS

MOV BX,CS:segmento_real RET

INT 21h ; activar nuevo PSP pop_dta ENDP

MOV BYTE PTR [DI],0 ; anular error crítico

POP BX push_crit_err PROC

POP DS CMP dosver,300h

JMP psp_ok JB push_crit_fin ; necesario DOS 3.0+

getpsp3: MOV AH,62h MOV AH,59h

INT 21h ; BX = PSP activo (DOS 3+) MOV BX,0


PROGRAMAS RESIDENTES 161

INT 21h

MOV errinfo_ax,AX ; preservar información de init_nomfich PROC

MOV errinfo_bx,BX ; errores críticos PUSH DS

MOV errinfo_cx,CX MOV AX,40h

push_crit_fin: RET MOV DS,AX

push_crit_err ENDP MOV AX,DS:[4Ah] ; anchura de pantalla

POP DS

pop_crit_err PROC MOV AH,AL

CMP dosver,300h SHR AH,1

JB pop_crit_fin ; necesario DOS 3.0+ SHR AH,1

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' ; binario -> hex

pop_crit_fin: RET ; errores críticos CMP AL,'9'

pop_crit_err ENDP JBE al_es_hex

ADD AL,'A'-'9'-1

kbuff_limp PROC ; limpiar buffer del teclado al_es_hex: CMP AH,'9'

MOV AH,1 JBE ah_es_hex

INT 16h ADD AH,'A'-'9'-1

JZ kbuff_limpio ah_es_hex: XCHG AH,AL

MOV AH,0 MOV WORD PTR fich_nom+3,AX ; anchura de pantalla

INT 16h RET

JMP kbuff_limp init_nomfich ENDP

kbuff_limpio: RET

kbuff_limp ENDP ; ------------ Obtener segmento de vídeo y tamaño de la pantalla

; ------------ Proceso residente que puede emplear el DOS dscx_eq_video PROC ; devolver CX = tamaño pantalla

MOV AX,40h ; y apuntar DS a la misma

tarea_TSR PROC MOV DS,AX

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 ; abrir fichero 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 ; grabar pantalla JNE modo_ok ; tarjeta modesta

JC tarea_err XOR AH,AH

PUSH CS MOV AL,DS:[84h]

POP DS INC AL ; AX = líneas EGA/VGA

MOV BX,fich_handle modo_ok: MUL WORD PTR DS:[4Ah] ; líneas*columnas = caracteres

MOV AH,3Eh SHL AX,1 ; AX = tamaño buffer de vídeo

INT 21h ; cerrar fichero MOV CX,AX

JC tarea_err video_ok: MOV DS,BX

CALL inc_nombre ; preparar futuro nombre RET

CALL sonidoDown dscx_eq_video ENDP

RET

tarea_err: PUSH CS ; ------------ Incrementar número de fichero para siguiente vez

POP DS

RET inc_nombre PROC

tarea_TSR ENDP LEA BX,fich_nom

MOV AX,[BX+6]

; ------------ Inicializar nombre de fichero con anchura de pantalla. INC AH


161 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 ; activar sonido

JBE inc_ok MOV AL,182

MOV AL,'9' JMP SHORT $+2

inc_ok: MOV [BX+6],AX JMP SHORT $+2

RET OUT 43h,AL ; preparar canal 2

inc_nombre ENDP POP AX

RET

; ------------ Sonido ascendente sonidoON ENDP

sonidoUp PROC ; ------------ Inhibir sonido

CALL espera55ms

CALL sonidoON sonidoOFF PROC

MOV AX,2400 PUSH AX

MOV CX,18 IN AL,61h

sonar_arriba: CALL sonidoAX AND AL,255-3

CALL espera55ms JMP SHORT $+2

SUB AX,30 JMP SHORT $+2

LOOP sonar_arriba OUT 61h,AL ; desactivar sonido

CALL sonidoOFF POP AX

RET RET

sonidoUp ENDP sonidoOFF ENDP

; ------------ Sonido descendente ; ------------ Programar la nota AX en el temporizador

sonidoDown PROC sonidoAX PROC

CALL espera55ms PUSH AX

CALL sonidoON OUT 42h,AL

MOV AX,3000 MOV AL,AH

MOV CX,18 JMP SHORT $+2

sonar_abajo: CALL sonidoAX JMP SHORT $+2

CALL espera55ms OUT 42h,AL ; canal 2 del 8253 programado

ADD AX,30 POP AX

LOOP sonar_abajo RET

CALL sonidoOFF sonidoAX ENDP

RET

sonidoDown ENDP ; ------------ Fin del área residente

; ------------ Pausa de 55 milisegundos fin_residente EQU $

espera55ms PROC bytes_resid EQU fin_residente-ini_residente

XPUSH <AX, DS>

MOV AX,40h parrafos_resid EQU (bytes_resid+15)/16

MOV DS,AX

STI ; por si acaso

MOV AL,DS:[6Ch] ; *****************************

espera_tic: CMP AL,DS:[6Ch] ; * *

JE espera_tic ; * I N S T A L A C I O N *

XPOP <DS, AX> ; * *

RET ; *****************************

espera55ms ENDP

main PROC

; ------------ Activar sonido LEA DX,scrcap_txt ; mensaje inicial

CALL print

sonidoON PROC CALL inic_general ; inicializar ciertas variables

PUSH AX CALL detectarEGA


PROGRAMAS RESIDENTES 161

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

params_ok: 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 ; terminar residente

CALL adaptar_param ; parámetros en copia residente fin_noresid: MOV AX,4C00h

LEA DX,ya_install_txt INT 21h ; terminar no residente

CALL print main ENDP

CALL info_ya_ins ; informar de teclas activación

JMP fin_noresid

desinst: MOV ES,tsr_seg ; *************************************

MOV AH,ES:multiplex_id ; * *

CALL mx_unload ; desinstalarlo: ; * SUBRUTINAS PARA LA INSTALACION *

LEA DX,des_ok_txt ; * *

JNC no_pesame ; ha sido posible ; *************************************

LEA DX,des_no_ok_txt ; no es posible

no_pesame: CALL print ; ------------ Extraer posibles parámetros de la línea de comandos

JMP fin_noresid

no_residente: CMP AX,0 ; ¿reside una versión distinta? obtener_param PROC

JE instalable ; no: se admite instalación MOV BX,81h ; apuntar a zona de parámetros

CALL error_version ; error de versión incompatible otro_pmt_mas: CALL saltar_esp ; saltar delimitadores

JMP fin_noresid JNC otro_pmt ; quedan más parámetros

instalable: CMP param_u,1 ; no residente: ¿desinstalar? JMP fin_proc_pmt ; no más parámetros

JNE instalar ; no lo piden otro_pmt: CMP AL,'/'

LEA DX,imp_desins_txt ; lo piden, ¡serán despistados! JE pmt_barrado ; parámetro precedido por '/'

CALL print CMP AL,'?'

JMP fin_noresid JE pmt_hlp

instalar: MOV AX,parrafos_resid ; área residente JMP mal_proc_pmt

ADD AX,16 ; 256 bytes de PSP (completo) pmt_barrado: INC BX

MOV memoria,AX MOV AL,[BX] ; letra del parámetro

CALL mx_get_handle ; obtener entrada Multiplex CMP AL,13 ; ¿fin de mandatos?

JNC handle_ok JE mal_proc_pmt ; falta parámetro

LEA DX,nocabe_txt ; no quedan entradas CMP AL,'?'

CALL print JE pmt_hlp

JMP fin_noresid OR AL,' ' ; poner en minúsculas

handle_ok: 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 ; parámetro /S=

CALL preservar_ints ; tomar nota de vectores CMP AL,'t'

CMP param_ml,0 ; ¿se indicó parámetro /ML? JE pmt_T ; parámetro /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" ; ¿parámetro /ML?

CALL UPPER_alloc ; pedir memoria superior DOS 5 JE pmt_ML

JC instalar_ml ; no hay la suficiente mal_proc_pmt: STC ; error en parámetro(s)

STC ; indicar que usa memoria DOS RET

instalar_umb: MOV ES,AX ; segmento del bloque UMB fin_proc_pmt: CLC ; parámetros procesados ok.

MOV DI,256 ; ES:256 zona a donde reubicar RET

CALL inicializa_id ; inicializar identificación pmt_hlp: MOV param_ayuda,1

CALL reubicar_prog ; reubicar el programa a ES:DI JMP mal_proc_pmt ; «error» de ayuda

CALL activar_ints ; interceptar vectores pmt_S: MOV param_s,1

JMP fin_noresid ; programa instalado «arriba» CALL get_num

instalar_ml: STC JC mal_proc_pmt


161 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

MOV marcas,AL JE fin_num

CMP AX,15 CMP AL,32 ; fin número

JA fuera_rango JE fin_num

AND AL,AL CMP AL,9 ; fin número

JZ fuera_rango JE fin_num

JMP otro_pmt_mas CMP AL,'/' ; fin número (otro parámetro)

fuera_rango: MOV marcas,255 JE fin_num

JMP mal_proc_pmt CMP AL,':' ; fin número (otro dato)

pmt_T: 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

pmt_U: MOV param_u,1 fin_num: MOV SI,BX

INC BX DEC SI

JMP otro_pmt_mas XOR DX,DX

pmt_ML: MOV param_ml,1 ; en efecto MOV AX,1 ; AX = 10 elevado a la 0 = 1

ADD BX,2 otro_car: DEC BX ; próximo carácter a procesar

JMP otro_pmt_mas MOV CL,[BX]

obtener_param ENDP CMP CL,'='

JE ok_num ; delimitador: fin de número

; ------------ Saltar espacios, tabuladores, ... buscando un parámetro CMP CL,':'

JE ok_num ; delimitador: fin de número

saltar_esp: MOV AL,[BX] CMP CL,'.'

INC BX JNE no_millar ; saltar los puntos de millar

CMP AL,9 ; carácter tabulador CMP AX,1000

JE saltar_esp JE otro_car

CMP AL,32 ; espacio en blanco JMP mal_num ; separador millar descolocado

JE saltar_esp no_millar: CMP CL,'0'

CMP AL,0Dh ; fin de zona de parámetros JB mal_num

JE fin_param CMP CL,'9'

DEC BX ; puntero al primer carácter JA mal_num

CLC ; hay parámetro SUB CL,'0' ; pasar ASCII a binario

RET MOV CH,0 ; CX = 0 .. 9

fin_param: STC ; no hay parámetro PUSH AX ; AX = 10 elevado a la N

RET AND AX,AX

JNZ multiplica

; ------------ Obtener número chequeando delimitadores /= y /: AND CL,CL

JNZ mal_num_pop ; a la izda sólo permitir ceros

get_num: INC BX multiplica: PUSH DX ; tras completar 5º dígito

MOV AL,[BX] MUL CX

INC BX POP DX

CMP AL,'=' JC mal_num_pop

JE delimit_ok ADD DX,AX ; DX = DX + digito (CX) * 10 ^ N (AX)

CMP AL,':' JC mal_num_pop

JE delimit_ok POP AX

err_sintax: STC ; sintaxis incorrecta CMP AX,10000

RET JNE potencia ; AX*10 no se desbordará

delimit_ok: 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 potencia: MOV DI,10

INC BX PUSH DX ; no manchar DX al multiplicar

RET MUL DI ; AX = AX elevado a la (N+1)

POP DX

; ------------ Extraer nº de 16 bits y depositarlo en AX; al final, el 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

STC ; condición de error

obtener_num PROC RET

CMP AL,0Dh ; fin zona parámetros y número ok_num: MOV BX,SI ; número correcto
PROGRAMAS RESIDENTES 161

MOV AX,DX ; resultado INT 2Fh ; sí: obtener su dirección

CLC ; condición de Ok. MOV XMS_off,BX ; y preservarla

RET MOV XMS_seg,ES

obtener_num ENDP MOV xms_ins,1

POP ES

; ------------ Mensajes de error / ayuda RET

XMS_ausente: MOV xms_ins,0

info_err_param PROC RET

CMP param_ayuda,1 inic_XMS ENDP

JNE 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

RET ; «tsr_off» inicializadas apuntando a la cadena de

otro_error: LEA DX,err_sintax_txt ; identificación de la copia residente. Si CF=1, el

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

err_ok: CALL print residente? PROC

LEA DX,err_sintax_fin PUSH CX

CALL print PUSH SI

RET PUSH DI

info_err_param ENDP PUSH ES

PUSH AX

; ------------ Ya está instalada otra versión distinta del programa LEA DI,autor_nom_ver ; identificación del programa

MOV SI,DI

error_version PROC 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 ; tamaño autor+programa+versión

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 DL,ES:[DI] ; número de versión MOV tsr_off,DI ; anotar la dirección programa

MOV AH,2 MOV tsr_seg,ES ; por si estaba instalado

INT 21h POP AX

MOV DL,'.' JNC resid_ok ; CF=0 -> programa ya residente

MOV AH,2 POP ES

INT 21h PUSH ES

MOV DL,ES:[DI+2] ; revisión 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

RET SUB DI,SI

error_version ENDP MOV CX,DI ; tamaño autor+programa

MOV AX,1492h

; ------------ Considerar presencia de controlador XMS MOV ES,AX

MOV DI,1992h ; ES:DI protocolo de búsqueda

inic_XMS PROC CALL mx_find_tsr ; buscar si está en memoria

MOV AX,4300h MOV tsr_off,DI ; anotar dirección del programa

INT 2Fh ; chequear presencia XMS MOV tsr_seg,ES ; por si instalada otra versión

CMP AL,80h MOV AX,0

JNE XMS_ausente ; no instalado JC resid_ok ; CF=1, AX=0 -> no residente

PUSH ES MOV AX,1

MOV AX,4310h STC ; CF=1, AX=1 -> sí: otra vers.


161 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

resid_ok: 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

residente? ENDP JZ alt?

LEA DX,act_ctrl

; ------------ Inicializar ciertas variables CALL print_

alt?: TEST AL,8

inic_general PROC JZ shift_izq?

XPUSH <ES, DS> ; ** LEA DX,act_alt

MOV AH,30h CALL print_

INT 21h shift_izq?: TEST AL,2

XCHG AH,AL JZ shift_der?

MOV dosver,AX ; versión del DOS LEA DX,act_shift_izq

CALL inic_XMS ; detectar controlador XMS CALL print_

MOV AH,34h shift_der?: TEST AL,1

INT 21h JZ fin

MOV indos_off,BX LEA DX,act_shift_der

MOV indos_seg,ES ; dirección de InDOS CALL print_

INC BX fin: CMP cod_rastreo,0

CMP dosver,300h JE no_mas_teclas

JB crit_ok ; Critical Error detrás en 2.x LEA DX,act_c_txt

SUB BX,2 CMP AH,54h

CMP dosver,300h JE act_ok

JE crit_ok ; Critical Error antes en 3.0 LEA DX,act_otra_txt

MOV AX,5D06h act_ok: CALL print_

INT 21h no_mas_teclas: LEA DX,act_fin_txt

XPUSH <DS, SI> CALL print

XPOP <BX, ES> RET

crit_ok: POP DS ; * print_: CALL print

MOV crit_err_off,BX PUSH AX

MOV crit_err_seg,ES ; dirección de ese flag MOV DL,'-'

POP ES ; * MOV AH,2

RET INT 21h

inic_general ENDP POP AX

RET

; ------------ Detectar EGA o tarjeta superior info_ya_ins ENDP

detectarEGA PROC ; ------------ Adaptar parámetros de un SCRCAP ya instalado en memoria

MOV BL,10h

MOV AH,12h adaptar_param PROC

INT 10h ; pedir información EGA al BIOS PUSH ES

CMP BL,10h MOV ES,tsr_seg

MOV AL,OFF CMP param_s,1

JE ega_ini ; no es EGA JNE s_ok

MOV AL,ON MOV AL,marcas

ega_ini: MOV ega,AL MOV ES:marcas,AL

RET s_ok: CMP param_t,1

detectarEGA ENDP JNE c_ok

MOV AL,cod_rastreo

; ------------ Informar de las teclas que activan SCRCAP MOV ES:cod_rastreo,AL

c_ok: POP ES

info_ya_ins PROC RET

PUSH DS adaptar_param ENDP

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

tec_no_res: MOV AL,marcas ; y CF=1 si se utiliza memoria superior XMS.


PROGRAMAS RESIDENTES 161

CMP xms_ins,1

inicializa_id PROC JNE no_umb_disp ; no hay controlador XMS

PUSHF MOV DX,AX ; número de párrafos

MOV segmento_real,ES ; anotar segmento del bloque MOV AH,10h ; solicitar memoria superior

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

POPF ; CF=0: usar memoria UMB XMS POP DX ; ok

JNC info_ok POP CX

DEC AL ; usar memoria convencional POP BX

info_ok: OR info_extra,AL CLC

RET RET

inicializa_id ENDP no_umb_disp: MOV AX,0

XMS_fallo: POP DX

; ------------ Preservar vectores de interrupción previos POP CX

POP BX

preservar_INTs PROC STC

PUSH ES RET

PUSH DI UMB_alloc ENDP

LEA DI,tabla_vectores

MOV CL,[DI-1] ; ------------ Reservar memoria superior, con DOS 5.0, del tamaño

MOV CH,0 ; CX vectores interceptados ; solicitado (AX párrafos). Si no hay bastante CF=1,

otro_vector: PUSH CX ; en caso contrario devuelve el segmento en AX.

PUSH DI

MOV AH,35h UPPER_alloc PROC

MOV AL,[DI] PUSH AX

INT 21h ; obtener vector de INT xx MOV AH,30h

POP DI INT 21h

POP CX CMP AL,5

MOV [DI+1],BX ; anotar donde apunta POP AX

MOV [DI+3],ES JAE UPPER_existe

ADD DI,5 STC

LOOP otro_vector ; repetir con los restantes JMP UPPER_fin ; necesario DOS 5.0 mínimo

POP DI UPPER_existe: PUSH AX ; preservar párrafos...

POP ES MOV AX,5800h

RET INT 21h

preservar_INTs ENDP MOV alloc_strat,AX ; preservar estrategia

MOV AX,5802h

; ------------ Liberar espacio de entorno INT 21h

MOV umb_state,AL ; preservar estado UMB

free_environ PROC MOV AX,5803h

PUSH ES MOV BX,1

MOV ES,DS:[2Ch] ; dirección del entorno INT 21h ; conectar cadena UMB's

MOV AH,49h MOV AX,5801h

INT 21h ; liberar espacio de entorno MOV BX,41h

POP ES INT 21h ; High Memory best fit

RET POP BX ; ...párrafos requeridos

free_environ ENDP MOV AH,48h

INT 21h ; asignar memoria

; ------------ Reservar bloque de memoria superior del nº párrafos AX, PUSHF

; devolviendo en AX el segmento donde está. CF=1 si no PUSH AX ; guardado el resultado

; 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 ; restaurar estrategia

UMB_alloc PROC MOV AX,5803h

PUSH BX MOV BL,umb_state

PUSH CX XOR BH,BH

PUSH DX INT 21h ; restaurar estado cadena UMB


161 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

POP AX SUB CX,AX

POPF MOV DS,CX

JC UPPER_fin ; hubo fallo LEA SI,offsets_ints

PUSH DS MOV CX,CS:[SI] ; CX vectores a desviar

DEC AX ADD SI,2

MOV DS,AX desvia_otro: 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 ; desviar INT xx a DS:DX

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 activar_INTs ENDP

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

CLD ; residentes instalados con esta técnica). Si CF=0, se

REP MOVSB ; copiar nombre de programa ; devuelve en AH un valor de entrada libre en la INT 2Fh.

POP ES

POP DS mx_get_handle PROC

CLC MOV AH,0C0h

UPPER_fin: RET mx_busca_hndl: PUSH AX

UPPER_alloc ENDP MOV AL,0

INT 2Fh

; ------------ Reubicar programa residente a su dirección definitiva. CMP AL,0FFh

; Se copia también el PSP. POP AX

JNE mx_si_hueco

reubicar_prog PROC INC AH

PUSH DI JNZ mx_busca_hndl

LEA SI,ini_residente mx_no_hueco: STC

MOV CX,bytes_resid RET

CLD mx_si_hueco: CLC

REP MOVSB RET

XOR SI,SI mx_get_handle ENDP

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 ; nuevo segmento de la JFT ; 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

reubicar_prog ENDP ; mismo. Si no, CF=1 y ningún registro alterado.

; ------------ Desviar vectores de interrupción a las nuevas rutinas. mx_find_tsr PROC

; Se tendrá en cuenta que está ensambladas para correr en MOV AH,0C0h

; un offset inicial (100h) y que el offset real en que mx_rep_find: 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

activar_INTs PROC PUSH DI

PUSH CX MOV AL,0

PUSH DS ; preservar DS para el retorno PUSH CX

MOV AX,100h INT 2Fh

SUB AX,DI ; AX = 100h-DI POP CX

MOV CL,4 CMP AL,0FFh

SHR AX,CL ; AX = (100h-DI)/16 JNE mx_skip_hndl ; no hay TSR ahí

MOV CX,ES CLD


PROGRAMAS RESIDENTES 161

PUSH DI ADD SI,5

REP CMPSB ; comparar identificación JMP mx_ul_2f

POP DI mx_ul_pasok: PUSH ES

JE mx_tsr_found ; programa buscado hallado PUSH AX

mx_skip_hndl: POP 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 ; vector en ES:BX

mx_tsr_found: ADD SP,4 ; «sacar» ES y DI de la pila POP AX

POP DS MOV CL,4

POP SI SHR BX,CL

POP CX MOV DX,ES

POP AX ADD DX,BX ; INT xx en DX (aprox.)

CLC MOV AH,0C0h

RET mx_ul_masmx: CALL mx_ul_tsrcv?

mx_find_tsr ENDP JNC mx_ul_tsrcv

JMP mx_ul_otro

; ------------ Eliminar TSR del convenio si es posible. A la entrada, mx_ul_tsrcv: PUSH ES:[DI-16] ; ...TSR del convenio en ES:DI

; 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] ; offset a la tabla de vectores

; 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 ; número de vectores en CX

mx_ul_buscav: CMP AL,ES:[DI]

mx_unload PROC JE mx_ul_usavect ; este TSR usa vector analizado

PUSH ES ADD DI,5

CALL mx_ul_tsrcv? LOOP mx_ul_buscav

JNC mx_ul_able ADD SP,4 ; no lo usa

POP ES JMP mx_ul_otro

RET mx_ul_usavect: POP CX ; tamaño del TSR

mx_ul_able: XOR AL,AL POP BX ; segmento del TSR

XCHG AH,AL CMP DX,BX

MOV BP,AX ; BP=entrada Multiplex del TSR JB mx_ul_otro ; la INT xx no le apunta

MOV CX,2 ADD BX,CX

mx_ul_pasada: PUSH CX ; siguiente pasada CMP DX,BX

LEA SI,tabla_vectores JA mx_ul_otro ; la INT xx le apunta

MOV CL,ES:[SI-1] PUSH AX

MOV CH,0 ; CX = nº vectores XOR AL,AL

mx_ul_masvect: POP AX XCHG AH,AL

PUSH AX ; pasada en curso CMP AX,BP ; ¿es el propio TSR?

DEC AL POP AX

PUSH CX JNE mx_ul_chain ; no

mx_ul_2f: MOV AL,ES:[SI] ; vector en curso POP ES ; sí: ¡posible reponer vector!

JNZ mx_ul_pasok POP CX

CMP CX,1 ; ¿último vector? POP BX

JNE mx_ul_noult PUSH BX

MOV AL,2Fh PUSH CX

LEA SI,tabla_vectores PUSH ES

mx_ul_busca2f: CMP ES:[SI],AL ; ¿INT 2Fh? DEC BX

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

mx_ul_noult: CMP AL,2Fh ; ¿restaurar INT 2Fh? PUSH DS

JNE mx_ul_pasok MOV BX,CS:mx_ul_tsroff ; restaurar INT's


161 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

MOV DS,CS:mx_ul_tsrseg JNE mx_ul_ncvexit

CLI CMP WORD PTR ES:[DI-2],"*#"

MOV CX,ES:[SI+1] JNE mx_ul_ncvexit

MOV [BX+1],CX ADD SP,4 ; CF=0

MOV CX,ES:[SI+3] POP AX

MOV [BX+3],CX RET

STI mx_ul_ncvexit: POP DI ; ...no es TSR del convenio

POP DS POP ES

mx_ul_norest: POP ES POP AX

POP CX STC ; CF=1

ADD SI,5 ; siguiente vector RET

DEC CX mx_ul_tsroff DW 0

JZ mx_unloadable ; no más, ¡desinstal-ar/ado! mx_ul_tsrseg DW 0

JMP mx_ul_masvect mx_unload ENDP

mx_ul_chain: MOV CS:mx_ul_tsroff,DI ; ES:DI almacena la dirección

MOV CS:mx_ul_tsrseg,ES ; de la variable vector ; ------------ Imprimir cadena en DS:DX delimitada por un 0

MOV DX,ES:[DI+1]

MOV CL,4 print PROC

SHR DX,CL XPUSH <AX, BX, CX, DX>

MOV CX,ES:[DI+3] MOV BX,DX

ADD DX,CX ; INT xx en DX (aprox.) print_mas: MOV AL,[BX]

MOV AH,0BFh AND AL,AL

mx_ul_otro: INC AH ; a por otro TSR JZ fin_print

JZ mx_ul_exitnok ; ¡se acabaron! MOV DL,AL

JMP mx_ul_masmx MOV AH,2

mx_ul_exitnok: ADD SP,6 ; equilibrar pila PUSH BX

POP ES INT 21h

STC POP BX

RET ; imposible desinstalar INC BX

mx_unloadable: POP CX JMP print_mas

DEC CX fin_print: XPOP <DX, CX, BX, AX>

JZ mx_ul_exitok ; desinstalado RET

JMP mx_ul_pasada ; 1ª pasada exitosa: por la 2ª print ENDP

mx_ul_exitok: TEST ES:info_extra,111b ; ¿tipo de instalación?

MOV ES,ES:segmento_real ; segmento real del bloque

JZ mx_ul_freeml ; cargado en RAM convencional

CMP xms_ins,1

JNE mx_ul_freeml ; no hay controlador XMS (¿?)

MOV DX,ES

MOV AH,11h

CALL gestor_XMS ; liberar memoria superior

POP ES

CLC

RET

mx_ul_freeml: MOV AH,49h

INT 21h ; liberar bloque de memoria ES:

POP ES

CLC

RET

mx_ul_tsrcv?: PUSH AX ; ¿es TSR del convenio?...

PUSH ES

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],"#*"


PROGRAMAS RESIDENTES 161

; ********************************** nocabe_txt DB ": Instalación imposible.",13,10

; * * DB " Ya hay 64 programas residentes con la "

; * DATOS PARA LA INSTALACION * DB "misma técnica.",13,10,0

; * *

; ********************************** 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

ON EQU 1 ; constantes booleanas err_sintax_fin DB 13,10," Ejecute SCRCAP /? para obtener "

OFF EQU 0 DB "ayuda.",13,10,7,0

xms_ins DB 0 ; a 1 si presente controlador XMS mal_ver_txt1 DB 13,10

gestor_XMS LABEL DWORD ; dirección del controlador XMS DB " - Error: ya está instalada la versión ",0

XMS_off DW 0 mal_ver_txt2 DB " de este programa.",13,10,7,0

XMS_seg DW 0

des_ok_txt DB " desinstalado.",13,10,0

alloc_strat DW 0 ; estrategia asignación (DOS 5)

umb_state DB 0 ; estado de bloques UMB (DOS 5) des_no_ok_txt DB 13,10," - Desinstalación imposible (se ha "

DB "instalado después un programa"

tsr_dir LABEL DWORD ; dirección de la copia residente DB 13,10," que no respeta el convenio y tiene "

tsr_off DW 0 DB "alguna interrupción común).",13,10,7,0

tsr_seg DW 0

imp_desins_txt DB 13,10," - Programa aún no instalado: "

memoria DW 0 ; párrafos que ocupará SCRCAP DB "imposible desinstalarlo.",13,10,0

offsets_ints DW 6 ; número de vectores interceptados ayuda_txt LABEL BYTE

DB 8 ; tabla de offsets de los vectores DB 13,9," SCRCAP 1.0 - Utilidad de captura de pantallas de texto."

DW ges_int08 ; de interrupción interceptados DB 13,10

DB 9 DB " (c) 1992 CiriSOFT, (c) Grupo Universitario de Informática - "

DW ges_int09 DB "Valladolid.",13,10,10

DB 13h DB 9," SCRCAP [/ML] [/S=marcas] [/T=codigo de rastreo] [/U] [/?|H]"

DW ges_int13 DB 13,10,10

DB 21h DB " Una vez instalado, al pulsar Alt-SysReq (Alt-PetSis) la "

DW ges_int21 DB "pantalla actual se",13,10

DB 28h DB " salvará en disco con nombre SCRxx-nn.SCR, donde xx es la "

DW ges_int28 DB "anchura hexadecimal",13,10

DB 2Fh DB " de la misma (en columnas) y nn el número de fichero; ya que, "

DW ges_int2F DB "partiendo de 00",13,10

param_ml DB 0 ; a 1 si se indicó parámetro /ML DB " tras instalar el programa, se crean sucesivamente cada vez "

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 " utilidad. Se salvan también pantallas de texto no estándar "

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 " u 80 columnas); las pantallas gráficas generan ficheros "

DB "inservibles. Lo que",13,10

; ------------ Texto DB " 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

act_teclas_txt DB 13,10," - Pulse ",0 DB " (incluso sin indicar DOS=UMB en el CONFIG del DOS 5.0): con "

act_ctrl DB "Ctrl",0 DB "/ML se fuerza la",13,10

act_alt DB "Alt",0 DB " instalación en memoria convencional. Consumo: 2208 bytes (2,16 "

act_shift_der DB "ShiftDer",0 DB "Kb).",13,10,10

act_shift_izq DB "ShiftIzq",0 DB " El parámetro /S permite elegir la combinación de teclas de "

act_c_txt DB "SysReq",0 DB "activación (se",13,10

act_otra_txt DB 8," y la tecla elegida",0 DB " obtiene sumando: 1-shift derecho, 2-shift izdo, 4-Ctrl, "

act_fin_txt DB 8," para activarlo.",13,10,0 DB "8-Alt); con /T puede",13,10

DB " 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);

#include <dos.h> if (ultimo) {

#include <dir.h> printf("\nNombre de fichero incorrecto.\n"); exit(1); }

#include <fcntl.h>

#include <conio.h> while (!ultimo) {

#include <string.h> fnmerge (ruta, disco, direct, fichero.ff_name, "");

if (fichero.ff_name[3]=='2') {

_AX=1; __emit__(0xcd, 0x10); } /* modo de 40x25 */

else {

void main(int argc, char **argv) _AX=3; __emit__(0xcd, 0x10); } /* modo 80x25 */

{ if ((handle=open(ruta, O_RDONLY | O_BINARY, 0)) == -1) {

int handle, ultimo; printf("Error al abrir fichero de entrada.\n"); exit(1); }

void far *buffer; read(handle, buffer, 30000); close(handle);

struct ffblk fichero; ultimo=(getch()==27) || findnext (&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 <fcntl.h>

/* */ #include <conio.h>

/* SCR2TXT 1.0 - Utilidad para convertir pantallas capturadas por */ #include <string.h>

/* SCRCAP a modo texto. Borland C en modo "Large". */

/* */

/********************************************************************/ void main(int argc, char **argv)

int handler, handlew, ultimo, ancho, ih, il;

#include <dos.h> struct ffblk fichero;

#include <dir.h> char buffer[512], *p,


PROGRAMAS RESIDENTES 161

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");

if (argc<2) { ih=fichero.ff_name[3]-'0'; if (ih>9) ih-='A'-'9'-1;

printf("Indique el(los) fichero(s) a convertir.\n"); exit (1); } il=fichero.ff_name[4]-'0'; if (il>9) il-='A'-'9'-1;

ancho=(ih<<4)+il;

fnsplit (argv[1], disco, direct, fich, ext); if ((ancho!=40) && (ancho!=80) && (ancho!=94) && (ancho!=100) &&

if (!*ext) strcpy (ext, ".*"); (ancho!=114) && (ancho!=120) && (ancho!=132) && (ancho!=160)) {

fnmerge (rutar, disco, direct, fich, ext); printf(" - Error: el fichero %s no es del tipo SCRxx-nn.SCR\n",

ultimo=findfirst (rutar, &fichero, FA_ARCH|FA_HIDDEN|FA_RDONLY); rutar); exit(1); }

if (ultimo) {

printf("Nombre de fichero incorrecto.\n"); exit(1); } if ((handler=open(rutar, O_RDONLY | O_BINARY, 0)) == -1) {

printf("Error al abrir fichero de entrada.\n"); exit(1); }

while (!ultimo) { if ((handlew=_creat(rutaw, 0)) == -1) {

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 ; habilitar acceso a tabla de caracteres
CALL restaurar8k ; 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 ; códigos a enviarle
MOV CX,4
PROGRAMAS RESIDENTES 161

CLD
CLI ; precauciones
def_on_1: LODSW
OUT DX,AX ; programar registro
LOOP def_on_1
STI ; no más precauciones
MOV DL,0CEh ; 3CEh = puerto del controlador de gráficos
MOV CX,3
def_on_2: LODSW
OUT DX,AX ; programarlo
LOOP def_on_2
RET
car_on DW 100h, 402h, 704h, 300h, 204h, 5, 6 ; datos
def_car_on ENDP

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
PROGRAMAS RESIDENTES 161

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 ; longitud de la estructura
DW ?; identificador del API (1-NETBIOS, 2-802.2, 3-TCP/IP, 4-Tuberías LanManager,
5-NetWare IPX)
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
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
PROGRAMAS RESIDENTES 161

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
CONTROLADORES DE DISPOSITIVOS 203

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 DR-
DOS 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 │
│ offset 10 DB "AUX " ; nombre del dispositivo (8 caracteres) │
└──────────────────────────────────────────────────────────────────────────────────────┘

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 │
├──────────────────────────────────────────────────────────────────────────────────────┤
CONTROLADORES DE DISPOSITIVOS 203

│ 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). │
│ bit 3: 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 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 │
├──────────────────────────────────────────────────────────────────────────────────────┤
│ estrategia PROC FAR ; de tipo FAR │
│ MOV CS:pcab_pet_desp,BX │
│ MOV CS:pcab_pet_segm,ES │
│ RET │
│ estrategia 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 no usada │
│ 13h (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. │
CONTROLADORES DE DISPOSITIVOS 203

│ 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 0 13 BYTES: Ya vistos con anterioridad. │
│ offset 0Dh BYTE: A la vuelta, indicar al DOS el nº de unidades de disco │
│ definidas (solo en dispositivos de bloque). │
│ offset 0Eh: DWORD: A la vuelta, indica el último byte residente con un │
│ 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). │
│ offset 12h: DWORD: 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 │
│ (esto último sólo en los dispositivos de bloques). │
│ offset 16h: BYTE: 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). │
└──────────────────────────────────────────────────────────────────────────────────────┘

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 0 13 BYTES: Ya vistos con anterioridad. │
│ offset 13 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
CONTROLADORES DE DISPOSITIVOS 203

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 0 13 BYTES: Ya vistos con anterioridad. │
│ offset 13 BYTE: A la entrada, el DOS indica el descriptor del soporte. │
│ (solo en dispositivos de bloque) │
│ offset 14 DWORD: A la entrada, el DOS apunta a un buffer que contiene el │
│ 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 DWORD: A la vuelta, el driver devuelve aquí la dirección del BPB │
│ 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 0 13 BYTES: Ya vistos con anterioridad. │
│ offset 13 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: Número de sector de comienzo (solo en los dispositivos de │
│ bloques y de menos de 32 Mb) │
│ offset 22 DWORD: 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. │
│ offset 26 DWORD: 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. │
└──────────────────────────────────────────────────────────────────────────────────────┘

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 ║
CONTROLADORES DE DISPOSITIVOS 203

║ 0070:00EE Carácter COM4 06F5 0733 8000 ║


╚═════════════════════════════════════════════════════════════════════════════╝

// DRV 1.0 while (FP_OFF(siguiente)!=0xffff) {

// Utilidad para listar los controladores de dispositivo instalados. disp = (unsigned char huge *) siguiente;

printf("║\n║ %04X:%04X ", FP_SEG(disp), FP_OFF(disp));

#include <dos.h> if (disp[5] & 0x80) {

#include <stdio.h> printf("Carácter ");

for (i=10; i<18; i++) printf("%c",disp[i]); printf("

struct REGPACK r; ");

unsigned long huge *siguiente; }

unsigned char huge *disp; else {

int i, disco, dosver; printf("Bloque ");

if (disp[10]==1)

void main() printf("Unidad %c: ", disco--);

{ else {

r.r_ax=0x3000; intr (0x21, &r); /* obtener versión del DOS */ printf("Unidades %c:-%c:",disco-disp[10]+1, disco);

dosver=(r.r_ax << 8) | (r.r_ax >> 8); disco-=disp[10];

if ((dosver & 0xFF00)==0x200) i=0x17; /* DOS 2.XX }

*/ }

else if ((dosver>0x2FF) && (dosver<0x30A)) i=0x28; /* DOS 3.0X printf(" %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); /* "Get List of Lists" */ if (disp[i]>=' ') printf("%c",disp[i]); else printf(" ");

printf(" %6u ",(disp[-13] | (disp [-12] << 8)) << 4);

siguiente=MK_FP(r.r_es, r.r_bx+i); disco='A'-1; }

while (FP_OFF(siguiente)!=0xffff) { else

disp = (unsigned char huge *) siguiente; printf(" ");

if (!(disp[5] & 0x80)) disco+=disp[10]; /* contar discos */ siguiente = (unsigned long huge *) *siguiente;

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 Este es un fiche


00000010 72 6F 20 64 65 20 70 72 - 75 65 62 61 73 2E 20 53 ro de pruebas. S
00000020 A2 6C 6F 20 73 69 72 76 - 65 20 70 61 72 61 20 70 ólo sirve para p
00000030 72 6F 62 61 72 2E 0A 0D 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.
CONTROLADORES DE DISPOSITIVOS 203

; ******************************************************************** DW ioctl_output

; * * DW open

; * HEX$ 1.0 - (c) 1992 Ciriaco García de Celis. * DW close

; * * DW remove

; * Controlador de dispositivo para volcado hexadecimal en salida. *

; * * ini_buffer EQU $

; ******************************************************************** DB 8 DUP (0) ; buffer para contener los caracteres

med_buffer EQU $ ; recibidos de 16 en 16.

DB 8 DUP (0)

; ------------ Macros de propósito general fin_buffer EQU $

XPUSH MACRO RM ; apilar lista de registros puntero DW ini_buffer ; puntero al buffer

IRP reg, <RM>

PUSH reg dirl DW 0 ; offset relativo del carácter del

ENDM dirh DW 0 ; fichero o canal en proceso

ENDM

; ------------ Rutina de estrategia.

XPOP MACRO RM ; desapilar lista de registros

IRP reg, <RM> estrategia PROC FAR

POP reg MOV CS:pcab_pet_desp,BX

ENDM MOV CS:pcab_pet_segm,ES

ENDM RET

estrategia ENDP

; ************ Inicio del área residente. ; ------------ Rutina de interrupción.

HEXSEG SEGMENT interrupción PROC FAR

ASSUME CS:HEXSEG, DS:HEXSEG XPUSH <AX,BX,CX,DX,SI,DI,BP,DS,ES>

LDS BX,CS:pcab_peticion

DD -1 ; encadenamiento con otros drivers MOV AL,[BX+2] ; AL = orden

tipo_drive DW 8800h ; palabra de atributo: CBW ; AX = orden (AH = 0)

; bit 15 a 1: dispositivo caracteres CMP AL,0Fh

; bit 14 a 0: sin control IOCTL JBE orden_ok ; orden correcta

; bit 11 a 1: soportados Open/Close MOV AX,8102h

; y Remove (DOS 3.0+) JMP exit_interr

DW estrategia ; rutina de estrategia orden_ok: SHL AX,1 ; orden = orden * 2

DW interrupción ; rutina de interrupción LEA SI,p_rutinas

DB "HEX$ " ; nombre del dispositivo ADD SI,AX

XPUSH <BX,DS>

; ------------ Variables y tablas de datos globales fijas. CALL CS:[SI] ; ejecutar orden

XPOP <DS,BX>

pcab_peticion LABEL DWORD ; puntero a la cabecera de petición exit_interr: MOV [BX+3],AX ; devolver palabra de estado

pcab_pet_desp DW 0 XPOP <ES,DS,BP,DI,SI,DX,CX,BX,AX>

pcab_pet_segm DW 0 RET

interrupción ENDP

p_rutinas LABEL WORD ; 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: MOV AX,100h ; no hay error, ignorar orden

DW output_status RET

DW output_flush
203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

media_check: write ENDP

build_bpb:

read: ; sólo soportada la salida procesa_AL PROC ; permitido corromper registros

read_nowait: MOV BX,puntero

ioctl_input: MOV AX,8103h ; órdenes no soportadas MOV [BX],AL ; guardar carácter

RET INC puntero

CMP BX,OFFSET ini_buffer

open PROC ; inicio de transferencia: JNE no_direcc ; no es inicio de «párrafo»

MOV CS:puntero,OFFSET ini_buffer ; inicializa puntero CALL imprimir_desp ; imprimir desplazamiento

MOV CS:dirl,0 no_direcc: CMP BX,OFFSET med_buffer

MOV CS:dirh,0 ; offset relativo a cero JNE no_sep ; aún no alzanzada la mitad

JMP retorno_ok CALL imprimir_sep

open ENDP no_sep: ADD dirl,1 ; INC no afecta al acarreo

ADC dirh,0 ; incrementada dirección

close PROC ; fin de transferencia: CALL print_8hex ; imprimir byte en hexadecimal

MOV AX,CS MOV AL,' '

MOV DS,AX CALL print_AL ; espacio separador

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 ; acabado el buffer:

MOV AX,CX ; para un párrafo MOV puntero,BX

ADD CX,CX CALL imprimir_asc ; imprimirlo en ASCII

ADD CX,AX ; CX = CX * 3 AL_procesado: RET

CMP BX,OFFSET med_buffer procesa_AL ENDP

JA tam_ok

ADD CX,2 ; dos espacios de separación imprimir_desp PROC ; imprimir desplazamiento

tam_ok: MOV AL,' ' PUSH AX

JCXZ esp_escr MOV AL,' '

escr_esp: CALL print_AL CALL print_AL

LOOP escr_esp CALL print_AL ; dos espacios al principio

esp_escr: MOV BX,puntero MOV AX,dirh

limpia_buffer: CMP BX,OFFSET fin_buffer XCHG AH,AL

JAE fin_buff CALL print_8hex ; byte alto palabra alta

MOV BYTE PTR [BX],' ' XCHG AH,AL

INC BX CALL print_8hex ; byte bajo palabra alta

JMP limpia_buffer MOV AX,dirl

fin_buff: LEA BX,ini_buffer ; acabado el buffer: XCHG AH,AL

MOV puntero,BX CALL print_8hex ; byte alto palabra baja

CALL imprimir_asc ; imprimirlo en ASCII XCHG AH,AL

JMP retorno_ok CALL print_8hex ; byte bajo palabra baja

close ENDP MOV AL,' '

CALL print_AL

remove PROC CALL print_AL ; dos espacios separadores

MOV AX,300h ; indicar POP AX

RET ; «controlador ocupado» RET

remove ENDP imprimir_desp ENDP

write PROC imprimir_sep PROC ; imprimir guión separador

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 ; DS: -> HEX$ MOV AL,' '

otro_car: MOV AL,ES:[DI] CALL print_AL

XPUSH <CX, DI> POP AX

CALL procesa_AL ; procesar carácter RET

XPOP <DI, CX> imprimir_sep ENDP

INC DI ; otro carácter

LOOP otro_car imprimir_asc PROC ; imprimir en ASCII 16 bytes

JMP retorno_ok ; siempre Ok. MOV AL,' ' ; a partir de DS:BX


CONTROLADORES DE DISPOSITIVOS 203

CALL print_AL ; espacio separador MOV [BX+10h],CS ; sin quedar residente

MOV CX,16 LEA BX,mal_dos_txt

asc_dump: MOV AL,[BX] CALL print

CMP AL,' ' MOV AX,100h

JAE asc_ok RET

MOV AL,'.' ; no imprimir los de control dos_ok: LEA AX,retorno_ok

asc_ok: CALL print_AL MOV CS:p_rutinas,AX ; anular rutina INIT

INC BX MOV WORD PTR [BX+0Eh],OFFSET init

LOOP asc_dump MOV [BX+10h],CS ; indicado área residente

MOV AL,0Dh LEA BX,instalado_txt

CALL print_AL ; retorno de carro CALL print

MOV AL,0Ah MOV AX,100h ; instalación siempre Ok.

CALL print_AL ; salto de línea RET

RET init ENDP

imprimir_asc ENDP

print PROC ; imprimir cadena en CS:BX

print_8hex PROC ; imprimir byte hexad. en AL MOV DL,CS:[BX]

PUSH AX AND DL,DL

MOV 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 fin_print: RET

RET print ENDP

print_8hex ENDP

instalado_txt DB 13,10,"Dispositivo HEX$ instalado.",13,10,0

print_4hex PROC ; imprimir nibble hexad. en AL mal_dos_txt DB 13,10,"Error: HEX$ necesita DOS 3.0 o superior."

PUSH AX DB 13,10,0

ADD AL,'0'

CMP AL,'9' HEXSEG ENDS

JBE hex_AL END

ADD AL,'A'-'9'-1

hex_AL: CALL print_AL

POP AX

RET

print_4hex ENDP

print_AL PROC ; imprimir ASCII en AL

INT 29h

RET

print_AL ENDP

; ************ Instalación invocada desde el CONFIG.SYS

init PROC

PUSH BX

MOV AH,30h

INT 21h ; obtener versión del DOS

POP BX

CMP AL,3

JAE dos_ok

MOV WORD PTR [BX+0Eh],0 ; OFFSET 0: terminar


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 │
└─────────────────────────────────────────────────────────────────────────────────┘
CONTROLADORES DE DISPOSITIVOS 203

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 (aunque esto no influye en KBSEC) 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, tanto HIMEM.SYS como EMM386.EXE a la vez (los resultados varían bastante │
│ en función de la gestión de memoria del sistema). Datos en Kb/segundo. │
├──────────────────────────────────────────────────────────────────────────────────────┤
│ VDISK RAMDRIVE TURBODSK │
│ 8088-8 MHz: │
│ - Memoria convencional: 563 573 573 │
│ 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 │
└──────────────────────────────────────────────────────────────────────────────────────┘

/*********************************************************************

* * #define MAXBUF 64512L /* 63 Kb (no sobrepasar 64 Kb en un acceso) */

* KBSEC 1.2 - Utility to calc with high precision the data transfer * #define TIEMPO 110L /* 6 segundos * 18,2 ≈ 110 tics (error < 1%) */

* rate (the read data transfer read) in a ramdisk. * #define TM 18.2 /* cadencia de interrupciones del temporizador */

* * #define HORA_BIOS MK_FP(0x40, 0x6c) /* variable de hora del BIOS */

* (C) 1992-1995 Ciriaco García de Celis *

* * unsigned long ti, vueltas, far *cbios;

* - Do not run this program with a cache program loaded; compile * unsigned segmento, tamsect, far *pantalla;

* it in LARGE memory model with «Test stack overflow» option * unsigned char far *sbuffer;

* disabled. Use Borland C. This program has english messages. * static unsigned tiempo;

* * int unidad;

*********************************************************************/ void interrupt (*viejaIRQ0)();

#include <stdio.h>

#include <dos.h> void interrupt nuevaIRQ0 () /* rutina ejecutada cada 55 ms */

#include <conio.h> {

#include <stdlib.h> tiempo++; /* incrementar nuestro contador de hora */


203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

outportb (0x20,0x20); /* EOI al controlador de interrupciones */ printf ("\nNeeds a disk from %2.0f Kb to 32 Mb\n", MAXBUF/1024.0);

} exit (3); }

void prep_hw (void) textmode (C80); clrscr();

{ 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 */ pantalla=MK_FP((peekb(0x40,0x49)==7 ? 0xB000:0xB800), 0x140);

outportb (0x21, 0xfe); /* inhibir todas las int. salvo timer */

} 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 */ if (absread (unidad, MAXBUF / tamsect, 0L, sbuffer)!=0) {

setvect (8, viejaIRQ0); /* restaurar vector de int. periódica */ rest_hw(ti-tiempo); printf ("\nError reading the disk.\n");

cbios=HORA_BIOS; *cbios+=tiempo_transcurrido_con_reloj_parado; exit(254); }

} 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:\

if (allocmem ((unsigned) ((MAXBUF+0x1800) >> 4), &segmento)!=-1) { %6.0f Kb/sec.\n", unidad+'A',MAXBUF/1024.0*vueltas/(TIEMPO/TM));

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 │ │ │
│ │ Página 3 │ E │ Página 3 │ │
│ ├───────────────┤ M ├───────────────┤-- ¿ │
│ │ │ O │ │ 16 │
│ │ Página 2 │ R │ Página 2 │ Kb │
│ ├───────────────┤ I ├───────────────┤-- À │
│ │ │ A │ │ │
│ │ Página 1 │ │ Página 1 │ │
│ ├───────────────┤ E ├───────────────┤ ½───── caso B │
│ │ │ M │ │ │
│ │ Página 0 │ S │ Página 0 │ │
│ - - - - -├───────────────┤- - - - - - - - - - - - - -├───────────────┤- - - - - │
│ │ │ │ │ │
│ │ │ │ │ │
│ ├───────────────┤ ½───── caso A ├───────────────┤ │
│ │
│ │
│ Resulta evidente, en el caso A, que si el buffer donde leer/escribir los datos comienza por │
CONTROLADORES DE DISPOSITIVOS 203

│ debajo de la dirección marcada por la flecha (o justo en esa dirección) no colisionará con la │
│ página 0, ya que no excede de 16 Kb de longitud. Como al convertir la dirección segmentada a │
│ párrafos se pierde precisión, TURBODSK se asegura que la dirección esté 401h párrafos (16 Kb │
│ más 1 párrafo) por debajo del inicio de la página 0. │
│ │
│ 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. Una vez más, por razones de redondeo, TURBODSK │
│ comprueba que el buffer comience al menos 401h párrafos por encima del inicio de la página 0. │
│ En realidad, bastaría con comprobar si dista al menos 400h bytes, ya que el redondeo al │
│ convertir la dirección segmentada se hace truncando. │
│ │
│ 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. En ese caso, bastaría con elegir │
│ 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, no puede invadir... sí, ¡sí puede invadir la página 2, aunque sólo un │
│ párrafo! (no olvidar que si empieza por encima de la flecha no colisiona con la página 0). Por │
│ tanto, tenemos que utilizar la página 3. En general, en un sistema con memoria EMS 4.0 donde │
│ las páginas pueden ser definidas por el usuario en la dirección que desee (parámetros /Pn= del │
│ EMM386 del MS-DOS 5.0), basta con asegurarse que la página alternativa a la 0, para los casos │
│ en que hay colisión, está alejada al menos 48 Kb de la página 0 (esto es, que entre ambas │
│ páginas hay una distancia absoluta de 32 Kb). │
│ │
│ 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 ¡y es preciso restaurar │
│ el contenido del buffer!. Además, hay que volver a salvar el contexto de manera inmediata para │
│ que quede salvado para otra ocasión (o para cuando se acabe el acceso al disco y haya de ser │
│ restaurado). │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

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 MS-
DOS 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 ├─────────────────────────┤½─┐ │
CONTROLADORES DE DISPOSITIVOS 203

│ │ │ │ │ │ │
│ │ │ │ │ │ │
│ │ │ aprox. 588 Kb ┌─¾├─────────────────────────┤ │ │
│ │ │ │ │ nueva pila de TDSK.EXE │ │ │
│ │ │ │ ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤ │ │
│ │ │ ┌────────¾ │ │ │ │ 128 Kb │
│ │ │ │ │ │ TDSK.EXE │ │ │
│ │ │ │ │ │ │ │ │
│ │ │ │ │ ├─────────────────────────┤ │ │
│ │ │ │ │ │ PSP TDSK.EXE (256 bytes)│ │ │
│ │ │ │ 576 Kb └─¾├─────────────────────────┤ │ │
│ │ │ │ │ 64 Kb libres (área de │ │ │
│ │ │ │ │ seguridad) │ │ │
│ │ │ │ 512 Kb ├─────────────────────────┤½─┘ │
│ │ . . . │ │ │ . . . │ │
│ . . . │ . . . │
│ . . . │ . . . │
│ │ │ │ │ │ │
│ ├─────────────────────────┤½─┐ │ │ Futuros programas │ │
│ │ pila de TDSK.EXE │ │ │ ├─────────────────────────┤ │
│ ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤ │ │ │ │ │
│ │ │ │ ────┘ │ Área de almacenamiento │ │
│ │ TDSK.EXE │ │ │ del disco virtual │ │
│ │ │ │ │ │ │
│ ├─────────────────────────┤ │ ├─────────────────────────┤ │
│ │ PSP TDSK.EXE (256 bytes)│ │ │ PSP TDSK.EXE (96 bytes) │ │
│ ├─────────────────────────┤½─┘ ├─────────────────────────┤ │
│ │ DOS/BIOS │ │ DOS/BIOS │ │
│ 0 Kb └─────────────────────────┘ 0 Kb └─────────────────────────┘ │
│ Antes Después │
│ │
│ │
│ En este esquema se muestra la autorelocalización de TDSK.EXE en memoria en el caso de │
│ definirse el disco en memoria convencional. No están reflejados los bloques de control de │
│ memoria ni otros detalles. Si la memoria está suficientemente fragmentada (por haber instalado │
│ programas residentes tras definir algún disco) puede que no fuera estrictamente necesario │
│ 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. El hecho de relocalizar TDSK │
│ 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. │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘

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).
CONTROLADORES DE DISPOSITIVOS 203

;┌───────────────────────────────────────────────────────────────────┐ ; documentado, indicando claramente en el listado y

;│ │ ; en el fichero DOC quién lo ha realizado.

;│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▄ │

;│ ▀▀▒▒█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │

;│ ▒▒█ ▒▒▄ ▒▒▄ ▒▒▒▒▄ ▒▒▒▒▄ ▒▒▄ ▒▒▒▒▄ ▒▒▒▒▒▒▄ ▒▒▄ ▒▒▄ │ ; ------------ Macros de propósito general

;│ ▒▒█ ▒▒█ ▒▒█ ▒▒█▀▒▒▄ ▒▒█▀▒▒▄ ▒▒▄▀▒▒▄ ▒▒█▀▒▒▄ ▒▒█▀▀▀▀ ▒▒█ ▒▒█▀ │

;│ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█▒▒█▀ │ XPUSH MACRO regmem ; apilar lista de registros

;│ ▒▒█ ▒▒█ ▒▒█ ▒▒▒▒▄▀▀ ▒▒▒▒▄▀▀ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒▒▒▒▒▄ ▒▒▒▒█▀ │ IRP rm, <regmem>

;│ ▒▒█ ▒▒█ ▒▒█ ▒▒█▀▒▒▄ ▒▒█▀▒▒▄ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▀▀▀▒▒█ ▒▒█▒▒▄ │ PUSH rm

;│ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒█ ▒▒▄ ▒▒█ ▒▒█ ▒▒▄ │ ENDM

;│ ▒▒█ ▒▒▒▒▒▒▒▄ ▒▒█ ▒▒█ ▒▒▒▒▄▀▀ ▀▒▒▄▀▀ ▒▒▒▒▒█▀ ▒▒▒▒▒▒█ ▒▒█ ▒▒▄ │ ENDM

;│ ▀▀ ▀▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀▀▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ │

;│ │ XPOP MACRO regmem ; desapilar lista de registros

;│ ░░▒▒▓▓██▓▓▒▒░░ Versión 2.3 ░░▒▒▓▓██▓▓▒▒░░ │ IRP rm, <regmem>

;│ │ POP rm

;│ │ ENDM

;│ CONTROLADOR DE DISCO VIRTUAL PARA SISTEMAS DOS Y WINDOWS 3 │ ENDM

;│ │

;│ * * * Programa de Dominio Público * * * │ ; ------------ Estructuras de datos

;│ │

;│ (C) 1992-1995 Ciriaco García de Celis. │ cab_PETICION STRUC ; parte inicial común a todos

;│ 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 ?

;│ Internet Email: ciri@gui.uva.es │ estado DW ?

;│ FidoNet: 2:341/21.8 │ dos_info DB 8 DUP (?)

;│ │ cab_PETICION ENDS

;│ Mensajes en alemán cortesía de Axel Christoph Frinke │

;│ Internet Email: acfrinke@uni-bonn.de │ cab_INIT_BBPB STRUC ; para comandos INIT/BUILD_BPB

;│ │ DB (TYPE cab_PETICION) DUP (?)

;└───────────────────────────────────────────────────────────────────┘ num_discos DB ? ; número de unidades definidas

fin_resid_desp DW ? ; área que quedará residente

; Aviso: Este programa contiene instrucciones exclusivas de los fin_resid_segm DW ?

; procesadores 386 y superiores. Debe ser ensamblado como bpb_cmd_desp DW ? ; línea de órdenes del CONFIG

; fichero EXE, de la siguiente manera, para asegurar la bpb_cmd_segm DW ? ; y puntero al BPB

; compatibilidad con los procesadores 8086 y 286: nuevo_disco DB ? ; (DOS 3+) (0-A:, 1-B:,...)

; cab_INIT_BBPB ENDS

; - Con TASM 2.0:

; TASM tdsk /m3 cab_MEDIACHECK STRUC ; estructura para MEDIA CHECK

; TLINK tdsk DB (TYPE cab_PETICION) DUP (?)

; media_descrip DB ? ; descriptor de medio

; - Con MASM 6.0 (versiones anteriores de MASM generarían cambio DB ? ; 1: no cambiado, 0FFh:sí, 0:?

; un disco virtual que requeriría un 386 o superior, cab_MEDIACHECK ENDS

; además habría que mover las directivas que controlan

; el tipo de procesador y colocarlas con «peligro»): cab_READ_WRITE STRUC

; DB (TYPE cab_PETICION) DUP (?)

; ML /Zm tdsk.asm DB ? ; descriptor de medio

; transfer_desp DW ? ; dirección de transferencia

; o alternativamente: transfer_segm DW ?

; 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 2 Kbytes menos en disco (a la tabla ; ************ Disco virtual: inicio del área residente.

; ubicada al final del programa se le asigna memoria

; en la cabecera del fichero EXE y no ocupando disco). _PRINCIPAL SEGMENT

; ASSUME CS:_PRINCIPAL, DS:_PRINCIPAL

; IMPORTANTE: Cualquier cambio realizado en el programa debe ser


203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

DD -1 ; encadenamiento con otros drivers ems_pagni DB ? ; nº de página física alternativa

tipo_drive DW 0800h ; palabra de atributo:

; 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 ?

; bit 11 a 1: soportados Open/Close

; y Remove (DOS 3.0+) cpu386 DB OFF ; a ON si 386 ó superior

DW estrategia ; rutina de estrategia

DW interrupcion ; rutina de interrupción f_tdsk_ctrl EQU $ ; final del área a actualizar

DB 1 ; número de unidades

letra_unidad DB ? ; letra ASCII del disco ('C', 'D',...)

; ------------ Variables y tablas de datos globales fijas. Estas

; variables no serán movidas de sitio en otras versiones bpb_ptr DW bpb ; puntero al BPB del disco

; de TURBODSK, con objeto de facilitar un control externo

; del disco virtual por parte de otros programas. Todo lo rutina_larga DB OFF ; a ON si reservado espacio en

; 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.

; ------------ Variables internas de TURBODSK; su ubicación podría

cs_tdsk DW ? ; Segmento de TDSK. Con QEMM-386, los drivers ; cambiar en futuras versiones del programa.

; pueden ser relocalizados en memoria superior

; de tal manera que parte de la cabecera queda pcab_peticion LABEL DWORD ; puntero a la cabecera de petición

; 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

; ejecutado. Tras la instalación, QEMM copia en

; memoria convencional los primeros 18 bytes de p_rutinas LABEL WORD ; tabla de rutinas del controlador

; la cabecera, entre los que está esta palabra, 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

id_tdsk DB "TDS23" ; esto es TURBODSK 2.3 y no otro DW write

; controlador de dispositivo DW write_verify

DW output_status

num_ordenes DB 10h ; nº de órdenes soportadas DW output_flush

DW ioctl_output

i_tdsk_ctrl EQU $ ; inicio del área a actualizar DW open ; DOS 3.0+

DW close ; DOS 3.0+

tipo_soporte DB 0FFh ; 0: disco no formateado DW remove ; DOS 3.0+

; 1: se emplea memoria XMS 2.0+

; 2: " " " EMS 3.2+ media EQU 0FAh ; byte descriptor de medio utilizado por

; 3: " " " convencional ; TURBODSK. No es 0F8h como en los discos

; 0FFh: aún no ejecutada INIT ; virtuales del sistema ya que TURBODSK

; 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 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

tdsk_psp DW ? ; 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


CONTROLADORES DE DISPOSITIVOS 203

sectores_fat DW 4 ; tamaño de los buffers de disco del DOS. input_status: ; tratamiento idéntico

fin_bpb EQU $ 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 ; no hay error, ignorar orden

RET

estrategia ENDP build_bpb: MOV [BX].bpb_cmd_desp,OFFSET bpb

MOV [BX].bpb_cmd_segm,CS

; ------------ Rutina de interrupción del disco virtual. TURBODSK, JMP retorno_ok

; al igual que RAMDRIVE o VDISK, no define una pila

; interna. Es responsabilidad del DOS que ésta tenga el ioctl_input: MOV AX,8103h ; orden no soportada

; tamaño adecuado (con el disco en memoria XMS, el RET

; controlador XMS puede requerir hasta 256 bytes de

; pila). TURBODSK no consume más de 64 bytes de pila en remove: MOV AH,3 ; fin de función, indicar

; ningún momento, y sólo alrededor de 48 antes de llamar RET ; «controlador ocupado»

; al controlador XMS cuando se emplea esta memoria.

nueva_int19 PROC ; Interceptar reinicialización

interrupcion PROC FAR .286

XPUSH <AX,BX,CX,DX,SI,DI,BP,DS,ES> PUSHA

LDS BX,CS:pcab_peticion XPUSH <DS,ES> ; Esto es una interrupción

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 ; no

MOV AL,3 ; " desconocida (IOCTL INPUT) MOV CS:tipo_soporte,AL ; sí: anularlo

orden_ok: CMP CS:tipo_soporte,AH CALL procesa_io ; CF=1: liberar memoria EMS/XMS

JNE no_test_fmt ; tipo_soporte distinto de 0 no_lib: LEA SI,ant19off

MOV AX,8102h ; disco no formateado: error MOV DI,64h ; desplazamiento de INT 19h

JMP exit_interr PUSH CS

no_test_fmt: SHL AX,1 ; orden = orden * 2 POP DS

MOV SI,AX CLI

XPUSH <BX,DS> MOVSW

XOR BP,BP MOVSW

MOV AX,100h XPOP <ES,DS>

CALL CS:[SI+OFFSET p_rutinas] ; ejecutar orden POPA

XPOP <DS,BX> DB 0EAh ; código de JMP FAR SEG:OFF

AND AH,AH ant19off DW ?

JNS exit_interr ; no hubo error (bit 15 = 0) ant19seg DW ?

CMP AL,3 .8086

JE exit_interr ; error de orden desconocida nueva_int19 ENDP

MOV [BX].transfer_sect,0 ; otro: movidos 0 sectores

exit_interr: MOV [BX].estado,AX read: INC BP ; indicar lectura (BP=1)

XPOP <ES,DS,BP,DI,SI,DX,CX,BX,AX> write: ; escritura (BP=0)

RET write_verify:

interrupcion 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 ; 1º sector ¡DS indefinido!

; entrada, BP=0 y AX=100h. io_proc: MOV SI,CS:bytes_sector

ADD AX,BX

media_check: MOV AL,CS:cambiado ; condición de «disco cambiado» JNC io_ok? ; último sector < 65536

MOV CS:cambiado,AH ; de momento ya no cambiará más io_no_ok: MOV AX,8108h ; «sector no encontrado»

MOV [BX].cambio,AL RET

io_ok?: CMP AX,CS:num_sect

read_nowait: ; conjunto de órdenes con JA io_no_ok ; sector final ¡fuera!


203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

SUB AX,BX ADD BX,CX ; AX = segmento de datos

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 ; intentar emplear página 0

NEG CX ; 10000h-CX: CF=1 si CX<>0 SUB BX,CX

CMC ; CF:CX bytes hasta fin de JNC rpos

RCR CX,1 ; segmento = (10000h-DI)/2 NEG BX ; valor absoluto

CMP AX,CX rpos: CMP BX,401h ; distancia respecto página EMS

JAE io_cx_ok JAE no_conflicto ; más de 16 Kb: no solapamiento

MOV CX,AX ; * tamaño: CX palabras CALL copia_contexto ; está CX apilado

io_cx_ok: 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

CLC ; ¡no reinicializando! no_conflicto: POP CX ; * pila totalmente equilibrada

init_io ENDP MOV BX,AX

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 <CX,SI> ; ++

; 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 ; no ocupada toda la página

procesa_io EQU $ MOV CX,SI

cx_ok: POP SI ; + SI=desplazamiento relativo

; ---- 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 ; ¿386 o superior?

; 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 ; transferencia ultrarrápida

procesa_ems PROC transferido: POPAD ; POPAD falla en muchos 386

JNC no_emslib .8086

MOV DH,45h ; sistema reinicializando: NOP ; arreglar fallo de POPAD

CALL llama_EMM ; liberar memoria EMS ADD CX,CX

RET ADD DI,CX ; simular cambio normal de DI

no_emslib: 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 ; mover palabras de 16 bit

MOV DX,SI ; recuperar DX fin_trans: CALL coloca_regs

MOV BX,4000h ; tamaño de página (16 Kb) AND BP,BP ; ¿se usó página alternativa?

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

procesa_pag: PUSH CX ; ** AND BP,1 ; de momento, no se usará más

MOV BX,DI ahorra_ms: POP CX ; **

MOV CL,4 JCXZ fin_leer ; no quedan más palabras

SHR BX,CL ; bytes del offset -> párrafos INC AX ; próxima página EMS

MOV CX,ES XOR SI,SI ; ahora desde inicio página EMS


CONTROLADORES DE DISPOSITIVOS 203

JMP procesa_pag

fin_leer: MOV DH,48h ; <<< Fin del código residente del disco virtual >>>

CALL llama_EMM ; DH=47h restaurar contexto EMS

MOV AX,100h ; no hubo problemas

RET ; ************ Instalación (invocada desde CONFIG.SYS).

procesa_ems ENDP

init PROC

; ---- ¡Cuidado!: esta rutina debe ser invocada siempre 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+

llama_EMM PROC AND CS:tipo_drive,0F7FFh ; ajustar atributos

XPUSH <AX,BX,CX,BP> MOV CS:num_ordenes,0Dh ; y número de órdenes

MOV AX,DX ; función en AX dos_ok: MOV SI,[BX].bpb_cmd_desp

llama_denuevo: MOV DX,CS:mem_handle ; handle EMS MOV ES,[BX].bpb_cmd_segm ; ES:SI -> parámetros

XPUSH <AX,BX> MOV [BX].num_discos,1 ; una unidad de disco

INT 67h ; llamar al EMM LEA AX,bpb_ptr

MOV CL,AH MOV [BX].bpb_cmd_desp,AX

XPOP <BX,AX> MOV [BX].bpb_cmd_segm,CS ; inicializado puntero BPB

AND CL,CL CALL desvia_int19 ; controlar INT 19h

JZ llama_ok ; además, ZF = 1 CALL inic_letra ; obtener letra de unidad

CMP CL,82h MOV BX,CS

JE llama_denuevo ; intentarlo hasta que funcione MOV DS,BX ; DS: -> _PRINCIPAL

llama_ok: XPOP <BP,CX,BX,AX> MOV BX,SI ; ES:BX -> parámetros

JNE ret_atras CALL salta_nombre ; buscar inicio parámetros

RET CALL procesar_param ; procesar parámetros

ret_atras: POP AX ; sacar dirección de retorno PUSH DS ;

MOV AX,810Ch ; error de «anomalía general» POP ES ; ES: -> _PRINCIPAL

RET ; retornar dos niveles atrás CMP param_b,ON

llama_EMM ENDP JNE pet_ayuda?

MOV AH,8 ; opción /B

; ---- ¡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 ; ¿nº de discos duros?

POP ES

copia_contexto PROC AND DL,3

XPOP <BX,CX> ; equilibrar pila a llama_EMM JZ pet_ayuda? ; no existe disco duro

MOV DH,48h LEA AX,procesa_io

CALL llama_EMM ; restaurar contexto EMS NEG AX

MOV DH,47h JMP bytes_res_ok ; no quedará residente

CALL llama_EMM ; preservarlo de nuevo pet_ayuda?: CMP param_h,ON

PUSH CX JE fin_instalar ; piden ayuda

JMP BX ; más rápido que PUSH BX/RET CALL max_sector ; obtener mayor sector

copia_contexto ENDP MOV BX,param_tsect

CMP BX,AX ; ¿el nuestro es mayor?

coloca_regs PROC ; ¿invertir sentido? JBE sect_def_ok ; no

TEST BP,1 MOV bytes_sector,BX ; sí: ajustar BPB

JNZ colocados sect_def_ok: CALL errores_config

XCHG SI,DI ; escritura: invertir sentido TEST lista_err,ERROR0+ERROR1

XPUSH <DS,ES> JNZ fin_instalar ; algún error importante

XPOP <DS,ES> CMP param_tdisco,0 ; ¿se define disco ahora?

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?

tam_proc_ems EQU $-OFFSET procesa_ems ; tamaño de esta rutina JE fin_instalar ; no: no hay más que hacer
203 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 ; no indicado nuevo tamaño

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 cont_instalar: CALL mem_info ; evaluar memoria del PC

CALL formatear_tdsk ; inic. BOOT, FAT y ROOT CMP tdisco,0 ; ¿se reservará memoria?

fin_instalar: 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 ; se utiliza memoria EMS JC exit_instalar ; fallo reservando memoria

CMP param_a,ON CALL test_CPU ; detectar 386 ó superior

JE res_largo ; se indicó /A 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 ; no hay memoria EMS CALL formatear_tdsk ; BOOT, FAT y ROOT

CMP xms_kb,0 exit_instalar: CALL info_disco ; informar sobre el disco

JNE res_corto ; la hay, pero también XMS CMP tipo_soporte,3 ; ¿memoria convencional?

res_largo: MOV AX,tam_proc_ems JNE fin_no_res ; no usada

MOV rutina_larga,ON ; dejar sitio a rutina EMS CALL renombrar_mcb ; cambiar nombre del MCB

JMP bytes_res_ok MOV DX,6 ; usada: 96 bytes de PSP

res_corto: MOV AX,tam_proc_xms ; dejar sitio a XMS/conv. MOV AX,3100h

MOV BX,tam_proc_con INT 21h ; terminar residente

CMP AX,BX fin_no_res: CALL set_errorlevel ; preparar ERRORLEVEL

JAE bytes_res_ok MOV AH,4Ch

XCHG AX,BX INT 21h ; final normal

bytes_res_ok: LDS BX,CS:pcab_peticion main ENDP

ADD AX,OFFSET procesa_io

MOV [BX].fin_resid_desp,AX ; reservar memoria para ; ------------ Inicializar la variable con la versión del DOS

MOV [BX].fin_resid_segm,CS ; las rutinas a usar

MOV AX,100h ; instalación siempre Ok. obtDosVer PROC

RET XPUSH <AX,BX,CX,DX>

init ENDP MOV AH,30h

INT 21h

; ------------ Redefinición (invocada desde el AUTOEXEC.BAT o el DOS). XCHG AH,AL

MOV CS:dosver,AX

main PROC FAR XPOP <DX,CX,BX,AX>

MOV CS:modo,AUTOEXEC ; ejecutando desde el DOS RET

CALL obtDosVer ; obtener versión del DOS obtDosVer ENDP

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 ; piden ayuda ; objetivo de esta maniobra es poder asignar memoria al

PUSH DS ; disco después (sólo si hace falta memoria convencional)

POP ES ; ES: --> _PRINCIPAL ; 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] ; segmento del entorno

JE cabria_ems ; cabe la rutina EMS MOV AH,49h

OR lista_err,ERROR2 INT 21h ; liberar área de entorno

cabria_ems: TEST lista_err,ERROR0+ERROR2 ; ¿error sintaxis ó EMS? POP ES ; ES: -> PSP

JNZ exit_instalar ; sí: no modificar disco MOV BX,6


CONTROLADORES DE DISPOSITIVOS 203

MOV AH,4Ah ; hacer creer al DOS que p_exit?: CMP AX,"?/" ; /H y /? son equivalentes

INT 21h ; TDSK ocupa sólo 96 bytes JE p_ayuda

RET CMP AX,"m/" ; ¿indicado /M?

gestionar_ram ENDP JNE param_id?

MOV param_m,ON

; ------------ Leer los parámetros de la línea de comandos (ES:BX). JMP p_barra_exit

; Se inicializan las correspondientes variables. En caso param_id?: CMP AX,"i/" ; ¿indicado /I= o /I:?

; 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],'='

procesar_param PROC JE p_id_ok

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" p_id_ok: CALL obt_num ; leer código telefónico

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

p_param2: CALL busca_param JMP p_barra_exit

JC fin_param param_fats?: CMP AX,"f/" ; ¿indicado /F= o /F:?

CALL param_barra JNE param_b?

JC p_param2 ADD BX,3

MOV param_tsect,AX ; tamaño de sector CMP BYTE PTR ES:[BX-1],'='

p_param3: 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 p_f_ok: CALL obt_num ; leer número de FATs

MOV param_tdir,AX ; entradas al directorio MOV param_f,AX

p_param4: CALL busca_param SUB BX,2

JC fin_param JMP p_barra_exit

CALL param_barra param_b?: CMP AX,"b/" ; ¿indicado /B?

JC p_param4 JNE param_unidad?

MOV param_tcluster,AX ; tamaño de cluster MOV param_b,ON

p_param5: CALL busca_param JMP p_barra_exit

JC fin_param param_unidad?: CMP AH,':' ; ¿parámetro de unidad?

CALL param_barra ; últimas opciones posibles JNE param_num?

JC p_param5 AND AL,255-32 ; poner en mayúsculas

fin_param: CALL validacion ; validación de parámetros MOV param_unidad,AL

RET JMP p_barra_exit

procesar_param ENDP param_num?: CMP AL,'/'

JNE param_num ; puede ser número

param_barra PROC param_b_mal: OR lista_err,ERROR0

CMP AX,"e/" ; ¿indicado /E? param_num: CALL obt_num ; es parámetro numérico: leerlo

JNE p_exp1? CLC ; no es parámetro barrado

MOV param_e,ON RET

JMP p_barra_exit p_barra_exit: ADD BX,2 ; saltar este parámetro

p_exp1?: CMP AX,"a/" ; ¿indicado /A? STC ; es parámetro barrado

JNE p_exp2? RET

p_exp: MOV param_a,ON param_barra ENDP

JMP p_barra_exit

p_exp2?: CMP AX,"x/" ; /A y /X son equivalentes validacion PROC

JE p_exp MOV AX,0FFFFh

CMP AX,"c/" ; ¿indicado /C? CMP AX,param_tdisco ; ¿números correctos?

JNE p_ayuda? JE sintax_err

MOV param_c,ON CMP AX,param_tsect

JMP p_barra_exit JE sintax_err

p_ayuda?: CMP AX,"h/" ; ¿indicado /H? CMP AX,param_tdir

JNE p_exit? JE sintax_err

p_ayuda: MOV param_h,ON CMP AX,param_tcluster

JMP p_barra_exit JE sintax_err


203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

CMP param_tdisco,0 salta_nombre ENDP

JE valida_tsect ; no indicado tamaño (o 0)

CMP param_tdisco,8 busca_param PROC ; saltar delimitadores

JB sintax_err DEC BX

CMP param_tdisco,65534 p_delimit: INC BX

JA sintax_err MOV AX,ES:[BX]

valida_tsect: MOV AX,param_tsect CMP AL,' '

CMP AX,0 JE p_delimit ; espacio en blanco

JE valida_tclus ; no indicado tamaño de sector CMP AL,9

CMP AX,32 JE p_delimit ; tabulador

JE valida_tclus CMP AL,13

CMP AX,64 JE p_final ; CR ó LF indican el final

JE valida_tclus CMP AL,10

CMP AX,128 JE p_final

JE valida_tclus OR AX," " ; poner en minúsculas

CMP AX,256 CLC

JE valida_tclus RET

CMP AX,512 p_final: STC ; se acabaron los parámetros

JE valida_tclus RET

CMP AX,1024 busca_param ENDP

JE valida_tclus

CMP AX,2048 obt_num PROC ; leer número: devolver 65535

JNE sintax_err XPUSH <CX,DX,SI> ; si hay error

valida_tclus: CMP param_tcluster,256 XOR AX,AX ; número en proceso de creación

JAE sintax_err ; debe estar entre 0..255 otro_digito: 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 no_digito: CMP CL,' ' ; posibles delimitadores...

JMP fin_validar JE fin_num

pf_a1: MOV param_f,1 CMP CL,9

JMP fin_validar JE fin_num

sintax_err: 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 ; aviso de error de sintaxis JMP num_incorr

fin_validar: RET digito_ok: XOR DX,DX

validacion ENDP MOV SI,10

MUL SI ; AX = AX * 10

salta_nombre PROC ; saltar nombre del driver en JC num_incorr

MOV AL,ES:[BX] ; línea de órdenes del CONFIG XOR CH,CH

INC BX SUB CL,'0'

CMP AL,' ' ADD AX,CX ; AX = AX + dato

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 ; indicar valor incorrecto

JE fin_nombre fin_num: XPOP <SI,DX,CX>

CMP AL,0Ah RET

JE fin_nombre obt_num ENDP

AND AL,AL

JZ fin_nombre ; necesario para DOS 2.x ; ------------ Detectar errores que se pueden producir sólo en la

JMP salta_nombre ; línea de comandos.

fin_nombre: DEC BX

RET errores_Dos PROC


CONTROLADORES DE DISPOSITIVOS 203

PUSH ES CMP ES:tipo_soporte,0

CMP dosver,200h ; necesario DOS 2.x+ JNE fin_cod_ok

JAE existe_tdsk? MOV AL,0 ; disco no formateado

OR err_grave,ERROR0 ; error de DOS incorrecto fin_cod_ok: RET

JMP fin_err_Dos set_errorlevel ENDP

existe_tdsk?: CALL reside_tdsk? ; ¿instalado TURBODSK?

CMP segm_tdsk,0 ; ------------ Obtener mayor tamaño de sector definido en el sistema.

JNE busca_unidad ; ya instalado

OR err_grave,ERROR1 ; error: TURBODSK no instalado max_sector PROC

JMP fin_err_Dos XPUSH <BX,ES>

busca_unidad: MOV ES,segm_tdsk ; ES: -> disco virtual MOV AH,52h

CMP param_unidad,0 INT 21h ; Get List of Lists

JE disco_defecto ; no se indicó letra de unidad ADD BX,10h

CALL obtener_segm ; segmento del TDSK indicado CMP CS:dosver,30Ah

JC fin_err_Dos ; fallo (no es unidad TDSK) JAE psect_ok

disco_defecto: CALL max_sector ; obtener mayor sector INC BX ; DOS anterior al 3.1

MOV BX,param_tsect psect_ok: MOV AX,ES:[BX] ; mayor tamaño de sector

CMP BX,AX XPOP <ES,BX> ; definido por cualquier disp.

JBE fin_err_Dos ; tamaño de sector correcto RET

OR lista_err,ERROR3 ; el tamaño no definible ahora max_sector ENDP

MOV param_tsect,0 ; ignorar tamaño indicado

fin_err_Dos: 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

RET test32Mb PROC

errores_Dos ENDP CMP param_tdisco,32768

JBE fin32mb

; ------------ Detectar errores que se pueden producir sólo desde CMP param_tsect,1024

; el CONFIG.SYS JAE fin32mb

OR lista_err,ERROR15 ; sector de menos de 1024

errores_config PROC MOV param_tdisco,32768 ; evitar fallo posterior

CMP param_unidad,0 fin32mb: RET

JE no_unidad test32Mb ENDP

OR lista_err,ERROR1

no_unidad: CMP param_c,ON ; ------------ Desde Windows, no se permite redefinir el disco.

JNE fin_err_con

OR lista_err,ERROR1 testWin PROC

fin_err_con: CALL test32Mb CMP param_tdiscof,ON

RET JNE fin_testWin ; no redefinido el disco

errores_config ENDP CMP dosver,300h

JB fin_testWin ; no buscar Windows en DOS 2.x

; ------------ Preparar valor de ERRORLEVEL para el retorno. MOV AX,1600h

INT 2Fh

set_errorlevel PROC AND AL,AL ; ¿Windows en modo extendido?

MOV AL,255 JZ noWinEnh

TEST err_grave,ERROR1 ; ¿TDSK no instalado? CMP AL,80h ; ¿Windows en modo extendido?

JNZ fin_cod_ok JE noWinEnh

DEC AL siWin: OR err_grave,ERROR3 ; estamos dentro de Windows

TEST err_grave,ERROR2 ; ¿unidad incorrecta? JMP fin_testWin

JNZ fin_cod_ok noWinEnh: MOV AX,4680h

DEC AL INT 2Fh

TEST err_grave,ERROR3 ; ¿dentro de Windows? AND AX,AX

JNZ fin_cod_ok JZ siWin ; Windows en modo real/estándar

DEC AL fin_testWin: RET

TEST lista_err,ERROR0 ; error de sintaxis testWin ENDP

JNZ fin_cod_ok

CMP param_h,ON ; ayuda: handle desconocido ; ------------ Verificar la presencia en memoria de TURBODSK. Se

JE fin_cod_ok ; inicializa «segm_tdsk» y «letra_unidad» indicando dónde

MOV AL,BYTE PTR ES:mem_handle ; handle XMS/EMS ; 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

reside_tdsk? PROC ; tarea virtual, ésta queda permanentemente ocupada hasta

XPUSH <AX, SI> ; 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

busca_final: 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 ; ir al final de la tabla ; controla INT 19h contiene código de 286, por lo que se

busca_tdsk: SUB SI,4 ; chequea la presencia de este procesador.

CMP SI,OFFSET area_trabajo

JB fin_busca ; no reside (segm_tdsk = 0) desvia_int19 PROC

CMP BYTE PTR [SI+3],1 XPUSH <BX,DS,ES>

JNE busca_tdsk MOV BX,CS

MOV AX,[SI] ; encontrada unidad TURBODSK MOV DS,BX

MOV segm_tdsk,AX CALL test_CPU

PUSH DS CMP cpu286,ON

MOV DS,AX JNE fin_desvia19 ; no es 286 ó superior

MOV AL,letra_unidad ; con esta letra de unidad MOV AX,3519h

POP DS INT 21h ; ES:BX anterior INT 19h

MOV letra_unidad,AL MOV ant19off,BX

fin_busca: XPOP <SI, AX> MOV ant19seg,ES

RET LEA DX,nueva_int19

reside_tdsk? ENDP MOV AX,2519h

INT 21h ; nueva rutina de control

; ------------ Obtener el segmento de la unidad TURBODSK indicada, si fin_desvia19: XPOP <ES,DS,BX>

; existe, accediendo a una tabla de dispositivos que se RET

; crea. A la salida, CF=1 si esa unidad no es TURBODSK. desvia_int19 ENDP

obtener_segm PROC ; ------------ Obtener la letra de la unidad de disco definida. Esta

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.

busca_ultimo: ADD SI,4

CMP WORD PTR [SI],0 inic_letra PROC

JNE busca_ultimo ; realmente, el primero XPUSH <AX,BX,SI,DS>

recorre_dsks: SUB SI,4 MOV AL,[BX].nuevo_disco ; unidad en DOS 3.0+

CMP SI,OFFSET area_trabajo ADD AL,'A'

JB tdsk_no_hay PUSH CS

CMP BYTE PTR [SI+3],1 POP DS ; DS -> _PRINCIPAL

JNE recorre_dsks CMP dosver,300h

PUSH DS JAE letra_ok

MOV DS,[SI] CALL lista_discos ; hallar unidad en DOS 2.x

MOV AL,letra_unidad ; unidad del TDSK residente LEA SI,area_trabajo

POP DS XOR AL,AL ; cuenta de discos

CMP AL,param_unidad ; disco TDSK: ¿es el buscado? cuenta_discos: ADD AL,[SI+2]

JNE recorre_dsks ADD SI,4

MOV letra_unidad,AL ; inicializar letra de unidad CMP WORD PTR [SI],0

MOV AX,[SI] JNE cuenta_discos

MOV segm_tdsk,AX ; inicializar segmento ADD AL,'A'

MOV ES,AX letra_ok: MOV letra_unidad,AL ; guardar letra de unidad

CLC XPOP <DS,SI,BX,AX>

RET RET

tdsk_no_hay: OR err_grave,ERROR2 ; unidad indicada no es TDSK inic_letra ENDP

STC

RET ; ------------ Crear una lista de todos los dispositivos de bloque

obtener_segm ENDP ; del sistema. La lista tiene una entrada de 4 bytes


CONTROLADORES DE DISPOSITIVOS 203

; 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 ; condición de error

; indicar si es una unidad TDSK o no. El final de la MOV ES,ES:tdsk_psp ; liberar PSP residente

; lista lo señaliza un segmento igual a 0. MOV AH,49h

INT 21h

lista_discos PROC PUSHF

XPUSH <AX,BX,CX,DX,SI,DI,ES> CMP dosver,31Eh

MOV AH,52h ; "Get list of lists" JA mcb_ok ; DOS 3.31+: el MCB es correcto

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 ; supuesto DOS 3.0x MOV CX,DI

CMP dosver,30Ah CLD

JB pdisp_ok MOV AL,' '

MOV CX,22h ; versiones del DOS superiores REP STOSB ; hasta DOS 3.30 borrar nombre

pdisp_ok: ADD BX,CX mcb_ok: POPF

LEA DI,area_trabajo-4 ; tabla de dispositivos-4 JNC lib_con_ok? ; liberado correctamente

disp_otro: ADD DI,4 POPF

disp_skip: LES BX,ES:[BX] ; siguiente dispositivo POP ES

CMP BX,-1 STC ; ha habido fallo

JE disp_fin JMP desinstalado

TEST BYTE PTR ES:[BX+5],80h lib_con_ok?: POPF ; recuperar condición de error

JNZ disp_skip ; es dispositivo de caracteres POP ES

MOV CL,ES:[BX+10] ; es de bloques JMP desinstalado

MOV [DI],ES ; anotar dirección libera_ext: MOV AH,0Ah

MOV [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 ; identificación de TURBODSK STC

MOV DI,SI JMP desinstalado ; fallo

MOV CX,5 libera_exp: MOV AH,45h

CLD INT 67h

REP CMPSB ; ¿es TURBODSK? CMP AH,0

POP DI JE desinstalado

JNE disp_otro ; es de bloques, pero no TDSK CMP AH,82h ; ¿EMM ocupado?

MOV AX,ES:cs_tdsk ; segmento real de TDSK JE libera_exp

MOV [DI],AX ; corregir dirección en tabla STC ; fallo al liberar memoria EMS

INC BYTE PTR [DI+3] ; indicar dispositivo TDSK desinstalado: MOV ES:tipo_soporte,0 ; disco «no formateado»

JMP disp_otro ; buscar hasta completar tabla JNC desins_ok

disp_fin: MOV WORD PTR [DI],0 ; final de la lista OR lista_err,ERROR14 ; fallo al liberar memoria

XPOP <ES,DI,SI,DX,CX,BX,AX> STC

RET desins_ok: RET

lista_discos ENDP desinstala ENDP

; ------------ Liberar la memoria ocupada por un TURBODSK residente. ; ------------ Determinar la configuración del sistema: tipos de

; memoria y cantidad de la misma. Se indica en «tdisco»

desinstala PROC ; 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 ; liberar memoria extendida mem_info PROC

DEC AL MOV tdisco,0 ; ley de Murphy

JZ libera_exp ; liberar memoria expandida CALL eval_xms ; inicializar «xms_kb»

PUSH ES CALL eval_ems ; inicializar «ems_kb»

MOV ES,DX CALL eval_con ; inicializar «con_kb»

MOV AH,49h ; liberar memoria convencional: MOV AX,param_tdisco ; cantidad de memoria necesaria

INT 21h CMP param_a,ON


203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

JNE no_ems ; no solicitan memoria EMS JAE usar_xms ; hay más o igual XMS que EMS

MOV BX,ems_kb ; solicitan memoria EMS... MOV AX,ems_kb

AND BX,BX JMP usar_ems ; hay algo de EMS (más que XMS)

JNZ usara_ems usar_con?: CMP modo,AUTOEXEC

OR lista_err,ERROR7 ; no hay memoria EMS disponible JE forzar_con ; sólo se puede usar mem. conv.

JMP mem_infoado OR lista_err,ERROR5 ; ho hay memoria EMS ni XMS

usara_ems: CMP AX,BX mem_infoado: RET

JBE usar_ems ; piden algo razonable mem_info ENDP

MOV AX,BX

OR lista_err,ERROR4 ; rebajado el tamaño ; ---- Calcular memoria extendida disponible

usar_ems: MOV tdisco,AX

MOV tipo_soporte,2 ; indicar memoria expandida eval_xms PROC

JMP mem_infoado PUSH ES

no_ems: CMP param_e,ON MOV AX,352Fh

JNE no_xms ; no solicitan memoria XMS INT 21h ; dirección de INT 2Fh en ES:BX

MOV BX,xms_kb ; solicitan memoria XMS... MOV AX,ES

AND BX,BX AND AX,AX

JNZ usara_xms JZ xms_ok ; apunta a 0000:XXXX (DOS 2.x)

OR lista_err,ERROR6 ; no hay memoria XMS disponible MOV AX,4300h

JMP mem_infoado INT 2Fh

usara_xms: CMP AX,BX CMP AL,80h ; ¿hay controlador XMS?

JBE usar_xms ; piden algo razonable JNE xms_ok

MOV AX,BX MOV AX,4310h ; obtener su dirección

OR lista_err,ERROR4 ; rebajado el tamaño INT 2Fh

usar_xms: MOV tdisco,AX MOV xms_segm,ES

MOV tipo_soporte,1 ; indicar memoria extendida MOV xms_desp,BX

JMP mem_infoado MOV AH,8

no_xms: CMP param_c,ON CALL xms_driver ; preguntar memoria libre

JNE no_con ; no solicitan memoria conv. AND AX,AX

forzar_con: MOV BX,con_kb ; solicitan memoria conv. ... JNZ xms_kb_ok ; no hubo fallo

AND BX,BX CMP BL,0A0h

JNZ usara_con JE xms_kb_ok ; asignada ya toda la memoria

OR lista_err,ERROR10 ; no hay memoria conv. libre TEST BL,80h

JMP mem_infoado JZ xms_kb_ok ; no hay memoria XMS disponible

usara_con: CMP AX,BX OR lista_err,ERROR8 ; fallo real del controlador

JBE usar_con ; piden algo razonable xms_kb_ok: CMP AX,8 ; mayor bloque XMS disponible

MOV AX,BX JB xms_ok

OR lista_err,ERROR4 ; rebajado el tamaño MOV xms_kb,AX ; mínimo necesario: 8 Kb

usar_con: MOV tdisco,AX xms_ok: POP ES

MOV tipo_soporte,3 ; indicar memoria convencional RET

JMP mem_infoado eval_xms ENDP

no_con: CMP AX,xms_kb ; no indicado tipo de memoria

JBE usar_xms ; intentar emplear memoria XMS ; ---- Calcular memoria expandida disponible. Si la

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 eval_ems PROC

JMP usar_con? PUSH ES

valdria_ems: MOV BX,ems_kb MOV AX,3567h

CMP AX,BX INT 21h ; vector de INT 67h en ES:BX

JA nv_ems MOV DI,10

JMP usar_ems ; emplear memoria EMS LEA SI,emm_id

nv_ems: 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 ; ¿instalado controlador EMS?

OR lista_err,ERROR4 ; rebajado el tamaño solicitado JE ems_existe

MOV AX,xms_kb JMP ems_ok

CMP AX,ems_kb ems_existe: MOV CX,8000h ; nº de intentos prudente


CONTROLADORES DE DISPOSITIVOS 203

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 ems_ok: POP ES

JZ emm_responde RET

CMP AH,82h eval_ems ENDP

LOOPE emm_llama

emm_fatal: OR lista_err,ERROR9 ; fallo del EMM emm_busca_pag PROC ; buscar página nº DX (EMS 4.0)

JMP ems_ok LEA SI,area_trabajo

emm_responde: MOV AH,41h PUSH CX

INT 67h emm_otra_pag: LODSW

AND AH,AH MOV BX,AX ; BX = segmento de la página

JZ emm_pag_ok LODSW ; AX = nº de la página

CMP AH,82h CMP AX,DX

JE emm_responde ; reintentar (EMM ocupado) JE hallada_pag

JMP emm_fatal LOOP emm_otra_pag

emm_pag_ok: MOV ems_pagina0,BX ; inicializar página EMS STC

ADD BX,0C00h hallada_pag: POP CX

MOV ems_paginai,BX RET

MOV ems_pagni,3 ; página alternativa: la 3 emm_busca_pag ENDP

MOV AH,46h

INT 67h ; obtener versión del EMM ; ---- Calcular el tamaño del mayor bloque de memoria

CMP AL,40h ; convencional disponible. Como mínimo se dejarán

JB emm_obt_kb ; versión anterior a la 4.0 ; unos 128 Kb libres en él, para que el usuario

MOV ems4,ON ; pueda volver a ejecutar TDSK y el DOS tenga algo

emm_obt_pag: XPUSH <ES,DS> ; de memoria libre. A la mitad de esos 128Kb (para

POP ES ; evitar solapamientos) es donde TURBODSK se

MOV AX,5800h ; obtener dirección de páginas ; autorelocalizará antes de formatear el disco.

LEA DI,area_trabajo

INT 67h eval_con PROC

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 ; pedir 1 Mb al DOS (fallará)

JE emm_obt_pag INT 21h

JMP emm_fatal MOV DX,BX ; tamaño del mayor bloque

emm_pags_ok: XOR DX,DX MOV CL,6

CALL emm_busca_pag ; buscar página 0 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

ems_busca_i: INC DX ; buscar la siguiente CMP BX,8

CMP DX,5 ; la 5ª y siguientes no valen JB conv_ok ; no quedan siquiera 8 Kb

JE emm_fatal ; ├──────┤ MOV con_kb,BX

CALL emm_busca_pag ; │ │ MOV BX,DX ; tamaño del mayor bloque

JC emm_fatal ; ┌> ┌>├──────┤<-- pág i MOV AH,48h

MOV ems_paginai,BX ;0C00h│ 32 │ │ │ PUSH BX

MOV ems_pagni,DL ; pá │ Kb │ ├──────┤ INT 21h ; localizarlo (AX=segmento)

SUB BX,ems_pagina0 ; rra │ │ │ │ POP BX

JNC bxpositivo ; fos │ └>├──────┤ XPUSH <ES,AX> ; preservar ES y segmento (AX)

NEG BX ; │ │ │ ADD AX,BX ; añadir longitud

bxpositivo: CMP BX,0C00h ; └> ├──────┤<-- pág 0 SUB AX,1024/16*64 ; restar 64 Kb

JB ems_busca_i ; no distan 32 Kb: buscar otra MOV segm_reubicar,AX ; segmento de autoreubicación

emm_obt_kb: MOV 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

emm_kb_ok: 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 ren_handle PROC ; detectar el handle EMS ligado

; en EMS (caso de QEMM386 y otros emuladores de EMS) y en XPUSH <ES,DS> ; al handle XMS y renombrarlo

; 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 ; crear nueva lista de handles

mem_reserva PROC LEA SI,area_trabajo

MOV AL,tipo_soporte ; tipo de memoria empleada LEA DI,area_trabajo[512]

DEC AL MOV CX,256

JZ mem_r_xms ; 1: memoria extendida XMS CLD

DEC AL REP CMPSW ; comparar con vieja lista

JZ mem_r_ems ; 2: memoria expandida EMS JE ren_hnld_fin

MOV CL,6 MOV DX,[DI-2] ; handle nuevo

MOV BX,tdisco ; 3: memoria convencional CALL nombrar_hndl

SHL BX,CL ren_hnld_fin: POP ES

MOV AH,48h RET

INT 21h ren_handle ENDP

MOV mem_handle,AX ; segmento del disco virtual

MOV BX,segm_psp lista_handles PROC ; crear en DS:BX una lista con

MOV tdsk_psp,BX ; inicializar esta variable MOV CX,256 ; los 256 posibles handles

RET XOR DX,DX ; activos indicando los usados

mem_r_xms: CMP ems4,ON listar_h: MOV AX,5300h

JNE skip_lst_hndl LEA DI,area_trabajo[tam_a_trabajo-8] ; zona no usada

LEA BX,area_trabajo XPUSH <BX,CX,DX>

CALL lista_handles ; EMS 4.0+: listado de handles INT 67h

skip_lst_hndl: MOV AH,9 XPOP <DX,CX,BX>

MOV DX,tdisco CMP AH,0

CALL xms_driver ; pedir memoria XMS JE handle_usado

AND AX,AX MOV WORD PTR [BX],0 ; error (handle no usado)

JNZ mem_rda_xms JMP lista_h

OR lista_err,ERROR8 ; fallo del controlador XMS handle_usado: MOV [BX],DX ; anotar número de handle

STC ; indicar error lista_h: ADD BX,2

mem_rda_xms: MOV mem_handle,DX INC DX

PUSHF ; preservar condición de error LOOP listar_h

CMP ems4,ON RET

JNE skip_ren_hndl lista_handles ENDP

CALL ren_handle ; en EMS 4.0+ renombrar handle

skip_ren_hndl: POPF nombrar_hndl PROC ; nombrar handle (EMS 4.0+)

RET MOV AX,5301h

mem_r_ems: MOV BX,tdisco LEA SI,nombre_tdsk

ADD BX,15 MOV BL,letra_unidad

AND BL,11110000b ; redondear para arriba MOV [SI+5],BL

MOV tdisco,BX INT 67h ; dar nombre al handle

MOV CL,4 RET

SHR BX,CL ; Kb -> nº páginas de 16 Kb nombrar_hndl ENDP

MOV AH,43h

INT 67h ; pedir memoria EMS ; ------------ Detectar 286 y 386 o superior.

AND AH,AH

JZ mem_rda_ems test_CPU PROC

OR lista_err,ERROR9 ; fallo del controlador EMS PUSHF

STC ; indicar error POP AX

RET OR AH,70h ; intentar activar bit 12, 13 ó 14

mem_rda_ems: MOV mem_handle,DX PUSH AX ; del registro de estado

CMP ems4,ON POPF

JNE nhandle_ok PUSHF

CALL nombrar_hndl ; en EMS 4.0+ nombrar handle POP AX

nhandle_ok: CLC AND AH,0F0h

RET CMP AH,0F0h

mem_reserva ENDP JE fin_test_CPU ; es 8086 o similar


CONTROLADORES DE DISPOSITIVOS 203

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

; ------------ Definir valores por defecto y adaptar los parámetros MOV CL,32

; indicados por el usuario a la realidad. Esta rutina prop_valido: MOV tdir,BX

; 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 ; menos de 65536 sectores: correcto

; nsect * tamsect 2 * nsect * tamsect OR lista_err,ERROR11

; ------------------ + 1 = --------------------- + 1 JMP tsect_rec ; asumir por defecto y recalcular

; tamcluster + 1,5 2 * tamcluster + 3 nsect_ok: MOV tsect,CX

; 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 ; BX = 1/2 del nº total de sectores

; 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

adaptar_param PROC JZ dir_ok?

MOV AX,tdisco ; en Kb INC AX ; redondear tamaño de ROOT

MOV BX,AX ; entradas de directorio propuestas dir_ok?: CMP AX,BX ; BX = 1/2 nº sectores del disco

MOV CL,1 ; sectores por cluster propuestos JB dir_ok

CMP AX,128 ; ¿disco de 128 Kb o menos? OR lista_err,ERROR12 ; directorio excesivo

JBE prop_ok JMP tdir_rec ; directorio por defecto

MOV BX,128 dir_ok: MOV sdir,AX

CMP AX,512 ; ¿disco de 512 Kb o menos? MUL tsect

JBE prop_ok MOV CX,32

MOV BX,256 CALL divCX

CMP AX,2042 ; ¿disco de casi 2 Mb o menos? MOV tdir,AX ; optimizar tamaño de directorio

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 ; 512 / tamaño de sector

JBE prop_ok MOV BL,tcluster

MOV CL,4 ; evitar FAT16 hasta 8 Mb XOR BH,BH

MOV BX,384 MUL BX ; ajustar tamaño de cluster

CMP AX,16384 ; ¿disco de menos de 16 Mb? AND AL,AL

JB prop_ok JZ propclus_ok

MOV BX,512 MOV tcluster,AL

prop_ok: CMP dosver,300h propclus_ok: MOV BX,param_tcluster

JAE prop_valido AND BX,BX

CMP AX,4084*2 ; en DOS 2.xx evitar FAT16 JNZ tcluster_def ; se ha definido tamaño de cluster
203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

tcluster_rec: MOV BL,tcluster ; tamaño por defecto SHL AX,1

XOR BH,BH RCL DX,1 ; DX:AX = nsect * tamsect * 2

tcluster_def: SHL BX,1 MOV CX,tamcluster

CMP BX,numsect ; ¿cabe seguro un cluster? SHL CX,1

JB tcluster_ok ADD CX,SI ; CX = 2 * tamcluster + SI

tcluster_mal: OR lista_err,ERROR13 ; tamaño de cluster incorrecto DIV CX

JMP tcluster_rec INC AX ; los clusters se numeran desde 2

tcluster_ok: SHR BX,1 AND DX,DX ; ¿sobra un «cacho» de cluster?

MOV AX,tsect JZ clust_eval ; redondear: ¡es preferible que

MUL BX ; DX:AX = tamaño de cluster INC AX ; sobre un poco de FAT a que falte!

JC tcluster_mal clust_eval: XOR DX,DX ; resultado en DX:AX

CMP AX,31*1024 RET

JA tcluster_mal ; cluster de más de 31 Kb eval_clust ENDP

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 ; considerar número de FATs ; 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 ; obtener nº más alto de cluster MOV sect_cluster,AL

CMP AX,4086 MOV AX,tdir

JAE fat16 ; el nº más alto supera 4085 MOV entradas_raiz,AX

MOV CX,3 MOV AX,numsect

MUL CX ; clusters * 3 MOV num_sect,AX

SHR DX,1 MOV AL,nfats

RCR AX,1 ; clusters * 3 / 2 = clusters * 1,5 MOV num_fats,AL

JMP calc_sfat MOV AX,sfat

fat16: MOV SI,4 MOV sectores_fat,AX

MOV CX,param_f ; considerar número de FATs MOV cambiado,0FFh ; ha habido «cambio» de disco

SHL SI,CL RET

SHR SI,1 preparar_BPB ENDP

CALL eval_clust

SHL AX,1 ; ------------ Preparar el disco para operar. ES apunta al disco al

RCL DX,1 ; clusters * 2 ; entrar. Se procederá a copiar la rutina necesaria en

calc_sfat: 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 ; redondeo

fat_ok: 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 ; instalar rutina XMS

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 ; instalar rutina EMS

MOV ultclus,AX LEA SI,procesa_con

RET MOV CX,tam_proc_con ; instalar rutina memoria conv.

adaptar_param ENDP prep_mem: LEA DI,procesa_io

CLD

eval_clust PROC ; obtener el nº más alto de cluster XPUSH <SI,DI,CX>

MOV AX,numsect REP MOVSB ; instalar rutina en el disco

DEC AX ; restar BOOT XPOP <CX,DI,SI>

SUB AX,sdir ; restar ROOT XPUSH <ES,DS>

MUL tsect ; DX:AX = nsect * tamsect POP ES


CONTROLADORES DE DISPOSITIVOS 203

REP MOVSB ; y en el propio TDSK.EXE (para

POP ES ; usarla después al formatear) ; ------------ Inicializar la BOOT, FAT y ROOT del disco virtual.

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 ; actualizar variables ; 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 BPB formatear_tdsk PROC

RET PUSH ES ; *

prep_driver 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 ; primeros 128 bytes del BOOT

; 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 ; a 0 resto del área de trabajo

; 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 ; borrar area de trabajo

CMP tipo_soporte,3 MOV AX,sfat

JE procede_reloc ; usada memoria convencional MOV CX,param_f ; considerar número de FATs

RET SHL AX,CL

procede_reloc: PUSH ES ; * preservar ES SHR AX,1

MOV ES,segm_reubicar ; segmento de reubicación ADD AX,sdir ; AX = sectores fat + dir. raiz

XOR SI,SI ini_fat: 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 pfat: 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 ; inicializar 3 bytes FAT...

MOV CX,BX ; CX = bytes a relocalizar MOV DS:[DI+1],AX

CLD CMP ultclus,4086 ; ¿menos de 4085 clusters?

REP MOVSB ; auto-copiaje arriba JB pfat_ok

MOV AX,ES MOV DS:[DI+3],AL ; inicializar 4º byte FAT

MOV DS,AX ; nuevo segmento de datos pfat_ok: MOV AX,1

POP ES ; * restaurar ES CALL escribe_sectAX ; primer sector FAT preparado

MOV BX,CS CALL fecha_hora

SUB AX,BX ; ES - CS --> cuantía del salto 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

RETF ; retorno cargando CS: MOV CX,param_f ; considerar número de FATs

relocalizar ENDP SHL AX,CL


203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

SHR AX,1 ADC AH,0

INC AX POP DX ; * recuperar fecha

CALL escribe_sectAX ; primer sector raiz preparado RET

POP ES ; * fecha_hora ENDP

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 ; unidad de disco virtual renombrar_mcb PROC

PUSH DX PUSH ES

INT 21h ; primer acceso al disco MOV AL,letra_unidad

POP DX MOV BYTE PTR nombre_tdsk+5,AL

NOT ES:media_byte ; restaurar descriptor de medio MOV BYTE PTR nombre_tdsk+4,'('

MOV ES:cambiado,0FFh ; nuevo «cambio» de disco MOV BYTE PTR nombre_tdsk+6,')'

MOV AH,36h MOV AX,segm_psp

INT 21h ; acceder otra vez al disco DEC AX

formateado: RET MOV ES,AX

formatear_tdsk ENDP LEA SI,nombre_tdsk

MOV DI,8

; ---- Escribir el sector nº AX del disco virtual. No MOV CX,DI

; se utiliza INT 26h (imposible desde el CONFIG). CLD

REP MOVSB

escribe_sectAX PROC POP ES

PUSHF ; preservar bit DF RET

XPUSH <AX,BX,CX,DX,SI,DI,BP,DS,ES> renombrar_mcb ENDP

XOR BP,BP ; indicar escritura

LEA DI,area_trabajo ; ES:DI buffer ; ------------ Informar sobre el disco virtual instalado.

MOV BX,AX ; número de sector

MOV AX,1 ; 1 sector info_disco PROC

CALL io_proc ; acceder al disco directamente CALL InitMultiPrint

XPOP <ES,DS,BP,DI,SI,DX,CX,BX,AX> LEA DX,ayuda_txt ; ayuda en español

POPF CMP param_h,ON ; ¿solicitud de ayuda?

RET JNE cont_info ; no

escribe_sectAX ENDP JMP info_exit

cont_info: TEST err_grave,0FFFFh

; ---- Obtener fecha y hora del sistema en DX y AX JZ info_no_fatal

LEA DX,err_grave_gen ; texto de encabezamiento

fecha_hora PROC CALL imprimir ; imprimir errores graves:

MOV AH,2Ah LEA DX,e0

INT 21h ; obtener fecha del sistema TEST err_grave,ERROR0

MOV AL,32 JZ otro_fallo ; no es error de DOS incorrecto

MUL DH ; AX = mes * 32 CALL imprimir

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 RETF ; ejecutar INT 20h de PSP:0

PUSH AX ; * guardar fecha otro_fallo: LEA DX,e1

MOV AH,2Ch TEST err_grave,ERROR1

INT 21h ; obtener hora del sistema JNZ info_g

MOV AL,32 LEA DX,e2

MUL CL ; AX = minutos*32 TEST err_grave,ERROR2

MOV CL,3 JNZ info_g

SHL CH,CL LEA DX,e3

XOR CL,CL ; CX = hora*2048 info_g: JMP info_exit

ADD AX,CX info_no_fatal: CMP ES:tipo_soporte,0 ; error no fatal

SHR DH,1 ; segundos/2 JNE info_reporte

ADD AL,DH LEA DX,info_ins ; disco no formateado


CONTROLADORES DE DISPOSITIVOS 203

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 ; memoria XMS

info_reporte: CALL pr_info ; disco formateado LEA DX,inf_mem_ems

CMP lista_err,0 DEC AL

JE info_ret ; sin mensajes de advertencia JZ mem_ifdo ; memoria EMS

LEA DX,cab_adv_txt ; ... o con ellos LEA DX,inf_mem_con

CALL imprimir ; cabecera de advertencias CMP ES:mem_handle,0A000h

info_err: 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 mem_ifdo: CALL imprimir

busca_err: ADD BX,2 LEA DX,inf_nclusters

SHR AX,1 CALL imprimir

JC informa

mas_mens: LOOP busca_err ; no se produce ese error MOV AX,ES:entradas_raiz

JMP info_ret MOV BX,32

informa: LEA DX,mens_cabec ; inicio común a los mensajes MUL BX ; bytes ocupados por directorio

CALL imprimir DIV ES:bytes_sector ; AX = sectores del directorio

MOV DX,[BX] ; dirección de ese mensaje ADD AX,ES:sect_reserv

CALL imprimir ADD AX,ES:sectores_fat

JMP mas_mens ; acabar con todos SUB AX,ES:num_sect

info_exit: CALL imprimir NEG AX ; AX = sectores libres

info_ret: RET XOR DX,DX

info_disco ENDP MOV BL,ES:sect_cluster

XOR BH,BH

pr_info PROC DIV BX ; 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 ; ¿FAT12?

MOV CL,5 JB ifat_ok

CALL print_32 LEA DX,inf_tfat16

LEA DX,inf_tdir ifat_ok: CALL imprimir

CALL imprimir LEA DX,inf_final

MOV AX,ES:entradas_raiz CALL imprimir

XOR DX,DX RET

MOV CL,5 pr_info ENDP

CALL print_32

LEA DX,inf_tdisco ; --- Imprimir letra de unidad en AL.

CALL imprimir

MOV AX,ES:num_sect impr_unidad PROC

MUL ES:bytes_sector XPUSH <AX, DX>

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 <DX, AX>

MOV AL,ES:sect_cluster RET

XOR AH,AH impr_unidad ENDP

XOR DX,DX

MOV CL,5 ; --- Imprimir un nº decimal de 32 bits en DXAX formateado por CL.
203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

; MOV CL,0FFh

; Entradas: rep_sub_pr32: INC CL

; Si bit 4 = 1 --> se imprimirán signos separadores de millar SUB AX,SI

; bits 0-3 = nº total de dígitos (incluyendo separadores de SBB DX,DI ; DXAX = DXAX - DISI

; millar y parte fraccional) JNC rep_sub_pr32 ; restar el factor cuanto se

; bits 5-7 = nº de dígitos de la parte fraccional (cuantos 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 ; CX se recupera ahora

; INC BX

; * Ejemplo, si DXAX=9384320 y CL=010 1 1011 LOOP digit_pr32 ; próximo dígito del número

; se imprimirá ( '_' representa un espacio en blanco ): __93.843,20 STD ; transferencias (MOVS) hacia

atrás

print_32 PROC DEC BX ; BX apunta al último dígito

PUSH DS MOV final_pr32,BX ; último dígito

PUSH ES MOV ent_frac_pr32,BX ; frontera parte

PUSH CS entera/fraccional

PUSH CS MOV CL,5

POP DS MOV AL,formato_pr32

POP ES SHR AL,CL ; AL = nº de decimales

PUSH AX ; preservar todos los registros AND AL,AL

PUSH BX JZ no_frac_pr32 ; ninguno

PUSH CX MOV CL,AL

PUSH DX XOR CH,CH

PUSH SI MOV SI,final_pr32

PUSH DI MOV DI,SI

PUSHF INC DI

MOV formato_pr32,CL ; byte del formato de impresión REP MOVSB ; correr cadena arriba (hacer

elegido hueco)

MOV CX,idioma_seps INC final_pr32

separ_pr32: MOV millares_pr32,CH ; separador de millares MOV AL,fracc_pr32

MOV fracc_pr32,CL ; separador parte fraccional MOV [DI],AL ; poner separador de parte

MOV BX,OFFSET tabla_pr32 fraccional

MOV CX,10 MOV ent_frac_pr32,SI ; indicar nueva frontera

digit_pr32: PUSH CX no_frac_pr32: MOV AL,formato_pr32

PUSH AX TEST AL,16 ; interpretar el formato

PUSH DX especificado

XOR DI,DI JZ poner_pr32 ; imprimir como tal

MOV SI,1 ; DISI = 1 entera_pr32: MOV CX,final_pr32 ; añadir separadores de millar

DEC CX ; CX - 1 SUB CX,ent_frac_pr32

JCXZ hecho_pr32 ADD CX,3

factor_pr32: SAL SI,1 MOV SI,final_pr32

RCL DI,1 ; DISI * 2 MOV DI,SI

MOV DX,DI INC DI

MOV AX,SI REP MOVSB ; correr cadena arriba (hacer

SAL SI,1 hueco)

RCL DI,1 MOV AL,millares_pr32

SAL SI,1 MOV [DI],AL ; poner separador de millares

RCL DI,1 ; DISI * 8 INC final_pr32

ADD SI,AX MOV ent_frac_pr32,SI ; usar esta variable como puntero

ADC DI,DX ; DISI = DISI*8 + DISI*2 = SUB SI,OFFSET tabla_pr32

DISI*10 CMP SI,3

LOOP factor_pr32 ; DISI = DISI*10*10* ... (CX-1 JAE entera_pr32 ; próximo separador

veces) poner_pr32: MOV BX,final_pr32

hecho_pr32: POP DX ; luego DISI = 10 elevado a MOV BYTE PTR [BX+1],0 ; delimitador de fin de cadena

(CX-1) MOV BX,OFFSET tabla_pr32

POP AX ; CX se recuperará más tarde MOV principio_pr32,BX ; inicio de cadena


CONTROLADORES DE DISPOSITIVOS 203

limpiar_pr32: MOV AL,[BX] print_32

CMP AL,'0' print_32 ENDP

JE blanco_pr32 ; cero a la izda --> poner " "

CMP AL,millares_pr32 ; separador millares a la izda ; ------------ Dividir DX:AX / CX sin desbordamientos (cociente: AX,

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 XPUSH <BX,SI,CX,AX,DX>

acabar_pr32: MOV AL,formato_pr32 ; imprimir MOV SI,32

AND AL,00001111b XOR BX,BX

XOR AH,AH divmas: SHL AX,1

MOV DX,final_pr32 RCL DX,1

SUB DX,AX RCL BX,1

INC DX ; DX = offset 'principio' CMP BX,CX

AND AX,AX JB dividido ; "no cabe"

JNZ format_pr32 ; longitud especificada por el SUB BX,CX

usuario INC AL ; 1 al cociente

MOV DX,principio_pr32 ; longitud obtenida del número dividido: DEC SI

format_pr32: CALL imprimir JNZ divmas

POPF ; restaurar todos los registros AND DX,DX

POP DI JZ div_ok

POP SI XPOP <DX,AX> ; error

POP DX STC

POP CX JMP div_fin

POP BX div_ok: MOV DX,BX ; resto en DX y cociente en AX

POP AX ADD SP,4 ; «sacar» sin sacar DX y AX

POP ES CLC

POP DS div_fin: XPOP <CX,SI,BX> ; recuperar CX, SI y BX

RET ; salida del procedimiento RET

blanco_pr32: MOV BYTE PTR [BX],' ' ; sustituir 0 ó separador de divCX ENDP

millares

INC BX ; a la izda. por espacio en ; ------------ Impresión en color o monocroma (esta última

blanco ; 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 ; es el número 0.000.000.00X imprimir PROC

JMP SHORT acabar_pr32 ; imprimir PUSH AX

formato_pr32 DB 0 MOV AL,param_m

DB 5 DUP (' ') ; espacios en blanco para cubrir CMP modo,CONFIG ; ¿en CONFIG.SYS?

la JNE m_ok ; no

; mayor plantilla que pueda ser XOR AL,ON ; sí: /M opera al revés

espe- m_ok: MOV pr_mono,AL

; cificada en el formato CALL print

tabla_pr32 DT 0 ; reservar 14 bytes (nº más ., POP AX

más ASCIIZ) RET

DW 0,0 ; aquí se solapa un buffer de 32 imprimir ENDP

bytes

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,

final_pr32 DW 0 ; offset al último byte a ; se supone que el mensaje es multilingüe y los diversos

imprimir ; idiomas (1, 2, ... N) separan sus cadenas por sucesivos

principio_pr32 DW 0 ; " " primer " " " ; códigos 255. El carácter de control 127 realiza una

ent_frac_pr32 DW 0 ; offset a la frontera ; pausa hasta que se pulsa una tecla.

entero-fracc.

DT 0 ; $ - tabla_pr32 = 32 bytes print PROC

usados por XPUSH <AX, BX, CX, DX, SI, DI, ES>

; INT 21h al principio de CMP idioma,0


203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

JNE pr_decidir CMP BYTE PTR [BX],255

PUSH DX ; * JNE pr_busca_ter

MOV AH,30h INC BX

INT 21h LOOP pr_busca_msg ; acaba en 255 pero no es ese

XCHG AH,AL pr_usar_ese: MOV BX,DX

MOV CX,AX ; CX = versión del DOS DEC BX

CMP param_i,ON pr_cad_lon: INC BX

MOV AX,codigo_tfno CMP BYTE PTR [BX],0

MOV BX,1234h JE prlong_ok

JNE pr_busca_cod ; parámetro /I=cod no indicado 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 ; calcular longitud

JAE pr_cod ; código mayor o igual de 255 JMP prlong_ok

MOV AL,BL ; código menor de 255 prpausa: PUSH BX

pr_cod: CMP CX,200h MOV CX,BX

JAE pr_cod_tfno ; DOS >= 2.X SUB CX,DX

pr_busca_cod: CMP CX,200h CALL pr_cad ; imprimir hasta el código 127

MOV AX,1 ; inglés para DOS < 2.X pr_limpbuf: MOV AH,1

JB pr_habla_ax INT 16h

MOV AL,0 JZ pr_notec

pr_cod_tfno: LEA DX,area_trabajo MOV AH,0

MOV AH,38h INT 16h ; limpiar buffer del teclado

XPUSH <BX, CX> JMP pr_limpbuf

INT 21h ; obtener información del pais pr_notec: MOV AH,0

XPOP <CX, AX> INT 16h ; esperar tecla

JC pr_habla_ax ; fallo en la función POP BX

CMP CX,20Bh INC BX

JE pr_habla_ax ; DOS 2.11: AX cód. telefónico MOV DX,BX

CMP CX,300h CMP AL,27 ; ¿tecla ESC?

MOV AX,1 STC

JB pr_habla_ax ; 2.x excepto 2.11: mala suerte JE pr_ret

MOV AX,BX JMP pr_cad_lon ; imprimir el resto

LEA BX,area_trabajo prlong_ok: MOV CX,BX

MOV CH,[BX+7] ; separador de millares SUB CX,DX

MOV CL,[BX+9] ; separador de decimales CALL pr_cad ; terminar impresión

MOV idioma_seps,CX CLC

pr_habla_ax: LEA BX,info_paises-2 pr_ret: XPOP <ES, DI, SI, DX, CX, BX, AX> ; CF=1 si se pulsó

MOV CX,1 ; supuesto idioma 1 ESC

pr_busca_idi: ADD BX,2 RET

MOV DX,[BX] pr_cad: ; MOV AH,40h

CMP AX,DX ; MOV BX,1

JE pr_habla_ese ; INT 21h ; imprimir con el DOS

AND DX,DX MOV SI,DX

JNZ pr_busca_idi LEA DI,area_trabajo

INC CX ; será otro idioma PUSH DS

CMP [BX+2],DX POP ES ; por si acaso

JNE pr_busca_idi ; no es fin de la tabla CLD

pr_habla_ese: MOV idioma,CL REP MOVSB

POP DX ; * MOV [DI],CL ; ASCIIZ

LEA DX,area_trabajo

pr_decidir: MOV CL,idioma CALL MultiPrint ; imprimir en color

MOV CH,0 ; nº de idioma a usar (1..N) RET

MOV BX,DX print ENDP

pr_busca_msg: MOV DX,BX

DEC BX ; ------------ Impresión en pantalla, en color o monocromo, usando el

pr_busca_ter: INC BX ; BIOS o el DOS respectivamente. Antes deberá ejecutarse

CMP BYTE PTR [BX],0 ; InitMultiPrint para inicializar. Al hacer scroll se

JE pr_usar_ese ; acaba en 0: no buscar más ; intenta respetar el posible color global de fondo.
CONTROLADORES DE DISPOSITIVOS 203

; Con «pr_mono» en ON se solicita imprimir en monocromo. JE pr_derecha ; código de control 3: avanzar

; CMP AL,10

; - El texto a imprimir es apuntado por DS:DX. JE pr_crlf ; código de control 10: CR & LF

; - 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

; se imprimirá el que viene detrás PUSH DX

; 3 -> avanzar cursor a la derecha INT 10h ; imprimir carácter

; 10 -> retorno de carro y salto de línea estilo UNIX POP DX

pr_derecha: ADD DL,pr_veces

MultiPrint PROC MOV pr_veces,1

XPUSH <AX,BX,CX,DX,SI,DI,BP,DS,ES> CMP DL,pr_maxX

PUSH DS JBE pr_av

POP ES pr_crlf: 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 ; es preciso hacer scroll

LEA AX,pr_AL_bios MOV AX,601h

pr_rut_ok: MOV pr_rut,AX ; instalar rutina de impresión MOV BH,pr_colorb ; color por defecto

MOV BX,DX XOR CX,CX

pr_otro: MOV AL,ES:[BX] MOV DL,pr_maxX

PUSH BX MOV DH,pr_maxY

CMP AL,' ' INT 10h ; hacer scroll usando BIOS

JAE pr_ASCII ; no es un código de control POP DX

AND AL,AL pr_av: MOV BH,pr_pagina

JZ pr_exit ; código de control 0: final MOV AH,2

CMP AL,1 INT 10h ; posicionar cursor

JE pr_setcolor ; código de control 1: color RET ; retorno del procedimiento

CMP AL,2 pr_AL_bios ENDP

JE pr_setveces ; código de control 2: repetir

pr_ASCII: CALL pr_rut pr_AL_dos PROC ; imprimir usando DOS

POP BX CMP AL,3

INC BX JNE pr_no_der

JMP pr_otro MOV AL,' ' ; código de control 3: avanzar

pr_setcolor: MOV AL,ES:[BX+1] pr_no_der: CMP AL,10

MOV pr_color,AL ; actualizar color 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

pr_setveces: MOV AL,ES:[BX+1] pr_dos: MOV CL,pr_veces

MOV pr_veces,AL ; actualizar repeticiones XOR CH,CH

POP BX MOV pr_veces,1

ADD BX,2 MOV DL,AL

JMP pr_otro pr_chr: XPUSH <DX,CX>

pr_exit: XPOP <BX,ES,DS,BP,DI,SI,DX,CX,BX,AX> MOV AH,2

RET INT 21h ; imprimir carácter

MultiPrint ENDP XPOP <CX,DX>

LOOP pr_chr

pr_AL_bios PROC ; imprimir en color usando BIOS RET

PUSH AX pr_AL_dos ENDP

MOV AH,3

MOV BH,pr_pagina InitMultiPrint PROC

INT 10h ; DX = coordenadas del cursor XPUSH <AX,BX,CX,DX,BP,DS,ES>

POP AX PUSH CS

CMP AL,3 POP DS


203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

MOV pr_veces,1 JZ xms_general

MOV pr_color,15 ; valores por defecto INC BP ; hacer BP = 0

MOV pr_mono,OFF PUSH ES

pr_i_80?: MOV AH,0Fh PUSH DI ; segmento:offset fuente

INT 10h PUSH BP ; handle fuente (BP=0)

CMP AH,80 ; ¿80 ó más columnas? xms_general: 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 ; forzar modo de 80 columnas PUSH CX ; tamaño bloque (parte baja)

JMP pr_i_80? MOV SI,SP

pr_i_video_ok: MOV 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

pr_i_maxy_ok: MOV AH,8 ; (BH = página) xms_proc_ok: XCHG AH,AL ; colocar resultado

INT 10h ; obtener color por defecto RET

MOV pr_colorb,AH procesa_xms ENDP

XPOP <ES,DS,BP,DX,CX,BX,AX>

RET llama_XMS PROC

InitMultiPrint ENDP MOV DX,DS ; handle en DS (si utilizado)

CALL CS:xms_driver ; ejecutar función XMS

pr_pagina DB 0 ; página de visualización activa RET

pr_veces DB 1 ; veces que se imprime cada carácter llama_XMS ENDP

pr_color DB 15 ; color BIOS para imprimir

pr_colorb DB ? ; color por defecto en pantalla tam_proc_xms EQU $-OFFSET procesa_xms ; tamaño de esta rutina

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

; ------------ 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 PROC 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

.286 ; rutina ejecutada desde 286+ XCHG SI,DI ; escritura: intercambiar

PUSHA ; sistema reinicializando: XPUSH <DS,ES>

MOV AH,0Dh XPOP <DS,ES>

CALL llama_XMS ; desbloquear EMB (prudente) con_general: CLD

MOV AH,0Ah CMP CS:cpu386,ON

CALL llama_XMS ; liberar EMB JE con_tr32bit

POPA REP MOVSW

.8086 JMP con_tr_fin

RET con_tr32bit: SHR CX,1 ; nº palabras de 32 bit a mover

no_xmslib: DEC BP ; leer/escribir en el disco JCXZ con_trdo ; evitar desgracia

JNZ xms_escribe .386

PUSH ES PUSHAD

PUSH DI ; segmento:offset destino XOR EAX,EAX ; asegurar no violación

PUSH BP ; handle destino (BP=0) DEC AX ; de segmento-64K

xms_escribe: PUSH DX AND ECX,EAX ; EAX = 0FFFFh

PUSH AX ; desplazamiento DX:AX AND ESI,EAX

PUSH DS ; handle fuente/destino AND EDI,EAX


CONTROLADORES DE DISPOSITIVOS 203

REP MOVSD ; transferencia ultrarrápida sector_cero LABEL BYTE

con_trdo: POPAD ; POPAD falla en muchos 386 JMP SHORT botar

NOP ; arreglar fallo de POPAD NOP

.8086 DB "TDSK 2.3" ; identificación del sistema

con_tr_fin: MOV AX,100h ; todo fue bien, por supuesto tsect DW 512 ; tamaño de sector por defecto

con_exit: RET tcluster DB ? ; sectores por cluster

procesa_con ENDP DW 1 ; sectores reservados

nfats DB ? ; número de FAT's

tam_proc_con EQU $-OFFSET procesa_con ; tamaño de esta rutina tdir DW ? ; número de entradas al dir. raiz

numsect DW ? ; nº sectores del disco (<=32Mb)

DB media ; descriptor de medio

; ************ Datos no residentes para la instalación sfat DW ? ; sectores por FAT

DW 1, 1 ; sectores por pista / cabezas

ON EQU 1 ; constantes booleanas DD 0 ; sectores ocultos

OFF EQU 0 DD 0 ; nº total de sectores (si > 32Mb)

DB 7 DUP (0) ; 7 bytes reservados

CONFIG EQU 1 ; TURBODSK ejecutado desde el CONFIG botar: DB 0EAh ; código de JMP FAR...

AUTOEXEC EQU 2 ; TURBODSK se ejecuta desde el DOS DW 0,0FFFFh ; ...FFFF:0000 (programa BOOT)

DB "(C)1992 CiriSOFT"; resto de primeros 64 bytes

emm_id DB "EMMXXXX0" ; identificación del controlador EMS DB ". Grupo Universi"

DB "tario de Informá"

nombre_tdsk DB "TDSK U: " ; para nombrar handle EMS y el MCB DB "tica (GUI) - Val"

DB "ladolid (España)"; resto de primeros 128 bytes

modo DB ? ; CONFIG/AUTOEXEC

dosver DW ? ; versión del DOS dir_raiz DB "TURBODSK "; Directorio raiz: primera entrada

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 ; ------------ Areas de datos para información del disco virtual

(1..N)

idioma_seps DW ",." ; separadores de millares/decimales ; --- Código telefónico de países de habla

; hispana (mucha o poca).

param_unidad DB 0 ; letra de unidad (si indicada)

param_tdiscof DB OFF ; a ON si se define tamaño de disco info_paises DW 54 ; Argentina

param_tdisco DW 0 ; tamaño de disco (si se define) DW 591 ; Bolivia

param_tsect DW 0 ; tamaño de sector (si se define) DW 57 ; Colombia

param_tdir DW 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

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 DB " ",1,colA,"│",1,colC,10

DB 2,12,3,1,colA,"│ Tamaño: ",1,colB," "

DW 41 ; Switzerland DB 255

DW 43 ; Austria

DW 49 ; Germany DB " ",1,colA,"│",1,colC,10

DW 0 ; fin de la información DB 2,10,3,1,colA,"│ Größe:",2,10," ",1,colB," "

DB 255

DW 0 ; no más idiomas

DB " ",1,colA,"│",1,colC,10

; ------------ Mensaje de no formateado DB 2,12,3,1,colA,"│ Size:",2,4," ",1,colB," "

DB 0

info_ins DB 10,1,10,"TURBODSK 2.3 - Unidad ",255

DB 10,1,10,"TURBODSK 2.3 - Laufwerk ",255 inf_tcluster DB " Kbytes ",1,colA,"│ Sectores/cluster:",1,colB,"

DB 10,1,10,"TURBODSK 2.3 - Drive ",0 "

DB 255

info_ins2 DB ": sin formatear.",10,1,14,255 DB " KB ",1,colA,"│ Sektoren/Cluster:",2,3,"

DB ": nicht formatiert.",10,1,14,255 ",1,colB," "

DB ": unformatted.",10,1,14,0 DB 255

DB " Kbytes ",1,colA,"│ Sectors/cluster: ",1,colB,"

; ------------ Cuadro de información "

DB 0

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 inf_mem DB " ",1,colA,"│",1,colC,10

colC EQU 15+0*16 ; color de lo que rodea a la ventana DB 2,12,3,1,colA,"│ Memoria: ",1,colB

colD EQU 10+1*16 ; color de «TURBODSK» DB 255

info_txt 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: ",1,colB

DB 10,2,10,3,1,colA,"│ ",1,colD,"TURBODSK 2.3",1,colA DB 0

DB " - Laufwerk ",1,colB

DB 255 inf_nclusters DB " ",1,colA,"│",1,colB," ",255

DB " ",1,colA,"│",1,colB," ",255

DB 10,2,12,3,1,colA,"┌",2,26,"─┬",2,25,"─┐",1,colC DB 1,colA,"│",1,colB," ",0

DB 10,2,12,3,1,colA,"│ ",1,colD,"TURBODSK 2.3",1,colA

DB " - Drive ",1,colB inf_tfat DB 1,colA," clusters (",1,colB,"FAT",255

DB 0 DB 1,colA," Cluster (",1,colB,"FAT",255

DB 1,colA," clusters (",1,colB,"FAT",0

inf_tsect DB ":",1,colA," │ Tamaño de sector:",1,colB," ",255

DB ":",1,colA," │ Sektorgröße:",2,8," ",1,colB," ",255 inf_tfat12 DB "12",0

DB ":",1,colA," │ Sector size:",2,5," ",1,colB," ",0 inf_tfat16 DB "16",0

inf_tdir DB " ",1,colA,"│",1,colC,10,2,12,3 inf_final DB 1,colA,") ",1,colA,"│",1,colC,10,2,12,3

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,") ",1,colA,"│",1,colC,10,2,12,3

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 inf_mem_xms DB "Extendida (XMS)",255


CONTROLADORES DE DISPOSITIVOS 203

DB "Erweitert (XMS)",255 DB 255

DB "Extended (XMS) ",0

DB "- Syntaxfehler oder ungültiger Parameter. Die

inf_mem_ems DB "Expandida (EMS)",255 RAM-Disk ist zur ",10,2,8,3

DB "Expansion (EMS)",255 DB " Zeit nicht definiert bzw. wurde nicht

DB "Expanded (EMS) ",0 modifiziert.",10

DB 255

inf_mem_sup DB " Superior (UMB) ",255

DB "Oberer Sp. (UMB)",255 DB "- Syntax error and/or parameter out of range. The

DB " Upper (UMB) ",0 Ramdisk is not",10,2,8,3

DB " defined now or the previous one is not

inf_mem_con DB " Convencional ",255 modified.",2,14," ",10

DB " Konventionell",255 DB 0

DB " Conventional ",0

m1 DB "- El parámetro /C o la letra de unidad sólo han de

; ------------ Errores «leves» emplearse",2,4," ",10,2,8,3

DB " desde la línea de comandos o el AUTOEXEC (les

ERROR0 EQU 1 ignoraré).",2,6," ",10

ERROR1 EQU 2 DB 255

ERROR2 EQU 4

ERROR3 EQU 8 ; TURBODSK es muy flexible y se instala DB "- Parameter /C und Laufwerksbuchstaben können nur

ERROR4 EQU 16 ; casi de cualquier forma, aunque a bei Aufrufen ",2,4," ",10,2,8,3

ERROR5 EQU 32 ; veces no se reserve memoria y sea DB " von TURBODSK in der AUTOEXEC verwendet werden.

ERROR6 EQU 64 ; necesario volver a ejecutarlo después ",2,6," ",10

ERROR7 EQU 128 ; desde el DOS para «formatearlo». DB 255

ERROR8 EQU 256

ERROR9 EQU 512 DB "- The /C parameter and the driver letter only can

ERROR10 EQU 1024 be used when ",10,2,8,3

ERROR11 EQU 2048 DB " executing TURBODSK in command line or AUTOEXEC

ERROR12 EQU 4096 (now, ignored).",10

ERROR13 EQU 8192 DB 0

ERROR14 EQU 16384

ERROR15 EQU 32768 m2 DB "- Para poder emplear memoria expandida hay que

incluir la opción",10,2,8,3

lista_err DW 0 ; palabra que indica los mensajes a imprimir DB " /A en CONFIG.SYS, con objeto de dejar espacio

para las rutinas",10,2,8,3

mens_cabec DB 2,8,3,0 DB " de control EMS: la memoria ocupada crecerá de

432 a 608 bytes.",10

tabla_mens DW m0,m1,m2,m3,m4,m5,m6,m7 DB 255

DW m8,m9,m10,m11,m12,m13,m14,m15

DB "- Zur Verwendung von EMS müssen Sie Option /A in

cab_adv_txt DB 10,2,8,3,1,12 CONFIG.SYS ",10,2,8,3

DB "Advertencias y/o errores de TURBODSK:",2,27," DB " setzen, um Speicher für die EMS-Unterstützung zu

",10,1,10 reservieren. ",10,2,8,3

DB 255 DB " Dadurch erhöht sich der Speicherbedarf von 432

auf 608 Bytes. ",10

DB 10,2,8,3,1,12 DB 255

DB "Warnungen und Fehlermeldungen von

TURBODSK:",2,27," ",10,1,10 DB "- In order to use expanded memory you must include

DB 255 the /A option",10,2,8,3

DB " in CONFIG.SYS, needed to reserve too space for

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 " routines: the memory used will increase from 432

DB 0 to 608 bytes.",10

DB 0

m0 DB "- Error de sintaxis o parámetro fuera de rango.

No se define el",10,2,8,3 m3 DB "- El tamaño de sector es mayor que el definido en

DB " disco virtual ahora o no se modifica el que cualquier otro",10,2,8,3

estaba definido. ",10 DB " controlador de dispositivo: indíquese ese tamaño


203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

en CONFIG.SYS",10,2,8,3 DB "- There is not XMS memory available: try to

DB " para que el DOS ajuste sus buffers (¡más consumo request EMS (/A). ",10

de memoria!).",10 DB 0

DB 255

m7 DB "- No existe memoria EMS: pruebe a indicar XMS en

DB "- Die Sektorengröße ist größer als in allen su lugar (/E) ",10

anderen Treibern; ",10,2,8,3 DB 255

DB " Sie müssen die Sektorgröße in CONFIG.SYS

festlegen, da DOS die",10,2,8,3 DB "- Kein EMS verfügbar: Versuchen Sie, XMS zu

DB " Puffergröße anpassen muß (höherer verwenden (/E). ",10

Speicherverbrauch) ",10 DB 255

DB 255

DB "- There is not EMS memory available: try to

DB "- Sector size is greater than any other defined request XMS (/E). ",10

by any device",10,2,8,3 DB 0

DB " driver loaded: you must indicate the sector size

in CONFIG.SYS",10,2,8,3 m8 DB "- Fallo del controlador XMS: imposible usar

DB " because DOS need adjust buffers length (more memoria extendida. ",10

memory spent!). ",10 DB 255

DB 0

DB "- Fehler des XMS-Managers: Verwendung von XMS

m4 DB "- La cantidad de memoria solicitada no existe, se unmöglich. ",10

ha rebajado. ",10 DB 255

DB 255

DB "- XMS controller failure: imposible to use

DB "- Die gewünschte Speichergröße existiert nicht und extended memory.",2,5," ",10

wurde reduziert.",10 DB 0

DB 255

m9 DB "- Fallo del controlador EMS: imposible usar

DB "- The amount of memory requested does not exist: memoria expandida. ",10

size reduced. ",10 DB 255

DB 0

DB "- Fehler des EMS-Managers: Verwendung von EMS

m5 DB "- No hay memoria XMS/EMS disponible: no la unmöglich. ",10

reservo; ejecute TDSK",10,2,8,3 DB 255

DB " de nuevo desde el DOS para utilizar memoria

convencional.",2,5," ",10 DB "- EMS controller failure: imposible to use

DB 255 expanded memory.",2,5," ",10

DB 0

DB "- Kein XMS/EMS verfügbar: Führen Sie TDSK nochmals

von der ",10,2,8,3 m10 DB "- No existe suficiente memoria convencional para

DB " Kommandozeile aus und benutzen Sie TURBODSK.",2,6," ",10

konventionellen Speicher. ",2,5," ",10 DB 255

DB 255

DB "- Nicht genügend konventioneller Speicher für

DB "- There is not XMS/EMS memory available: execute TURBODSK verfügbar.",2,6," ",10

TDSK again from",10,2,8,3 DB 255

DB " DOS command line or AUTOEXEC and use

conventional memory.",2,5," ",10 DB "- There is not sufficient conventional memory for

DB 0 TURBODSK.",2,5," ",10

DB 0

m6 DB "- No existe memoria XMS: pruebe a indicar EMS en

su lugar (/A) ",10 m11 DB "- Tamaño de sector incorrecto: lo establezco por

DB 255 defecto.",2,7," ",10

DB 255

DB "- Kein XMS verfügbar: Versuchen Sie, EMS zu

verwenden (/A). ",10 DB "- Ungültige Sektorengröße angegeben, Vorgabewert

DB 255 wird verwendet.",2,7," ",10

DB 255
CONTROLADORES DE DISPOSITIVOS 203

err_grave_gen DB 10,1,10,"TURBODSK 2.3",10,1,12,0

DB "- Incorrect sector size indicated: default values

assumed.",2,6," ",10 e0 DB " - Este disco virtual requiere DOS 2.0 o

DB 0 superior.",10,255

DB " - Diese RAM-Disk erfordert mindestens DOS

m12 DB "- Número de entradas incorrecto: lo establezco por 2.0.",10,255

defecto.",2,5," ",10 DB " - This Ram Disk needs at least DOS 2.0 or

DB 255 above.",10,0

DB "- Ungültige Anz. von Verzeichnisanträgen, e1 DB " - Instale primero TURBODSK desde CONFIG.SYS (con

Vorgabewert wird verwendet.",2,5," ",10 DEVICE).",10

DB 255 DB " - Puede solicitar ayuda con TDSK /?",10

DB 255

DB "- Incorrect number of root entries: default value

assumed.",2,6," ",10 DB " - Sie müssen zuerst TURBODSK von der CONFIG.SYS

DB 0 aus installieren",10

DB " (mit DEVICE). Hilfe erhalten Sie durch Eingabe

m13 DB "- Tamaño de cluster incorrecto: lo establezco por von TDSK /?",10

defecto.",2,6," ",10 DB 255

DB 255

DB " - You must install first TURBODSK from

DB "- Ungültige Clustergröße angegeben, Vorgabewert CONFIG.SYS (using DEVICE).",10

wird verwendet.",2,6," ",10 DB " - Help is available with TDSK /?",10

DB 255 DB 0

DB "- Incorrect cluster size indicated: default value e2 DB " - La unidad indicada no es un dispositivo TURBODSK

assumed.",2,6," ",10 2.3",10,255

DB 0 DB " - Angegebener Laufwerksbuchstabe bezeichnet keinen

Treiber von TURBODSK.", 10,255

m14 DB "- FATAL: fallo al liberar la memoria que ocupaba DB " - Drive letter indicated does not is a TURBODSK 2.3

el disco.",2,6," ",10 device.",10,0

DB 255

e3 DB " - No pueden modificarse las características de

DB "- ACHTUNG: Freigabe des belegten Speichers operación de",10

gescheitert.",2,6," ",10 DB " TURBODSK dentro de WINDOWS. Configúrelo con

DB 255 anterioridad.",10

DB 255

DB "- FATAL: imposible to free memory alocated by

TURBODSK.",2,9," ",10 DB " - TURBODSK kann nicht innerhalb einer

DB 0 WINDOWS-Sitzung modifiziert werden.",10

DB " Sie müssen die Einstellungen vorher

m15 DB "- Para discos de más de 32 Mb, hace falta un durchführen.",10

tamaño de sector de",10,2,8,3 DB 255

DB " al menos 1024 bytes.",2,42," ",10

DB 255 DB " - Operational characteristics of disk can not be

altered inside",10,2,4

DB "- Laufwerke mit mehr als 32 MB erfordern eine DB " a WINDOWS session. You must configure TURBODSK

Sektorgröße",10,2,8,3 before.",10

DB " von mindestens 1024 Bytes.",2,42," ",10 DB 0

DB 255

; ------------ Ayuda

DB "- In drives over 32 Mb, sector size must be at

least 1024 bytes.",10 colorA EQU 15+4*16+128 ; color de «TURBODSK»

DB 0 colorAm EQU 14+1*16 ; color del marco de fondo de «TURBODSK»

colorB EQU 13+1*16 ; color de la fecha

; ------------ Errores «graves» (se imprime sólo el más importante) colorC EQU 10+1*16 ; color de sintaxis y parámetros

colorD EQU 15+1*16 ; color principal del texto

err_grave DW 0 ; tipo de error grave a imprimir 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

colorE EQU 11+1*16 ; color del nombre del autor ",1,colorC,"/C"

colorF EQU 14+1*16 ; color para llamar la atención DB 1,colorD," se pide el uso de memoria convencional.

colorG EQU 12+1*16 ; color para la dirección de mail ",1,colorDm

colorH EQU 9+1*16 ; color para mensaje de dominio público DB "█",10

DB 3,1,colorD,32,1,colorF,"„",1,colorD," Tras ser

ayuda_txt LABEL BYTE instalado, se puede"

DB 10,3,1,colorDm," ",1,colorA," TURBODSK 2.3 DB " ejecutar desde el DOS para cambiar el tamaño

",1,colorAm,"▄" ",1,colorDm

DB 1,colorB,2,51," 12/12/95 ",1,colorDmx,"▄",10 DB "█",10

DB 3,1,colorE," ",1,colorAm,2,14,"▀",1,colorE DB 3,1,colorD," del disco (perdiéndose los datos

DB " (C) 1995 Ciriaco García de Celis. ",1,colorG almacenados): con "

DB "(Mail: ciri@gui.uva.es).",1,colorDm,"█",10 DB "un tamaño 0 se anula el ",1,colorDm,"█",10

DB 3,1,colorE," (C) Grupo Universitario de DB 3,1,colorD," disco por completo, liberándose la

Informática. " memoria. "

DB "Apartado 6062, Valladolid (España). DB "Utilizando memoria convencional ",1,colorDm,"█",10

",1,colorDm,"█",10 DB 3,1,colorD," es ",1,colorF,"MUY",1,colorD,"

DB 3,1,colorH,2,18," ","* * * Programa de Dominio conveniente anular el "

Público * * *" DB "disco previo antes de modificar su tamaño. Con

DB 2,18," ",1,colorDm,"█",10 ",1,colorDm

DB 3,1,colorD," Bienvenido al disco virtual DB "█",10

",1,colorF,"más rápido" DB 3,1,colorD," más de un disco presente se pueden

DB 1,colorD,", con soporte de memoria EMS, XMS y distinguir "

",1,colorDm,"█",10 DB "indicando la letra de unidad. ",1,colorDm,"█",10

DB 3,1,colorD," convencional; redimensionable, fácil DB 3,1,1*16,"▄",1,colorDm,2,76,"▄█",10

de usar. En DOS "

DB "5 ocupa 432-608 bytes. ",1,colorDm,"█",10 DB 255

DB 3,1,colorC,2,77," ",1,colorDm,"█",10

DB 3,1,colorC," DEVICE=TDSK.EXE [tamaño [tsector " DB 10,3,1,colorDm," ",1,colorA," TURBODSK 2.3

DB "[nfich [scluster]]]] [/E] [/A|X] [/C] [/M] ",1,colorAm,"▄"

",1,colorDm,"█",10 DB 1,colorB,2,52," 12/12/95 ",1,colorDmx,"▄",10

DB 3,1,colorC,2,77," ",1,colorDm,"█",10 DB 3,1,colorE," ",1,colorAm,2,14,"▀",1,colorE

DB 3,1,colorD," ",1,colorF,"„",1,colorD," El tamaño DB " (C) 1995 Ciriaco García de Celis. ",1,colorG

debe de estar en " DB "(Mail: ciri@gui.uva.es). ",1,colorDm,"█",10

DB "el rango 8 - 65534 Kb; son válidos sectores de DB 3,1,colorE," (C) Grupo Universitario de

",1,colorDm Informática. "

DB "█",10 DB "Apartado 6062, Valladolid (Spanien).

DB 3,1,colorD," 32 a 2048 bytes (en potencias de dos, ",1,colorDm,"█",10

aunque algún " DB 3,1,colorC,2,78," ",1,colorDm,"█",10

DB "sistema sólo los soporta ",1,colorDm,"█",10 DB 3,1,colorD," Willkommen bei der

DB 3,1,colorD," de 128 a 512). El número de ficheros ",1,colorF,"schnelleren"

del directorio " DB 1,colorD," RAM-Disk, die auch EMS-, XMS- und

DB "raiz debe estar entre 1 ",1,colorDm,"█",10 konven- ",1,colorDm,"█",10

DB 3,1,colorD," y 65534 y el de sectores por cluster DB 3,1,colorD," tionellen Speicher unterstützt;

entre 1 y 255 (" größenverstellbar,"

DB "en algún sistema han de ",1,colorDm,"█",10 DB " einfache Bedienung wie ",1,colorDm,"█",10

DB 3,1,colorD," ser potencia de dos). Según el tamaño DB 3,1,colorD," bei DOS-RAM-Disks, erfordert maximal

se ajustará " 608 Bytes. "

DB "lo demás automáticamente. ",1,colorDm,"█",10 DB 1,colorH," Das Programm ist Freeware!.

DB 3,1,colorD," ",1,colorF,"„",1,colorD," Se puede ",1,colorDm,"█",10

indicar ",1,colorC DB 3,1,colorC,2,78," ",1,colorDm,"█",10

DB "/E",1,colorD," para emplear memoria extendida XMS, DB 3,1,colorC," DEVICE=TDSK.EXE [Größe [Sekt.

y ",1,colorC [Dateien [Cluster]]]]"

DB "/A",1,colorD," o ",1,colorC,"/X",1,colorD," para DB " [/E] [/A|X] [/C] [/M] ",1,colorDm,"█",10

la ",1,colorDm DB 3,1,colorC,2,78," ",1,colorDm,"█",10

DB "█",10 DB 3,1,colorD," ",1,colorF,"„",1,colorD," Zulässig

DB 3,1,colorD," expandida EMS; aunque por defecto, für Größe: 8-65534 KB;"

TURBODSK utilizará" DB " zulässig für Sektoren: 32-2048 Bytes (2er-

DB " automáticamente estas ",1,colorDm,"█",10 ",1,colorDm,"█",10

DB 3,1,colorD," memorias si puede. Con la opción DB 3,1,colorD," Potenz), obwohl einige DOS-Versionen
CONTROLADORES DE DISPOSITIVOS 203

nur 128," DB 3,1,colorD," and conventional memory. Full

DB " 256 und 512 unterstützen. ",1,colorDm,"█",10 resizeable, easy to "

DB 3,1,colorD, " Zulässige Anzahl der DB "use like DOS RAM disks, ",1,colorDm,"█",10

Verzeichniseinträge: " DB 3,1,colorD," in DOS 5.0 it takes only about 432-608

DB "1-65534, Sektoren/Cluster: 1-255 bytes. "

",1,colorDm,"█",10 DB 1,colorH,"This program is freeware!.",2,4,"

DB 3,1,colorD," (einige Systeme erforden ",1,colorDm,"█",10

2er-Potenzen)." DB 3,1,colorC,2,77," ",1,colorDm,"█",10

DB " Nur die Größenangabe ist notwendig. DB 3,1,colorC," DEVICE=TDSK.EXE [size [s_sector [files

",1,colorDm,"█",10 [s_cluster]]]]"

DB 3,1,colorD," ",1,colorF,"„",1,colorD," Bei DB " [/E] [/A|X] [/C] [/M] ",1,colorDm,"█",10

",1,colorC, "/E",1,colorD DB 3,1,colorC,2,77," ",1,colorDm,"█",10

DB " wird XMS, bei ",1,colorC, "/A",1,colorD," oder DB 3,1,colorD," ",1,colorF,"„",1,colorD," Size must

",1,colorC,"/X",1,colorD be in the range "

DB " wird EMS, und bei ",1,colorC, "/C",1,colorD," DB "8 - 65534 Kb; are valid sectors from 32 to 2048

wird konventioneller ",1,colorDm ",1,colorDm

DB "█",10,3,1,colorD, " Speicher benutzt. DB "█",10

Normalerweise versucht TURBODSK," DB 3,1,colorD," bytes (in power of 2), though some DOS

DB " XMS oder EMS zu benutzen. ",1,colorDm,"█",10 versions only "

DB 3,1,colorD,32,1,colorF,"„",1,colorD," Nach der DB "support 128, 256 & 512 ",1,colorDm,"█",10

Installation in" DB 3,1,colorD," bytes. Files of root may be 1 to 65534

DB " CONFIG.SYS sollte TURBODSK später nochmal ausge- and sectors "

",1,colorDm,"█",10 DB "by cluster can vary from ",1,colorDm,"█",10

DB 3,1,colorD," führt werden, um die Größe zu ändern DB 3,1,colorD," 1 to 255 (some systems need a power of

(den" 2). Only the "

DB " Speicherverbrauch); dadurch wird DB "size is necessary.",2,6," ",1,colorDm,"█",10

",1,colorDm,"█",10 DB 3,1,colorD," ",1,colorF,"„",1,colorC,"

DB 3,1,colorD," der Inhalt der RAM-Disk gelöscht. /E",1,colorD," force the "

Durch Größe 0 wird" DB "use of XMS memory, ",1,colorC,"/A",1,colorD," and

DB " die RAM-Disk komplett ",1,colorDm,"█",10 ",1,colorC

DB 3,1,colorD," gelöscht, bei Verwendung von DB "/X",1,colorD," indicates the use of EMS memory

konventionellem Speicher" ",1,colorDm

DB " kann eine Annulierung ",1,colorDm,"█",10 DB "█",10

DB 3,1,colorF," VOR",1,colorD," der Größenveränderung DB 3,1,colorD," and ",1,colorC,"/C",1,colorD," the

sinnvoll sein. Wenn mehrere" conventional. By "

DB " TURBODSK's installiert ",1,colorDm,"█",10 DB "default, TURBODSK try to use XMS or EMS memory.

DB 3,1,colorD," sind, können diese durch ihren ",1,colorDm

Laufwerksbuchstaben"

DB " angesteuert werden. ",2,6," ",1,colorDm,"█",10

DB 3,1,1*16,"▄",1,colorDm,2,77,"▄█",10

DB 255

DB 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," ",1,colorAm,2,14,"▀",1,colorE

DB " (C) 1995 Ciriaco Garcia de Celis. ",1,colorG

DB "(Mail: ciri@gui.uva.es).",1,colorDm,"█",10

DB 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 1,colorDm,"█",10
203 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

DB "█",10

DB 3,1,colorD," ",1,colorF,"„",1,colorD," After been

installed in "

DB "CONFIG.SYS, TURBODSK must be executed in AUTOEXEC

",1,colorDm

DB "█",10

DB 3,1,colorD," or command line in order to vary the

disk size (the "

DB "amount of memory used); ",1,colorDm,"█",10

DB 3,1,colorD," this operation erase the disk

contents. 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 "█",10

DB 3,1,colorD," is installed, they can be identified

using in "

DB "adition the drive letter.",2,5,"

",1,colorDm,"█",10

DB 3,1,1*16,"▄",1,colorDm,2,76,"▄█",10

DB 0

tam_a_trabajo 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 2048 ; 2 Kb de pila son suficientes

_PILA SEGMENT STACK 'STACK'

DB tam_pila DUP (?)

_PILA 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).
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

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.

▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ AD6 ██▌ 10 31 ▐██ HOLD (-RQ/-GT0)

▌ ▐ ▌ ▐

GND ██▌ 1 40 ▐██ Vcc AD5 ██▌ 11 30 ▐██ HLDA (-RQ/-GT1)

▌ ▐ ▌ ▐

A14 ██▌ 2 39 ▐██ A15 AD4 ██▌ 12 29 ▐██ -WR (-LOCK)

▌ ▐ ▌ ▐

A13 ██▌ 3 38 ▐██ A16/S3 AD3 ██▌ 13 28 ▐██ IO/-M (S2)

▌ ▐ ▌ ▐

A12 ██▌ 4 37 ▐██ A17/S4 AD2 ██▌ 14 27 ▐██ DT/-R (-S1)

▌ ▐ ▌ ▐

A11 ██▌ 5 36 ▐██ A18/S5 AD1 ██▌ 15 26 ▐██ -DEN (-S0)

▌ ▐ ▌ ▐

A10 ██▌ 6 35 ▐██ A19/S6 AD0 ██▌ 16 25 ▐██ ALE

▌ ▐ ▌ ▐

A9 ██▌ 7 34 ▐██ -SS0 NMI ██▌ 17 24 ▐██ -INTA

▌ ▐ ▌ ▐

A8 ██▌ 8 33 ▐██ MN/-MX INTR ██▌ 18 23 ▐██ -TEST

▌ ▐ ▌ ▐

AD7 ██▌ 9 32 ▐██ -RD CLK ██▌ 19 22 ▐██ READY

▌ ▐ ▌ ▐
245 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

GND ██▌ 20 21 ▐██ RESET LÍNEAS COMUNES AL MODO MÁXIMO Y MÍNIMO DEL 8088.
▌ '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 Estado del bus


─────── ─────── ─────── ──────────────────────────────
1 0 0 Reconocimiento de interrupción
1 0 1 Lectura de puerto E/S
1 1 0 Escritura en puerto E/S
1 1 1 Estado Halt
0 0 0 Acceso a código
0 0 1 Lectura de memoria
0 1 0 Escritura en memoria
0 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 Estado del bus


─────── ─────── ─────── ──────────────────────────────
0 0 0 Reconocimiento de interrupción
0 0 1 Lectura de puerto E/S
0 1 0 Escritura en puerto E/S
0 1 1 Estado Halt
1 0 0 Acceso a código
1 0 1 Lectura de memoria
1 1 0 Escritura en memoria
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

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): 0-
AX/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:

▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 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
PA3 ██▌ 1 40 ▐██ PA4 colocados en modo entrada.
▌ ▐ -RD:Utilizada por la CPU para leer información de estado o datos procedentes del 8255.
PA2 ██▌ 2 39 ▐██ PA5 -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
PA1 ██▌ 3 38 ▐██ PA6 control.
▌ ▐ PA0..PA7:Puerto A: puerto de entrada/salida de 8 bits.
PA0 ██▌ 4 37 ▐██ PA7 PB0..PB7:Puerto B: puerto de entrada/salida de 8 bits.
▌ ▐ PC0..PC7:Puerto C: puerto de entrada/salida de 8 bits.
-RD ██▌ 5 36 ▐██ -WR
▌ ▐
-CS ██▌ 6 35 ▐██ RESET DESCRIPCIÓN FUNCIONAL
▌ ▐
GND ██▌ 7 34 ▐██ D0 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;
A1 ██▌ 8 33 ▐██ D1
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
A0 ██▌ 9 32 ▐██ D2
los 4 bits más significativos del puerto C; y el grupo B, constituido por
▌ ▐
PC7 ██▌ 10 31 ▐██ D3
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
PC6 ██▌ 11 30 ▐██ D4 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 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
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

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 A: │ │ │ │ │ │ │ │ │ GRUPO B:
-------- └───────┴───┬───┴───┬───┴───┬───┼───┬───┴───┬───┴───┬───┴───┬───┘ --------
└───┬───┘ │ │ │ │ └─Ψ Puerto C (parte baja)
Modo Χ────────────────────┘ │ │ │ │ 1 - Entrada, 0 - Salida
00 - 0, 01 - 1, 1X - 2 │ │ │ └─────────Ψ Puerto B
Puerto A Χ────────────────────────────┘ │ │ 1 - Entrada, 0 - Salida
1 - Entrada, 0 - Salida │ └─────────────────Ψ Modo
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 │
│ │ │ │ │ │ │ │ │
└───────┴───┬───┴───┬───┴───┬───┼───┬───┴───┬───┴───┬───┴───┬───┘
└───────┼───────┘ └───────┼───────┘ └───Ψ Nuevo valor de ese bit
Ϊ Ϊ
No importa su valor 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).
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

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.
D7 ██▌ 1 24 ▐██ Vcc OUT 0:Salida del contador 0.
▌ ▐ GATE 0:Puerta de entrada al contador 0.
D6 ██▌ 2 23 ▐██ -WR CLK 1:CLOCK 1, entrada de reloj al contador 1.
▌ ▐ OUT 1:Salida del contador 1.
D5 ██▌ 3 22 ▐██ -RD GATE 1:Puerta de entrada al contador 1.
▌ ▐ CLK 2:CLOCK 2, entrada de reloj al contador 2.
D4 ██▌ 4 21 ▐██ -CS OUT 2:Salida del contador 2.
▌ ▐ GATE 2:Puerta de entrada al contador 2.
D3 ██▌ 5 20 ▐██ A1 A0..A1:Líneas de dirección para seleccionar uno de los tres contadores
▌ ▐ o el registro de la palabra de control.
D2 ██▌ 6 19 ▐██ A0 -CS:Habilita la comunicación con la CPU.
▌ ▐ -WR:Permite al 8254 aceptar datos de la CPU.
D1 ██▌ 7 18 ▐██ CLK 2 -RD:Permite al 8254 enviar datos a la CPU.
▌ ▐
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:

═══════════════════════════════════════════════════════════════

═══════╦═══════════════════Ω═══════════════╦════════╦═════Ω══Ω═

║ ║ ║ ║ ║ ║ ║ ║

┌─────────────┐ ║ ║ ┌────────────┐ ┌──────Ϊ──────┐ ┌─────╨─────┐ ║ ║ ║ ║

│ BUFFER │ ║ ║ │ │Χ── CLK 0 │ REGISTRO DE │ │ LATCH DE │ ┌───────║─────┐ ║ ║ ║

╔══Ψ│ DEL BUS │Χ═════Ψ║ ║Χ═════Ψ│ CONTADOR 0 │Χ── GATE 0 │ LA PALABRA │ │ ESTADO │ │ ┌───┐ ║ │ ║ ║ ║

║ │ DE DATOS │ ║ ║ │ │──Ψ OUT 0 │ DE CONTROL │ └─────Ω─────┘ │ │ ┌─Ϊ─Ϊ─┐ ┌Ϊ──Ϊ─┐ ║ ║

Ϊ └─────────────┘ ║ ║ └─────Ω──────┘ └──────┬──┬───┘ ║ │ │ │ CR │ │ CR │ ║ ║

D0..D7 ║ ║ │ │ │ ║ │ │ │ M │ │ L │ ║ ║

║ ║ ┌────────┘ │ │ ┌─────╨─────┐ │ │ └──╥──┘ └──╥──┘ ║ ║

║ ║ │ │ └─────────Ψ│ REGISTRO │ │ │ ║ ║ ║ ║

-RD ─Ψ┌─────────────┐ ║ ║ │ ┌────────────┐ ┌─────Ϊ───────┐ │ DE ESTADO │ │ │ ║ ║ ║ ║

-WR ─Ψ│ LÓGICA │ ║ ║ │ │ │Χ── CLK 1 │ │ └──Ω─────Ω──┘ │ │ ║ ║ ║ ║

│ DE LECTURA │Χ═════Ψ║ ║Χ═════Ψ│ CONTADOR 1 │Χ── GATE 1 │ ├────────│─────│────┘ │ ┌──Ϊ────────Ϊ──┐ ║ ║


245 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

A0 ─Ψ│ Y ESCRITURA ├─┐ ║ ║ │ │ │──Ψ OUT 1 │ LÓGICA ├────────│─────│──────┘ │ │ ║ ║

A1 ─Ψ└─────Ω───────┘ │ ║ ║ │ └──────Ω─────┘ │ DE ├────────│─────│───────Ψ│ CE │ ║ ║

│ │ ║ ║ │ │ │ CONTROL │Χ───────│─────│────────│ │ ║ ║

-CS ────────┘ │ ║ ║ ├─────────┘ │ ├────────┘ │ └───╥────────╥─┘ ║ ║

│ ║ ║ │ │ ├──────────────│────────────║──────┐ ║ ║ ║

┌─────────────┐ │ ║ ║ │ ┌────────────┐ │ ├──────────────│──────────┐ ║ │ ║ ║ ║

│ REGISTRO DE │Χ┘ ║ ║ │ │ │Χ── CLK 2 └──Ω───Ω───┬──┘ │ │ ║ │ ║ ║ ║

│ LA PALABRA │Χ══════║ ║Χ═════Ψ│ CONTADOR 2 │Χ── GATE 2 │ │ ├─────────────────┘ ┌─Ϊ─Ϊ─┐ ┌─Ϊ─Ϊ─┐ ║ ║

│ DE CONTROL │ ║ ║ │ │ │──Ψ OUT 2 CLK n │ │ │ OL │ │ OL │ ║ ║

└──────┬──────┘ ║ ║ │ └──────Ω─────┘ │ │ │ M │ │ L │ ║ ║

└──────────────║ ║────┴─────────┘ GATE n │ └──╥──┘ └──╥──┘ ║ ║

║ ║ Ϊ ║ ╚══════╝ ║

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
┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐
│ │ │ │ │ │ │ │ │
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

│ 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 │ a 1 los contadores seleccionados
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 │ RW1 │ RW0 │ M2 │ M1 │ M0 │ BCD │
│ │ COUNT │ │ │ │ │ │ │
└────┬────┴────┬────┴─────────┴─────────┼────┬────┴────┬────┴────┬────┴────┬────┘
Ϊ └────────┐ └─────────┼─────────┘ Ϊ Contador:
valor de la patilla OUT │ Ϊ 0 Binario 16 bits
│ modo activo 1 BCD 4 décadas
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
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

B - Escribir al registro contador (CR) (2) NULL COUNT = 1


C - Nueva cuenta cargada en CE (CR -Ψ CE) 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) ┌───────────────────────────────────┐ (N=3) ┌────────────────────────────
-WR └──────────┘ └─────────┘
─────────────────────────────────────────┐ ┌─────────────────┐ ┌───────────┐ ┌──
OUT └─────┘ └─────┘ └─────┘
4 3 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
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

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 1 4 3 2 1 4 3
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌────────
OUT (N=4) ─────┘ └───────────┘ └───────────┘ └───────────┘
5 4 3 2 1 5 4 3 2 1 5 4 3 2
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
OUT (N=5) ─────┘ └───────────┘ └───────────┘ └──

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 └─────┘

╓─────────────────────────────────────────────────────────────────╥──────────────────────┐
║ Operación de GATE ║ Rango de las cuentas │
┌────────╫─────────────────────┬─────────────────────┬─────────────────────╫───────────┬──────────┤
│ 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) Inicia la cuenta │ ║ │ │
│ 1 ║ -- │ 2) Resetea OUT tras │ -- ║ 1 │ 0 │
│ ║ │ el siguiente CLK │ ║ │ │
├────────╫─────────────────────┼─────────────────────┼─────────────────────╫───────────┼──────────┤
│ ║ 1) Desactiva cuenta │ 1) Carga contador │ ║ │ │
│ 2 ║ 2) Pone OUT en alto │ 2) Inicia la cuenta │ Activa la cuenta ║ 2 │ 0 │
│ ║ inmediatamente │ │ ║ │ │
├────────╫─────────────────────┼─────────────────────┼─────────────────────╫───────────┼──────────┤
│ ║ 1) Desactiva cuenta │ 1) Carga contador │ ║ │ │
│ 3 ║ 2) Pone OUT en alto │ 2) Inicia la cuenta │ Activa la cuenta ║ 2 │ 0 │
│ ║ inmediatamente │ │ ║ │ │
├────────╫─────────────────────┼─────────────────────┼─────────────────────╫───────────┼──────────┤
│ 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.
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

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 (conexión contador 2 con el altavoz)
OR AL,00000001b ; activar bit 0 (línea GATE del contador 2)
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).

; ******************************************************************** retardo PROC

; * * PUSH AX

; * CRONOS.ASM - Subrutinas para hacer retardos de precisión. * PUSH BX

; * * CLI

; * INIT_RETARDO: llamarla al principio del todo. * OUT 42h,AL ; parte baja de la cuenta

; * 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 ; parte alta

; ******************************************************************** JMP SHORT $+2

IN AL,61h

XOR AL,1 ; bajar GATE

programa SEGMENT JMP SHORT $+2

ASSUME CS:programa, DS:programa OUT 61h,AL

XOR AL,1 ; subir GATE

JMP SHORT $+2

ORG 100h OUT 61h,AL

inicio: STI

JMP SHORT $+2

CALL inic_retardo MOV BX,0FFFFh

MOV CX,20 ; 20 retardos retardando: MOV AL,10000000b

MOV AX,59659 ; de 50 milisegundos OUT 43h,AL ; enclavamiento

retard: CALL retardo JMP SHORT $+2

LOOP retard IN AL,42h ; leer contador

INT 20h MOV AH,AL

JMP SHORT $+2

IN AL,42h

inic_retardo PROC XCHG AH,AL ; AX = valor del contador

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 ; contador 2, modo 2, binario retardo ENDP

JMP SHORT $+2

OUT 43h,AL

POP AX programa ENDS

RET END inicio

inic_retardo 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 ; contador 0, operación 11b, datos binarios


OUT 43h,AL
MOV BX,6553 ; valor de cuenta
MOV AL,BL
JMP SHORT $+2
OUT 40h,AL ; enviar byte bajo
MOV AL,BH
JMP SHORT $+2
OUT 40h,AL ; 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
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

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 <dos.h> asm push ax

asm push cx

#define MICRO(microseg) ((long)(microseg/15.08573727)) asm push dx

asm mov cx,word ptr cuenta /* DX:CX = cuenta */

void retardo_asm(), retardo_c(); asm mov dx,word ptr [cuenta+2]

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!) */


245 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

asm jcxz fin_l /* posible cuenta baja nula */

esp_ref: asm in al,61h

asm and al,10h /* aislar bit 5 */

asm cmp al,ah

asm je esp_ref /* esperar cambio de nivel */

asm mov ah,al

asm loop esp_ref /* completar cuenta baja */

fin_l: asm and dx,dx

asm jz fin_ret /* posible cuenta alta nula */

asm dec dx

asm jmp esp_ref /* completar cuenta alta */

fin_ret: asm pop dx

asm pop cx

asm pop ax

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 ; evitar posible INT 8, entre otras


IN 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
otro_ciclo: PUSH CX
IN AL,61h
XOR AL,2 ; invertir bit 1
JMP SHORT $+2
OUT 61h,AL
MOV CX,300 ; constante de retardo
retardo: LOOP retardo
POP CX
LOOP otro_ciclo
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:
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 ; frecuencia programada
JMP SHORT $+2
IN AL,61h
OR AL,00000011b
JMP SHORT $+2
OUT 61h,AL ; altavoz sonando
MOV CX,0
demora: LOOP demora ; 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 43 46 48 50 53 55 58 60 62

frec = ─┬──▄▄▄─▄▄▄──┬──▄▄▄─▄▄▄─▄▄▄──┬──▄▄▄─▄▄▄──┬──▄▄▄─▄▄▄─▄▄▄──┬─
... │ ███ ███ │ ███ ███ ███ │ ███ ███ │ ███ ███ ███ │ ...
│ ███ ███ │ ███ ███ ███ │ ███ ███ │ ███ ███ ███ │
... │ 40│ 42│ 44│ 45│ 47│ 49│ 51│ 52│ 54│ 56│ 57│ 59│ 61│ 63│ ...
─┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴─
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

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 18 ▐██ IR0
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▌ ▐
▌ ▐ CAS 0 ██▌ 12 17 ▐██ INT
-CS ██▌ 1 28 ▐██ Vcc ▌ ▐
▌ ▐ CAS 1 ██▌ 13 16 ▐██ -SP/-EN
-WR ██▌ 2 27 ▐██ A0 ▌ ▐
▌ ▐ GND ██▌ 14 15 ▐██ CAS 2
-RD ██▌ 3 26 ▐██ -INTA ▌ '8259 ▐
▌ ▐ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
D7 ██▌ 4 25 ▐██ IR7
▌ ▐ El significado e interpretación de las señales se muestra a
D6 ██▌ 5 24 ▐██ IR6 la derecha:
▌ ▐
D5 ██▌ 6 23 ▐██ IR5
▌ ▐
-CS:Habilita la comunicación con la CPU.
D4 ██▌ 7 22 ▐██ IR4
-WR:Permite al 8259 aceptar comandos de la CPU.
▌ ▐
-RD:Permite al 8259 dejar la información en el bus de datos.
D3 ██▌ 8 21 ▐██ IR3
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.
D2 ██▌ 9 20 ▐██ IR2
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
D1 ██▌ 10 19 ▐██ IR1
interconectados, constituyendo un bus local.
▌ ▐
245 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

-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 HARDWARE DE APOYO AL MICROPROCESADOR 245

DESCRIPCIÓN FUNCIONAL

El diagrama funcional del 8259, con la estructura interna de las diversas partes que lo componen, es el
siguiente:
INT
║ ║ INTA Ω
║ ║ │ │
┌───────────────┐ ║ ║ ┌──────────Ϊ─────────────────────┴────────────┐
╔═Ψ│ BUFFER DEL │ Χ═════Ψ ║ ║ │ LÓGICA │
║ │ BUS DE DATOS │ ┌────║ ║─────┤ DE CONTROL │
║ └───────────────┘ │ ║ ║ └────────┬─────────────Ω──────────────Ω───────┘
║ │ ║ ║ │ │ │
D0..D7 Χ╝ │ ║ ╚═══════════════════════════════════════════════════════════
│ ║ ╔══════════Ω═══════════════════════════════════Ω════════════
│ ║ ║ ║ │ │ │ ║
│ ║ ║ ║ │ │ │ ║
│ ║ ║ ║ │ │ │ ║
┌───────────────┐ │ ║ ║ ┌─────╨───Ϊ─┐ ┌─────Ϊ─────┐ ┌──┴──╨─────┐
-RD ──Ψ│ LÓGICA DE │ │ ║ ║ │ │ │ │ │ │Χ── IR0
-WR ──Ψ│ LECTURA Y │Χ───┤ ║ ║ │ I.S.R. │ │ LÓGICA │ │ I.R.R. │Χ── IR1
A0 ──Ψ│ ESCRITURA │ │ ║ ║ │ │ │ DE │ │ │Χ── IR2
└───────Ω───────┘ │ ║ ║ │ (In │Χ═══Ψ│ GESTIÓN │Χ════│(Interrupt │Χ── IR3
-CS ───────────┘ │ ║ ║ │ Service │ │ DE │ │ Request │Χ── IR4
│ ║ ║ │ Register) │ │ PRIORIDAD │ │ Register) │Χ── IR5
│ ║ ║ │ │ │ │ │ │Χ── IR6
│ ║ ║ │ │ │ │ │ │Χ── IR7
┌───────────────┐ │ ║ ║ └──────Ω────┘ └─────Ω─────┘ └────Ω──────┘
CAS 0 Χ──Ψ│ BUFFER DE │ │ ║ ║ │ │ │
CAS 1 Χ──Ψ│ CASCADA Y │Χ───┘ ║ ║ ┌─────┴────────────────┴────────────────┴─────┐
CAS 2 Χ──Ψ│ COMPARADOR │ ║ ║Χ═══Ψ│ IMR │
└───────Ω───────┘ ║ ║ │ (Interrupt Mask Register) │
-SP/-EN Χ──────────┘ ║ ║ └─────────────────────────────────────────────┘
║ ║
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.
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

A0 D7 D6 D5 D4 D3 D2 D1 D0
┌─────────┼─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐
│ │ │ │ │ │ │ │ │ │
│ 0 │ A7 │ A6 │ A5 │ 1 │ LTIM │ ADI │ SNGL │ IC4 │
│ │ │ │ │ │ │ │ │ │
└─────────┼────┬────┴────┬────┴────┬────┴─────────┼────┬────┴────┬────┴────┬────┴────┬────┘
└─────────┼─────────┘ │ Ϊ │ Ϊ
Ϊ │ "Call Address │ a 0 si ICW4
dirección del vector de interrupción, │ interval": │ innecesaria
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
┌─────────┼─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐
│ │ A15 │ A14 │ A13 │ A12 │ A11 │ │ │ │
│ 1 │ │ │ │ │ │ A10 │ A9 │ A8 │
│ │ ó T7 │ ó T6 │ ó T5 │ ó T4 │ ó T3 │ │ │ │
└─────────┼─────────┴─────────┴─────────┴─────────┼─────────┴─────────┴─────────┴─────────┘

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 │
│ │ │ │ │ │ │ │ │ │
└─────────┼────┬────┴────┬────┴────┬────┴─────────┼─────────┴────┬────┴────┬────┴────┬────┘
│ │ │ └─────────┼─────────┘
│ ┌───────┘ │ Ϊ
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

│ │ ┌───────────────┘ Nivel de IR sobre el que actuar


│ │ │
0 0 1 EOI no específico ─────┬──Ψ Fin de interrupción
0 1 1 (*) EOI específico ────┘
1 0 1 Rotar en comando EOI no específico ────┐
1 0 0 Activar rotación en modo AEOI ────────┼──Ψ Rotación automática
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
(*) 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: │ │ │ Comando de lectura de registro:
│ │ ------------------------- │ │ │ -------------------------------
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
Ϊ
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).
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

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 (INT 08h)


IRQ 1 Teclado (INT 09h)
IRQ 2 En los PC/XT: canal E/S (INT 0Ah)
IRQ 8 Reloj de tiempo real (INT 70h) ─┐
IRQ 9 Simulación de IRQ2 (INT 71h) │
IRQ 10 Reservado (INT 72h) │
IRQ 11 Reservado (INT 73h) │
IRQ 12 Reservado (INT 74h) │Ψ Sólo AT y PS/2
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) ¡poner 0 en PC/XT!

OUT 21h,AL ; enviar ICW3

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
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

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 Al producirse la INT 9 se lee el código de rastreo de la tecla y luego se


PUSH AX llama a la rutina que gestionaba con anterioridad a ésta la INT 9: ella se encargará
IN AL,puerto_teclado de limpiar el ISR que, por tanto, no es tarea de nuestra rutina. Si hubiera que
CALL anterior_int9 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
; procesar tecla
hay que enviar el EOI a ambos controladores de interrupción).
;
POP 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 * irq2: INT 10

; * las interrupciones hardware INT 8-INT 15 hacia * IRET

; * los vectores INT 50h a INT 57h. * irq3: INT 11

; ******************************************************************** IRET

irq4: INT 12

irqdemo SEGMENT IRET

ASSUME CS:irqdemo, DS:irqdemo irq5: INT 13

IRET

ORG 100h irq6: INT 14

inicio: IRET

JMP main irq7: INT 15

IRET

; ------------ Area residente

tam_resid EQU ($-OFFSET inicio+256+15)/16

irq0: INT 8 ; simular IRQ's normales (se

IRET ; podría aprovechar también ; ------------ Código de instalación

irq1: INT 9 ; para hacer algo más útil).


245 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

main PROC PUSHF

LEA BX,tabla_ints POP AX

MOV AL,50h ; nueva base para IRQ's 0-7 AND AX,0F000h

otra_int: PUSH AX CMP AX,0F000h

PUSH BX MOV AX,1 ; indicar AT

MOV AH,25h JNE es_AT

MOV DX,[BX] DEC AX ; indicar PC/XT

INT 21h ; desviar INT 50h-57h es_AT: RET

POP BX es_AT? ENDP

ADD BX,2

POP AX tabla_ints DW irq0, irq1, irq2, irq3, irq4, irq5, irq6, irq7

INC AL texto_txt DB 13,10,"Las interrupciones 8-15 son ahora 50-57h."

CMP AL,58h DB 13,10,"$"

JB otra_int

CALL es_AT? irqdemo ENDS

MOV BL,4 END inicio

MUL BL

MOV BL,AL ; BL = 4 en AT y 0 en PC/XT

CALL inic_8259

LEA DX,texto_txt

MOV AH,9

INT 21h ; mensaje de instalación

MOV ES,ES:[2Ch]

MOV AH,49h

INT 21h ; liberar entorno

MOV AH,31h

MOV DX,tam_resid

INT 21h ; terminar residente

main ENDP

; ------------ Subrutinas de apoyo a la instalación.

inic_8259 PROC ; Inicialización 8259 maestro

MOV AL,0FFh

OUT 21h,AL ; enmascarar todas las IRQ

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 ; permitir todas las IRQ

RET

inic_8259 ENDP

es_AT? PROC ; comprobar si es XT ó AT

PUSHF

POP AX

AND AX,0FFFh

PUSH AX

POPF
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

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.

▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ DREQ3 ██▌ 16 25 ▐██ DACK0


▌ ▐ ▌ ▐
-IOR ██▌ 1 40 ▐██ A7 DREQ2 ██▌ 17 24 ▐██ DACK1
▌ ▐ ▌ ▐
-IOW ██▌ 2 39 ▐██ A6 DREQ1 ██▌ 18 23 ▐██ DB5
▌ ▐ ▌ ▐
-MEMR ██▌ 3 38 ▐██ A5 DREQ0 ██▌ 19 22 ▐██ DB6
▌ ▐ ▌ ▐
-MEMW ██▌ 4 37 ▐██ A4 GND ██▌ 20 21 ▐██ DB7
▌ ▐ ▌ '8237 ▐
NC ██▌ 5 36 ▐██ -EOP ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
▌ ▐
READY ██▌ 6 35 ▐██ A3
▌ ▐
HLDA ██▌ 7 34 ▐██ A2
▌ ▐
ADSTB ██▌ 8 33 ▐██ A1
▌ ▐
AEN ██▌ 9 32 ▐██ A0
▌ ▐
HRQ ██▌ 10 31 ▐██ Vcc
▌ ▐
-CS ██▌ 11 30 ▐██ DB0
▌ ▐
CLK ██▌ 12 29 ▐██ DB1
▌ ▐
RESET ██▌ 13 28 ▐██ DB2
▌ ▐
DACK2 ██▌ 14 27 ▐██ DB3
▌ ▐
DACK3 ██▌ 15 26 ▐██ DB4
▌ ▐
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 enmascarado salvo en el caso del modo de autoinicialización.
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
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 ┌────────────────────────────────────────┬─────────┬──────────────┐
cuando varios canales de DMA │ Tipo de registro │ Tamaño │ Nº registros │

son accedidos a la vez; también ├────────────────────────────────────────┼─────────┼──────────────┤

posee un elevado número de │ Registro base de dirección │ 16 bits │ 4 │


│ Registro base contador de palabras │ 16 bits │ │
registros para gestionar el 4
│ Registro de dirección en curso │ 16 bits │ │
funcionamiento. Los registros 4
│ 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:

┌────────────────┐ ┌────────────────┐
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ C.P.U. │ │ │
│ │ │ '8237 │
│ │ ┌────────────────┐ │ │
│ │ │ DREQ │Χ───────│ HRQ │
│ │ │ DACK │───────Ψ│ HLDA │
│ │ │ │ │ │
│ │Χ───────│ 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) │ │ Dirección │
│ │ │ │ A3 A2 A1 A0 │
├───────┼─────────────────────────────────────────────────────────────┼──────────┼─────────────┤
│ │ Base de dirección y de dirección en curso │ Escribir │ 0 0 0 0 │
│ 0 │ De dirección en curso │ Leer │ 0 0 0 0 │
│ │ Base contador de palabras y contador de palabras en curso │ Escribir │ 0 0 0 1 │
│ │ Contador de palabras en curso │ Leer │ 0 0 0 1 │
├───────┼─────────────────────────────────────────────────────────────┼──────────┼─────────────┤
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

│ │ Base de dirección y de dirección en curso │ Escribir │ 0 0 1 0 │


│ 1 │ De dirección en curso │ Leer │ 0 0 1 0 │
│ │ Base contador de palabras y contador de palabras en curso │ Escribir │ 0 0 1 1 │
│ │ Contador de palabras en curso │ Leer │ 0 0 1 1 │
├───────┼─────────────────────────────────────────────────────────────┼──────────┼─────────────┤
│ │ Base de dirección y de dirección en curso │ Escribir │ 0 1 0 0 │
│ 2 │ De dirección en curso │ Leer │ 0 1 0 0 │
│ │ Base contador de palabras y contador de palabras en curso │ Escribir │ 0 1 0 1 │
│ │ Contador de palabras en curso │ Leer │ 0 1 0 1 │
├───────┼─────────────────────────────────────────────────────────────┼──────────┼─────────────┤
│ │ Base de dirección y de dirección en curso │ Escribir │ 0 1 1 0 │
│ 3 │ De dirección en curso │ Leer │ 0 1 1 0 │
│ │ Base contador de palabras y contador de palabras en curso │ Escribir │ 0 1 1 1 │
│ │ Contador de palabras en curso │ Leer │ 0 1 1 1 │
└───────┴─────────────────────────────────────────────────────────────┴──────────┴─────────────┘
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
a nivel bajo │ │ │ │ │ │ 1 modo memoria-memoria
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
│ 0 modo incremento de direcciones │ 11 seleccionar canal 3
│ 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 │
│ │ │ │ │ │ │ │ │
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

└────┬────┴────┬────┴────┬────┴────┬────┼────┬────┴────┬────┴────┬────┴────┬────┘
Ϊ Ϊ Ϊ Ϊ Ϊ Ϊ Ϊ Ϊ
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 │ Modo de │ Dirección │
│ u operación │ acceso │ A3 A2 A1 A0 │
├───────────────────────────────────────────────────────────────────────┼──────────┼─────────────┤
│ Read Status Register (leer registro de estado) │ Leer │ 1 0 0 0 │
│ Write Command Register (escribir registro de comandos) │ Escribir │ 1 0 0 0 │
│ Read Request Register (leer registro de petición de DMA) │ Leer │ 1 0 0 1 │
│ Write Request Register (escribir registro de petición de DMA) │ Escribir │ 1 0 0 1 │
│ Read Command Register (leer registro de comandos) │ Leer │ 1 0 1 0 │
│ Write Single Mask Bit (escribir un solo bit de máscara de DMA) │ Escribir │ 1 0 1 0 │
│ Read Mode Register (leer registro de modo) │ Leer │ 1 0 1 1 │
│ Write Mode Register (escribir registro de modo) │ Escribir │ 1 0 1 1 │
│ Set Byte Pointer F/F (activar flip-flop primero/último) │ Leer │ 1 1 0 0 │
│ Clear Byte Pointer F/F (borrar flip-flop primero/último) │ Escribir │ 1 1 0 0 │
│ Read Temporary Register (leer registro temporal) │ Leer │ 1 1 0 1 │
│ Master Clear (inicialización principal) │ Escribir │ 1 1 0 1 │
│ Clear Mode Register Counter (limpiar contador de registro de modo) │ Leer │ 1 1 1 0 │
│ Clear Mask Register (borrar registro de máscara de DMA) │ Escribir │ 1 1 1 0 │
│ Read All Mask Bits (leer todos los bits de máscara de DMA) │ Leer │ 1 1 1 1 │
│ Write All Mask Bits (escribir todos los bits de máscara de DMA) │ Escribir │ 1 1 1 1 │
└───────────────────────────────────────────────────────────────────────┴──────────┴─────────────┘
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.
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐
│ │ │ │ │ │ │ │ │
│ 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 ├───────┼────────────────────┤
adoptada por los diseñadores del PC consistió en añadir unos registros │ 0 │ 87h (sólo AT) │
│ 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:

┌──── Registro de página ┌──── Registro de direcciones del 8237


Ϊ Ϊ
┌───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
PC/XT │A19│A18│A17│A16│ │A15│A14│A13│A12│A11│A16│ A9│ A8│ A7│ A6│ A5│ A4│ A3│ A2│ A1│ A0│
└───┴───┴───┴───┘ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
D3 D2 D1 D0

┌──── Registro de página ┌──── Registro de direcciones del 8237


Ϊ Ϊ
┌───┬───┬───┬───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
AT (DMA 8) │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 D0

┌──── Registro de página ┌──── Registro de direcciones del 8237


EL HARDWARE DE APOYO AL MICROPROCESADOR 245

Ϊ Ϊ
┌───┬───┬───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ ┌───┐
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 ; modo cascada en el canal 4
OUT 0D6h,AL
JMP SHORT $+2
MOV AL,41h ; establecer modo para el canal 1
OUT 0Bh,AL
OUT 0D6h,AL ; y para el 5
JMP SHORT $+2
MOV AL,42h ; establecer modo para el canal 2
OUT 0Bh,AL
OUT 0D6h,AL ; y para el 6
JMP SHORT $+2
MOV AL,43h ; 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.
EL HARDWARE DE APOYO AL MICROPROCESADOR 245

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 <dos.h> dmako3(); dmako7();

#include <string.h> printf ("\n Ralentización máxima activa.\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) {
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 el ██████
test de 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
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

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 │ 2 bytes │ 54-116 bytes 4E │ ... │
─┴─────────┴─────────┴─────────────────────────────────┴─────────┴─────────────────┴─ ┘

GAP 4B
─┬─────────────┬─ ─┬─────────────────────────────┐
... │ otro sector │ ... │ 100-400 bytes 4E │ Χ── Fin de pista
─┴─────────────┴─ ─┴─────────────────────────────┘

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.

▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▌ ▐
▌ ▐ DB3 ██▌ 9 32 ▐██ PS0
RESET ██▌ 1 40 ▐██ Vcc ▌ ▐
▌ ▐ DB4 ██▌ 10 31 ▐██ PS1
-RD ██▌ 2 39 ▐██ -RW/SEEK ▌ ▐
▌ ▐ DB5 ██▌ 11 30 ▐██ WR DATA
-WR ██▌ 3 38 ▐██ LCT/DIR ▌ ▐
▌ ▐ DB6 ██▌ 12 29 ▐██ DS0 ó US0
-CS ██▌ 4 37 ▐██ FR/STP ▌ ▐
▌ ▐ DB7 ██▌ 13 28 ▐██ DS1 ó US1
A0 ██▌ 5 36 ▐██ HDL ▌ ▐
▌ ▐ DRQ ██▌ 14 27 ▐██ HDSEL
DB0 ██▌ 6 35 ▐██ RDY ▌ ▐
▌ ▐ -DACK ██▌ 15 26 ▐██ MFM
DB1 ██▌ 7 34 ▐██ WP/TS ▌ ▐
▌ ▐ TC ██▌ 16 25 ▐██ WE
DB2 ██▌ 8 33 ▐██ FLT/TRK0 ▌ ▐
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

IDX ██▌ 17 24 ▐██ VCO


▌ ▐ SEÑALES DEL 765
INT ██▌ 18 23 ▐██ RD DATA
▌ ▐ Interface con la CPU.
CLK ██▌ 19 22 ▐██ DWIN RESET:Reset. Línea de reinicialización al estado por defecto.
▌ ▐ -CS:Chip Selection. Línea de selección del integrado.
GND ██▌ 20 21 ▐██ WR CLK -RD:Read. Patilla por la que la CPU lee datos del FDC.
▌ '765 ▐ -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 ─┤
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

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
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

(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 │
└─────┴─────┴─────┴─────┼──┬──┴──┬──┴─────┴─────┘
│ ┌───┘ Modo:
00 - IGUAL 10 - MENOR O IGUAL 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 (16-
SR)*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
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

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).
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

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
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

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.) │ 300/360(*) │ 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 │

│ Sectores por pista │ 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 │

└─────────────────────────────────────────┴───────────────────┴──────────────────┴───────────────────┴──────────────────┴──────────────────┘

(*) 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
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

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. │
cuelgue del sistema (a no ser que haya mucha │ Programación directa del controlador NEC765 y el DMA 8237. │

suerte). Sin embargo, cuando el DOS se │ Funcionamiento probado bajo sistemas PC XT, AT, 386 y 486. │

carga en memoria al principio del arranque, │ Soporte para disquetes de 360K, 720K, 1.2M, 1.44M y 2.88M. │

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: 500 Kbit/seg en MFM - Cilindro 0 y Cabezal 0 │

│ │

│ │

│ 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 │ │

especial, para idear y probar métodos de │ Tamaño de sector: │

│ │
protección anticopia y, en general, para
0 -> 1-128 bytes

│ 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 │ │

seleccionarlas al principio y, en general, │ [ST0=0x01] [ST1=0x00] [ST2=0x00] │

cada vez que se cambie de disco. Una línea │ [Cilindro 1] [Cabezal 0] [Sector 1] [Tamaño 2] │

│ │
inferior informa permanentemente de los
│ │
principales parámetros activos, si bien no
Pulsa una tecla para ver el sector [ESC=salir].

│ │
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 │ │

│ δ<ÉMSDOS5.0..... │
aconsejable después sería recalibrar, ya que
0000: EB 3C 90 4D 53 44 4F 53 - 35 2E 30 00 02 01 01 00

│ 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<NO NA │
70 y las opciones de leer y escribir sector │ 0030: 4D 45 20 20 20 20 46 41 - 54 31 36 20 20 20 FA 33 ME FAT16 Χ3 │
fallarán; ya que no preguntan el número de │ 0040: C0 8E D0 BC 00 7C 16 07 - BB 78 00 36 C5 37 1E 56 └Ä╨╝.|..╗x.6┼7.V │
cilindro y emplean el que se supone activo │ 0050: 16 53 BF 3E 7C B9 0B 00 - FC F3 A4 06 1F C6 45 FE .S┐>|╣.._£ñ..╞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.═. │
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

│ │ Utiliza los cursores [ESC=salir] │

│ │ │

│ Bytes 0000-0255 del sector (1/2) │ │

│ │ │

│ Utiliza los cursores [ESC=salir] └──────────────────────────────────────────────────────────────────────────────────┘

│ Figura 12.6.5.2 LECTURA DE UN SECTOR


└──────────────────────────────────────────────────────────

────────────────────────┘

┌──────────────────────────────────────────────────────────

────────────────────────┐

│ 0100: 58 58 58 EB E8 8B 47 1A - 48 48 8A 1E 0D 7C 32

FF XXXδΦïG.HHè..|2 │

│ 0110: F7 E3 03 06 49 7C 13 16 - 4B 7C BB 00 07 B9 03

00 »π..I|..K|╗..╣.. │

│ 0120: 50 52 51 E8 3A 00 72 D8 - B0 01 E8 54 00 59 5A

58 PRQΦ:.r╪░.ΦT.YZX │

│ 0130: 72 BB 05 01 00 83 D2 00 - 03 1E 0B 7C E2 E2 8A

2E r╗...â╥....|ΓΓè. │

│ 0140: 15 7C 8A 16 24 7C 8B 1E - 49 7C A1 4B 7C EA 00

00 .|è.$|ï.I|íK|Ω.. │

│ 0150: 70 00 AC 0A C0 74 29 B4 - 0E BB 07 00 CD 10 EB

F2 p.¼.└t)┤.╗..═.δ³ │

│ 0160: 3B 16 18 7C 73 19 F7 36 - 18 7C FE C2 88 16 4F

7C ;..|s.»6.|n┬ê.O| │

│ 0170: 33 D2 F7 36 1A 7C 88 16 - 25 7C A3 4D 7C F8 C3

F9 3╥»6.|ê.%|úM|°├Χ │

│ 0180: C3 B4 02 8B 16 4D 7C B1 - 06 D2 E6 0A 36 4F 7C

8B ├┤.ï.M|▒.╥µ.6O|ï │

│ 0190: CA 86 E9 8A 16 24 7C 8A - 36 25 7C CD 13 C3 0D

0A ╩åΘè.$|è6%|═.├.. │

│ 01A0: 45 72 72 6F 72 2C 20 64 - 65 20 64 69 73 63 6F

20 Error, de disco │

│ 01B0: 64 65 20 73 69 73 74 65 - 6D 61 0D 0A 52 65 65

6D de sistema..Reem │

│ 01C0: 70 6C 61 63 65 20 79 20 - 70 72 65 73 69 6F 6E

65 place y presione │

│ 01D0: 20 63 75 61 6C 71 75 69 - 65 72 20 74 65 63 6C

61 cualquier tecla │

│ 01E0: 0D 0A 00 49 4F 20 20 20 - 20 20 20 53 59 53 4D

53 ...IO SYSMS │

│ 01F0: 44 4F 53 20 20 20 53 59 - 53 00 00 00 00 00 55

AA DOS SYS.....U¬ │

│ Bytes 0256-0511 del sector (2/2)


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 │ │ Longitud (ms) Sector Tamaño Cilindro

│ [ 43.07] 10.76 12 512 ( 2) 0 0 0x00 0x00 0x00 │ Cabeza ST0 ST1 ST2 │

│ [ 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 │ │ [ 399.32] 399.32 12 512 ( 2) 0 0

│ [ 86.30] 10.77 16 512 ( 2) 0 0 0x00 0x00 0x00 │ 0x40 0x01 0x00 │

│ [ 97.07] 10.77 17 512 ( 2) 0 0 0x00 0x00 0x00 │ │ [ 798.94] 399.62 12 512 ( 2) 0 0

│ [ 111.31] 14.24 18 512 ( 2) 0 0 0x00 0x00 0x00 │ 0x40 0x01 0x00 │

│ [ 122.07] 10.76 1 512 ( 2) 0 0 0x00 0x00 0x00 │ │ [ 1198.43] 399.50 12 512 ( 2) 0 0

│ [ 132.85] 10.78 2 512 ( 2) 0 0 0x00 0x00 0x00 │ 0x40 0x01 0x00 │

│ [ 143.61] 10.76 3 512 ( 2) 0 0 0x00 0x00 0x00 │ │ [ 1598.09] 399.66 12 512 ( 2) 0 0

│ [ 154.38] 10.77 4 512 ( 2) 0 0 0x00 0x00 0x00 │ 0x40 0x04 0x00 │

│ [ 165.15] 10.77 5 512 ( 2) 0 0 0x00 0x00 0x00 │ │ [ 1997.53] 399.44 12 512 ( 2) 0 0

│ [ 175.93] 10.78 6 512 ( 2) 0 0 0x00 0x00 0x00 │ 0x40 0x01 0x00 │

│ [ 186.69] 10.77 7 512 ( 2) 0 0 0x00 0x00 0x00 │ │ [ 2396.95] 399.41 12 512 ( 2) 0 0

│ [ 197.46] 10.77 8 512 ( 2) 0 0 0x00 0x00 0x00 │ 0x40 0x01 0x00 │

│ [ 208.24] 10.78 9 512 ( 2) 0 0 0x00 0x00 0x00 │ │ [ 2796.40] 399.45 12 512 ( 2) 0 0

│ [ 219.00] 10.76 10 512 ( 2) 0 0 0x00 0x00 0x00 │ 0x40 0x01 0x00 │

│ [ 229.78] 10.79 11 512 ( 2) 0 0 0x00 0x00 0x00 │ │ [ 3196.00] 399.61 12 512 ( 2) 0 0

│ │ 0x40 0x01 0x00 │

│ Una tecla para leer más ID's [ESC=salir]. │ │ [ 3595.62] 399.61 12 512 ( 2) 0 0
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

0x40 0x04 0x00 │ que no hay humano tan rápido que en


│ [ 3995.22] 399.61 12 512 ( 2) 0 0 0x40 0x01 0x00 │ menos de 15 ms después de haber escogido
│ [ 4394.62] 399.40 12 512 ( 2) 0 0 0x40 0x04 0x00 │ la opción de posicionar cabezal pueda elegir
│ [ 4794.18] 399.56 12 512 ( 2) 0 0 0x40 0x04 0x00 │
la de escribir sector en el menú principal.
│ [ 5193.60] 399.42 12 512 ( 2) 0 0 0x40 0x04 0x00 │
Pero en otros programas, donde se posicione
│ [ 5593.10] 399.50 12 512 ( 2) 0 0 0x40 0x01 0x00 │
repetidamente el cabezal y se acceda al
│ │
disco en escritura repetitivamente, conviene
[ 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 │


no olvidar hacer la pausa. Bueno, si se
│ [ 7191.33] 399.70 12 512 ( 2) 0 0 0x40 0x01 0x00 │
olvida, no sucede nada: sólo se podría
│ [ 7590.84] 399.50 12 512 ( 2) 0 0 0x40 0x01 0x00 │
producir algún error al escribir que no se
│ [ 7990.23] 399.40 12 512 ( 2) 0 0 0x40 0x01 0x00 │ detectaría hasta una posterior lectura. Lo
│ [ 8389.74] 399.51 12 512 ( 2) 0 0 0x40 0x01 0x00 │ malo es que estos errores son esporádicos y
│ │ resulta muy difícil localizar su origen.
│ Una tecla para leer más ID's [ESC=salir]. │

Las funciones leer_sector() y


└──────────────────────────────────────────────────────────────────────────────────┘

Figura 12.6.5.3 LECTURAS CORRECTA E INCORRECTA DE ID's 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 error-
aunque 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().

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: │

│ │ │

│ │ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 │

│ Tamaño de sector: │ 21 22 23 24 25 │

│ │ │

│ 0 -> 128 bytes │ Puedes elegir lo siguiente: │

│ │ │

│ 1 -> 256 bytes │ 1 - Introducir tú los 4 bytes de un sector. │

│ │ 2 - Modificar un cierto byte en todos los sectores. │

│ 2 -> 512 bytes │ ESC - Dejar las cosas como están ahora. │

│ │ │

│ 3 -> 1024 bytes │ Elige opción. │

│ │ │

│ 4 -> 2048 bytes │ Sector a alterar: 6 │

│ │ Nº Cilindro (anterior=0): 0 │

│ 5 -> 4096 bytes │ Nº cabezal (anterior=0): 0 │

│ │ Nº sector (anterior=6): 6 │

│ │ Tamaño sector (anterior=0): 1 │

│ │ ¿De acuerdo (S/N)? │

│ Elige: 0 │ │

│ │ │

│ └──────────────────────────────────────────────────────────────────────────────────┘

│ Número de sectores: 25 ┌──────────────────────────────────────────────────────────────────────────────────┐

│ │ Resultado de la operación: │

│ │ │

│ │ [ST0=0x01] [ST1=0x00] [ST2=0x00] │

│ Valor para el GAP 3: 50 │ [Cilindro 65] [Cabezal 1] [Sector 0] [Tamaño 0] │

│ │ │

│ │ Formateo correcto. Pulsa una tecla. │

│ │ │

│ Byte para inicializar sectores: 65 │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

│ │ │

└────────────────────────────────────────────────────────── └──────────────────────────────────────────────────────────────────────────────────┘

────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────────┐

┌────────────────────────────────────────────────────────── │ Longitud (ms) Sector Tamaño Cilindro Cabeza ST0 ST1 ST2 │

────────────────────────┐ │ ──────────────────- ────── ──────────── ──────── ────── ───── ───── ───── │

│ Puntualizaciones sobre el formateo: │ [ 6.25] 6.25 19 128 ( 0) 0 0 0x01 0x00 0x00 │


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

│ [ 12.52] 6.26 20 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 18.77] 6.26 21 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 25.03] 6.26 22 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 31.30] 6.27 23 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 37.56] 6.26 24 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 50.42] 12.86 25 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 56.68] 6.26 1 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 62.93] 6.25 2 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 69.19] 6.26 3 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 75.46] 6.27 4 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 81.72] 6.26 5 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 87.98] 6.26 6 256 ( 1) 0 0

0x01 0x00 0x00 │

│ [ 94.25] 6.27 7 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 100.51] 6.26 8 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 106.77] 6.26 9 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 113.03] 6.26 10 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 119.28] 6.26 11 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 125.55] 6.26 12 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 131.81] 6.26 13 128 ( 0) 0 0

0x01 0x00 0x00 │

│ [ 138.07] 6.26 14 128 ( 0) 0 0

0x01 0x00 0x00 │

│ Una tecla para leer más ID's

[ESC=salir]. │

└──────────────────────────────────────────────────────────

────────────────────────┘

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 │ │

propuestos por defecto- con el cilindro y el │ Tamaño de sector: │

│ │
cabezal en que esté dicho sector; cambiar
0 -> 1-128 bytes

│ 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 │ │

programaron al formatear el sector, que │ Resultado de la operación: │

pueden ser cualquier otro. Este programa, a │ │

la hora de leer los sectores no pregunta el │ [ST0=0x41] [ST1=0x20] [ST2=0x20] │

número de cilindro ni cabezal -para ahorrar │ [Cilindro 0] [Cabezal 0] [Sector 6] [Tamaño 1] │

│ │
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 284

│ 00D0: 4E 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

FB ............íííΦ │

│ 00F0: 41 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,

* * cilindro, buffer); break;

* 765DEBUG 3.1 - Programa de análisis avanzado a bajo nivel de * case LEERIDS: leer_id (unidad, mf_mfm, cabezal); break;

* los disquetes, programando el 765 y el 8237. * case SALIR: adios(); break;

* * }

* Compilar en Turbo C 2.0 o Borland C (modelo Large). * }

* *

*********************************************************************/

void reservar_memoria (unsigned char far **buffer)

#include <dos.h> unsigned long dir;

#include <alloc.h>

#include <conio.h> if ((*buffer=farmalloc(SMAX<<1))==NULL) {

printf("\nMemoria insuficiente\n");

#define SMAX 32768L /* mayor sector soportado por el programa */ exit(1);

#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 *buffer+=SMAX; /* evitar buffer entre dos páginas de DMA */

#define LEER 5 }

#define ESCRIBIR 6

#define FORMATEAR 7

#define SALIR 8 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 */ int opc, opcion;

#define ODIGITAL 0x3F2 /* registro de salida digital */

#define CONTROL 0x3F7 /* registro de control del disquete */ clrscr();

puts("765DEBUG 3.1 - UTILIDAD PARA ANALISIS AVANZADO A BAJO NIVEL DE

DISQUETES.");

int menu(), infdc(); puts(" Programación directa del controlador NEC765 y

void reservar_memoria(), seleccionar(), adios(), recalibrar(), el DMA 8237.");

posicionar(), leer_sector(), escribir_sector(), puts(" Funcionamiento probado bajo sistemas PC XT, AT,

formatear_pista(), editar_tabla_fmt(), leer_id(), 386 y 486.");

mostrar_resultados(), mostrar_sector(), motor_on(), puts(" Soporte para disquetes de 360K, 720K, 1.2M,

motor_off(), outfdc(), esperar_int(), prepara_dma(); 1.44M y 2.88M.");

puts("");

puts(" (C) 1992, 1993, 1994 - Ciriaco

void main() García de Celis.");

{ puts("");

unsigned char far *buffer; /* buffer para sector de hasta 4 Kb */ puts("");

int unidad=0, vunidad=0, mf_mfm=1, cabezal=0, cilindro=0; puts(" F2 - Seleccionar unidad/densidad y

resetear.");

outportb (CONTROL, vunidad); /* velocidad por defecto */ puts(" F3 - Recalibrar cabezal (necesario tras

reservar_memoria (&buffer); F2).");

puts("");

for (;;) puts(" F4 - Cambiar de cabezal.");

switch (menu (unidad, vunidad, &mf_mfm, cilindro, &cabezal)) { puts(" F5 - Posicionar cabezal.");

case SELECT: seleccionar (&unidad, &vunidad); break; puts(" F6 - Leer ID's.");

case RECALIBRAR: recalibrar (unidad,&cabezal,&cilindro); break; puts(" F7 - Leer sector.");

case SEEK: posicionar (unidad, cabezal, vunidad, puts(" F8 - Escribir sector.");

&cilindro); break; puts(" F9 - Formatear pista.");

case LEER: leer_sector (unidad, mf_mfm, cabezal, puts(" F10 - Conmutar MF/MFM.");

cilindro, buffer); break; puts(" ESC - Salir");

case ESCRIBIR: escribir_sector (unidad, mf_mfm, cabezal, gotoxy(7,25);

cilindro, buffer); break; cputs("Elige una opción:");


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

(void) infdc();

while (kbhit()) getch(); opcion=0;

do { /**** Enviar comando 'Specify' ****/

gotoxy(18, 22);

printf("Unidad %c: %4d Kbit/seg en %s - Cilindro %2d y Cabezal %d", outfdc (3); /* comando */

unidad+'A', !vunidad?500:vunidad==1?300:vunidad==2?250:1000, if (*vunidad==3)

!*mf_mfm?"MF ":"MFM", cilindro, *cabezal); outfdc (0xAF); /* tiempo de acceso pista-pista y head unload */

gotoxy (25, 25); else if (!*vunidad)

opc=getch(); if (!opc) opc=getch(); outfdc (0xBF);

switch (opc) { else

case 60: opcion=SELECT; break; outfdc (0xDF);

case 61: opcion=RECALIBRAR; break; outfdc (2); /* head load time = 1; modo DMA */

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; int recal, res, pis;

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;

} *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)

{ outfdc (7); /* comando de recalibrado */

clrscr(); outfdc (*cabezal << 2 | unidad); /* byte 1 de dicho comando */

printf("\n\n\n\n\n\n\n\n\t\t\t Unidad (A, B,...): ");

do *unidad=(getch() | 0x20)-'a'; while ((*unidad>3) || (*unidad<0)); esperar_int(); /* esperar interrupción */

printf("%c\n\n\n", *unidad+'A');

outfdc (8); /* comando 'leer estado de interrupciones' */

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(); /* leer resultado */

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("\n\n\t\t\t ST0=0x%02X - Pista=%d", res, pis);

printf("\t\t\t 2.88M: 1000 Kbit/seg -> 3\n");

if (!((res ^ 32) & (0xF0))) break; /* resultado correcto */

printf("\n\t\tElige densidad: "); }

do *vunidad=getch()-'0'; while ((*vunidad<0) || (*vunidad>3)); motor_off(); delay (1500);

outportb (CONTROL, *vunidad);

/**** Modo DMA, arrancar motor y reset ****/ void posicionar (unidad, cabezal, vunidad, cilindro)

int *cilindro;

outportb (ODIGITAL, 1<<(*unidad+4) | *unidad | 8); /* reset */ {

delay (1); int r;

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 Cilindro (0..N): ");

scanf("%d", cilindro);

outfdc (8); /* comando 'leer estado de interrupciones' */

(void) infdc(); /* leer y desechar resultado */ if ((vunidad==1) && cilindro) {


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; outfdc (1); /* GAP para leer: poco importante */

outfdc (t128); /* tamaño si tsector=0 */

motor_on (unidad); /* 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 */ 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");

outfdc (8); /* comando 'leer estado de interrupciones' */ }

printf(" Pulsa una tecla para ver el sector [ESC=salir].");

printf("\n\t\t\t ST0=0x%02X", infdc()); if (getch()!=27) mostrar_sector (buffer, tsector, t128);

printf(" 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", &sector);

printf("\n\nTamaño de sector:\n");

clrscr(); printf(" 0 -> 1-128 bytes\n");

printf("Sector a leer: "); scanf("%d", &sector); 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 Elige: ");

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) {

do tsector=getch()-'0'; while ((tsector<0) || (tsector>8)); printf("\n Concreta el tamaño (1-128): ");

printf("%d\n", tsector); scanf("%d", &t128);

if (tsector==0) { }

printf("\n 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<SMAX; r+=2) {

buffer[r]=0x5A; buffer[r+1]=0xA5; /* "borrar" el buffer */ for (i=0; i<SMAX; i++) buffer[i]=pokete; /* 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);


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

outfdc (cabezal); outfdc (tsector);

outfdc (sector); outfdc (sectores);

outfdc (tsector); outfdc (gap);

outfdc (sector); outfdc (pokete); /* byte de relleno */

outfdc (gap);

outfdc (t128); /* tamaño si tsector=0 */ esperar_int(); /* esperar interrupción */

esperar_int(); /* esperar interrupción */ mostrar_resultados (&r);

mostrar_resultados (&r); motor_off();

motor_off(); if ((r & 0xC0)!=0) {

printf ("Error al formatear. Pulsa una tecla.");

if ((r & 0xC0)!=0) { getch();

printf ("Error de escritura. Pulsa una tecla."); }

getch(); else {

} printf ("Formateo correcto. Pulsa una tecla.");

else { getch();

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(" He establecido por defecto una tabla con los cuatro\n");

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<numsect; i++) printf ("%4d", buffer[i*4+2]);

printf(" 2 -> 512 bytes\n"); printf("\n\n Puedes elegir lo siguiente: \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 Elige: "); printf("\n Elige opción.");

do tsector=getch()-'0'; while ((tsector<0) || (tsector>8));

printf("%d\n", tsector); do {

printf("\nNúmero de sectores: "); scanf("%d", &sectores); opcion=getch(); if (!opcion) opcion=getch()<<8;

printf("\nValor para el GAP 3: "); scanf("%d", &gap); } while (((opcion<'1') || (opcion>'3')) && (opcion!=27));

printf("\nByte para inicializar sectores: "); scanf("%d", &pokete);

if (opcion=='1') {

for (i=0; i<sectores; i++) { /* tabla propuesta para formatear */ do {

buffer[i*4]=cilindro; printf("\n\nSector a alterar: "); scanf ("%d", &sector);

buffer[i*4+1]=cabezal; for (i=0; i<numsect; i++) if (buffer[i*4+2]==sector) break;

buffer[i*4+2]=i+1; if (buffer[i*4+2]!=sector)

buffer[i*4+3]=tsector; printf("Ese sector no existe. No discutamos ");

} 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]);

motor_on (unidad); scanf ("%d", &dato); buffer[i*4+1]=(char) dato;

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

printf("¿De acuerdo (S/N)?"); if (cnth<9)

} while ((getch() | 0x20)!='s'); tmp[i]=cnth*65535L + (65535-lectura);

} else {

else if (opcion=='2') { tmp[i]=0L; /* error */

do { nec[i][0]=-1; /* no informar */

printf("\n\nCaracterística a cambiar: \n"); pokeb (0x40, 0x40, 0xFF); /* asegurar motor en marcha */

printf(" (0) Nº Cilindro, (1) Nº cabezal,"); } /* porque probablemente se está perdiendo mucho tiempo */

printf(" (2) Nº sector, (3) Tamaño de sector: "); }

opcion=getch();

} while ((opcion<'0') || (opcion>'3')); outportb (0x61, inportb(0x61) & 0xFC);

printf("\n Nuevo valor para todos los sectores: ");

scanf ("%d", &dato); clrscr();

for (i=0; i<numsect; i++) buffer[i*4+opcion-'0']=(char) dato; printf("\r Longitud (ms) ");

} printf(" Sector 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; }

int nec[22][7]; else

unsigned i, j, lectura, antlectura, cnth; printf(" N.D. ");

if (nec[j][0]>=0) {

do { printf(" %3d ", nec[j][5]);

clrscr(); printf("%5d (%3d)", nec[j][6]<9?128<<nec[j][6]:0, nec[j][6]);

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(" ?? ?? ?? ??\n");

outportb (0x42, 0xFF); outportb (0x42, 0xFF); /* cuenta 0xFFFF */ }

for (i=0; i<22; i++) { printf("\n\t\t 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); /* velocidad normal */

lectura=inportb(0x42); /* parte baja de la cuenta */ clrscr(); printf("Fin de 765DEBUG\n");

lectura|=inportb(0x42) << 8; /* parte alta de la cuenta */ exit (0);

if (lectura>antlectura) if (cnth++>8) break; /* timeout */ }

} 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());


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

printf("[Cabezal %d] ", infdc()); int i;

printf("[Sector %d] ", infdc());

printf("[Tamaño %d]\n\n", infdc()); /**** Evitar que la BIOS pare el motor (al menos en 14") ****/

else { pokeb(0x40,0x40,0xFF);

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);

unsigned char far *p; pokeb(0x40,0x40,0xFF);

int vv, i, j, k, tecla; }

vv = (1 << tamano) >> 1; if (!vv) vv++;

if (tamano) tt=256;

void motor_off()

i=0; {

do { pokeb(0x40,0x40,55); /* la BIOS lo detendrá en 55/18.2 segundos */

p=&buffer[i*256]; }

clrscr(); printf("\n\n\n");

for (j=0; j<tt; j+=16) {

printf(" %04X: ", p-buffer); void outfdc (unsigned char dato) /* enviar byte al FDC */

for (k=0; k<8; k++) printf("%02X ", *p++); { /* no esperando más de 440 ms */

printf("- "); int t, i=0, rd;

for (k=8; k<16; k++) printf("%02X ", *p++); p-=16;

printf(" "); do {

for (k=0; k<16; k++) { i++; t=peekb(0x40, 0x6C);

if (*p<' ') printf("."); else printf("%c", *p); while ((t==peekb(0x40, 0x6C)) && ((rd=inportb(FDCSTATUS)>>7)==0));

p++; } while ((i<8) && !rd);

printf("\n"); if (rd) outportb (FDCDATA, dato);

} }

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

; * 765NODMA.ASM 2.0 - 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 ; cargar dicho sector

; ************ Macros de propósito general. JC fallo

CALL imprime_sector ; mostrar su contenido

XPUSH MACRO regmem ; apilar lista de registros JMP main

IRP rm, <regmem> escribir: LEA DX,cls_txt

PUSH rm CALL print ; limpiar pantalla

ENDM LEA DX,escritura_txt

ENDM CALL print ; mensaje inicial

LEA DX,aviso_txt

XPOP MACRO regmem ; desapilar lista de registros CALL print

IRP rm, <regmem> CALL pide_sector ; pedir pista, cabeza, ...

POP rm CALL pide_relleno ; pedir byte de relleno

ENDM MOV orden,F_WRITE

ENDM CALL init_drv

CALL recalibrar

; ************ Programa principal. JC fallo

CALL seek_drv

fdc_test SEGMENT JC fallo

ASSUME CS:fdc_test, DS:fdc_test LEA DI,buffer

CALL sector_io ; grabar dicho sector

ORG 100h JC fallo

JMP main

main PROC fallo: LEA DX,fallo_txt ; mensaje de error

CALL menu ; opciones CALL print

DEC AL CALL getch

JZ leer ; opción de leer sector JMP main

DEC AL main ENDP

JZ escribir ; opción de escribirlo

LEA DX,adios_txt ; ************ Subrutinas de apoyo

CALL print ; opción de salir:

MOV AX,40h menu PROC

MOV DS,AX LEA DX,cls_txt

MOV AL,DS:[8Bh] ; velocidad previa al programa CALL print

MOV CL,6 LEA DX,opciones_txt ; texto del menú

SHR AL,CL ; pasarla a bits 0..1 CALL print

MOV DX,3F7h espera_opc: CALL getch

OUT DX,AL ; restaurar velocidad previa CMP AL,'1'

INT 20h JE opc_ok ; elegida opción 1

leer: LEA DX,cls_txt CMP AL,'2'

CALL print ; borrar pantalla JE opc_ok ; elegida opción 2

LEA DX,lectura_txt CMP AL,27

CALL print ; mensaje inicial JE opc3_ok ; ESC (opción '3')

LEA DX,aviso_txt CMP AX,2D00h

CALL print JNE espera_opc ; no es ALT-X

CALL pide_sector ; pedir pista, cabeza, ... opc3_ok: MOV AL,'3'

MOV orden,F_READ opc_ok: SUB AL,'0'


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

RET MOV CX,16 ; 16 líneas

menu ENDP otra_linea: PUSH CX

MOV CX,16 ; de 16 caracteres

; ------------ Solicitar información del sector a ser accedido. pr_hexa: MOV AL,' '

CALL printAL

pide_sector PROC MOV AL,[BX]

LEA DX,unidad_txt INC BX

CALL input_AL ; pedir unidad CALL print8hex

MOV unidad,AL LOOP pr_hexa

LEA DX,vunidad_txt MOV AL,' '

CALL input_AL ; seleccionar velocidad CALL printAL

MOV vunidad,AL CALL printAL

LEA DX,tdisco_txt SUB BX,16

CALL input_AL ; problema de 40/80 pistas MOV CX,16

MOV tunidad,AL pr_ascii: MOV AL,[BX]

LEA DX,tamano_txt INC BX

CALL input_AL ; preguntar tamaño sector CMP AL,' '

MOV tsector,AL JAE ascii_ok

LEA DX,gap_rw_txt MOV AL,'.'

CALL input_AL ; preguntar tamaño sector ascii_ok: CALL printAL

MOV gap,AL LOOP pr_ascii

LEA DX,pista_txt MOV AL,13

CALL input_AL ; pedir pista CALL printAL

MOV cilindro,AL MOV AL,10

LEA DX,cabeza_txt CALL printAL

CALL input_AL ; pedir cabeza POP CX

MOV cabezal,AL LOOP otra_linea

LEA DX,sector_txt LEA DX,ptecla_txt

CALL input_AL ; pedir sector 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 ; CX: 1-128 bytes, 2-256, ... imprime_sector ENDP

MOV AX,64

computab: SHL AX,1 ; ------------ Pedir byte para llenar el sector a grabar.

LOOP computab

MOV bsector,AX ; bytes/sector pide_relleno PROC

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 ; tamaño de sector en bytes

MOV byte1,AL ; byte 1 común a muchas órdenes CLD

RET REP STOSB

pide_sector ENDP RET

pide_relleno ENDP

; ------------ Imprimir sector en hex/ASCII en bloques de 256 bytes.

; ------------ Imprimir cadena en DS:DX terminada en un '$'.

imprime_sector PROC

LEA BX,buffer print PROC

MOV AX,bsector PUSH AX

MOV CL,AH MOV AH,9 ; función de impresión

MOV CH,0 ; CX secciones de 256 bytes INT 21h ; llamar al sistema

AND CX,CX POP AX

JNZ otra_mitad RET

INC CX ; al menos imprimir una vez print ENDP

otra_mitad: PUSH CX

LEA DX,cls_txt ; ------------ Imprimir carácter en AL

CALL print
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

printAL PROC PUSH DX

PUSH AX PUSH AX

PUSH DX ; registros usados preservados pedir_dato: CALL print

MOV AH,2 ; función de impresión del DOS MOV AH,0Ah ; función de entrada (teclado)

MOV 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

RET ; retornar MOV CL,[BX+1]

printAL ENDP XOR CH,CH ; número de caracteres pulsados

POP AX

; ------------ Imprimir carácter hexadecimal (AL). POP DX

PUSH DX

print4hex PROC PUSH AX

PUSH AX ; preservar AX JCXZ pedir_dato ; se pulsó RETURN: reiterar

ADD AL,'0' ; pasar binario a ASCII XOR DX,DX

CMP AL,'9' gen_num: MOV AX,10

JBE no_sup9 ; no es letra MUL DX

ADD AL,'A'-'9'-1 ; lo es MOV DX,AX

no_sup9: CALL printAL ; imprimir dígito hexadecimal MOV AL,[BX+2]

POP AX ; restaurar AX SUB AL,'0'

RET INC BX

print4hex ENDP XOR AH,AH

ADD DX,AX

; ------------ Imprimir byte hexadecimal en AL. LOOP gen_num ; conversión ASCII -> binario

POP AX

print8hex PROC MOV AL,DL ; resultado

PUSH CX POP DX

PUSH AX POP CX

MOV CL,4 POP BX

SHR AL,CL ; pasar bits 4..7 a 0..3 RET

CALL print4hex ; imprimir nibble más input_AL ENDP

significativo

POP AX ; restaurar AL ; ------------ Encender motor y esperar a que tome cierta velocidad.

PUSH AX ; y preservarlo de nuevo

AND AL,1111b ; dejar nibble menos init_drv PROC

significativo PUSH CX

CALL print4hex ; e imprimirlo CALL reset_drv

POP AX MOV CX,18

POP CX CALL retardo ; esperar aceleración disco

RET POP CX

print8hex ENDP RET

init_drv ENDP

; ------------ Esperar pulsación de tecla y devolverla en AX.

; ------------ Establecer modalidad de operación del controlador

getch PROC ; y asegurar que el motor está en marcha.

MOV AH,1 ; esperar carácter (algunos

INT 16h ; KEYB de XT se cuelgan reset_drv PROC

JZ getch ; al usar directamente el XPUSH <DS, AX, BX, CX, DX>

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

getch ENDP MOV BYTE PTR DS:[BX],255 ; menos durante 14 seg.

POP DS

; ------------ Leer nº decimal de hasta 3 dígitos y devolverlo en AL. MOV DX,3F2h ; registro de salida digital

MOV CL,unidad

input_AL PROC ADD CL,4

PUSH BX MOV AL,1

PUSH CX SHL AL,CL ; colocar bit del motor


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

OR AL,unidad ; seleccionar unidad; NO DMA RET

OUT DX,AL ; reset recalibrar ENDP

OR AL,00000100b

JMP SHORT $+2 ; ------------ Llevar el cabezal a la pista indicada.

OUT DX,AL ; fin del reset

CALL espera_int seek_drv PROC

MOV AL,3 XPUSH <AX, CX>

CALL fdc_write ; Comando 'Specify': CLI

MOV AL,0DFh CALL habilita_int ; usar interrupciones

CALL fdc_write MOV AL,0Fh

MOV AL,3 ; modo NO DMA CALL fdc_write ; comando 'seek'

CALL fdc_write ; head load y modo JZ fallo_seek

PUSH DS MOV AL,byte1

MOV BX,40h CALL fdc_write ; enviar HD, US1, US0

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 ; indicar motor ON SHL AL,1 ; cilindro=cilindro*2

POP DS pista_ok: CALL fdc_write ; enviar cilindro

MOV DX,3F7h CALL espera_int ; esperar interrupción

MOV AL,vunidad ; velocidad de transferencia CLI

OUT DX,AL MOV AL,8

XPOP <DX, CX, BX, AX, DS> CALL fdc_write ; comando 'leer estado int...'

RET JZ fallo_seek

reset_drv ENDP CALL fdc_read ; leer registro de estado 0

CALL fdc_read ; leer cilindro actual

; ------------ Recalibrar la unidad (si hay error se intenta otra vez STI

; para el caso de que deba moverse más de 77 pistas). MOV CX,1

CALL retardo ; esperar asentamiento cabezal

recalibrar PROC XPOP <CX, AX>

XPUSH <AX, CX> CLC ; retornar con éxito

MOV CX,2 ; dos veces como mucho RET

recalibra: CALL habilita_int fallo_seek: STI

MOV AL,7 XPOP <CX, AX>

CALL fdc_write ; comando de 'recalibrado' STC ; retornar indicando fallo

JZ fallo_recal RET

MOV AL,byte1 seek_drv ENDP

CALL fdc_write ; enviar HD, US1, US0

JZ fallo_recal ; ------------ Habilitar interrupción disquete (y modo DMA).

CALL espera_int ; esperar interrupción

JZ fallo_recal habilita_int PROC

MOV AL,8 XPUSH <AX, CX, DX>

CALL fdc_write ; comando 'leer estado int...' MOV CL,unidad

JZ fallo_recal ADD CL,4

CALL fdc_read ; leer registro de estado 0 MOV AL,1

JZ 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 ; modo DMA

XPOP <CX, AX> JMP SHORT $+2

CLC ; Ok. OUT DX,AL

RET XPOP <DX, CX, AX>

fallo_recal: LOOP recalibra ; reintentar comando RET

XPOP <CX, AX> habilita_int ENDP

STC ; condición de fallo


284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

; ------------ Esperar interrupción de disquete y volver de nuevo al CALL fdc_write ; GAP de lectura/escritura

; modo NO DMA (lo que inhibe interrupción disquete). MOV AL,128

CALL fdc_write ; tamaño sector si longitud=0

espera_int PROC CLD

STI MOV AL,sector_fin

XPUSH <AX, CX> SUB AL,sector_ini

XPUSH <DS, 40h> INC AL

POP DS XOR AH,AH ; AX = nº de sectores

MOV AH,0FFh MUL bsector

esperar_int: 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] espera_exec: IN AL,DX

INC AH TEST AL,80h ; ¿alcanzada fase ejecución?

CMP AH,37 ; no esperar más de 2 segundos JZ espera_exec

JA fin_espera ; timeout CMP orden,F_WRITE

mira_int: TEST BYTE PTR DS:[3Eh],80h JE fdc_wr_sect

JZ esperar_int fdc_rd_sect: IN AL,DX

fin_espera: AND BYTE PTR DS:[3Eh],127 ; resetear flag TEST AL,80h ; ¿listo para E/S?

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 ; ES:[DI++] <-- AL

MOV DX,3F2h LOOP fdc_rd_sect ; repetir hasta fin sector(es)

OUT DX,AL JMP sect_io_fin

XPOP <CX, AX> fdc_wr_sect: IN AL,DX

RET TEST AL,80h ; ¿listo para E/S?

espera_int ENDP JZ fdc_wr_sect

TEST AL,64

; ------------ Cargar o escribir CX sector(es) del disco en ES:DI, JNZ sector_io_ko ; fallo en escritura

; 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 ; hasta acabar sector(es)

sect_io_fin: MOV CX,7

sector_io PROC sect_io_rx: CALL fdc_read ; leyendo resultados del éxito

XPUSH <AX, BX, CX, DX, DI> LOOP sect_io_rx

MOV AL,orden STI ; ...fin de la fase crítica

CLI POP CX ; «sacar» DI sin cambiarlo

CALL fdc_write ; comando leer/escribir del 765 CLC ; indicar éxito

JNZ io_proc JMP sector_io_fin

JMP sector_io_ko sector_io_ko: MOV DX,3F4h ; leer resultados del fallo

io_proc: MOV AL,byte1 kill_info: IN AL,DX

CALL fdc_write ; enviar HD, US1, US0 TEST AL,80h ; ¿listo para E/S?

MOV AL,cilindro JZ kill_info

CALL fdc_write ; enviar cilindro TEST AL,64

MOV AL,cabezal JZ info_killed ; el 765 no devuelve datos

CALL fdc_write ; enviar cabezal INC DX ; apuntar al registro de datos

MOV AL,sector_ini IN AL,DX ; leer byte de resultados

CALL fdc_write ; enviar nº sector DEC DX

MOV AL,tsector JMP kill_info

CALL fdc_write ; longitud sector info_killed: STI

MOV AL,sector_fin POP DI ; anular cambio de DI

CALL fdc_write ; último sector STC ; indicar fallo

MOV AL,gap sector_io_fin: XPOP <DX, CX, BX, AX>


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

RET POP AX

sector_io ENDP POP DS

RET

; ------------ Recibir byte del FDC en AL. A la vuelta, ZF = 1 si retardo ENDP

; la operación fracasó (el FDC no estaba listo).

fdc_read PROC

PUSH CX

PUSH DX

MOV DX,3F4h ; registro de estado del FDC

XOR CX,CX ; evitar cuelgue total si falla

espera_rd: 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

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

espera_wr: 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 ; ... cambie lo suficiente

POP CX
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

; ************ Mensajes relleno DB ? ; byte de relleno (al escribir)

cls_txt DB 10,10,10,10,10,10,10,10,10,10,10,10 buffer EQU $ ; para leer/escribir sector

DB 10,10,10,10,10,10,10,10,10,10,10,10,13,"$"

fdc_test ENDS

opciones_txt DB "765NODMA.ASM 2.0 - Acceso a disquete sin DMA." END main

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," ESC-Salir"

DB 13,10,10," Elige una opción: $"

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," Hasta luego. ",13,10,"$"

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: $"

tdisco_txt DB 13,10,"Disquete 40 pistas en unidad de 80: "

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


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
│ 5¼-DD │ 1 │ 3 │
elevadísimo porcentaje de veces el funcionamiento y la portabilidad
│ 5¼-HD │ 2 │ 3 │ serán satisfactorios.
│ 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 │ │

son mucho más rápidos que los creados por el │ Volume in drive B is unlabeled Serial number is 2FE6:7632 │

│ 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, │ │

etc): emplear el FORMAT actual no es de │ 512 bytes en cada unidad de asignación │

conservadores sino de no informados. 2M │ 3735 total de unidades de asignación en el disco │

│ │
(abreviatura de 2 megas, aunque no se alcanza esa 3735 unidades de asignación disponibles en disco

│ │
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


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

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 15,16,17,18,19

┌─────────────────────────────┬──────────────────────────────────┬────────┐ │

│ Ensamblador │ Comentario │ Offset │ │ InfpX DB 11, 40 ; nº sectores / GAP de formateo

├─────────────────────────────┴──────────────────────────────────┴────────┤ │

│ JMP SHORT BootP ; 2 bytes 0 │ │ DB 3 ; tamaño

│ NOP ; 1 byte 2 │ │

│ DB "2M-STV08" ; ID sistema 3 │ │ DB 1, 2 ; desplazamiento numeración

│ DW 512 ; bytes/sector 11 │ │

│ DB 1 ; sectores por cluster 13 │ │ InfTm DB 3,3,3,3,3,3 ; tamaño sector 1, 2, 3,...

│ DW 1 ; sectores reservados al principio 14 │ │

│ DB 2 ; nº copias de la FAT 16 │ │ DB 3,3,3,3,3

│ DW 224 ; entradas al directorio raíz 17 │ │

│ DW 3608 ; nº total de sectores del disco 19 │ │ BootP ... ; programa del sector de

│ 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 │ SECTOR DE ARRANQUE DE UN DISQUETE 2M


│ DD 0 ; sectores especiales reservados 28 │ DE 3½ A 1.80M
│ 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 │
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.
más sectores en la primera pista física. Detras de este área está el

El sector de arranque de los nuevos


disquetes es en principio similar al de
cualquier otro disco, pero contiene más
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

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 │

│ Ensamblador │ Comentario │ DW InfpX ; T2: información demás pistas 72 │

│ Offset │ │ 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 │

│ JMP SHORT BootP ; 2 bytes │ Infp0 DB 19, 70 ; nº sectores / GAP de formateo │

0 │ │ DB 1,2,3,4,5,6,7,8 ; sectores ordenados (20..23 no existen) │

│ NOP ; 1 byte │ DB 9,10,11,12,13,14 │

2 │ │ DB 15,16,17,18,19 │

│ DB "2M-STV04" ; ID sistema │ InfpX DB 64, 3 ; nº sectores / GAP de formateo │

3 │ │ DB 7 ; nº sectores a renumerar │

│ DW 512 ; bytes/sector │ DB 128+1, 4, 4 ; tabla de renumeración formateo: │

11 │ │ DB 128+12, 1, 4 ; nº sector, nuevo número, tamaño │

│ DB 1 ; sectores por cluster │ DB 128+23, 5, 4 │

13 │ │ DB 128+34, 2, 4 │

│ DW 1 ; sectores reservados al │ DB 128+45, 6, 3 │

principio 14 │ │ DB 128+51, 3, 4 │

│ DB 2 ; nº copias de la FAT │ DB 128+62, 7, 2 │

16 │ │ InfTm DB 4,4,4,4,4,3,2 ; tamaño sector 1, 2, 3,... │

│ DW 224 ; entradas al directorio raíz │ BootP:... ; programa del sector de arranque │

17 │ └─────────────────────────────────────────────────────────────────────────┘

│ DW 3772 ; nº total de sectores del SECTOR DE ARRANQUE DE UN DISQUETE 2M DE 3½ A 1.88M


disco 19 │

│ DB 0F0h ; byte descriptor de medio

21 │

│ DW 11 ; sectores ocupados por la FAT

22 │

│ DW 23 ; 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 4B368A0Eh ; número de serie (aleatorio)

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 1 ; a 1 si escribir al formatear

65 │

│ DB 0 ; velocidad transferencia pista


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 ┌───────────────────────────────────────┐

permitido hará que 2M falle en │ GAPs y /X e /Y probados en 2MF /F │

la comprobación del checksum ├─────────┬─────────┬─────────┬─────────┤

la primera vez que el disco es │ 5¼-DD │ 5¼-HD │ 3½-DD │ 3½-HD │

┌──────────────────────────────────────────────────┼─────────┼─────────┼─────────┼─────────┤
introducido en la unidad; en
│ GAP mínimo de lectura soportado en las pruebas │ │ │ │ │
este caso INT 13h
1 2 1 2

│ 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 │


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

└─────────────────────────────────────

─────────────┴─────────┴─────────┴────

─────┴─────────┘

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 │ Bytes netos obtenidos por los principales formateadores │

│ formatear (con 82 pistas y en ├─────────────────────┬─────────────────────┬─────────────────────┬─────────────────────┤

│ 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) │ 368.640 (360K) │ 839.680 (820K) │ 839.680 (820K) │ 923.648 (902K) │

│ 5¼-HD │ 1.708.224 bytes (1,63 Mb) │ 1.228.800 (1200K) │ 1.511.424 (1476K) │ 1.511.424 (1476K) │ 1.595.392 (1558K) │

│ 3½-DD │ 1.230.000 bytes (1,17 Mb) │ 737.280 (720K) │ 839.680 (820K) │ 1.007.616 (984K) │ 1.091.584 (1066K) │

│ 3½-HD │ 2.050.000 bytes (1,96 Mb) │ 1.474.560 (1440K) │ 1.763.328 (1722K) │ 1.847.296 (1804K) │ 1.931.264 (1886K) │

└───────┴────────────────────────────────┴─────────────────────┴─────────────────────┴─────────────────────┴─────────────────────┘

(*) 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
│ [ 235.71] 17.86 11 1024 ( 3) 0 1

0x04 0x00 0x00 │

┌──────────────────────────────────────────────────────────────────────────────────┐ │ [ 253.71] 18.00 1 1024 ( 3) 0 1

│ Longitud (ms) Sector Tamaño Cilindro Cabeza ST0 ST1 ST2 │ 0x04 0x00 0x00 │

│ ──────────────────- ────── ──────────── ──────── ────── ───── ───── ───── │ │ [ 271.57] 17.86 2 1024 ( 3) 0 1

│ [ 19.58] 19.58 10 1024 ( 3) 0 1 0x04 0x00 0x00 │ 0x04 0x00 0x00 │

│ [ 37.44] 17.86 11 1024 ( 3) 0 1 0x04 0x00 0x00 │ │ [ 289.44] 17.87 3 1024 ( 3) 0 1

│ [ 55.31] 17.87 1 1024 ( 3) 0 1 0x04 0x00 0x00 │ 0x04 0x00 0x00 │

│ [ 73.18] 17.87 2 1024 ( 3) 0 1 0x04 0x00 0x00 │ │ [ 307.43] 17.99 4 1024 ( 3) 0 1

│ [ 91.05] 17.87 3 1024 ( 3) 0 1 0x04 0x00 0x00 │ 0x04 0x00 0x00 │

│ [ 108.91] 17.86 4 1024 ( 3) 0 1 0x04 0x00 0x00 │ │ [ 325.43] 17.99 5 1024 ( 3) 0 1

│ [ 126.79] 17.87 5 1024 ( 3) 0 1 0x04 0x00 0x00 │ 0x04 0x00 0x00 │

│ [ 144.65] 17.86 6 1024 ( 3) 0 1 0x04 0x00 0x00 │ │ [ 343.42] 17.99 6 1024 ( 3) 0 1

│ [ 162.52] 17.87 7 1024 ( 3) 0 1 0x04 0x00 0x00 │ 0x04 0x00 0x00 │

│ [ 180.39] 17.87 8 1024 ( 3) 0 1 0x04 0x00 0x00 │ │ [ 361.28] 17.87 7 1024 ( 3) 0 1

│ [ 198.26] 17.87 9 1024 ( 3) 0 1 0x04 0x00 0x00 │ 0x04 0x00 0x00 │

│ [ 217.85] 19.59 10 1024 ( 3) 0 1 0x04 0x00 0x00 │ │ [ 379.16] 17.87 8 1024 ( 3) 0 1


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

0x04 0x00 0x00 │ cada disquetera. A lo largo de la mayoría


│ │ del código residente, el registro SI estará
│ Una tecla para leer más ID's [ESC=salir]. │ 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
│ Longitud (ms) Sector Tamaño Cilindro Cabeza ST0 ST1 ST2 │

│ ──────────────────- ────── ──────────── ──────── ────── ───── ───── ───── │


residente y permitir su posible futura
│ [ 33.95] 33.95 3 2048 ( 4) 0 1 0x04 0x00 0x00 │
desinstalación.
│ [ 45.32] 11.37 7 512 ( 2) 0 1 0x04 0x00 0x00 │

│ [ 79.14] 33.82 4 2048 ( 4) 0 1 0x04 0x00 0x00 │ La rutina que controla la INT 13h ó
│ [ 112.94] 33.80 1 2048 ( 4) 0 1 0x04 0x00 0x00 │ INT 40h es más importante. Su labor
│ [ 146.76] 33.82 5 2048 ( 4) 0 1 0x04 0x00 0x00 │ consiste en pasar el control de las funciones
│ [ 180.58] 33.82 2 2048 ( 4) 0 1 0x04 0x00 0x00 │ 2 (lectura), 3 (escritura), 4 (verificación) y 5
│ [ 198.97] 18.39 6 1024 ( 3) 0 1 0x04 0x00 0x00 │ (formateo) a 2M (si el disquete introducido
│ [ 232.78] 33.82 3 2048 ( 4) 0 1 0x04 0x00 0x00 │ es de este tipo) o a la interrupción original
│ [ 244.16] 11.37 7 512 ( 2) 0 1 0x04 0x00 0x00 │
(si el disquete introducido no es de tipo
│ [ 277.97] 33.81 4 2048 ( 4) 0 1 0x04 0x00 0x00 │
2M). Existe una variable por cada unidad
│ │
[ 311.78] 33.81 1 2048 ( 4) 0 1 0x04 0x00 0x00
que indica en todo momento si el disquete
│ │
introducido es de tipo 2M
[ 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 │


(control2m_flag=ON) o no. Otro cometido
│ [ 431.62] 33.82 3 2048 ( 4) 0 1 0x04 0x00 0x00 │
consiste en detectar los cambios de disco,
│ [ 443.00] 11.38 7 512 ( 2) 0 1 0x04 0x00 0x00 │
para actualizar dicha variable en
│ [ 476.95] 33.95 4 2048 ( 4) 0 1 0x04 0x00 0x00 │ consecuencia. Ante el primer cambio de
│ [ 510.75] 33.81 1 2048 ( 4) 0 1 0x04 0x00 0x00 │ disco detectado se retorna con un error 6
│ [ 544.57] 33.82 5 2048 ( 4) 0 1 0x04 0x00 0x00 │ (porque así lo hace la BIOS original).
│ [ 578.40] 33.83 2 2048 ( 4) 0 1 0x04 0x00 0x00 │

│ [ 596.79] 18.38 6 1024 ( 3) 0 1 0x04 0x00 0x00 │

│ │

│ 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 │ Longitud (ms) Sector Tamaño Cilindro Cabeza ST0 ST1 ST2 │

disco. En caso de que se haya producido (o │ ──────────────────- ────── ──────────── ──────── ────── ───── ───── ───── │

la primera vez absoluta que se ejecuta la │ [ 31.72] 31.72 2 1024 ( 3) 0 1 0x05 0x00 0x00 │

rutina tras haber instalado 2M en memoria) │ [ 63.27] 31.55 3 1024 ( 3) 0 1 0x05 0x00 0x00 │

se intenta leer el sector de arranque del │ [ 103.25] 39.98 4 1024 ( 3) 0 1 0x05 0x00 0x00 │

│ │
mismo para determinar la densidad del
[ 134.76] 31.51 5 1024 ( 3) 0 1 0x05 0x00 0x00

│ [ 166.35] 31.59 1 1024 ( 3) 0 1 0x05 0x00 0x00 │


mismo y averiguar si es de tipo 2M. Primero │ [ 197.98] 31.63 2 1024 ( 3) 0 1 0x05 0x00 0x00 │
se intenta bajar la línea de cambio de disco: │ [ 229.53] 31.55 3 1024 ( 3) 0 1 0x05 0x00 0x00 │
si no fuera posible, es que la unidad está sin │ [ 269.51] 39.98 4 1024 ( 3) 0 1 0x05 0x00 0x00 │
disquete introducido. El acceso se intenta │ [ 301.01] 31.50 5 1024 ( 3) 0 1 0x05 0x00 0x00 │
tres veces, con todas las densidades posibles │ [ 332.61] 31.60 1 1024 ( 3) 0 1 0x05 0x00 0x00 │

(500, 300, 250 Kbit/seg y finalmente 1 │ [ 364.24] 31.63 2 1024 ( 3) 0 1 0x05 0x00 0x00 │

Mbps). Si no se pudiera leer el sector de │ [ 395.79] 31.55 3 1024 ( 3) 0 1 0x05 0x00 0x00 │

arranque, podría deberse a que es un disco │ [ 435.77] 39.98 4 1024 ( 3) 0 1 0x05 0x00 0x00 │

sin formatear, o tratarse de otro medio │ [ 467.27] 31.50 5 1024 ( 3) 0 1 0x05 0x00 0x00 │

físico, por lo que se le devuelve el control a │ [ 498.86] 31.59 1 1024 ( 3) 0 1 0x05 0x00 0x00 │

la INT 13h original hasta un futuro nuevo │ [ 530.59] 31.72 2 1024 ( 3) 0 1 0x05 0x00 0x00 │

│ │
cambio de disco. Esto mismo puede suceder [ 562.13] 31.54 3 1024 ( 3) 0 1 0x05 0x00 0x00

│ │
si se consigue leer el sector de arranque y la
[ 602.12] 39.99 4 1024 ( 3) 0 1 0x05 0x00 0x00

│ [ 633.62] 31.50 5 1024 ( 3) 0 1 0x05 0x00 0x00 │


rutina set_info comprueba que el disco es │ [ 665.22] 31.60 1 1024 ( 3) 0 1 0x05 0x00 0x00 │
estándar del DOS. Cuando no hay disco en │ [ 696.85] 31.63 2 1024 ( 3) 0 1 0x05 0x00 0x00 │
la unidad y se falla al bajar la línea de │ │
cambio, se delega el control a la BIOS pero │ Una tecla para leer más ID's [ESC=salir]. │

si ésta logra bajarla └──────────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────────┐

│ 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 <Nadie> │

│ MAPAMEM 2.1 │ │ Sistema A000-D3B4 211.792 0008 │

│ - Información sobre la memoria del sistema. │ │ Sistema D3B6-D3C2 208 D3B6 │

│ │ │ Sistema D3C4-D50D 5.280 D3C4 │

│ Tipo Ubicación Tamaño PID Propietario │ │ 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 4DOS │ │ Libre E674-E68C 400 0000 <Nadie> │
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

│ Programa E68E-E810 6.192 E68E SHARE │ que lo que ha tardado la lectura desde el disco. Este proceso de
│ Programa E812-E97A 5.776 E812 PRINT │ 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 │
de golpe y ya no serán necesarios más accesos a disco si hubiera
688 EBF3 KEYBSP

│ Programa EC1F-EC77 1.424 EC1F RCLOCK │

│ Programa EC79-EDBB 5.168 EC79 2M │


que transferir también las 3 restantes, porque el sector en que
│ Programa EDBD-EDD8 448 EDBD DISKLED │
están ya se encuentra en el buffer. La escritura es algo más
│ Libre EDDA-EDF3 416 0000 <Nadie> │
compleja, y hay que distinguir dos casos: por un lado, cuando
│ Programa EDF5-F281 18.640 EDF5 DATAPLUS │ hay que volcar a disco un número de secciones consecutivas
│ Programa F283-F34D 3.248 F283 HBREAK │ suficientes para completar un sector físico; por otro, cuando hay
│ Programa F34F-F354 96 F34F TDSK(D) │ que escribir una o varias secciones que no completan un sector
│ 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 <Nadie> │ 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.

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 │ [14464/109040] C:\>dir b: │

información del sector de arranque del │ │


│ │
disquete que fue anotada cuando se le Volume in drive B is unlabeled Serial number is EA82:3F1B
│ File not found "B:\*.*" │
reconoció por vez primera.
│ 0 bytes in 0 file(s) │
│ 1.912.320 bytes free │
La rutina motor_ok arranca el │ │
motor de la unidad si aún no estaba en │ [14464/109040] C:\>diskcopy b: b: │
marcha. En caso de estar parado, o de llevar │ │
poco tiempo encendido a causa de una │ Inserte el disquete de ORIGEN en la unidad B: │
reciente lectura de la línea de cambio │ │
│ 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.
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

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?.

;┌───────────────────────────────────────────────────────────────────┐ ;│ SUPERBOOT -> Indica que el código ejecutable se ensambla para │

;│ │ ;│ ocupar 2560 bytes exactamente (para autoarranque). │

;│ █████ █ █ █ █ █▀▀▀▀ █▀▀▄ █ █ █▀▀▀▀ █ │ ;│ │

;│ █ ██ ██ █ █ █ █ █ ██ █ █ █ │ ;└───────────────────────────────────────────────────────────────────┘

;│ █████ █ █ █ ██▄ █████ ████ █ █ █ █████ █ │

;│ █ █ █ █ █ █ █ █ █ ██ █ █ │

;│ █████ █ █ █ █ █▄▄▄▄ █ █ █ █ █▄▄▄▄ █████ │

;│ │

;│ 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. │

;│ │
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

; ------------ Códigos de modos y órdenes del DMA y del FDC. ; --- Interpretación BIOS de los bits de ST1

F_READ EQU 46h ; modo DMA para lectura lista_errs DB 4 ; 'sector not found'

F_WRITE EQU 4Ah ; modo DMA para escritura 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

; ------------ Estructura de datos con información para cada unidad. DB 4 ; 'sector not found'

DB 3 ; 'write-protect error'

info_drv STRUC DB 2 ; 'address mark not found'

maxs EQU 13 ; máximo 13 sectores físicos/pista DB 20h ; en otro caso: 'bad NEC'

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 ; * C O D I G O R E S I D E N T E *

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 ; ------------ Rutina de gestión de INT 2Fh.

tabla_tsect DB maxs DUP (?) ; tamaños de sectores 1, 2, ..., N

tam_fat DB ? ; sectores/FAT en la unidad IFNDEF SUPERBOOT ; Código SuperBOOT no soporta INT 2Fh

ENDS

ges_int2F PROC FAR

; ------------ Variables del programa. STI

CMP AH,CS:multiplex_id

info_ptr DW info_A ; punteros a datos de las unidades JE preguntan

DW info_B JMP CS:ant_int2F ; saltar al gestor de INT 2Fh

IFDEF SUPERBOOT preguntan: CMP DI,1992h

DB "30" ; Versión 2MFBOOT 3.0 JNE ret_no_info ; no llama alguien del convenio

ENDIF MOV AX,ES

id_sistema DB "2M-STV" ; identificación de disco 2M CMP AX,1492h

IFDEF XT JNE ret_no_info ; no llama alguien del convenio

tbase DW ? ; base de tiempos para retardos PUSH CS

ENDIF POP ES ; sí llama: darle información

unidad DB ? ; unidad física de disco en curso LEA DI,autor_nom_ver

numsect DW ? ; sectores a transferir ret_no_info: MOV AX,0FFFFh ; "entrada multiplex en uso"

sectini DW ? ; primer sector DOS a transferir IRET

cilindro DB ? ; cilindro del disco a acceder ges_int2F ENDP

cabezal DB ? ; cabezal a emplear

sector DB ? ; número de sector físico ENDIF

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 IFNDEF SUPERBOOT

orden DB ? ; operación F_READ/F_WRITE/F_VERIFY

tab_ordenes DB F_READ ges_int13 PROC FAR

DB F_WRITE STI

DB F_VERIFY ; órdenes 2, 3 y 4 CLD

PUSHF
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

CMP DL,2 MOV unidad,DL

JAE ges13bios ; no es disquetera A: ó B: CALL set_SI_drv

PUSH SI MOV cilindro,CH

CALL set_SI_drv MOV cabezal,DH

CMP CS:[SI].tipo_drv,2 ; ¿unidad 1.2M? OR CH,DH

JE ges_2m JNZ format_trx ; no es cilindro 0 y cabezal 0

CMP CS:[SI].tipo_drv,4 ; ¿unidad 1.44/2.88M? INC CL

ges_2m: POP SI JNZ no_f_chg

JC ges13bios ; no es unidad de alta densidad MOV [SI].cambio,ON ; simular cambio de disco

CMP AH,2 JMP fmt_exit

JB ges13bios ; no Read/Write/Verify/Format no_f_chg: XPUSHA

CMP AH,5 MOV AL,1 ; formatear (AH=5)

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 PUSHF

POPF CALL ant_int13 ; avisar al DOS del nuevo disco

STC ; hubo cambio: CLD ; mantener DF=0

MOV AX,600h STC

RET 2 ; retornar con error CALL reset_drv ; asegurar aceleración motor

sin_cambio: CMP AH,5 XPOPA

JNE dilucida ; no es orden de formateo CALL set_info ; características nuevo soporte

CALL leer_lin_camb format_trx: CALL genera_info ; tabla de información formateo

JNZ format_bios ; no hay disquete en la unidad CALL motor_ok ; asegurar que está en marcha

CMP AL,7Fh CALL seek_drv

JNE format_bios ; no es orden formateo de 2M CALL formatea_pista

CMP SI,"2M" PUSHF

JE format_2m ; es orden de formateo de 2M CLC

format_bios: CLC CALL motor_off_cnt ; cuenta normal detención motor

CALL set_flag_STV ; CF = 0 -> indicar no 2M POPF

dilucida: PUSH SI CALL set_err

CALL set_SI_drv ; SI -> variables de la unidad CALL set_bios_err ; no altera flags

CMP CS:[SI].control2m_flag,OFF fmt_exit: XPOPA ; **

POP SI MOV AH,status

JE ges13bios ; la unidad la controla la BIOS POP DS ; *

POPF RET 2

CALL control2m ; la controla 2M ges_int13 ENDP

RET 2

ges13bios: POPF ELSE ; El código SuperBOOT no formatea

JMP CS:ant_int13 ; saltar al gestor de INT 13h

ges_int13 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 ; no es disquetera A: ó B:

; 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 ; ¿unidad 1.2M?

; 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 ; ¿unidad 1.44/2.88M?

; los TSR previos del cambio de soporte. ges_2m: JC ges13bios ; no es unidad de alta densidad

; El intento de formateo en la pista/cabezal 0 con CMP AH,2

; CL=255 sirve para simular un cambio de disco. JB ges13bios ; no Read/Write/Verify/Format

CMP AH,5

format_2m: POPF JA ges13bios ; no Read/Write/Verify/Format

PUSH DS ; * JNE no_format

XPUSHA ; ** CALL set_flag_STV ; CF = 0 -> "disco no 2M"

PUSH CS JMP ges13bios

POP DS no_format: CALL detecta_cambio ; ¿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

POPF ; variable que indica lo que resta para detenerlo

STC ; hubo cambio: ; es llevada a su valor normal, por lo que el disco no

MOV AX,600h ; tardará mucho en detenerse (incluso sin quizá haber

RET 2 ; retornar con error ; acelerado aún). En la práctica, invocando esta rutina

dilucida: CMP CS:[SI].control2m_flag,OFF ; desde INT 13h nunca será necesario arrancar el motor

JE ges13bios ; la unidad la controla la BIOS ; ya que el DOS ejecuta antes la función equivalente,

POP SI ; la 16h, que lo pone en marcha. Es simplemente una

POPF ; medida de seguridad contra las BIOS «de marca».

CALL control2m ; la controla 2M

RET 2 leer_lin_camb PROC

ges13bios: POP SI XPUSHA ; *

POPF PUSH DS

JMP CS:ant_int13 ; saltar al gestor de INT 13h DDS

ges_int13 ENDP MOV AL,1

MOV CL,DL

ENDIF SHL AL,CL ; bit de motor en 0..3

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 rodando: MOV AH,DL

PUSHF XSHL AH,4

PUSH BX OR AH,AL ; AH = byte BIOS

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

POPF MOV DS:[3Fh],AH ; actualizar variable BIOS

RET OUT DX,AL ; arrancado motor en la unidad

set_SI_drv ENDP ADD DX,5

DELAY

; ------------ Si CF=1, indicar disquete 2M presente. A la IN AL,DX ; leer línea de cambio de disco

; entrada, DL indica la unidad de disco. STI

TEST AL,80h ; ZF=0 -> cambio de disco

set_flag_STV PROC POP DS

XPUSHA XPOPA ; *

CALL set_SI_drv RET

MOV AL,ON ; indicar 2M leer_lin_camb ENDP

JC tipo_stv_ok

MOV AL,OFF ; indicar no 2M ; ------------ Determinar si ha habido cambio de disco y, en ese caso,

tipo_stv_ok: MOV CS:[SI].control2m_flag,AL ; 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

set_flag_STV ENDP ; o no (esta variable está a ON tras instalar 2M para

; forzar la detección del tipo de disco introducido; se

; ------------ Devolver ZF=1 si cilindro y cabezal 0. ; 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

pista0? PROC ; sí lo lograra -forzando así una detección posterior-).

PUSH AX

MOV AL,cabezal detecta_cambio PROC

OR AL,cilindro XPUSHA ; *

POP AX CALL set_SI_drv ; SI -> variables de la unidad

RET CMP CS:[SI].cambio,ON ; ¿cambio de soporte?

pista0? ENDP MOV CS:[SI].cambio,OFF

JE hubo_cambio

; ------------ Devolver ZF=1 si la línea de cambio de disco está CALL leer_lin_camb ; leer línea de cambio de disco
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

JNZ hubo_cambio INC AX ; próxima velocidad

XPOPA CMP AL,3

CLC ; no hay cambio de disco JA otro_intento

RET MOV [SI].vunidad0,AL

hubo_cambio: CLC JMP intenta_io ; probar otra velocidad

CALL set_flag_STV ; CF = 0 -> supuesto no 2M otro_intento: MOV [SI].vunidad0,0

XPUSH <DS, ES> ; ** POP CX

MOV BX,90h LOOP intenta_io0 ; reintento

ADD BL,DL fin_detecta_c: STC ; indicar cambio de disco

DDS fin_detecta: XPOP <ES, DS> ; **

AND BYTE PTR [BX],255-16 ; densidad no determinada XPOPA ; *

XPUSH <CS, CS> RET

XPOP <DS, ES> detecta_cambio ENDP

MOV unidad,DL

STC ; 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 ; bajar línea cambio de disco ; no se da cuenta del cambio de disco y conviene ayudar).

DEC cilindro

CALL seek_drv set_info PROC

CLC XPUSHA

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 ; no es disco 2M

JZ disco_dentro ; se pudo: hay disco dentro MOV [SI].chk,AL ; anotar checksum

MOV [SI].cambio,ON ; futura detección tipo disco MOV [SI].version_fmt,CL ; y versión del formato

CLC ; NO indicar cambio de disco... MOV DL,unidad

JMP fin_detecta ; ...para pasar control a BIOS STC

disco_dentro: PUSH DS CALL set_flag_STV ; CF = 1 -> indicar disco 2M

DDS MOV AL,ES:[BX+22] ; tamaño de FAT

MOV BYTE PTR DS:[41h],6 ; error 'media changed' MOV [SI].tam_fat,AL

POP DS MOV CL,ES:[BX+65] ; CL a 0 si acceso multi-sector

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 ; velocidad pista 0 / demás

ENDIF MOV AL,ES:[BX+24]

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] ; GAP de formateo

intenta_io0: PUSH CX MOV AH,AL

CMP CX,2 ; CF=1 la 3ª vez (a 0 si CX<>1) 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 ; empezar con 500 Kbit/seg. ADD AH,190

intenta_io: MOV AL,0 MOV AL,11

MOV cilindro,AL MUL AH ; AX = (190+GAP)*11

MOV cabezal,AL SUB AX,2048+62

MOV sector,1 ; sector de arranque gap_rw_ok: SHR AL,1 ; GAP R/W para /M

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 ; es otra densidad de disco genera_ts: MOV AL,ES:[DI]

POP CX MOV [BX],AL

MOV BX,buffer INC BX

CALL set_info ; características nuevo soporte INC DI

CLC LOOP genera_ts ; información estructura pistas

JMP fin_detecta_c ; indicar cambio de disco set_info_exit: MOV AL,[SI].vunidad0

otra_densidad: MOV AL,[SI].vunidad0 IFDEF XT


284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

MOV CL,6 AND AL,10110111b ; aislar condiciones de test

SHL AL,CL LEA BX,lista_errs

ELSE MOV CX,9

XSHL AL,6 ; velocidad en bits 7:6 busca_err: MOV AH,[BX] ; código de error BIOS

ENDIF SHL AL,1

OR AL,00010111b ; establecido otro medio físico JC err_ok ; es ese error

CMP [SI].tipo_drv,2 INC BX

JA modo_ok ; es unidad de 3½ LOOP busca_err ; buscar otro error

AND AL,11111000b err_ok: OR status,AH

OR AL,00000101b ; 1.2 en 1.2 err_retc: STC ; condición de error

TEST AL,01000000b err_ret: XPOPA

JZ modo_ok RET

XOR AL,00100001b ; 360 en 1.2 y seek * 2 set_err ENDP

modo_ok: PUSH DS

MOV BX,90h ; ------------ Actualizar variables de error de la BIOS.

ADD BL,unidad

DDS set_bios_err PROC

AND BYTE PTR DS:[BX],8 ; respetar bit de 2.88M PUSHF ; *

OR DS:[BX],AL ; actualizar variable BIOS XPUSHA ; **

POP DS ; con el tipo de densidad PUSH ES ; ***

XPOPA DES

RET MOV DI,41h ; bytes de resultados del 765

set_info 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 ; *

RET

calc_chk PROC set_bios_err ENDP

XPUSH <SI, DI>

LEA DI,[BX+3] ; DI=BX+3 ; ------------ Realizar lecturas, escrituras y verificaciones: rutina

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 ; comparar identificación ; tres fases: el fragmento anterior a la FAT2, la zona

STC ; correspondiente a la FAT2 (se ignora la escritura y se

JNE chk_ret ; el disco no es 2M ; simula su lectura leyendo la FAT1) y un último bloque

XOR AX,AX ; ubicado tras la FAT2. El sector de arranque es emulado

MOV CL,ES:[BX+64] ; versión del formateador ; empleando el primer sector físico de la FAT2 (aunque en

CMP CL,6 ; los discos de versión de formato anterior a la 7 se usa

JB chk_ok ; no usaba este checksum ; el sector de arranque verdadero -permitiendo escribirlo

MOV DI,ES:[BX+68] ; sólo si es válido-). En cualquier caso, si el número de

chk_sum: 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

chk_ok: CLC ; se traducen a las unidades del DOS por mayor comodidad.

chk_ret: XPOP <DI, SI>

RET control2m PROC

calc_chk ENDP PUSH DS ; *

XPUSHA ; **

; ------------ Determinar el tipo de error producido en el acceso. PUSH CS

POP DS

set_err 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


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

chk_valido: PUSH AX ; *** CMP orden,F_WRITE

MOV AH,0 JNE emula_fat1

MOV numsect,AX ; nº sectores IFDEF XT

MOV AL,CH ; cilindro XCHG CH,CL

SHL AL,1 SHL CH,1

MOV DL,DH ELSE

AND DH,01111111b XSHL CX,9 ; CX = CX * 512

ADD AL,DH ; cabezal físico ENDIF

MUL [SI].sectpista ADD DI,CX ; ES:DI actualizado

ADD AL,CL ; sector JMP acceso_final

ADC AH,0 emula_fat1: MOV DL,[SI].tam_fat

DEC AX ; AX = nº sector DOS MOV DH,0

MOV sectini,AX ; 0FFFFh si sector 0 (error) SUB AX,DX ; leer de FAT1 y no de la FAT2

MOV DI,BX ; ES:DI -> dirección CALL ejecuta_io ; CX sectores desde AX

POP BX ; *** JNE fin_ctrl ; error

MOV BL,BH acceso_final: CMP numsect,0

MOV BH,0 JE fin_ctrl ; fin de la transferencia

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 fin_ctrl: CLC

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 exit_2m_ctrl: XPOPA ; **

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 ; resultado correcto (CF=0)

CALL ejecuta_io ; el primer sector FAT2 STC ; error

JNE fin_ctrl MOV AL,0 ; 0 sectores movidos

boot_fin_op: DEC numsect st_ok: RET

INC sectini calc_iop: SUB CX,AX

MOV AX,sectini INC CX ; CX sectores

JMP io_emula CMP CX,numsect

boot_real: CMP orden,F_WRITE JBE nsect_ok

JNE io_emula MOV CX,numsect ; sólo quedan CX

MOV BX,DI ; BOOT de 2M 1.3 y anteriores nsect_ok: SUB numsect,CX

CALL calc_chk ADD sectini,CX

JC si_skip ; no es de tipo 2M RET

AND AL,AL control2m ENDP

JZ io_emula ; lo es y con checksum correcto

si_skip: ADD DI,512 ; ------------ A la entrada, AX indica el sector inicial (coordenadas

JMP boot_fin_op ; impedir estropicio de BOOT ; del DOS) y CX el número de sectores a procesar.

io_emula: MOV CL,[SI].tam_fat ; * Definiciones: «Sector físico» es un sector del disco

MOV CH,0 ; CX = primer sector FAT2 - 1 ; de 512, 1024 ó 2048 bytes (números de sector del 1 al N

CMP AX,CX ; en la pista). Este sector físico está dividido en

JA en_fat2? ; ¿la operación afecta a FAT2? ; «secciones» de 512 bytes, constando por tanto de 1, 2 ó

CALL 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 ; fin de la transferencia ; cabezal y cilindro 0 (con un formato más convencional

en_fat2?: 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 ; CX = último sector FAT2 ; * 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)
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 ; en las demás pistas

; 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 s_xx: 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 direct_acceso: CALL 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 ; las que restan (AH = 0)

; cargarlo al buffer (realizar una prelectura), JMP multi_proc

; actualizar la sección correspondiente y volverlo a decide_multi: 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 ; no acceso a inicio sector

; última sección); en los dos formatos de disco se hace JC fin_io

; la operación multisector en la primera pista. Las multi_acc: CMP secciones,AH ; AH = 0

; 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 ; AH = 0

DIV CL

ejecuta_io PROC AND AL,AL

MOV BX,AX ; AX = sector DOS inicial JZ io_pasos ; no quedan sectores enteros

CMP AH,0FFh MOV secciones,AH ; las que restan

JE no_cabe ; (acceso a sector BIOS 0) multi_proc: CALL acceso_multi ; de AL sectores

MOV secciones,CL ; CX sectores (CL realmente) JC fin_io

DIV [SI].sectpista io_pasos: CMP secciones,0

INC AH ; numerado desde 1... JE fin_io ; no restan secciones finales

MOV sector,AH ; ...el resto es el sector CALL acceso_secc

SHR AL,1 JNC io_pasos

MOV cilindro,AL ; cilindro fin_io: CMP status,0 ; ZF = 1 -> operación correcta

RCL AL,1 RET

AND AL,1

MOV cabezal,AL ; cabezal acceso_secc: PUSH AX

MOV AL,sector CMP orden,F_WRITE ; acabar transferencia sector

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

no_cabe: MOV status,4 ; 'sector no encontrado' hay_que_leer: CALL acceso_sector ; efectuar E/S

JMP fin_io JC acc_ret ; ha habido fallo

si_cabe: MOV AL,AH ; sector en AL ya_leido: CALL trans_secc ; buffer -> memoria

CBW ; sección 0 (AH = 0) JMP acc_ret

CALL pista0? escritura: CMP seccion,0

JZ s_xx ; sector físico en pista/cara 0 JNE prelectura ; sólo parte del sector cambia

LEA BX,[SI].tabla_tsect-1 CALL num_secciones

DEC AX ; AH = 0 CMP secciones,AL

resta_secc: INC BX JAE escribir ; Todo el sector físico cambia

INC AH prelectura: 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...


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

CALL acceso_sector ; efectuar E/S JZ acc_mult3 ; primer sector problemático

MOV orden,F_WRITE ; ... restaurar orden original MOV AH,sector

JC acc_ret ; ha habido fallo MOV sector_ini,AH

escribir: CALL trans_secc ; memoria -> buffer ADD AH,CL

CALL acceso_sector ; volcar buffer al disco DEC AH

JMP acc_ret MOV sector_fin,AH

verificacion: PUSH BX INC AH

MOV BL,seccion SUB AL,CL

CALL num_secciones CALL acceso_sector ; sectores no problemáticos

dec_sec_veri: DEC secciones MOV sector,AH

JZ verifica JC acc_mult_fin

INC BX acc_mult3: AND AL,AL ; ahora el sector problemático

CMP BL,AL JZ acc_mult_fin

JB dec_sec_veri ADD secciones,BL ; compensar futuro decremento

verifica: POP BX CALL acceso_secc ; a través del buffer auxiliar

CALL acceso_sector ; leer para forzar verificación JC acc_mult_fin

acc_ret: PUSHF DEC AL

INC sector ; preparado para otro sector JMP acc_mult1 ; sectores que restan

MOV seccion,0 ; desde su primera sección acc_mult_fin: RET

POPF

POP AX ENDIF

RET

ejecuta_io ENDP

IFDEF SUPERBOOT ; SuperBOOT: válido el cruce del DMA

; ------------ Mover secciones desde el buffer hacia la memoria (con

acceso_multi: PUSH AX ; AL = sectores a transferir ; 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 <AX, BX, CX, SI> ; *

MOV sector_fin,AL MOV BL,seccion ; desde esta sección

INC AL CALL num_secciones ; nº secciones del sector

CALL acceso_sector ; sectores no problemáticos otra_secci: PUSH BX

MOV sector,AL IFDEF XT

acc_mult_fin: POP AX MOV BH,BL

RET SHL BH,1

MOV BL,0

ELSE ; No es SuperBOOT: Evitar cruce frontera DMA ELSE

XSHL BX,9 ; sección * 512

acceso_multi: PUSH AX ; AL = sectores a transferir ENDIF

MOV BX,ES ; desde 'sector' teniendo ADD BX,buffer ; dirección

XSHL BX,4 ; cuidado con el DMA MOV SI,BX

ADD BX,DI MOV CX,256 ; tamaño sección (palabras)

NEG BX ; BX = bytes hasta frontera DMA CALL swap_reg ; ¿intercambiar origen-destino?

CALL num_secciones REP MOVSW ; copiar 512 bytes

MOV CH,AL ; AL secciones de 512 bytes CALL swap_reg ; ¿intercambiar origen-destino?

MOV CL,0 POP BX

SHL CX,1 ; CX = bytes por sector DEC secciones ; una menos

XCHG AX,BX ; BL = secciones por sector JZ fin_secc

XOR DX,DX INC BX ; otra sección del sector

DIV CX CMP BL,AL ; ¿sector agotado?

MOV CL,AL ; CL = sectores que caben JB otra_secci ; aún no

POP AX ; AL = sectores a transferir fin_secc: XPOP <SI, CX, BX, AX> ; *

CMP AL,CL RET

JA acc_mult2 ; no hay problemas con el DMA swap_reg: CMP CS:orden,F_WRITE

acc_mult1: MOV CL,AL JE interc

acc_mult2: AND CL,CL CLC


284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

RET MOV sector_fin,0 ; no acceder a más de 1 sector

interc: XCHG SI,DI ; en escritura, invertir el PUSHF ; **1

XPUSH <ES, DS> ; sentido de la operación JMP acceso_rep ; en el futuro (por defecto)

XPOP <ES, DS> acceso_buffer: XPUSH <ES, DI>

RET PUSH CS

trans_secc ENDP POP ES

MOV DI,buffer ; acceso con buffer auxiliar

; ------------ Comprobar si el sector ya está en el buffer. MOV AL,sector ; mismo sector inicial/final

MOV sector_ini,AL

leido? PROC MOV sector_fin,AL

PUSH AX CALL sector_io

MOV AL,buf_unidad MOV sector_fin,0

CMP AL,unidad XPOP <DI, ES>

JNE no_leido ; es en otra unidad PUSHF ; **2

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 ; es en otro cilindro/cabezal JE acceso_rep ; nada leído físicamente

MOV AL,buf_sector MOV AL,unidad

CMP AL,sector acceso_anota: MOV buf_unidad,AL

JNE no_leido ; es otro sector MOV AL,cilindro

POP AX MOV AH,cabezal

RET ; está en el buffer MOV buf_cilcab,AX

no_leido: STC MOV AL,sector

POP AX MOV buf_sector,AL ; anotado el sector en buffer

RET ; sector no leído acceso_rep: POPF ; ** mucho cuidado con la pila

leido? ENDP CALL set_err ; ajustar variable «status»

acceso_fin: XPOP <BX, AX>

; ------------ Leer o escribir sector(es). Se selecciona el tamaño de RET

; sector correcto antes de llamar a sector_io. En esta acceso_sector ENDP

; rutina se actualiza la variable «status» en función de

; los posibles errores de acceso. Si sector_fin es ; ------------ Devolver el número de secciones del sector en curso.

; distinto de 0 se accede a los sectores indicados, si es

; 0 se accede sólo al sector «sector» a través del buffer num_secciones PROC

; 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 512 en cil./cab. 0

; sectores lógicos consecutivos. XPUSH <BX, CX>

LEA BX,[SI].tabla_tsect

acceso_sector PROC ADD BL,sector

XPUSH <AX, BX> ADC BH,0

CALL seek_drv ; posicionar el cabezal MOV CL,[BX-1]

JNC en_pista SUB CL,2

CMP status,0 ; ¿error ya determinado? MOV AL,1

JNE acc_fin_err SHL AL,CL

OR status,40h ; no: pues 'seek error' XPOP <CX, BX>

acc_fin_err: STC num_secc_ok: RET ; resultado en AL

JMP acceso_fin num_secciones ENDP

en_pista: CALL pista0?

MOV AL,2 ; ------------ Asegurar que el motor está en marcha.

JZ tam_acc_ok ; sectores 512 en cil./cab. 0

LEA BX,[SI].tabla_tsect motor_ok PROC

ADD BL,sector XPUSHA ; *

ADC BH,0 PUSH DS ; **

MOV AL,[BX-1] MOV BX,40h

tam_acc_ok: MOV tsector,AL PUSH BX

CMP sector_fin,0 ; ¿usar buffer intermedio? POP DS

JE acceso_buffer MOV CH,255-18 ; CH = 255 - 1 segundo

CALL sector_io CLI


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

MOV CL,CS:unidad MOV CX,50

MOV AL,1 respiro: LOOP respiro

SHL AL,CL ELSE

TEST [BX-1],AL ; ¿motor en marcha? CALL fdc_respiro ; tiempo reconocer reset en 486

JZ arrancarlo ; arrancarlo ENDIF

CMP [BX],CH ; Si encendido y acelerado... OR AL,00000100b

JBE ok_motor ; ...seguir OUT DX,AL ; fin de señal de reset

arrancarlo: MOV AH,CL CALL espera_int ; rehabilitará interrupciones

MOV CL,4 MOV AL,8

SHL AH,CL ; unidad << 4 CALL fdc_write ; comando 'leer estado int...'

OR AL,AH CALL fdc_read

MOV [BX-1],AL ; nuevo estado motores CALL fdc_read

MOV BYTE PTR [BX],255 ; asegurar que no se pare CALL envia_specify ; comando 'specify' adecuado

MOV DX,3F2h ; registro de salida digital XPOPA

ADD CL,CS:unidad RET

MOV AL,1 reset_drv ENDP

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.

STI

MOV AX,90FDh envia_specify PROC

CLC 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

ok_motor: MOV [BX],CH ; cuenta máxima detención motor MOV AL,3 ; comando 'specify'

STI ; sin forzar futura aceleración CALL fdc_write

POP DS ; ** MOV AL,0BFh ; step rate para 500 kbps

XPOPA ; * AND AH,11000000b

RET JZ spec1_ok

motor_ok ENDP MOV AL,0AFh ; step rate para 1 Mbps

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 ; step rate para 250/300 Kbps

; además a la unidad para que acelere. spec1_ok: CALL fdc_write

MOV AL,2

reset_drv PROC CALL fdc_write ; head load y modo DMA

XPUSHA POP AX

CALL motor_off_cnt ; cuenta detención motor RET

MOV CL,unidad envia_specify ENDP

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

DDS XPUSHA

CLI PUSH DS

MOV DS:[3Fh],AL MOV AL,0FFh ; valor máximo

AND BYTE PTR DS:[3Eh],70h ; bit IRQ=0 y recalibrar JC motor_off_ok

POP DS ; * IFDEF XT

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 ELSE

MOV DX,3F2h ; registro de salida digital PUSH 0

OUT DX,AL ; señal de reset POP DS

IFDEF XT ENDIF
284 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] ; byte 2 tabla base disco CALL fdc_read ; leer cilindro actual

motor_off_ok: DDS TEST AH,11000000b ; comprobar ST0

MOV BYTE PTR DS:[40h],AL ; cuenta parada motor JNZ fallo_seek

POP DS MOV AL,15 ; estabilización para escritura

XPOPA CMP orden,F_WRITE

RET JE rseek_ok

motor_off_cnt ENDP MOV AL,1 ; estabilización para lectura

rseek_ok: CBW ; AH = 0

; ------------ Llevar el cabezal a la pista indicada, recalibrando si CALL retardo ; esperar asentamiento cabezal

; hubo un reset (se invocó la función 0 de la INT 13h o seek_ok: XPOPA

; se ejecutó reset_drv) antes de esta operación. Primero CLC ; retornar con éxito

; se selecciona la velocidad de transferencia y se borra RET

; el resultado de cualquier operación anterior, para que fallo_seek: XPOPA

; todo quede listo para el próximo acceso a disco. STC ; retornar indicando fallo

RET

seek_drv PROC seek_drv 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 ; AH = 1 (A:) ó 2 (B:) set_rate PROC

PUSH DS XPUSHA

DDS CALL pista0?

TEST AH,DS:[3Eh] MOV AX,[SI].vunidad ; velocidad pista 0 / demás

POP DS JZ vel_ok

JNZ do_seek ; la unidad ya fue recalibrada MOV AL,AH

CALL recalibrar vel_ok: PUSH DS ; *

JC fallo_seek ; fallo al recalibrar DDS

do_seek: MOV BX,94h MOV AH,DS:[8Bh]

ADD BL,unidad IFDEF XT

MOV AL,cilindro MOV CL,6

PUSH DS ; * SHR AH,CL

DDS ELSE

OR DS:[3Eh],AH ; unidad ya recalibrada SHR AH,6 ; aislar bits de velocidad

MOV AH,DS:[41h] ; código de error previo ENDIF

CMP AL,[BX] CMP AL,AH

MOV [BX],AL JE vel_set ; velocidad ya seleccionada

POP DS ; * MOV DX,3F7h

JNE hacer_seek ; seek necesario OUT DX,AL ; seleccionarla

CMP AH,40h ; ¿error de seek previo? XSHL AL,6

JNE seek_ok ; no, evitar seek innecesario AND BYTE PTR DS:[8Bh],00111111b

hacer_seek: MOV AL,0Fh OR DS:[8Bh],AL

CALL fdc_write ; comando 'seek' vel_set: POP DS ; *

JC fallo_seek LEA DI,status

MOV AL,cabezal MOV CX,8

XSHL AL,2 borra_status: MOV [DI],CH ; borrar información de estado

OR AL,unidad INC DI

CALL fdc_write ; enviar HD, US1, US0 LOOP borra_status

MOV AL,cilindro XPOPA

CALL fdc_write ; enviar cilindro RET

CALL espera_int ; esperar interrupción set_rate ENDP

JC fallo_seek

MOV AL,8 ; ------------ Recalibrar la unidad (si hay error se intenta otra vez

CALL fdc_write ; comando 'leer estado int...' ; para el caso de que deba moverse más de 77 pistas).

JC fallo_seek

CALL fdc_read ; leer registro de estado 0 recalibrar PROC

JC fallo_seek XPUSHA
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

MOV BX,94h DEC CX ; bytes totales - 1

ADD BL,unidad MOV AX,ES

PUSH DS ; * CALL calc_dir_DMA ; AX:DI -> base BX y página AH

DDS IFDEF SUPERBOOT

MOV [BX],BH ; pista actual = 0 JC sector_io_ko ; chequear cruce frontera DMA

POP DS ; * ENDIF

MOV CX,2 ; dos veces como mucho MOV AL,orden ; modo DMA necesario

recalibra: MOV AL,7 CALL prepara_DMA

CALL fdc_write ; comando de 'recalibrado' CMP AL,F_WRITE

JC fallo_recal MOV AL,11000101b ; comando de escritura del FDC

MOV AL,cabezal JE orden_io_ok

XSHL AL,2 MOV AL,11100110b ; comando leer (verif.) del FDC

OR AL,unidad orden_io_ok: CALL fdc_write ; comando leer/escribir del FDC

CALL fdc_write ; enviar HD, US1, US0 JC sector_io_ko

JC fallo_recal MOV AL,cabezal

CALL espera_int ; esperar interrupción XSHL AL,2

JC fallo_recal OR AL,unidad

MOV AL,8 CALL fdc_write ; byte 1 de la orden

CALL fdc_write ; comando 'leer estado int...' MOV AL,cilindro

JC fallo_recal CALL fdc_write ; enviar cilindro

CALL fdc_read ; leer registro de estado 0 MOV AL,cabezal

JC fallo_recal CALL fdc_write ; enviar cabezal

MOV AH,AL MOV AL,sector_ini

CALL fdc_read ; leer cilindro actual CALL fdc_write ; enviar nº sector

XOR AH,00100000b ; bajar bit de 'seek end' MOV AL,tsector

TEST AH,11110000b ; comprobar resultado y ST0 CALL fdc_write ; longitud sector

JNZ fallo_recal ; sin 'seek end' o TRK0 MOV AL,sector_fin

MOV AX,1 ; pausa de 1 ms CALL fdc_write ; último sector

CALL retardo MOV AL,[SI].gap

JMP recal_ret CALL fdc_write ; GAP de lectura/escritura

fallo_recal: LOOP recalibra ; reintentar comando MOV AL,128

STC ; condición de fallo CALL fdc_write ; tamaño sector si longitud=0

recal_ret: XPOPA CALL espera_int

RET PUSHF ; *

recalibrar ENDP LEA BX,fdc_result

MOV CX,7

; ------------ Cargar o escribir sector(es) del disco en ES:DI, sect_io_res: CALL fdc_read ; 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 ; actualizar dirección

sector_io PROC CLC ; Ok

XPUSH <AX, BX, CX, DX> JMP sector_io_fin

MOV CL,tsector sector_io_ko: STC ; indicar fallo

MOV CH,0 sector_io_fin: XPOP <DX, CX, BX, AX>

STC RET

RCL CH,CL sector_io ENDP

MOV CL,0 ; nº de bytes por sector

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,

CBW ; AX sectores (AH = 0) ; aunque el código SuperBOOT devuelve error en ese caso.

MUL CX

MOV DX,AX ; bytes totales calc_dir_DMA PROC

MOV CX,AX 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

IFDEF SUPERBOOT MUL cilindro

MOV DX,CX ; comprobar cruce en SuperBOOT MOV DX,AX

ADD DX,BX POP AX

JNC dir_DMA_ok MUL cabezal

MOV status,9 ; error de frontera de DMA ADD AX,DX

ENDIF XOR DX,DX

dir_DMA_ok: POP DX MOV BL,ES:[DI]

RET MOV BH,0

calc_dir_DMA ENDP DIV BX ; DL = módulo

SUB DL,ES:[DI]

; ------------ Crear tabla con información para formatear. En ES:BX NEG DL

; está el futuro sector de arranque del disquete. MOV AL,DL

POP DX ; restaurar tamaño en DH

IFNDEF SUPERBOOT ; en SuperBOOT no se formatea MOV DL,AL ; primer sector de la pista - 1

MOV BL,ES:[DI] ; nº sectores en la pista

genera_info PROC genera_pn: ADD SI,4

XPUSHA INC DX

MOV buf_unidad,-1 ; invalidar contenido buffer CMP DL,BL

MOV SI,buffer JBE ns_ok

MOV DI,BX MOV DL,1 ; empezar desde el 1

CALL pista0? ns_ok: 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 ; CX sectores en pista 0 LOOP genera_pn

INC DI XPOPA

MOV AL,ES:[DI] ; GAP para pista 0 RET

MOV AH,0 ; byte de relleno info_stv: MOV CH,ES:[DI] ; nº sectores

INC DI MOV CL,0 ; CL:CH sectores

MOV BYTE PTR [SI],2 ; tamaño de sector MOV [SI],CX ; tamaño (CL=0) y número

MOV 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

genera_0: 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 ; datos para cada sector genera_otro: ADD SI,4

MOV AL,ES:[DI] INC DX

MOV AH,2 ; LOG2 (tamaño)-7 MOV AL,cilindro

INC DI MOV AH,cabezal

MOV [SI+2],AX ; nº de sector / tamaño MOV [SI],AX ; datos para cada sector

LOOP genera_0 XPUSH <CX, DI> ; *

XPOPA MOV CL,ES:[DI+2] ; CH está a 0

RET busca_num: ADD DI,3

no_cilcab0: 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 ; tamaño en DH hallado: XPOP <DI, CX> ; *

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


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

RET PUSH DS

genera_info ENDP DDS

MOV AX,9001h

; ------------ Formatear una pista. CLC

INT 15h ; permitir multitarea

formatea_pista PROC 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 ; ¿llegó la interrupción?

MOV CH,0 ; CX sectores JNZ fin_espera

XSHL CX,2 PMICRO

DEC CX ; nº de bytes - 1 LOOP esp_int ; esperar durante casi 1 seg.

MOV AX,DS DEC DH

CALL calc_dir_DMA ; AX:DI -> base BX y página AH JNZ esp_int_1s

MOV AL,4Ah ; modo DMA para escribir timeout_int: OR CS:status,DL ; timeout

ADD BX,4 ; saltar primeros 4 bytes STC

CALL prepara_DMA fin_espera: PUSHF

MOV BX,buffer AND BYTE PTR [BX],7Fh ; para la próxima vez

MOV AL,F_FORMAT POPF

CALL fdc_write POP DS

JC fallo_fmt XPOPA

MOV AL,cabezal RET

XSHL AL,2 espera_int ENDP

OR AL,unidad

CALL fdc_write ; byte 1 de la orden ELSE ; Si es XT...

JC fallo_fmt

MOV CX,4 espera_int PROC

format_cmd: MOV AL,[BX] STI

CALL fdc_write XPUSHA

INC BX XPUSH <DS, 40h>

LOOP format_cmd POP DS

CALL espera_int MOV AH,0FFh

fallo_fmt: PUSHF esperar_int: CMP AL,DS:[6Ch]

LEA BX,fdc_result JE mira_int

MOV CX,7 MOV AL,DS:[6Ch]

format_res: CALL fdc_read ; leyendo resultados INC AH

MOV [BX],AL CMP AH,37 ; ¿más de 2 segundos?

INC BX JB mira_int

LOOP format_res timeout_int: OR CS:status,80h ; timeout

POPF STC

JC fallo_format JMP fin_espera

TEST fdc_result,11000000b mira_int: TEST BYTE PTR DS:[3Eh],80h

JZ format_ret JZ esperar_int

fallo_format: STC ; fallo AND BYTE PTR DS:[3Eh],7Fh ; CF=0

format_ret: XPOPA fin_espera: POP DS

RET XPOPA

formatea_pista ENDP RET

espera_int ENDP

ENDIF

ENDIF

; ------------ Esperar interrupción de disquete durante casi 2

; segundos antes de considerar que ha sido un fracaso. ; ------------ Preparar DMA para E/S. A la entrada, BX = dirección de

; base, AH = registro de página y CX = nº bytes - 1.

IFNDEF XT

prepara_DMA PROC

espera_int PROC PUSH AX

STI CLI

XPUSHA OUT 0Bh,AL ; registro de modo del DMA


284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

MOV AL,0 CLC ; Ok

DELAY RET

OUT 0Ch,AL ; clear first/last flip-flop fdc_read ENDP

MOV AL,BL

DELAY ELSE

OUT 4,AL

MOV AL,BH fdc_read PROC

DELAY XPUSH <CX, DX>

OUT 4,AL ; enviada dirección base MOV DX,3F4h ; registro de estado del FDC

DELAY XOR CX,CX ; evitar cuelgue total si falla

MOV AL,AH espera_rd: IN AL,DX ; leer registro de estado

OUT 81h,AL ; registro de página del DMA TEST AL,80h ; ¿bit 7 inactivo?

MOV AL,CL LOOPZ espera_rd ; así es: el FDC está ocupado

DELAY JCXZ fdc_rd_nok

OUT 5,AL INC DX ; apuntar al registro de datos

MOV AL,CH IN AL,DX ; leer byte del FDC

DELAY CLC

OUT 5,AL ; enviada cuenta de bytes XPOP <DX, CX>

STI RET

MOV AL,2 fdc_rd_nok: OR status,80h ; timeout

DELAY STC

OUT 0Ah,AL ; habilitar canal 2 de DMA XPOP <DX, CX>

POP AX RET

RET fdc_read ENDP

prepara_DMA ENDP

ENDIF

; ------------ Recibir byte del FDC en AL. A la vuelta, CF=1 si

; la operación fracasó (el FDC no estaba listo) y ; ------------ Enviar byte AL al FDC. A la vuelta, CF=1 si

; se indica la condición de timeout en «status». ; la operación fracasó (el FDC no estaba listo) y

; se indica la condición de timeout en «status».

IFNDEF XT

IFNDEF XT

fdc_read PROC

XPUSH <CX, DX, AX> fdc_write PROC

CALL fdc_respiro ; no abrasar el FDC XPUSH <CX, DX, AX>

MOV DX,3F4h ; registro de estado del FDC CALL fdc_respiro ; no abrasar el FDC

MOV CX,133 ; constante para 0,002 segundos MOV DX,3F4h ; registro de estado del FDC

espera_rd: DELAY MOV CX,133 ; constante para 0,002 segundos

IN AL,DX espera_wr: DELAY

AND AL,11000000b IN AL,DX

CMP AL,11000000b ; ¿dato listo? TEST AL,80h ; ¿listo para E/S?

JE fdc_rd_ok JNZ fdc_wr_ok

DELAY DELAY

IN AL,61h IN AL,61h

AND AL,10h AND AL,10h

CMP AL,AH CMP AL,AH

JE espera_rd ; reintentarlo durante 15,09 µs JE espera_wr ; reintentarlo durante 15,09 µs

MOV AH,AL MOV AH,AL

LOOP espera_rd LOOP espera_wr

XPOP <AX, DX, CX> XPOP <AX, DX, CX>

OR status,80h ; timeout OR status,80h ; timeout

MOV AL,0 STC ; fallo

STC ; fallo RET

RET fdc_wr_ok: INC DX ; apuntar al registro de datos

fdc_rd_ok: POP AX POP AX

INC DX ; apuntar al registro de datos DELAY

DELAY OUT DX,AL ; enviar byte al FDC

IN AL,DX ; leer byte del FDC XPOP <DX, CX>

XPOP <DX, CX> CLC ; Ok


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

RET JZ retardado

fdc_write ENDP DEC DX

JMP retardando

ELSE retardado: XPOPA

POPF

fdc_write PROC RET

XPUSH <AX, CX, DX> retardo ENDP

MOV DX,3F4h ; registro de estado del FDC

XCHG AH,AL ; preservar AL en AH ELSE

XOR CX,CX ; evitar cuelgue total si falla

espera_wr: IN AL,DX ; leer registro de estado retardo PROC

TEST AL,80h ; ¿bit 7 inactivo? PUSHF

LOOPZ espera_wr ; así es: el FDC está ocupado XPUSH <AX, BX, CX, DX>

JCXZ fdc_wr_nok retarda_mas: CMP AX,54 ; como máximo 54 ms cada vez

XCHG AH,AL ; recuperar el dato de AL JBE retarda_fin

INC DX ; apuntar al registro de datos PUSH AX

OUT DX,AL ; enviar byte al FDC MOV AX,54

XPOP <DX, CX, AX> CALL rt_ax

CLC POP AX

RET SUB AX,54

fdc_wr_nok: OR status,80h ; timeout JMP retarda_mas

XPOP <DX, CX, AX> retarda_fin: CALL rt_ax

STC XPOP <DX, CX, BX, AX>

RET POPF

fdc_write ENDP RET

ENDIF rt_ax: MOV DX,1000 ; retardo de hasta 54 ms

MUL DX

; ------------ Retardo de 60 µs para dar tiempo al FDC en 486 rápidos. MUL CS:tbase

MOV CX,54925

IFNDEF XT DIV CX ; AX = contador iteraciones

MOV CX,AX

fdc_respiro PROC EVEN ; forzar alineamiento

XPUSH <AX, CX> retarda: DEC CX

MOV CX,4 JMP SHORT $+2

fdc_ret: PMICRO JNZ retarda

LOOP fdc_ret

XPOP <CX, AX>

RET

fdc_respiro ENDP

ENDIF

; ------------ Esperar exactamente AX milisegundos.

IFNDEF XT

retardo PROC

PUSHF

XPUSHA

MOV DX,16970 ; 16970 = 1193180/18*256/1000

MUL DX

MOV CL,AH ; dividir DX:AX entre 256 y

MOV CH,DL ; dejar el resultado en DX:CX

MOV DL,DH

MOV DH,0 ; DX:CX 15,09 µs-avos

retardando: PMICRO

LOOP retardando

AND DX,DX
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

RET

retardo ENDP

ENDIF

IFDEF SUPERBOOT

; ------------ Esta subrutina sustituye a la macro PMICRO en el

; código SuperBOOT por razones de espacio.

pmicro_iter: DELAY ; retardo de aprox. 15,09 µs

IN AL,61h ; (exactamente 18/1193180 sg.)

AND AL,10h ; La rutina se puede ejecutar

CMP AL,AH ; repetitivamente (se apoya en

JE pmicro_iter ; AX) para hacer retardos a

MOV AH,AL ; través de la temporización

RET ; del refresco de la memoria

; ------------ Código invocado durante el SuperBOOT desde 2MFBOTHD.ASM

; A la entrada: CS=ES, SS=0 y AX = tipo unidades.

initcode: PUSH DS

PUSH SS

POP DS

MOV ES:[info_A.tipo_drv],AL ; anotar tipo de A:

MOV ES:[info_B.tipo_drv],AH ; anotar tipo de B:

LEA DI,ant_int13

MOV SI,13h*4 ; vector de INT 13h

CLD

CLI

MOVSW

MOVSW ; anotada dirección INT 13h

MOV WORD PTR [SI-4],OFFSET ges_int13

MOV [SI-2],ES

STI ; desviada INT 13h

POP DS

RETF ; volver a 2MFBOTHD

DB 4 DUP(0) ; esto ocupa 2560 bytes exactos

ant_int13 LABEL DWORD ; vector de la INT 13h previa

ant_int13_off DW initcode

ant_int13_seg DW 0AA55h ; significa "2MFBOOT correcto"

ENDIF

; --- Ubicación del sector de hasta 2048 bytes.

EVEN

buffer_io EQU $

tbuffer EQU 2048

12.6.7.4 - DESCRIPCION DEL PROGRAMA DE FORMATEO (2MF) PARA 2M.

El formateo de los disquetes 2M puede realizarse desde un lenguaje de alto nivel por medio de las
funciones de la BIOS implementadas por 2M cuando está residente. El siguiente programa de ejemplo
demuestra lo sencilla que es esta tarea. El único problema importante que se presentó durante su desarrollo
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

fueron los conflictos que generaba WINDOWS al intentar formatear un disco en el formato de máxima
capacidad (opción /M): por algún motivo, era imposible crear este tipo de pistas al producirse un extraño error
en la función de formatear. Este problema ya se había presentado en versiones anteriores de 2M, que también
formateaban los discos. La solución adoptada es, sencillamente, invocar la INT 13h mediante un CALL a la
dirección del vector de interrupción. De este modo no se ejecuta el código WINDOWS responsable de la
incompatibilidad, que entraba en marcha al llamar a la INT 13h en modo protegido. Tenga en cuenta el lector
que una inocente instrucción INT es mucho más que eso bajo WINDOWS o con un controlador de memoria
instalado. Este fragmento de código de 2MF ha sido codificado en ensamblador, entre otros motivos porque
antes de llamar con CALL a una interrupción hay que apilar los flags y eso resulta difícil en C. Durante las
restantes fases del formateo (lectura para verificar y la escritura previa en los formatos de máxima capacidad) se
utilizan las funciones estándar de la BIOS vía INT 13h. Aunque WINDOWS no estorbara, tampoco hubiera
sido posible llamar con la función de formateo BIOS del compilador, ya que los parámetros cambian
ligeramente, si bien se podría haber hecho con código C.

El programa admite varios parámetros para controlar el formateo. Por defecto realiza el formateo
normal, más fiable (o indicando la opción /F). Para seleccionar el formateo de máxima capacidad hay que
indicar /M. Desde 2MF 3.0, el programa es capaz de detectar la densidad en discos de 3½ vírgenes (con la
excepción de las unidades que permiten formatear en alta densidad los discos de doble) y lo intenta en los de 5¼
(sólo funciona si ya tenían algún tipo de formato previo). En cualquier caso, siempre se puede indicar la opción
/HD, /DD ó /ED para seleccionar la densidad necesaria y evitar la pequeña pérdida de tiempo en detectarla.

El número de pistas, por defecto 82, puede elegirse con /T, ya que muchas unidades soportan 84 pistas
o más; de todas maneras, 2MF 3.0 no permite formatear más pistas de las que admita la unidad, al contrario que
las versiones anteriores. Los ficheros permitidos en el directorio raíz se indican con /R. El parámetro /S evita la
producción de sonido. Con /N se evita la verificación, /K y /J eliminan la pausa inicial y final, respectivamente;
/Z anula el parpadeo del led mientras se cambia el disco y /L y /V permiten poner etiquetas de volumen
(serializadas en el último caso) al disco destino.

Finalmente, hay varios parámetros no documentados oficialmente que no deberían ser alterados, salvo
quizá en algún ordenador muy concreto y por parte de usuarios muy especializados, que permiten elegir los
factores de desplazamiento en la numeración de los sectores al conmutar de cabezal (/X) y de cilindro (/Y) en el
formato normal (/F); en el formato de máxima capacidad (/M) no tienen efecto. El parámetro /G permite indicar
el GAP o separación de sectores en todas las pistas -salvo la primera- en el formato /F; en el formato /M este
valor de GAP se refiere al GAP empleado en la primera pasada del formateo (con sectores de 128 bytes). Con
/D0 se formatea en 3½-DD con 820/902K (en lugar de 984/1066K), algo necesario en las controladoras de
algunos portátiles que no soportan la densidad de 300 Kbps (propia exclusivamente de las unidades de 5¼); si
bien no es preciso emplearlo ya que por defecto el programa formatea de esta manera en esas unidades al
autodetectar la densidad del disco destino. /D1 formatea 1148K en lugar de 1066K, pero el disco resultante es
poco seguro y extremadamente lento. Por último, la opción /W hace que se marquen sólo los clusters
defectuosos y no la pista completa.

┌───────────────────────────────────────────────────────────────────────────────┐
│ TIEMPO EMPLEADO EN EL FORMATEO │
├───────────────┬───────────────┬───────────────┬───────────────┬───────────────┤
│ FORMAT │ FDFORMAT (*) │ FDFORMAT (**) │ 2MF 3.0 /F │ 2MF 3.0 /M │
┌───────┼───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤
│ 5¼-DD │ 0:37 │ 0:42 │ 1:28 │ 1:26 │ 2:37 │
│ 5¼-HD │ 1:13 │ 1:24 │ 1:52 │ 1:29 │ 2:38 │
│ 3½-DD │ 1:24 │ 1:38 │ 1:46 │ 1:39 │ 2:51 │
│ 3½-HD │ 1:34 │ 1:42 │ 2:17 │ 1:47 │ 3:22 │
└───────┴───────────────┴───────────────┴───────────────┴───────────────┴───────────────┘
(*) Usando el formato estándar del DOS (360-720-1.2-1.44) y los parámetros /X e /Y adecuados.
(**) Formatos de máxima capacidad soportados (820-1.48-1.72) y los parámetros /X e /Y adecuados.

La parte más compleja del programa es la función CrearSector0(), que como su propio nombre indica
se encarga de crear el sector de arranque del futuro disquete. En un programa de copia de discos esta función no
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

sería necesaria, ya que al leer el disquete origen tendríamos ya el sector de arranque del futuro disquete destino
y, por tanto, podríamos formatearle directamente (recordar que la función de formateo de discos 2M sólo
necesita como parámetro el sector de arranque del futuro disco). Sin embargo, aquí nos vemos obligados a crear
dicho sector, lo cual es una tarea un tanto engorrosa, teniendo en cuenta la variedad de formatos. Una tabla más
o menos complicada, de 5 dimensiones, contiene toda la información necesaria para la tarea. Además, el código
ejecutable del sector de arranque resultaba difícil incluirlo dentro del listado C y finalmente se optó por crear un
fichero proyecto e incluir en él 2MF.C y 2MFKIT.ASM (este último integra los sectores de arranque para alta y
doble densidad -con y sin soporte SuperBOOT, respectivamente- así como el código SuperBOOT y las rutinas
de utilidad).

Durante el proceso de formateo, en FormatearDisco() se está pendiente de una posible pulsación de la


tecla ESC. Se controlan los posibles errores fatales, tales como unidad protegida de escritura o no preparada,
que suponen el fin del proceso de formateo, pero se toleran los demás errores -si no afectan a las áreas del
sistema del disco- marcando los clusters afectados como defectuosos si al tercer intento de formateo siguen
fallando. Al final del formateo, en InformeDisco() se imprimen las características del nuevo disquete pero sin
emplear funciones del DOS. Realmente, el DOS ya se ha dado cuenta del cambio de disco e informaría
correctamente, pero de esta manera se asegura a ultranza que la información es correcta.

La función TipoDrive() devuelve el tipo de la disquetera que se le indique consultando esta


información a través de la BIOS. La función InicializaDisco() escribe, al final del formateo, el sector de
arranque físico, el virtual, el código SuperBOOT (si el disco es de alta densidad) y la FAT; de esta última sólo
la primera copia, ya que 2M emulará la segunda.

Las funciones de sonido crean efectos especiales bastante atractivos gracias al empleo de retardos de
medio milisegundo con la función PicoRetardo(); este retardo es idéntico en todas las máquinas, con total
independencia de la velocidad de la CPU, y permite que el sonido suene igual en todas. En los PC/XT no se
realiza retardo alguno y, curiosamente, el sonido suena igual que en los AT (en máquinas de 8 MHz).

La función EsperarCambioDisco() espera a que se retire el disquete de la unidad y se introduzca uno


nuevo, o bien a que se pulse una tecla (considerando el caso de ESC, para abortar el formateo). Esto permite
formatear varios disquetes introduciéndolos unos tras otros en la unidad sin necesidad de pulsar teclas. En
WINDOWS se puede abrir una ventana para formatear disquetes 2M y, dejándola en la sombra, cada vez que se
oiga el sonido de fin de formateo, sin abandonar lo que se tenga en ese momento entre manos, se puede sacar el
disco e introducir otro para que el proceso continúe automáticamente sin tener que activar la ventana para pulsar
una tecla. El sonido de final de formateo permite distinguir entre un formateo correcto y otro con errores (se
considera correcto aunque haya sectores defectuosos, lo de errores va por lo de disco protegido contra escritura,
etc.). Las rutinas de bajo nivel que acceden a la controladora de disco en 2MF lo hacen, exclusivamente, para
conseguir el efecto intermitente en el led de la unidad mientras se cambia de disco (y para reducir el ruido que
emite la función de detección de nuevo disco de la BIOS).

Para fomentar que los usuarios envíen la postal al autor, el programa tiene un contador de discos
formateados añadido cuando formatea el primer disco por el método de alargar el tamaño del fichero EXE. Al
cabo de 100 discos, imprime un mensaje recordando al usuario su deber. Naturalmente, si 2MF se ejecuta desde
una unidad protegida contra escritura, no será posible actualizar el contador...

Finalmente, la función HablaSp() comprueba el país en que se ejecuta el programa para inicializar una
variable global que indique si los mensajes han de ser imprimidos en castellano o en inglés.

/*───────────────────────────────────────────────────────────────────\ │ █████ █ █ █ █▀▀ │

│ │ │ █ █ █ █ │

│ █████ █ █ █▀▀▀▀ │ │ █████ █ █ █ │

│ █ ██ ██ █ │ │ │
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

│ 2MF.C 3.0 - UTILIDAD DE FORMATEO DE DISQUETES 2M │ long NumSerie;

│ │ char Titulo[11], TipoFat[8];

│ (C) 1994 Ciriaco García de Celis. │ char Flags;

│ │ char CheckSum;

│ - Para cualquier Turbo C o Borland C en modelo de memoria LARGE. │ char VersionFmt, FlagWr, VelPista0, VelPistaX;

│ - Este programa se compila abriendo un proyecto e introduciendo │ short OffsetJmp, OffsetPista0, OffsetPistaX, OffsetListaTam;

│ en él 2MF.C y 2MFKIT.OBJ │ short FechaF;

│ - Importante: no activar ciertas optimizaciones que no lo están │ short HoraF;

│ por defecto (como la de alineamiento a palabra o la de salto). │ char Resto[512-BOOT2M]; /* depende del tamaño de lo anterior */

│ │ } Boot;

│ - NOTA: Las funciones de bajo nivel que acceden directamente a │

│ la controladora de disquetes no son indispensables, tan │ typedef struct { /* entrada de directorio */

│ sólo se emplean para producir menos ruido al detectar │ char Etiqueta[11];

│ la introducción de un nuevo disquete en la unidad. │ char Tipo;

│ │ char Reservado[10];

│ Este programa detecta además la presencia de una posible │ int Hora;

│ utilidad de intercambio de unidades A:-B: llamada FDSWAP │ int Fecha;

│ para que en caso de estar activado dicho intercambio sea │ char Resto[6];

│ posible acceder a la unidad física correspondiente. │ } Root;

│ │

\───────────────────────────────────────────────────────────────────*/ typedef struct { /* parámetros en línea de comandos */

int Unidad, HD, ED, TipoFmt,

NoVerify, MarcaPoco,

#include <stdlib.h> Pistas, FichRaiz, Silencioso, NoPausa, NoTecla, X, Y, G,

#include <stdio.h> Tipoetiq, NoFlash;

#include <string.h> char Volumen[12];

#include <dos.h> } Parametros;

#include <bios.h>

#include <time.h>

#include <alloc.h> int HablaSp (void),

#include <conio.h> Hay2m (void),

#include <io.h> Hay2mBoot (void),

#include <fcntl.h> FdswapOn (void),

TipoDrive (int),

EsperarCambioDisco (int, int),

#define CARDWARE 100 /* nº discos formateados antes del aviso */ infdc (void),

#define MAXSECT 46 /* máximo número de sectores por pista */ ValeDensidad (Boot *, Parametros *),

#define MAXFAT 6128 /* mayor FAT de 12 bits posible */ FormatearDisco (Boot *,unsigned char far *,unsigned char far *,

#define BOOT2M 80 /* bytes principales del Boot */ Parametros *, long *, int *),

#define FD_DATA 0x3F5 /* registro de datos del 765 */ MarcaFat (int, int, Boot *, int, int, unsigned char far *,

#define FD_STATUS 0x3F4 /* registro principal de estado del 765 */ unsigned char far *, long *),

#define FD_DOR 0x3F2 /* registro de salida digital */ InicializaDisco (int, Boot *, unsigned char far *,

#define FD_DIR 0x3F7 /* registro de entrada digital (RD) unsigned char far *);

*/ void Ayuda (void),

#define FD_DCR 0x3F7 /* registro de control del disquete ProcesarParametros (int, char **, Parametros *),

(WR) */ DetectaMedio (Parametros *, Boot *),

CrearSector0 (Boot *, Parametros),

DiagnosticoError (int),

typedef struct { /* sector arranque disquetes 2M */ InformeDisco (Boot *, Parametros *, long, int),

unsigned char Salto[3], IdSis[8]; IncrementarEtiqueta (Parametros *),

short BytesSect; SonidoSube (void),

char SectCluster; SonidoBaja (void),

short SectReserv; SonidoError (void),

char NumFats; SonidoOn (void),

short FichRaiz, NumSect; SonidoOff (void),

char MediaId; Sonido (int),

short SectFat, SectPista, Caras; posicionar (int, int),

long Especiales, Sect32; outfdc (unsigned char),

char Unidad, Reservado, Flag; EsperarInt (void),


284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

CardWare (char *, int); dir = ((unsigned long) FP_SEG(buffer) <<4) + FP_OFF(buffer);

extern BootHDPrg, BootHDPrgLong, BootDDPrg, BootDDPrgLong, if ((dir >> 16) != ((dir + ((unsigned long) MAXSECT << 9)) >> 16))

Boot2mCode, Boot2mLong, buffer+=(unsigned long) MAXSECT << 9;

biosdsk (int, int, int, int, int, int, void far *);

void interrupt NuevaInt24 (void); if (!cmd.NoPausa) {

extern void PicoRetardo (void), interrupt (*ViejaInt24) (void); if (sp)

printf(" Pulsa una tecla para formatear en");

else

printf(" Press any key to format on");

int sp; /* 1-español 0-inglés */ printf(" %c:", cmd.Unidad+'A');

salir=getch()==27;

unsigned long far *cbios=MK_FP(0x40, 0x6C); /* reloj del sistema */ }

unsigned char far *irq6=MK_FP(0x40, 0x3E); /* flag BIOS de IRQ6 */ else

salir=0;

void main (int argc, char **argv) /* si no se indica densidad detectarla */

Boot sector0; detectar = (cmd.HD==-1);

Parametros cmd;

int salir, result, sg, detectar; /* formateo de múltiples disquetes */

long bytes_err, dir;

unsigned char far *buffer; /* para contener toda una pista */ while (!salir) {

unsigned char far *fat; /* para contener toda la FAT */ if (detectar) DetectaMedio (&cmd, &sector0);

int disquetes=0; /* nº discos formateados */ CrearSector0 (&sector0, cmd);

void interrupt if (!cmd.Silencioso) SonidoSube();

(*ViejaInt24) (void); switch (result=FormatearDisco (&sector0, fat, buffer, &cmd,

&bytes_err, &sg)) {

sp=HablaSp(); /* determinar idioma del país */ case 0: InformeDisco (&sector0, &cmd, bytes_err, sg);

if (!cmd.Silencioso) SonidoBaja();

ProcesarParametros (argc, argv, &cmd); if (cmd.Tipoetiq==2) IncrementarEtiqueta (&cmd);

disquetes++;

if (!Hay2m()) break;

if (!Hay2mBoot()) { case 1: DiagnosticoError (result);

if (sp) break;

printf(" 2M ó 2MX 3.0 no está instalado, imposible default: DiagnosticoError (result);

formatear.\n"); if (!cmd.Silencioso) SonidoError(); break;

else }

printf(" 2M or 2MX 3.0 is not installed, impossible to if (cmd.NoTecla)

format.\n"); salir=1;

exit(128); else {

} if (sp)

else { printf("\n Introduce otro disquete para formatear en");

if (sp) else

printf(" Modo SuperBOOT: instale 2M para dar formato.\n"); printf("\n Please insert another disk to format in");

else printf(" %c:", cmd.Unidad+'A');

printf(" SuperBOOT mode: needed to install 2M to

format.\n"); if (!EsperarCambioDisco(cmd.Unidad, cmd.NoFlash)) salir=1;

exit(127); }

} }

printf("\r \r");

if (((fat=farmalloc( (unsigned long) MAXFAT))==NULL) ||

((buffer=farmalloc( (unsigned long) MAXSECT<<10))==NULL)) { ViejaInt24=getvect(0x24);

if (sp) printf(" Memoria insuficiente.\n"); setvect (0x24, NuevaInt24); /* evitar error crítico */

else printf(" Insufficient memory.\n"); CardWare (argv[0], disquetes); /* intentar actualizar 2MF.EXE */

exit(126); setvect (0x24, ViejaInt24);

} }

/* Definir el buffer para que no cruce una frontera de DMA */

void ProcesarParametros (int argc, char **argv, Parametros *cmd)


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

{ }

int pm, error=0, hlp=0, id=1; }

cmd->Unidad=cmd->TipoFmt=cmd->ED=cmd->NoVerify=cmd->MarcaPoco=0; if (cmd->ED && (cmd->HD!=1)) cmd->HD=1; /* /DD ó /Dx + /E = /E */

cmd->HD=-1; cmd->Pistas=82;

cmd->FichRaiz=cmd->Silencioso=cmd->NoFlash=cmd->NoPausa=\ if ((argc<=1) || (argc==id)) hlp++;

cmd->NoTecla=cmd->Tipoetiq=0;

cmd->X=cmd->Y=cmd->G=-1; if (hlp) Ayuda();

for (pm=1; pm<argc; pm++) if (sp)

if (strstr(&argv[pm][1], "/")!=NULL) error=-1; /* parámetros unidos printf("\n2MF 3.0 - Utilidad de formateo de disquetes 2M

*/ (ESC Salir)\n");

else

if (!error) { printf("\n2MF 3.0 - Format utility program for 2M diskettes

for (pm=1; pm<argc; pm++) { (ESC Aborts)\n");

if ((strstr(argv[pm],"/L")!=NULL) || if (error==1) {

(strstr(argv[pm],"/l")!=NULL)) { if (sp)

strncpy (cmd->Volumen, &argv[pm][3], 11); printf(" Error de sintaxis. Ejecute 2MF /?.\n");

cmd->Volumen[11]=0; else

while (strlen(cmd->Volumen)<11) strcat(cmd->Volumen, " "); printf(" Incorrect parameter(s). Execute 2MF /?.\n");

cmd->Tipoetiq=1; exit (2);

continue; }

} if (error==-1) {

else if ((strstr(argv[pm],"/V")!=NULL) || if (sp)

(strstr(argv[pm],"/v")!=NULL)) { printf(" Error: Los parámetros deben separarse por

strncpy (cmd->Volumen, &argv[pm][3], 11); espacios.\n");

cmd->Volumen[11]=0; else

while (strlen(cmd->Volumen)<11) strcat(cmd->Volumen, " "); printf(" Error: Parameters must be separated by blank

cmd->Tipoetiq=2; spaces.\n");

continue; exit (2);

} }

strupr (argv[pm]); if (TipoDrive(cmd->Unidad)==0) {

if (strstr(argv[pm],"/?")!=NULL) hlp++; if (sp)

else if ((strstr(argv[pm],"/H")!=NULL) && (strlen(argv[pm])==2)) printf(" La unidad física indicada no existe.\n");

hlp++; else

else if ((strstr(argv[pm],"A:")!=NULL) || printf(" Physical drive indicated does not exist.\n");

(strstr(argv[pm],"B:")!=NULL)) cmd->Unidad=*argv[pm]-'A'; exit (2);

else if (strstr(argv[pm],"/HD")!=NULL) cmd->HD=1; }

else if (strstr(argv[pm],"/DD")!=NULL) cmd->HD=0; if ((TipoDrive(cmd->Unidad)!=2) && (TipoDrive(cmd->Unidad)<4)) {

else if (strstr(argv[pm],"/D0")!=NULL) cmd->HD=2; if (sp)

else if (strstr(argv[pm],"/D1")!=NULL) cmd->HD=3; printf(" La unidad indicada no es de alta densidad.\n");

else if (strstr(argv[pm],"/F")!=NULL) cmd->TipoFmt=0; else

else if (strstr(argv[pm],"/M")!=NULL) cmd->TipoFmt=1; printf(" Drive indicated it is not high density one.\n");

else if (strstr(argv[pm],"/ED")!=NULL) cmd->ED=1; exit (2);

else if (strstr(argv[pm],"/N")!=NULL) cmd->NoVerify=1; }

else if (strstr(argv[pm],"/W")!=NULL) cmd->MarcaPoco=1; if ((TipoDrive(cmd->Unidad)<5) && (cmd->ED==1)) {

else if (strstr(argv[pm],"/T")!=NULL) if (sp)

cmd->Pistas = atoi (&argv[pm][3]); printf(" Necesaria unidad de 2.88M para formato ED.\n");

else if (strstr(argv[pm],"/R")!=NULL) else

cmd->FichRaiz = atoi (&argv[pm][3]); printf(" Needs a 2.88M drive to perform ED format.\n");

else if (strstr(argv[pm],"/S")!=NULL) { cmd->Silencioso=1; id++; } exit (2);

else if (strstr(argv[pm],"/K")!=NULL) cmd->NoPausa=1; }

else if (strstr(argv[pm],"/J")!=NULL) cmd->NoTecla=1; if ((cmd->Pistas<80) || (cmd->Pistas>86)) {

else if (strstr(argv[pm],"/Z")!=NULL) cmd->NoFlash=1; if (sp)

else if (strstr(argv[pm],"/X")!=NULL) cmd->X=atoi(&argv[pm][3]); printf(" Error: Número de pistas incorrecto.\n");

else if (strstr(argv[pm],"/Y")!=NULL) cmd->Y=atoi(&argv[pm][3]); else

else if (strstr(argv[pm],"/G")!=NULL) cmd->G=atoi(&argv[pm][3]); printf(" Error: Incorrect number of tracks.\n");

else if (strstr(argv[pm],"/I")!=NULL) { sp^=1; id++; } exit (2);

else error=1; }
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

if (cmd->FichRaiz && ((cmd->FichRaiz<1) || (cmd->FichRaiz>240))) { " (C) 1994 Ciriaco García de Celis - Grupo Universitario de

if (sp) Informática\n"

printf(" Error: Nº de ficheros en directorio raiz erróneo.\n"); " C/Renedo, 2, 4-C; 47005 Valladolid (Spain) -

else ciri@gui.uva.es - 2:341/21.8\n\n"

printf(" Error: Bad number of files in root directory.\n"); " 2MF U: [/HD|DD|ED] [/F|M] [/N] [/L|V=label] [/S][/Z] [/R=nn]

exit (2); [/T=nn] [/K][/J]\n\n"

} " This program formats diskettes at a higher capacity and/or

} speed than the\n"

" normal ones. 2M must be installed on memory to provide

support for the new\n"

void Ayuda() " diskettes. Also, high-density diskettes can be left into A:

{ drive and then\n"

if (sp) { " computer can be rebooted: really it will boot from hard disk

printf("\n\n" and after this\n"

" 2MF 3.0 - UTILIDAD ESTANDAR DE FORMATEO DE DISQUETES " moment 2M diskettes will be supported in the standard

PARA 2M\n" read-write operation.\n\n"

" (C) 1994 Ciriaco García de Celis - Grupo Universitario de " /HD High density format (by default if 2MF can't detect

Informática\n" diskette density).\n"

" C/Renedo, 2, 4-C; 47005 Valladolid (España) - " /DD Request a double-density format (but 2MF perhaps can

ciri@gui.uva.es - 2:341/21.8\n\n" detect DD disk).\n"

" 2MF U: [/HD|DD|ED] [/F|M] [/N] [/L|V=etiq] [/S] [/Z] [/R=nn] " /ED Formats 3.5-ED diskettes at 3608K (or 3772K if /M option

[/T=nn] [/K] [/J]\n\n" enabled).\n"

" Este programa formatea disquetes a una mayor capacidad y/o " /F Fast and secure diskettes -by default- (5¼:820-1476K,

velocidad de la\n" 3½:984-1804K).\n"

" normal. Para que estos nuevos disquetes funcionen debe estar " /M Formats diskettes up to maximum capacity (5¼:902-1558K,

instalado 2M en\n" 3½:1066-1886K).\n"

" memoria. Alternativamente, si son de alta densidad se pueden " /N Do not verify target diskette (dangerous in /M mode).\n"

dejar dentro de\n" " /L Sets diskette volume label (case sensitive).\n"

" la unidad A: y reinicializar el ordenador, que botará pese a " /V Automatic sequencing of labels (if specified one is

todo del disco\n" number terminated).\n"

" duro y podrá acceder a los disquetes 2M sin problemas en " /S Tells 2MF not to make sound effects /Z Turn disk led

lectura/escritura.\n\n" «flashing» off.\n"

" /HD Formateo en alta densidad (por defecto si 2MF no detecta " /R Sets root entries number (1-240) /T Sets number of

la densidad).\n" tracks (80-86).\n"

" /DD Fuerza el formateo en doble densidad (aunque 2MF quizá " /K No initial pause before formatting /J No end pause

la detecte).\n" after formatting.\n");

" /ED Formatear disquetes de 3½-ED (3608K por defecto o 3772K }

indicando /M).\n" exit (1);

" /F Disquetes rápidos y seguros -por defecto- (5¼:820-1476K, }

3½:984-1804K).\n"

" /M Formatear disquetes a la máxima capacidad (5¼:902-1558K,

3½:1066-1886K).\n" int Hay2m() /* devolver 1 si 2M está instalado */

" /N No verificar el disquete destino (peligroso en modo {

/M).\n" int entrada, instalado=0;

" /L Poner etiqueta de volúmen al disco destino (minúsculas union REGS r; struct SREGS s;

permitidas).\n"

" /V Etiqueta incremental en series de discos (si termina en for (entrada=0xc0; (entrada<=0xff) && (!instalado); entrada++) {

número).\n" r.x.ax=entrada << 8; s.es=0x1492; r.x.di=0x1992;

" /S Funcionamiento silencioso /Z Evitar parpadeo de int86x (0x2f, &r, &r, &s);

led de disco.\n" if (r.x.ax==0xFFFF)

" /R Elegir nº ficheros raíz (1-240) /T Cambiar número de if ((peek(s.es,r.x.di-4)==9002) && (peek(s.es,r.x.di-2)==10787))

pistas (80-86).\n" if (strstr (MK_FP(s.es, r.x.di),"2M:3.0")) instalado=1;

" /K No realizar pausa inicial /J No realizar pausa if (strstr (MK_FP(s.es, r.x.di),"2MX:3.0")) instalado=1;

final.\n"); }

} return (instalado);

else { }

printf("\n\n"

" 2MF 3.0 - STANDARD FORMAT UTILITY FOR 2M

DISKETTES\n" int Hay2mBoot() /* devolver 1 si 2M instalado en modo SuperBOOT */


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

{ Significado de la tabla /M:

return (strstr(MK_FP(((unsigned) peek(0x40, 0x13) * 64), 4), {SectLogPistaX, fichraiz, verFmt, flagWr, velpista0, velpistaX},

"2M-STV")!=NULL); {sectpista0, GAP3pista0, primsectpista0, interleavepista0},

} {Sectpreformat, GAP3pistaX, SectFisPistaX, sects numerados...},

{tamaños de sectores por orden...}

*/

int FdswapOn() /* devolver 1 si FDSWAP 1.1+ está instalado y activo */

{ if ((cmd.HD==2) && (TipoDrive(cmd.Unidad)>=4)) {

int entrada, instalado=0; cmd.HD=0; tabla=0; tipo=0;

union REGS r; struct SREGS s; infofis[0][0][cmd.TipoFmt][0][4]=2; /* 3½-DD a 250 Kbps */

infofis[0][0][cmd.TipoFmt][0][5]=2;

for (entrada=0xc0; (entrada<=0xff) && (!instalado); entrada++) { }

r.x.ax=entrada << 8; s.es=0x1492; r.x.di=0x1992; else if ((cmd.HD==3) && (TipoDrive(cmd.Unidad)>=4)) {

int86x (0x2f, &r, &r, &s); cmd.HD=tipo=0;

if (r.x.ax==0xFFFF) cmd.TipoFmt=1; tabla=2; /* 3½-DD con 1148K */

if ((peek(s.es,r.x.di-4)==9002) && (peek(s.es,r.x.di-2)==10787)) }

if (strstr (MK_FP(s.es, r.x.di),":FDSWAP:")) instalado=1; else {

} if (cmd.HD>1) cmd.HD=0;

return ((instalado) && (peekb(s.es, peek(s.es,r.x.di-6)-1)==1)); tabla=cmd.HD+cmd.ED; /* seleccionar tabla de datos */

} if (TipoDrive(cmd.Unidad)<3)

tipo=0; /* 5¼ */

else

void CrearSector0 (Boot *s0, Parametros cmd) tipo=1; /* 3½ */

{ }

unsigned tipo, tabla, i, j, k, m, t, s, tam, ini, fin, inc;

char id[8]="2M-STV00", ch, sum, far *p; ch=1+cmd.HD;

struct time h; if (TipoDrive(cmd.Unidad)>2) ch+=2; if (!cmd.TipoFmt) ch+=4;

struct date f; if (cmd.ED) ch=10-cmd.TipoFmt;

static unsigned char infofis [2][3][2][4][20] = id[6]=(ch/10)+'0'; id[7]=(ch % 10)+'0'; strncpy (s0->IdSis, id, 8);

{{{{{10,176,7,0,1,1}, {9,80,1,1}, /* 5¼-DD /F */

{5,100,3,1,1} }, s0->BytesSect=512;

{{11,176,7,1,1,1}, {9,80,1,1}, /* /M */ s0->SectCluster = s0->SectReserv = 1; s0->NumFats=2;

{32,4,5,3,1,4,2,0}, {4,2,4,3,0} }}, if (cmd.ED) s0->SectCluster=2;

{{{18,224,7,0,0,0}, {16,60,1,1}, /* 5¼-HD /F */

{9,50,3,1,2} }, if (!cmd.FichRaiz)

{{19,224,7,1,0,0}, {17,25,1,2}, /* /M */ s0->FichRaiz=infofis[tipo][tabla][cmd.TipoFmt][0][1];

{53,3,6,4,1,5,2,6,3}, {4,4,2,4,4,3} }}, else

{{{0,0,0,0,0,0}, {0,0,0,0}, /* no usado */ if (cmd.FichRaiz % 16)

{0,0,0,0,0}, }, s0->FichRaiz=((cmd.FichRaiz >> 4) + 1) << 4;

{{14,192,7,1,2,1}, {9,80,1,1}, /* 3½-DD /D1 */ else

{38,2,4,3,1,4,2}, {4,3,4,4} }}}, s0->FichRaiz=cmd.FichRaiz;

{{{{12,192,7,0,2,1}, {9,80,1,1}, /* 3½-DD /F */

{6,100,3,1,1} }, if (ch==6)

{{13,192,7,1,2,1}, {9,80,1,1}, /* /M */ s0->MediaId=0xF0; /* compatible SCANDISK */

{38,5,6,3,1,4,2,0,0}, {4,2,4,4,0,0} }}, else

{{{22,224,7,0,0,0}, {19,70,1,1}, /* 3½-HD /F */ s0->MediaId=0xFA; /* compatible SCANDISK */

{11,40,3,1,2} },

{{23,224,7,1,0,0}, {19,70,1,1}, /* /M */ s0->SectPista=infofis[tipo][tabla][cmd.TipoFmt][0][0];

{64,3,7,4,1,5,2,6,3,7}, {4,4,4,4,4,3,2} }}, s0->Caras=2;

{{{44,240,7,0,3,3}, {36,108,1,1}, /* 3½-ED /F */ s0->NumSect=cmd.Pistas*s0->Caras*s0->SectPista;

{11,126,4,1,2} },

{{46,240,7,1,3,3}, {36,108,1,1}, /* /M */ j = 3 * (s0->NumSect - (s0->FichRaiz>>4) - 1);

{127,5,12,1,7,2,8,3,9,4,10,5,11,6,12}, k = 6 + 1024 * s0->SectCluster;

{4,4,4,4,4,4,4,4,4,4,4,3} }}}}; s0->SectFat = j/k; if (j % k) s0->SectFat++;

/* Significado de la tabla /F: s0->Unidad = s0->Reservado = 0; s0->Especiales = s0->Sect32 = 0L;

{SectLogPistaX, fichraiz, verFmt, flagWr, velpista0, velpistaX}, s0->Flag=0x29; randomize();

{sectpista0, GAP3pista0, primsectpista0, interleavepista0}, for (i=0; i<4; i++)

{SectFisPistaX, GAP3pistaX, tamsectpistaX, /X, /Y} s0->NumSerie = (s0->NumSerie<<8) | (unsigned char) random(32767);
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

ini=fin; s0->OffsetListaTam=ini;

if (cmd.Tipoetiq) if (!s0->FlagWr)

strncpy (s0->Titulo, cmd.Volumen, 11); for (i=0; i<k; i++)

else s0->Salto[ini+i]=infofis[tipo][tabla][cmd.TipoFmt][2][2];

strncpy (s0->Titulo, "NO NAME ", 11); else

for (i=0; i<k; i++)

strncpy (s0->TipoFat, "FAT12 ", 8); s0->Salto[ini+i]=infofis[tipo][tabla][cmd.TipoFmt][3][i];

fin=ini+k;

s0->VersionFmt=infofis[tipo][tabla][cmd.TipoFmt][0][2];

s0->FlagWr=infofis[tipo][tabla][cmd.TipoFmt][0][3]; ini=fin; s0->OffsetJmp=ini;

s0->VelPista0=infofis[tipo][tabla][cmd.TipoFmt][0][4]; s0->Salto[0]=0xE9;

s0->VelPistaX=infofis[tipo][tabla][cmd.TipoFmt][0][5]; s0->Salto[1]=(ini-3) % 256; s0->Salto[2]=(ini-3) >> 8;

s0->Flags=1; /* Fecha y hora de formateo almacenada */ if (cmd.HD == 0) {

gettime (&h); getdate (&f); p=(char far *) &BootDDPrg; k=BootDDPrgLong; }

s0->FechaF=((f.da_year-1980)<<9) | (f.da_mon<<5) | f.da_day; else {

s0->HoraF=(h.ti_hour<<11) | (h.ti_min<<5) | (h.ti_sec>>1); p=(char far *) &BootHDPrg; k=BootHDPrgLong; }

tam=BOOT2M; /* lo que precede a la primera tabla */ for (i=0; (i<k) && (ini+i<509); i++) s0->Salto[ini+i]=*p++;

s0->OffsetPista0=tam; fin=ini+i;

s0->Resto[0]=infofis[tipo][tabla][cmd.TipoFmt][1][0];

s0->Resto[1]=infofis[tipo][tabla][cmd.TipoFmt][1][1]; for (i=fin; i<510; i++) s0->Salto[i]=0;

ch=infofis[tipo][tabla][cmd.TipoFmt][1][2]; if (fin<497) strncpy (&s0->Salto[496], "Made in Spain", 13);

inc=infofis[tipo][tabla][cmd.TipoFmt][1][3]; s0->Salto[509]=0; s0->Salto[510]=0x55; s0->Salto[511]=0xAA;

ini=tam+2; fin=ini+s0->Resto[0]; k=0;

for (i=j=0; j<s0->Resto[0]; j++) { for (sum=0, j=64; j<ini; j++) sum+=s0->Salto[j]; /* checksum */

s0->Salto[ini+i]=ch++; if (ch>s0->Resto[0]) ch=1; s0->CheckSum=-sum;

i+=inc; if (ini+i>=fin) i=++k; }

ini=fin; s0->OffsetPistaX=ini; void DetectaMedio (Parametros *cmd, Boot *sector0)

if (!s0->FlagWr) { {

k=infofis[tipo][tabla][cmd.TipoFmt][2][0]; j=5; int sg;

for (i=0; i<j; i++)

s0->Salto[ini+i]=infofis[tipo][tabla][cmd.TipoFmt][2][i]; /* simular cambio de disco para inicialización plena de 2M */

if (cmd.X!=-1) s0->Salto[ini+3]=cmd.X;

if (cmd.Y!=-1) s0->Salto[ini+4]=cmd.Y; biosdsk (5, cmd->Unidad, 0, 0, 0xFF, 0x7F, NULL);

else { /* hacer reset */

k=infofis[tipo][tabla][cmd.TipoFmt][2][2]; j=(k+1)*3;

for (i=0; i<3; i++) biosdsk (0, cmd->Unidad, 0, 0, 0, 0, NULL);

s0->Salto[ini+i]=infofis[tipo][tabla][cmd.TipoFmt][2][i];

m=129; for (sg=0; sg<2; sg++) {

for (i=3; i<=k*3; i+=3) { if (sp)

s0->Salto[ini+i]=m; printf("\r Determinando densidad del disquete...

s=infofis[tipo][tabla][cmd.TipoFmt][2][i/3+2]; ");

s0->Salto[ini+i+1]=s; else

t=infofis[tipo][tabla][cmd.TipoFmt][3][s-1]; printf("\r Detecting diskette media density...

switch (t) { ");

case 0: m+=1; break; case 1: m+=2; break; printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");

case 2: m+=3; break; case 3: m+=6; break;

case 4: m+=11; break; case 5: m+=22; break; if (TipoDrive(cmd->Unidad)==2) /* en 5¼ intento pacífico */ {

} cmd->HD=1; sg=2;

s0->Salto[ini+i+2]=t; sg=biosdsk (0, cmd->Unidad, 0, 0, 0, 0, NULL);

} sg=biosdsk (2, cmd->Unidad, 0, 0, 1, 1, (void *) sector0);

} if (sg==6) /* cambio de disco */

if (cmd.G!=-1) s0->Salto[ini+1]=cmd.G; sg=biosdsk (2, cmd->Unidad, 0, 0, 1, 1, (void *) sector0);

fin=ini+j; if (sg) break;

if ((peekb (0x40, 0x8B) >> 6)!=0) cmd->HD=0; break;


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

} else

printf("\r WARNING: Undocumented /W switch activated!\n");

cmd->ED=0; cmd->HD=1;

if ((sg=ValeDensidad (sector0, cmd))==0) break; /* vale HD */ if ((cmd->X!=-1) || (cmd->Y!=-1))

if (kbhit()) if (getch()==27) break; if (sp)

if ((sg==3) || (sg==6) || (sg==128)) break; /* error */ printf("\r AVISO: ¡Parámetro indocumentado /X ó /Y activo!\n");

cmd->HD=0; else

if (!ValeDensidad (sector0, cmd)) break; /* vale DD */ printf("\r WARNING: Undocumented /X or /Y switch

cmd->HD=1; if (kbhit()) if (getch()==27) break; activated!\n");

cmd->HD=2;

if (!ValeDensidad (sector0, cmd)) break; /* vale D0 */ if (sp)

cmd->HD=1; if (kbhit()) if (getch()==27) break; printf("\r Formateo de disquete ");

cmd->ED=1; else

if (!ValeDensidad (sector0, cmd)) break; /* vale ED */ printf("\r Formatting ");

cmd->HD=1; cmd->ED=0; if (kbhit()) if (getch()==27) break;

} switch (TipoDrive (cmd->Unidad)) {

} case 2: printf("%s", cmd->HD==1?"5¼-1.2M":"5¼-360K"); break;

case 4: printf("%s", cmd->HD==1?"3½-1.44M":"3½-720K"); break;

default: if (cmd->ED) printf("3½-2.88M");

int ValeDensidad (Boot *sector0, Parametros *cmd) else printf("%s", cmd->HD==1?"3½-1.44M":"3½-720K");

{ }

CrearSector0 (sector0, *cmd);

biosdsk (0, cmd->Unidad, 0, 0, 0, 0, NULL); if (sp)

biosdsk (5, cmd->Unidad, 0, 0, 0, 0x7F, printf(" en %c: con %dK \n",

(unsigned char far *) sector0); cmd->Unidad+'A', sector0->NumSect>>1);

return (biosdsk (2, cmd->Unidad, 0, 0, else

cmd->HD==1?15:cmd->ED==1?36:9, 1, printf(" diskette on %c: with %dK \n",

(unsigned char far *) sector0)); cmd->Unidad+'A', sector0->NumSect>>1);

for (i=0; i<MAXFAT; i++) fat[i]=0; /* poner a 0 la futura FAT */

fat[0]=sector0->MediaId; fat[1]=fat[2]=0xFF;

int FormatearDisco (sector0, fat, buffer, cmd, bytes_def, segundos)

Boot *sector0; for (i=0; i < ((unsigned long) MAXSECT <<9); i++) buffer[i]=0;

unsigned char far *fat;

unsigned char far *buffer; cilindros=sector0->NumSect/(sector0->SectPista*sector0->Caras);

Parametros *cmd; spista=sector0->SectPista; *bytes_def=0L;

long *bytes_def;

int *segundos; fases=1L*cilindros*sector0->Caras*(1+(1-cmd->NoVerify)+sector0->FlagWr);

{ fase=0L;

unsigned long dir, tiempo, rest, tini, hist[86], i, fase, fases;

int cilindros, cilindro, cabezal, intento, error=1, spista, t; tini=*cbios;

for (cilindro=0; cilindro < cilindros ; cilindro++) {

if (cmd->G!=-1) for (cabezal=0; cabezal<sector0->Caras; cabezal++) {

if (sp) for (intento=0; intento<3; intento++) {

printf("\r AVISO: ¡Valor de GAP alterado con opción /G!\n"); if (sp)

else printf("\r Cilindro %2d - Cara %d [F-] %3lu%%",

printf("\r WARNING: GAP value modified with /G switch!\n"); cilindro, cabezal, fase*100/fases);

else

if (cmd->HD>1) printf("\r Cylinder %2d - Side %d [F-] %3lu%%",

if (sp) cilindro, cabezal, fase*100/fases);

printf("\r AVISO: ¡Parámetro indocumentado /D%d activo!\n", if (error) biosdsk (0, cmd->Unidad, 0, 0, 0, 0, NULL);

cmd->HD-2); t=0; while (bioskey(1)) t=bioskey(0);

else if ((t & 0xFF)==0x1B) { error=1; goto AbortFormat; }

printf("\r WARNING: Undocumented /D%d switch activated!\n", else if ((t==0x1000) && (cilindro>1)) goto FinFormat;

cmd->HD-2); error=biosdsk (5, cmd->Unidad, cabezal,

cilindro, 0, 0x7F, (unsigned char far *)

if (cmd->MarcaPoco) sector0);

if (sp) if (sector0->FlagWr==1) if (!error && (cilindro | cabezal)) {

printf("\r AVISO: ¡Parámetro indocumentado /W activo!\n"); printf ("\b\b\b\b\b\b\b\b\bI-] %3lu%%",(fase+1)*100/fases);


284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

error=biosdsk (3, cmd->Unidad, cabezal | 0x80, {

cilindro, 1, spista, buffer); unsigned long st, ua, bt;

} int cilindros;

if (!error&&(!cmd->NoVerify||(cmd->NoVerify && cilindro<2))) { char label[12];

printf ("\b\b\b\b\b\b\b\b\b-V] %3lu%%",

(fase+1+sector0->FlagWr)*100/fases); st = s0->NumSect - s0->NumFats * s0->SectFat

error=biosdsk (2, cmd->Unidad, cabezal, - s0->SectReserv - (s0->FichRaiz>>4);

cilindro, 1, spista, buffer); ua = st / (unsigned long) s0->SectCluster; bt = st*512L;

if (!error) break; cilindros=s0->NumSect/(s0->SectPista*s0->Caras);

if (error) strncpy (label, s0->Titulo, 11); label[11]=0;

if ((error==128) || (error==3) || (error==6))

goto AbortFormat; /* error fatal */ if (sp) {

else printf ("\r Tiempo transcurrido formateando %2d:%02d\n",

if (!MarcaFat(cmd->Unidad, cmd->MarcaPoco, sector0, tiempo/60, tiempo % 60);

cilindro, cabezal, fat, buffer, bytes_def)) printf (" Volúmen con número de serie %04X-%04X",

goto AbortFormat; /* error en áreas del sistema */ (int) (s0->NumSerie >> 16), (int) s0->NumSerie);

fase+=(1+(1-cmd->NoVerify)+sector0->FlagWr); if (strstr(label, "NO NAME ")==NULL)

} printf (" y etiqueta %11s\n", label);

hist[cilindro]=*cbios; else

tiempo=(*cbios-tini)*10/182; printf("\n");

printf(" [%2lu:%02lu ]", tiempo/60, tiempo % 60); printf ("%9d ficheros permitidos en el raíz.\n",

if (cilindro>5) { s0->FichRaiz);

rest=(*cbios-hist[cilindro-5])*(cilindros-cilindro)*10/910; printf ("%9d unidades de asignación.\n", ua);

printf("\b+%2lu:%02lu =%2lu:%02lu ]", rest/60, rest % 60, printf ("%9d bytes por unidad de asignación.\n",

(tiempo+rest)/60, (tiempo+rest) % 60); s0->SectCluster*512);

} printf ("%9lu bytes totales en el disco.\n", bt);

if (!error && (cilindro>79)) /* verificar siempre aquí */ { printf ("%9lu bytes en sectores defectuosos.\n", bd);

error=biosdsk (2, cmd->Unidad, 0, cilindro-1, 1, spista, buffer); printf ("%9lu bytes disponibles en el disco.\n", bt-bd);

if (error) { /* no soportadas tantas pistas */ if (cilindros!=cmd->Pistas)

cilindros=cilindro; cilindro-=2; printf(" Aviso: formateado con %dK (esta unidad sólo"

biosdsk (0, cmd->Unidad, 0, 0, 0, 0, NULL); " soporta %d pistas).\n", s0->NumSect>>1, cilindros);

} }

} else {

} printf ("\r Time elapsed in the process %2d:%02d\n",

tiempo/60, tiempo % 60);

if (cmd->Pistas!=cilindros) { /* no soportadas tantas pistas */ printf (" Volume serial number is %04X-%04X",

t=cmd->Pistas; (int) (s0->NumSerie >> 16), (int) s0->NumSerie);

cmd->Pistas=cilindros; /* nº pistas correcto */ if (strstr(label, "NO NAME ")==NULL)

CrearSector0 (sector0, *cmd); /* sector de arranque final */ printf (" labeled %11s\n", label);

cmd->Pistas=t; /* restaurar parámetro */ else

} printf("\n");

printf ("%9d file capacity of root directory.\n",

FinFormat: error=InicializaDisco(cmd->Unidad, sector0, fat, buffer); s0->FichRaiz);

printf ("%9d total clusters on disk.\n", ua);

AbortFormat: printf("\r"); for (i=0; i<79; i++) printf(" "); printf ("%9d bytes per cluster.\n",

s0->SectCluster*512);

*segundos=(*cbios-tini)*10/182; printf ("%9lu total bytes on disk.\n", bt);

printf ("%9lu bytes on bad sectors.\n", bd);

return (error); printf ("%9lu bytes available on disk.\n", bt-bd);

} if (cilindros!=cmd->Pistas)

printf(" Note: formatted with %dK (this drive supports"

" only %d tracks).\n", s0->NumSect>>1, cilindros);

void InformeDisco (s0, cmd, bd, tiempo) }

Boot *s0; }

Parametros *cmd;

long bd;

int tiempo; void IncrementarEtiqueta (Parametros *cmd)


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

{ unsigned char far *fat;

int j=10; unsigned char far *buffer;

long *bytes_mal;

while ((cmd->Volumen[j]==' ') && j) j--; {

unsigned malclus, i, ini, tamsys;

while (j)

if ((cmd->Volumen[j] >= '0') && (cmd->Volumen[j] <= '8')) { tamsys = sector0->NumFats*sector0->SectFat+(sector0->FichRaiz>>4)+1;

cmd->Volumen[j]++;

break; for (i=1; i<=sector0->SectPista; i++) {

} ini=(cil*sector0->Caras+cab)*sector0->SectPista+i-1;

else if (cmd->Volumen[j] == '9') { if (modosuave)

cmd->Volumen[j]='0'; malclus=biosdsk (2, unidad, cab, cil, i, 1, buffer);

j--; else

} malclus=1; /* por defecto marcar la pista entera */

else break; if (malclus) {

} if (ini<tamsys) break; /* error en áreas del sistema */

*bytes_mal+=sector0->SectCluster*512L;

ini-=tamsys; ini=ini/sector0->SectCluster+2;

void DiagnosticoError (int codigo) if (ini % 2) { /* posición impar */

{ fat [ini*3/2] = fat [ini*3/2] & 0x0F | 0x70;

if (sp) { fat [ini*3/2+1] = 0xFF;

switch (codigo) { }

case 1: printf("\r Formateo interrumpido por el usuario."); else { /* posición par */

break; fat [ini*3/2] = 0xF7;

case 2: printf("\r La densidad seleccionada es incorrecta."); fat [ini*3/2+1] = fat [ini*3/2+1] & 0xF0 | 0x0F;

break; }

case 3: printf("\r Disquete protegido contra escritura."); ini=0x7FFF;

break; }

case 6: }

case 128: printf("\r Unidad no preparada (¿puerta abierta?)."); return (ini>=tamsys);

break; }

default: printf("\r Anomalía general: ¿densidad

incorrecta?.");

break; int TipoDrive (int unidad)

} {

} union REGS r;

else {

switch (codigo) { r.h.ah=8; r.h.dl=unidad;

case 1: printf("\r Format aborted by user."); int86 (0x13, &r, &r);

break;

case 2: printf("\r Selected density is incorrect."); return ((unsigned char) r.h.bl);

break; }

case 3: printf("\r Diskette is write-protected.");

break;

case 6: InicializaDisco (unidad, sector0, fat1, buffer)

case 128: printf("\r Drive not ready (door open?)."); int unidad;

break; Boot *sector0;

default: printf("\r General failure: incorrect density?."); unsigned char far *fat1;

break; unsigned char far *buffer;

} {

} unsigned char far *p;

printf(" \n"); int sectpista0=sector0->Salto[sector0->OffsetPista0],

} spraiz=sector0->SectFat*2+1,

error;

Root raiz;

int MarcaFat (unidad, modosuave, sector0, cil, cab, fat, buffer, struct time h;

bytes_mal) struct date f;

Boot *sector0;

int unidad, modosuave, cil, cab; memset (buffer, 0, (unsigned long) MAXSECT << 9);
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

memset (&raiz, 0, sizeof (raiz)); {

int frec1=50, frec2=6000;

if (strstr(sector0->Titulo, "NO NAME ")==NULL) {

strncpy (raiz.Etiqueta, sector0->Titulo, 11); SonidoOn();

raiz.Tipo=8; while (frec1<5000) {

gettime (&h); getdate (&f); Sonido (frec1); PicoRetardo(); Sonido (frec1+1000); PicoRetardo();

raiz.Fecha=((f.da_year-1980)<<9) | (f.da_mon<<5) | f.da_day; Sonido (frec2); PicoRetardo(); Sonido (frec2-1000); PicoRetardo();

raiz.Hora=(h.ti_hour<<11) | (h.ti_min<<5) | (h.ti_sec>>1); frec1+=10; frec2-=10;

} }

SonidoOff();

p=buffer; }

memcpy (p, sector0, 512); /* BOOT físico */

p+=512;

memcpy (p, fat1, sector0->SectFat*512); /* FAT1 (la 2 emulada) */ void SonidoOn()

p+=sector0->SectFat<<9; {

memcpy (p, sector0, 512); /* BOOT virtual */ disable(); outportb (0x61, inportb (0x61) | 3); enable();

if (sector0->SectPista>=15) /* HD */ { outportb (0x43, 182); /* preparar canal 2 */

p+=512; }

memcpy (p, &Boot2mCode, Boot2mLong); /* código SuperBOOT */

p=buffer+(spraiz<<9); void SonidoOff()

memcpy (p, &raiz, sizeof(raiz)); /* 1ª entrada ROOT */ {

disable(); outportb (0x61, inportb (0x61) & 0xFC); enable();

biosdsk (0, unidad, 0, 0, 0, 0, NULL); }

error=biosdsk(3, unidad, 0x80, 0, 1, sectpista0, buffer);

if (!error)

error=biosdsk(3, unidad, spraiz/sector0->SectPista, 0, void Sonido (int frecuencia)

(spraiz % sector0->SectPista) + 1, 1, &buffer[spraiz*512]); {

return (error); unsigned periodo;

periodo=1193180L/frecuencia;

outportb (0x42, periodo & 0xFF); outportb (0x42, periodo >> 8);

void SonidoSube() }

int frec=50;

int EsperarCambioDisco (int disquetera, int flash)

SonidoOn(); {

while (frec<5000) { int i, unidad;

Sonido (frec); PicoRetardo(); Sonido (frec+1000); PicoRetardo(); long hora, iter;

frec+=10;

} unidad=disquetera;

SonidoOff(); if (FdswapOn()) unidad^=1; /* unidades intercambiadas por FDSWAP */

while (kbhit()) (void) getch(); /* limpiar buffer teclado */

void SonidoBaja() pokeb(0x40,0x3F, peekb(0x40, 0x3F) & 0xF0); /* "motores apagados" */

int frec=6000; do { /* esperar que retiren el disquete */

hora=*cbios+5;

SonidoOn(); while (*cbios<hora);

while (frec>1050) { outportb (FD_DOR, (1<<(unidad+4)) | unidad | 4+8); /* encender */

Sonido (frec); PicoRetardo(); Sonido (frec-1000); PicoRetardo(); i=inportb (FD_DIR); /* leer línea de cambio */

frec-=10; outportb (FD_DOR, unidad | 4+8); /* apagar motor */

} i = (i >> 7) | kbhit();

SonidoOff(); } while (!i);

if (flash) /* intento de bajar la línea de cambio */

iter=2000000000L;

void SonidoError() else


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

iter=8L; { /* no esperando más de 440 ms */

while (i && !kbhit()) { /* y parpadeo del LED */ int i=0, rd;

hora=*cbios+6; long t;

pokeb (0x40, 0x40, 0xFF); /* para BIOS pelmas no estándar */

outportb (FD_DOR,(1<<(unidad+4)) | unidad | 4+8); /* encender */ do {

pokeb(0x40,0x3F, peekb(0x40, 0x3F) | (1<<unidad)); i++; t=*cbios;

posicionar (unidad, 1); while ((t==*cbios) && ((rd=inportb(FD_STATUS)>>7)==0));

while ((*cbios<hora) && !kbhit()); } while ((i<8) && !rd);

posicionar (unidad, 0);

i = inportb (FD_DIR) >> 7; /* leer línea de cambio */ if (rd) return (inportb (FD_DATA)); else return (-1); /* fallo */

if (i && !iter) { }

outportb (FD_DOR, unidad | 4+8); /* apagar motor */

pokeb(0x40,0x3F, peekb(0x40, 0x3F) & 0xF0);

hora+=12; void EsperarInt() /* Esperar interrupción no más de 2 seg. */

while ((*cbios<hora) && !kbhit()); {

} int i=0;

if (iter) iter--; long t;

do {

/* simular cambio de disco para anular efecto de bajada de línea */ i++; t=*cbios;

while ((t==*cbios) && !(*irq6 & 0x80));

biosdsk (5, disquetera, 0, 0, 0xFF, 0x7F, NULL); /* función de 2M */ } while ((i<37) && !(*irq6 & 0x80));

/* 3 segundos para detención del motor */ *irq6=*irq6 & 0x7F;

pokeb (0x40, 0x40, 54);

return (kbhit()?(getch() & 0xFF)!=0x1B:1);

} void CardWare (char *nfich, int discos)

int fich, aviso=0, lcad, valor=CARDWARE;

void posicionar (int unidad, int cilindro) /* mover cabezal */ struct ftime fechahora;

{ unsigned char contador,

outfdc (0xF); /* comando 'Seek' */ chk[10],

outfdc (unidad); /* byte 1 de dicho comando */ cmp[]="Cnt",

outfdc (cilindro); num[]="000";

EsperarInt(); /* esperar interrupción */ lcad=strlen(cmp)+1;

if ((fich=open(nfich, O_BINARY | O_RDWR))==-1) return;

outfdc (8); /* comando 'leer estado de interrupciones' */ if (getftime (fich, &fechahora)==-1) { close(fich); return; }

if (lseek (fich, -lcad, SEEK_END)==-1) { close(fich); return; }

(void) infdc(); (void) infdc(); if (read (fich, chk, lcad)==-1) { close(fich); return; }

} chk[lcad-1]=0;

if (strcmp(chk, cmp)) /* contador no inicializado */ {

write (fich, cmp, lcad);

void outfdc (unsigned char dato) /* enviar byte al FDC */ if (discos) discos--;

{ /* no esperando más de 440 ms */ }

int i=0, rd; if (lseek (fich, -1L, SEEK_END)==-1) { close(fich); return; }

long t; if (read (fich, &contador, 1)==-1) { close(fich); return; }

contador+=discos;

do { if (contador >= CARDWARE) {

i++; t=*cbios; contador-=CARDWARE;

while ((t==*cbios) && ((rd=inportb(FD_STATUS)>>7)==0)); if (contador > (CARDWARE>>1)) /* posible fallo extraño */

} while ((i<8) && !rd); contador=CARDWARE>>1;

aviso++; /* avisar (si se puede actualizar 2MF.EXE) */

if (rd) outportb (FD_DATA, dato); }

} if (lseek (fich, -1L, SEEK_END)==-1) { close(fich); return; }

if (write (fich, &contador, 1)==-1) { close(fich); return; }

flushall();

int infdc() /* leer byte del FDC */ setftime (fich, &fechahora);


284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

close (fich); textcolor (LIGHTCYAN); cputs ("very happy");

textcolor (LIGHTRED);

num[0]=valor / 100 +'0'; valor%=100; cputs (" with you");

num[1]=valor / 10 +'0'; valor%=10; gotoxy (15,13); cputs ("and within next "); cputs (num); cputs("

num[2]=valor+'0'; ones I will thank you again.");

gotoxy (15,15); textcolor (LIGHTGREEN); cputs ("Good luck!");

if (aviso) textcolor (WHITE);

if (sp) { gotoxy (1,16);

clrscr(); }

textcolor (LIGHTCYAN + BLINK); textbackground (BLUE); }

gotoxy (27, 5);

cputs(" ¡¡AVISO MUY IMPORTANTE!! ");

textcolor (LIGHTRED); textbackground (BLACK); int HablaSp() /* devolver 1 si mensajes en castellano */

gotoxy (15,7); cputs ("Esta copia de 2MF ya ha formateado "); {

textcolor (YELLOW); cputs (num); textcolor union REGS r; struct SREGS s;

(LIGHTRED); char info[64];

cputs (" disquetes."); int i, idioma, spl[]={54, 591, 57, 506, 56, 593, 503, 34, 63, 502,

gotoxy (15,8); cputs ("Recuerda que 2M es un programa "); 504, 212, 52, 505, 507, 595, 51, 80, 508, 598, 58, 3, 0};

textcolor (LIGHTGREEN); cputs ("CardWare");

textcolor (LIGHTRED); idioma=0; /* supuesto el inglés */

cputs (". Si aún");

gotoxy (15,9); cputs ("no has enviado tu "); if (_osmajor>=3) {

textcolor (LIGHTMAGENTA); cputs ("tarjeta r.x.ax=0x3800; s.ds=FP_SEG(info); r.x.dx=FP_OFF(info);

postal"); textcolor (LIGHTRED); intdosx (&r, &r, &s);

cputs (" al autor, no"); i=0; while (spl[i++]) if (spl[i-1]==r.x.bx) idioma=1;

gotoxy (15,10); cputs ("deberías continuar utilizando estos }

discos.");

gotoxy (15,12); cputs ("Si ya la has enviado, estoy "); return (idioma);

textcolor (LIGHTCYAN); cputs ("muy contento"); }

textcolor (LIGHTRED);

cputs (" contigo");

gotoxy (15,13); cputs ("y dentro de otros "); cputs (num); ###################################################################

cputs(" volveré a felicitarte.");

gotoxy (15,15); textcolor (LIGHTGREEN); cputs ("¡Suerte!");

textcolor (WHITE); ;┌───────────────────────────────────────────────────────────────────┐

gotoxy (1,16); ;│ │

} ;│ █████ █ █ █▀▀▀▀ █ █ ▀▀█▀▀ ▀▀█▀▀ │

else { ;│ █ ██ ██ █ █ █ █ █ │

clrscr(); ;│ █████ █ █ █ █▀▀ █▀█ █ █ │

textcolor (LIGHTCYAN + BLINK); textbackground (BLUE); ;│ █ █ █ █ █ █ █ █ │

gotoxy (27, 5); ;│ █████ █ █ █ █ █ ▄▄█▄▄ █ │

cputs(" ¡¡VERY IMPORTANT NOTICE!! "); ;│ │

textcolor (LIGHTRED); textbackground (BLACK); ;│ FICHERO CON CODIGO ENSAMBLADOR LINKABLE CON 2MF.C │

gotoxy (15,7); cputs ("This 2MF program has already formatted ;│ │

"); ;│ Código de 2M que será almacenado en los sectores de │

textcolor (YELLOW); cputs (num); textcolor ;│ los disquetes, sectores de arranque de los mismos y │

(LIGHTRED); ;│ algunas funciones ASM de utilidad. │

cputs (" disks."); ;│ │

gotoxy (15,8); cputs ("Remember that 2M is a "); ;│ Proceso: │

textcolor (LIGHTGREEN); cputs ("CardWare"); ;│ │

textcolor (LIGHTRED); ;│ TASM 2MFKIT /m5 /mx │

cputs (" program. If you"); ;│ │

gotoxy (15,9); cputs ("haven't send still your "); ;│ El fichero 2MFBOOT.DB que se carga con INCLUDE debe obtenerse │

textcolor (LIGHTMAGENTA); cputs ("postcard"); ;│ previamente a partir de 2MFBOOT.ASM con ayuda de 2MFBMAKE.BAS │

textcolor (LIGHTRED); ;│ │

cputs (" to the author,"); ;└───────────────────────────────────────────────────────────────────┘

gotoxy (15,10); cputs ("you musn't continue on using this

diskettes."); _DATA SEGMENT WORD PUBLIC 'DATA'

gotoxy (15,12); cputs ("If you have send it yet, I'm "); ASSUME CS:_DATA, DS:_DATA
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

LES BX,DWORD PTR [BP+18]

PUBLIC _Boot2mCode, _Boot2mLong MOV SI,"2M"

PUBLIC _biosdsk, _PicoRetardo, _NuevaInt24 RETF ; ejecutar INT 13h

PUBLIC _BootHDPrg, _BootHDPrgLong bdsk_ret: POP DI

PUBLIC _BootDDPrg, _BootDDPrgLong POP SI

POP ES

; ------------ Código 2M para grabar en los 5 sectores ocultos de los POP BP

; disquetes de alta densidad al formatear. MOV AL,AH ; resultado

MOV AH,0

_Boot2mCode: INCLUDE 2MFBOOT.DB RET

_Boot2mLong DW $-OFFSET _Boot2mCode _biosdsk ENDP

; ------------ Sectores de arranque de los disquetes 2M. ; ------------ Pequeño retardo de medio milisegundo.

_BootHDPrg: INCLUDE 2MBOOTHD.INC _PicoRetardo PROC FAR

_BootHDPrgLong DW $-OFFSET _BootHDPrg PUSH AX

PUSHF

_BootDDPrg: INCLUDE 2MBOOTDD.INC POP AX

_BootDDPrgLong DW $-OFFSET _BootDDPrg OR AH,70h

PUSH AX

; ------------ Rutina de acceso a disco vía BIOS. No se utiliza la POPF

; función biosdisk() del compilador porque en algunas PUSHF

; versiones del mismo hace tonterías que no debe. Así, POP AX

; además, se puede llamar a INT 13h con CALL (bueno, AND AH,0F0h

; con RETF) para que dentro de WINDOWS 2MF /M no de CMP AH,0F0h ; ¿es PC/XT?

; problemas; además, la función de formateo de 2M JE xt

; requiere SI="2M" al llamar. MOV CX,33 ; 18Έ1193180*33*1000 = 0.5 ms

wrf: IN AL,61h

_biosdsk PROC FAR AND AL,10h

PUSH BP CMP AL,AH

MOV BP,SP JE wrf ; esperar pulso refresco memoria

PUSH ES MOV AH,AL

PUSH SI LOOP wrf

PUSH DI xt: POP AX

PUSHF ; estructura para futuro IRET RET

PUSH CS _PicoRetardo ENDP

LEA AX,bdsk_ret

PUSH AX ; ------------ Nuevo gestor de errores críticos.

XOR AX,AX

MOV ES,AX _NuevaInt24 PROC

PUSH ES:[13h*4+2] ; INT 13h -> pila MOV AL,3 ; error en la función DOS invocada.

PUSH ES:[13h*4] IRET

MOV AH,[BP+6] _NuevaInt24 ENDP

MOV DL,[BP+8]

MOV DH,[BP+10] _DATA ENDS

MOV CH,[BP+12]

MOV CL,[BP+14] END

MOV AL,[BP+16]

12.6.7.5 - UN PROGRAMA PARA MEDIR EL RENDIMIENTO DE LOS DISQUETES.

En las páginas donde se describía el funcionamiento de 2M aparecía una tabla con los tiempos
cronometrados de un COPY de múltiples ficheros, desde y hacia un disquete en los formatos de disco más
comunes. Sin embargo, resulta interesante conocer la velocidad real del sistema de disco cuando éste es
utilizado óptimamente: acceso a múltiples pistas completas y consecutivas en el disco. Los buenos programas de
copia de discos, que leen de un golpe todas las pistas consecutivas que pueden antes de guardarlas en un fichero
auxiliar (o que las almacenan en EMS ó XMS), dependerán de la velocidad que sea capaz de dar el formato de
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

disco empleado, ya que las disqueteras giran a una velocidad fija en todos los ordenadores. Si pierden tiempo
entre pista y pista (tal vez por escribirlas en el fichero auxiliar una por una) la velocidad obtenida podría
dividirse por dos, al intentar pillar el primer sector de la siguiente pista justo cuando acaba de pasar de largo por
delante del cabezal.

┌────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┐
│ Velocidad máxima teórica sin │ Velocidad real en Kb/seg estimada por 2M-FDTR (nivel BIOS). │
│ considerar tiempos de acceso ├───────────────┬───────────────┬───────────────┬───────────────┬───────────────┤
│ pista-pista ni el porcentaje │ FORMAT │ FDFORMAT (**) │ FDFORMAT (***)│ 2MF 3.0 /F │ 2MF 3.0 /M │
│ de superficie magnética que ├───────┬───────┼───────┬───────┼───────┬───────┼───────┬───────┼───────┬───────┤
│ se aprovecha en cada pista. │ Lect. │ Escr. │ Lect. │ Escr. │ Lect. │ Escr. │ Lect. │ Escr. │ Lect. │ Escr. │
┌───────┼────────────────────────────────┼───────┴───────┼───────┴───────┼───────┴───────┼───────┴───────┼───────┴───────┤
│ 5¼-DD │ 36,62 Kb/seg (300 Kbit/seg) │ 18.16 18.16 │ 22.11 22.12 │ 25.00 25.00 │ 25.04 25.00 │ 16.49 16.49 │
│ 5¼-HD │ 61,03 Kb/seg (500 Kbit/seg) │ 30.13 30.13 │ 39.73 39.73 │ 25.26 25.23 │ 46.33 46.33 │ 28.50 28.47 │
│ 3½-DD │ 30,52 Kb/seg (250 Kbit/seg)*│ 15.05 15.05 │ 19.32 19.32 │ 21.78 21.75 │ 25.72 25.76 │ 16.25 16.25 │
│ 3½-HD │ 61,03 Kb/seg (500 Kbit/seg) │ 30.14 30.14 │ 39.58 39.53 │ 24.79 24.79 │ 48.49 48.50 │ 28.74 28.77 │
└───────┴────────────────────────────────┴───────────────┴───────────────┴───────────────┴───────────────┴───────────────┘
(*) 2M emplea 300 Kbit/seg (no es compatible con controladoras de doble densidad de PC/XT).

(**) Usando el formato estándar del DOS (360-720-1.2-1.44) y los parámetros /X e /Y adecuados.

(***) Formatos de máxima capacidad soportados (820-1.48-1.72) y los parámetros /X e /Y adecuados.

Con objeto de uniformizar los índices, el siguiente programa de ejemplo realiza la lectura y escritura
completa de un disco (en este último caso, si no contenía datos, ya que se estropearían) llamando a la BIOS. La
primera versión del programa empleaba el DOS (funciones absread() y abswrite() del C) y obtenía
exactamente los mismos índices, aunque problemas de fiabilidad aconsejaron utilizar funciones de la BIOS, con
lo que el programa ya no puede, por ejemplo, analizar el rendimiento de un disco duro (debido a la incomodidad
que supone buscar el sector de arranque a través de la tabla de particiones). Se recorren en lectura y escritura
todos los cilindros del disco, a partir del 1 y llegando hasta el último que exista. El motivo de saltar el cilindro 0
es doble: por un lado, saltar las áreas del sistema (de cara a no escribir sobre el sector de arranque, por ejemplo,
ya que por simplicidad se escribe basura y no lo que se ha leído al principio); por otro lado, los tiempos de este
cilindro pueden ser diferentes de los obtenidos en los demás cilindros, bien debido a la interferencia del sistema
o los programas de caché o, simplemente, porque tiene un formato físico muy especial (como es el caso de los
disquetes 2M). En el caso de los disquetes 2M, de esta forma no se tiene en cuenta el tiempo extra que se pierde
en este primer cilindro debido a la extraña maniobra que supone simular la existencia de la segunda copia de la
FAT (que implica volver momentáneamente al primer cabezal después de haber pasado al segundo).

El programa, 2M-FDTR (2M Floppy Data Transfer Rate), utiliza el contador de hora de la BIOS unido
al temporizador 8254 para cronometrar. Antes de comenzar el test y arrancar el cronómetro se lee uno de los
últimos sectores del cilindro 1 para asegurar que el cabezal está ya sobre el mismo y a punto de pillar el primer
sector. El buffer donde se realizará la lectura/escritura es asignado de tal manera que no cruce una frontera de
DMA (para que INT 13h no tenga que segmentar en varias fases la operación, lo que disminuiría la velocidad).
El acceso a INT 13h se realiza de manera directa, ya que la versión 3.1 del compilador hace alguna oscura
maniobra con biosdisk y al final termina perdiendo demasiado tiempo (lo suficiente como para que en alguna
máquina el disco aparente ser más lento de lo que realmente es). Con Borland C 2.0 no hay problemas, pero...

NOTA:Los resultados de 2M-FDTR contradicen los que facilitan muchos afamados programas comerciales de test, sencillamente porque dichos programas
no miden correctamente (y de hecho dan en cada ordenador, e incluso en la misma máquina entre ejecuciones consecutivas, resultados diferentes
y contradictorios). Si estuviera instalado un programa de caché, los resultados podrían verse alterados por lo que se recomienda no instalarlos
para la prueba. De todas maneras, con un disquete recién introducido no hay programa alguno de caché que pueda disminuir el tiempo de lectura
del mismo (quizá sí la escritura). Insisto en que los resultados de 2M-FDTR son reales y cualquier programa de aplicación que acceda a disco a
medio o bajo nivel, como el propio 2M-FDTR, puede lograrlos si utiliza correctamente las funciones de acceso a sectores del DOS o de la
BIOS.

/********************************************************************* * *

* * * Para Borland C++ 2.0 ó superior en modelo de memoria large. *

* 2M-FDTR 2.2 - Cálculo de la tasa de transferencia de disquetes. * * *

* (C) 1994 Ciriaco García de Celis. * *********************************************************************/


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

if ((buf=farmalloc (SMAX << 1))==NULL) {

#define SMAX 23*512L /* máximo soportado: 63 sectores por pista */ if (sp)

#define RD 2 printf(" n ¡Memoria insuficiente!.\n");

#define WR 3 else

printf(" n Insufficient memory!.\n");

#include <stdio.h> exit (2);

#include <stdlib.h> }

#include <conio.h>

#include <dos.h> dir = ((unsigned long) FP_SEG(buf) <<4) + FP_OFF(buf);

#include <alloc.h> if ((dir>>16)!=((dir+SMAX)>>16)) buf+=SMAX; /* por el DMA */

#include <math.h>

#include <string.h> if (biosdsk (2, unidad, 0, 0, 1, 1, sector0)) {

printf(" n Fatal ????.\n");

int evalua_io (int, unsigned char far *, int, int, int, int), exit (4);

biosdsk (int, int, int, int, int, int, unsigned char far *), }

HablaSp (void);

void ayuda (void); sectores=sector0[24]+256*sector0[25]; cabezales=sector0[26];

unsigned long tiempo (void); cilindros=(sector0[19]+256*sector0[20])/sectores/cabezales;

int sp; /* 1-español 0-inglés */ if (sectores>63) {

if (sp)

printf(" n ¡No soportados más de 63 sectores por pista!.\n");

void main (int argc, char **argv) else

{ printf(" n Not supported more than 63 sectors per track!.\n");

unsigned char sector0[512], far *buf; exit (3);

unsigned long dir; }

int unidad, cilindros, sectores, cabezales;

struct dfree dsk; if (sp) {

printf(" n Determinando tasa de transferencia BIOS a disco.\n");

sp=HablaSp(); /* determinar idioma del país */ printf(" + Rendimiento en lectura:\n");

if ((!strcmp(strupr(argv[1]),"/I")) || else {

(!strcmp(strupr(argv[2]),"/I"))) printf(" n Computing BIOS floppy data transfer rate.\n");

sp^=1; /* parámetro /I */ printf(" + Read performance:\n");

printf("\n 2M Floppy Data Transfer Rate 2.2\n");

if (evalua_io (RD, buf, unidad, cilindros, sectores, cabezales)) {

unidad=(*argv[1] | 0x20)-'a'; if (dsk.df_avail < dsk.df_total) {

if (sp)

if ((argc<2) || ((unidad!=0) && (unidad!=1))) ayuda(); printf(" + Disquete no vacío -> test de escritura

omitido.\n");

getdfree (unidad+1, &dsk); else

printf(" + Diskette not empty -> write test skipped.\n");

if (dsk.df_sclus==65535) { exit (4);

if (sp) }

printf(" n Error de acceso a la unidad.\n"); if (sp)

else printf(" + Rendimiento en escritura:\n");

printf(" n Error on drive access.\n"); else

exit (3); printf(" + Write performance:\n");

} evalua_io (WR, buf, unidad, cilindros, sectores, cabezales);

if ((long) dsk.df_total*dsk.df_sclus>65535L) { }

if (sp)

printf(" n Unidades de más de 32M no soportadas.\n");

else void ayuda()

printf(" n Drive above 32M can not be tested.\n"); {

exit (1); printf(" (C) 1994 Ciriaco García de Celis.\n");

} if (sp) {
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

printf(" n Indica la unidad A: o B: para medir su

velocidad.\n"); r.h.ah=cmd; r.h.dl=drive; r.h.dh=head; r.h.ch=track; r.h.cl=sector;

printf(" - El test se realiza accediendo a través de las r.h.al=nsects; s.es=FP_SEG(buffer); r.x.bx=FP_OFF(buffer);

funciones BIOS.\n");

printf(" - El buffer E/S no cruza nunca una frontera de DMA int86x (0x13, &r, &r, &s);

de 64K.\n"); return (r.h.ah);

printf(" - El acceso afecta siempre a pistas completas.\n"); }

printf(" - El software residente puede alterar el

resultado.\n");

printf(" - El test de escritura no se realiza si el disquete int evalua_io (operacion, buffer, unidad, cilindros, nsect, cabezales)

contiene datos.\n"); int operacion, unidad, cilindros, nsect, cabezales;

} unsigned char far *buffer;

else { {

printf(" n Choose drive A: or B: to test it absolute int cilindro, cabezal, fin_io=0, res;

speed.\n"); unsigned long tini, tfin;

printf(" - Test is performed always through BIOS float bseg;

functions.\n");

printf(" - The I/O buffer never cross a 64K DMA /* Leer parte del cilindro 1 para colocar el cabezal al inicio. */

frontier.\n"); /* Se leen dos sectores alejados para esquivar la caché de 2M y */

printf(" - Access is done always using the whole track.\n"); /* forzar un auténtico posicionamiento en este cilindro */

printf(" - The TSR software may alter results.\n");

printf(" - Write test is not performed if diskette contains outportb (0x43, 0x36); /* asegurar que cnt0 usa byte bajo-alto */

data.\n");

exit (255);

unsigned long tiempo()

unsigned long tm;

asm {

cli

mov al,6

out 43h,al /* enclavamiento contador 0 */

in al,40h

mov ah,al

in al,40h

xchg ah,al

neg ax /* ax = valor del contador 0 del 8254 */

push ds

mov bx,40h

mov ds,bx

mov bx,ds:[6ch] /* bx = contador hora BIOS */

sti

pop ds

mov word ptr tm,ax

mov word ptr tm+2,bx

return (tm);

int biosdsk (cmd, drive, head, track, sector, nsects, buffer)

int cmd, drive, head, track, sector, nsects;

unsigned char far *buffer;

union REGS r; struct SREGS s;


EL HARDWARE DE APOYO AL MICROPROCESADOR 284

outportb (0x40, 0); outportb (0x40, 0);

biosdsk (2, unidad, 0, 1, 1, 1, buffer); /* anular caché 2M */

biosdsk (2, unidad, 0, 1, nsect-2, 1, buffer); /* sincronizar */

tini=tiempo(); res=0;

for (cilindro=1; cilindro<cilindros; cilindro++)

for (cabezal=0; cabezal<cabezales; cabezal++) {

if (kbhit()) if (getch()==27) goto aborta_io;

if (res) {

if (sp)

printf("\r ¡Fallo en el acceso a disco!.\n");

else

printf("\r Failure on disk access!.\n");

goto aborta_io;

if (sp)

printf("\r\r Cilindro %2d - Cara %d", cilindro, cabezal);

else

printf("\r\r Cylinder %2d - Side %d", cilindro, cabezal);

res=biosdsk (operacion, unidad, cabezal, cilindro, 1, nsect,

buffer);

tfin=tiempo(); fin_io=1;

bseg=(512L*nsect*(cilindros-1)*cabezales)/((tfin-tini)/1193180.0);

if (sp)

printf("\r %7.2f segundos =%7.2f Kb/seg [%7.0f bits/seg]\n",

(tfin-tini)/1193180.0, bseg/1024.0, bseg*8);

else

printf("\r %7.2f seconds =%7.2f Kb/sec [%7.0f bits/sec]\n",

(tfin-tini)/1193180.0, bseg/1024.0, bseg*8);

aborta_io:

printf("\r \r");

return (fin_io);

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);

12.6.7.6 - LA VERSION PARA PC/XT DE 2M: 2MX [Listado no incluido en este libro].

Aunque 2M fue inicialmente concebido para máquinas AT, a partir de la versión 1.2 ha estado
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

acompañado de una versión para PC/XT. El único requisito es que el ordenador esté equipado con una
controladora y unidades de alta densidad. Algunas máquinas modernas de tipo subnotebook, que caben en la
palma de la mano, vienen preparadas para conectar una de estas disqueteras externas. Otros PC/XT de reciente
fabricación traen ya controladoras de alta densidad y BIOS que las soportan, aunque luego el tacaño fabricante
haya colocado una unidad de doble densidad que el usuario puede sustituir. Finalmente, a aquellas máquinas
más antiguas que no pertenecen a ninguna de estas dos categorías, se les puede sustituir la controladora y
unidades de doble densidad por otras de alta, que en el futuro el usuario podrá colocar en su máquina AT
cuando se la compre; se trata por tanto de una inversión rentable. Si bien resulta difícil encontrar actualmente en
el mercado controladoras de alta densidad para PC/XT, el usuario puede optar por poner una de AT. Yo, por
ejemplo, para probar 2MX me vi obligado a pinchar una controladora de 16 bits en un slot de 8 bits. La tarjeta
era una IDE multi-io; sin embargo, la parte alta del bus (que no se puede pinchar al ser de 8 bits el slot) sólo se
utiliza para acceder al disco duro bus AT, pudiendo ser inhibida con el jumper de marras (si bien ni esto resultó
necesario). La parte correspondiente al control de disquetes, y probablemente los puertos serie/paralelo, era
totalmente funcional, ya que sólo opera con la mitad baja del bus.

El principal problema radica en que la BIOS de los PC/XT en el 99% de los casos no está preparada
para soportar alta densidad. Al hacer DIR sobre un disquete de alta densidad nada más encender el ordenador, lo
más probable es que funcione, ya que ésta es la densidad por defecto normalmente. Sin embargo, con los discos
de doble densidad (donde tiene que seleccionar 250 ó 300 Kbit/seg) es imposible sacar el DIR. En cualquier
caso, sacar un DIR es una cosa y otra muy diferente conseguir que el disco funcione. Como la BIOS informa
siempre que todo es de doble densidad, el muy patoso del DOS modifica la tabla base del disco para indicar
como 9 el último número de sector en la pista (¿quién le mandará tocar las variables de la BIOS?) por lo que ni
los discos de alta densidad funcionan a nivel de COPY (el directorio sí aparece porque coincide en los primeros
sectores de las pistas). La solución en este tipo de máquinas pasa por instalar una BIOS más moderna... pero sin
tener que regrabar la eprom. Basta con cargar 2M-XBIOS.EXE, un programa residente que emula la BIOS AMI
de AT en los XT. De hecho, 2MX solicita al usuario la instalación de este driver cuando advierte que no puede
detectar el tipo de las unidades.

En ese sentido, la combinación 2M-XBIOS + 2MX permite a cualquier máquina PC/XT obsoleta
equipada con una barata controladora de disquetes de AT trabajar con discos de cualquier densidad y cualquier
formato (estándar/2M). Los problemas de versiones anteriores de 2MX han sido eliminados gracias a la
extensión BIOS en que se apoya. De hecho, 2MX es en sus últimas versiones prácticamente idéntico a 2M, sólo
cambia en algunos aspectos puntuales relacionados con la diferente arquitectura de los XT respecto a los AT.

12.6.7.7 - LA OPCION BIOS DE 2M: 2M-ABIOS Y 2M-XBIOS [Listados no incluídos en este libro].

Algunos ordenadores poseen una BIOS antigua o con un diseño propio poco compatible en el control
de disco. En estas máquinas, 2M y otros programas de acceso a bajo nivel pueden fallar. En dichos casos, se
puede instalar esta utilidad antes que 2M, y en general que cualquier otro software que acceda al subsistema de
disco. La versión 2M-ABIOS es para AT y 2M-XBIOS para PC/XT.

Estos programas actualizan el soporte de disco flexible al nivel de las BIOS AMI de 1993. Si con ellos
instalados 2M no opera de manera totalmente correcta (aunque en general 2M depende realmente muy poco de
la BIOS, pero ya conozco algún caso al respecto) y en la máquina no está instalado algún otro software de disco
incompatible con 2M, entonces el ordenador no es 100% compatible hardware con el estándar; esto es
particularmente cierto si ni siquiera se reconocen los discos estándar del DOS.

Esta utilidad también sirve para añadir soporte de 1.44M a máquinas con BIOS antigua, algunas de
ellas incluso AT. En estos casos, el usuario debe ignorar la información sobre el tipo de la unidad que pueda
reportar dicha BIOS al arrancar. El programa se carga desde el CONFIG.SYS con una sintaxis sencilla:

DEVICE=2M-ABIOS.EXE [A:tipo] [B:tipo] [/C] [/13]

El consumo de memoria es de unos 3.4-4.2 Kb de RAM, y contiene una emulación al 100% del eficaz
código de control de disco de las BIOS AMI, relevando así por completo de esta tarea a la BIOS del sistema. De
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

ahí que haya sido diseñado en este formato, para forzar al usuario a instalarlo antes de los demás programas de
disco, a los que anularía por completo (ya que nunca más vuelve a llamar a la interrupción de disco anterior). En
AT generalmente no hará falta indicar el tipo de las unidades (0:no hay, 1:360K, 2:1.2M, 3:720K, 4:1.44M,
5:2.88M) pero en PC/XT casi siempre será necesario. La opción /C evita en los equipos AT ajustar la CMOS,
por si la máquina en cuestión tiene un algoritmo no estándar para calcular el checksum de la misma y aparece
un "Incorrect CMOS checksum" al arrancar (muy poco probable). Así mismo, si en algún momento el usuario
dudara acerca de si 2M-ABIOS está controlando realmente las unidades, puede utilizar la opción /13 para
asegurarlo, si bien esta opción es poco recomendable cuando no es estrictamente necesaria (se desvía también
INT 13h además de INT 40h, incluso aunque detecte el soporte de esta última).

El listado comentado de estos programas (realmente uno solo, con ensamblaje condicional en 2M 3.0)
se omite porque ya hay demasiadas rutinas de acceso a disco a bajo nivel en este libro.

12.6.7.8 - LA UTILIDAD 2MDOS [Listado no incluído en este libro].

Debido a la ineficiencia de FORMAT a la hora de crear discos rápidos y teniendo en cuenta la


limitación de DISKCOPY en el sentido de no poder formatear discos destino en formato 2M, se comprende la
necesidad de un sustituto de FORMAT y DISKCOPY. Sin embargo, todos los programas al respecto existentes
en la actualidad, a mi juicio, son un perfecto desastre. La mayoría no son rápidos incluso con discos
optimizados, por una cuestión elemental: no colocar los buffers de transferencia de manera que no crucen las
fronteras de DMA (para evitar que el DOS tenga que hacer accesos redundantes para salvarlas). La mayoría, de
hecho, no generan discos optimizados con la clásica técnica de Sector Sliding (que en absoluto implica
reducción de compatibilidad o fiabilidad; más bien al contrario: es como se debe formatear correctamente un
disco y como de hecho se hace con los discos duros). Otros son poco flexibles y no soportan discos 2M (¡hasta
DISKCOPY los supera en esto!) o tienen absurdas rutinas que encuentran virus en sectores de arranque poco
oficiales, o necesitan VGA y ratón (aparte de ser lentos), o no son fiables...

La solución adoptada ha sido crear un programa residente que haga trabajar a todos los demás (con la
excepción de los que también acceden directamente a la controladora de disco) de la manera adecuada. Se trata
de crear una utilidad para que FORMAT o cualquier otro programa que llame a la BIOS formatee discos
optimizados (aún sin saberlo) y que amplíe los formatos de disco oficiales de la BIOS para que DISKCOPY (y
el DUPDISK de las Norton y programas de similar flexibilidad) sean capaces, durante el proceso de copia, de
formatear el disco destino 2M si es preciso.

Con 2MDOS instalado los discos se formatean automáticamente de manera óptima y DISKCOPY
soporta el formateo de discos 2M. Incluso FORMAT puede crear discos 2M (indicando pistas y sectores) si bien
el de MS-DOS (no DR-DOS) tiene problemas con los de alta densidad y necesita un parámetro opcional (de
todas maneras, 2MF sigue siendo más eficiente). Además 2MDOS da soporte por defecto a disquetes no
estándar, creados por la utilidad FDFORMAT y permite a FORMAT poder crear disquetes FDREAD. El
programa consume 5,7 Kb en equipos sin memoria extendida o 2,5 Kb con ella (sólo 1,7 Kb si no está activo el
soporte para hacer DISKCOPY hacia un disco 2M sin formato; esto es, con sólo las opciones de optimización
de formateo y soporte FDREAD activas).

Por si esto fuera poco, 2MDOS incorpora una nueva técnica para acelerar aún más los discos estándar
de 1.2M y 1.44M, que recibe el nombre de DiskBoost por razones de marketing. El truco consiste en evitar la
necesidad de Sector Sliding, para de esta manera alcanzar, por ejemplo, una tasa de transferencia de datos de 45
Kb/seg en 1.44M (frente a los 39 Kb/seg del Sector Sliding o los 30 Kb/seg del FORMAT habitual). El truco
consiste en añadir un sector adicional en el cabezal 1 y dos en el cabezal 0, que no se usan, algo que no reduce
sensiblemente el nivel de seguridad del disco (sería el equivalente en seguridad a un disco de 1.64M, por
ejemplo). Los sectores adicionales, no usados, son colocados al principio de la pista. De esta manera, cuando la
controladora acaba de acceder a una pista completa en el cabezal 0 (y está al inicio justo de la pista tras
completar una vuelta) se conmuta al cabezal 1 para acceder a la pista siguiente. Recordemos que en el cabezal 1
había un sector no utilizado al principio: este sector pasará por delante del cabezal mientras se conmuta, pero no
transcurrirá demasiado tiempo como para que no se pueda pillar el primer sector de la pista que viene
inmediatamente a continuación. Cuando se acabe de leer la pista en el cabezal 1 (y se está de nuevo al inicio
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

justo de la pista tras completar la vuelta) se conmuta al cabezal 0 pero del siguiente cilindro, algo que lleva más
tiempo que antes... pero para eso ya habíamos dejado dos sectores no utilizados al inicio del cabezal 0. Por
tanto, también da tiempo a pillar el primer sector.

Con la técnica DiskBoost es factible leer o escribir un disco completo de 1.44M en poco más de 31
segundos, al emplear sólo una vuelta por cada pista. La diferencia de velocidad, contra todo pronóstico, es aún
más espectacular en las operaciones COPY o XCOPY normales. Los discos de 1.2M y 1.44M creados por
FORMAT con 2MDOS instalado son un 50% más rápidos en el uso normal.

Sin embargo, 2MDOS no es la solución definitiva. Aunque es útil para que cada cual utilice sus
programas de copia/formateo favoritos de manera óptima, lo ideal sería un programa de copia/formateo
realmente eficiente. Con dicho programa, 2MDOS no sería necesario...

El listado de 2MDOS tampoco se incluye en estas páginas. 2MDOS también incorpora el código
SuperBOOT a los discos 2M de alta densidad que se formatean bajo su control, aunque su tarea es ampliar la
funcionalidad de algunas interrupciones de la BIOS y no realiza accesos directos al hardware.

12.6.7.9 - COMO SUPERAR LOS 2.000.000 DE BYTES EN 3½: 2MGUI [Listado no incluído en el libro].

En cierta ocasión un programa llamado 1968 llegó a mis manos. Se trataba de una utilidad para
formatear discos de 1.44M a esa capacidad. Sin embargo, no funcionaba en mi unidad, ni tampoco en la de mis
máquinas de uso habitual. En alguna de ellas lograba formatear (a base de reintentos ante los errores) todo el
disco, pero por desgracia la primera pista quedaba mal. Nunca logré crear un disco de estos, aunque se que si lo
hubiera conseguido, ese disco -como bien decía el autor en la documentación- sí podría ser leído en las demás
unidades.

El método de este programa consistía en introducir 3 sectores de 4 Kb en cada pista. El problema es que
eso requiere (4096+62)*3 = 12474 bytes, sin contar los GAP entre sectores, y la mayoría de las unidades giran
algo más deprisa de lo normal (y por tanto, se alejan del límite teórico de 12500 bytes por pista). Por otro lado,
26 bytes son incluso pocos para respetar las marcas de inicio de pista y demás. Al final, el tercer sector suele
acabar pisando al primero.

Después de algún tiempo, han aparecido más formateadores que soportan (o dicen soportar) este
formato, alguno incluso en nuestro país. Sin embargo, todos tienen el mismo problema: no hay unidades que
soporten a esos programas. Por tanto, todo parecía indicar que el límite de capacidad se quedaría para siempre
en los 1.72M del FDFORMAT ó los 1.88M de 2M, únicos formatos soportados por todas las unidades y
ordenadores (eso sí, compatibles). Pues no. Cierto día, Jesús Arias tuvo una idea genial y me la contó. A raíz de
esa idea, y tras superar numerosas y difíciles trabas técnicas, finalmente ha sido posible el milagro: lograr
utilizar toda la capacidad disponible en la pista del disco, como si estuviera sin formatear.

El programa que realiza esto, 2MGUI (abreviatura de 2M-Guinness), es ya una realidad. Durante su
desarrollo se han puesto de relieve circunstancias curiosas. Por ejemplo, una determinada unidad admite 12440
bytes por pista al grabar información aleatoria, pero si se escribe toda la pista con bits a 0 ó a 1 sólo caben
12405 bytes. ¿Por qué?: la respuesta sigue siendo un misterio. Las rutinas residentes de 2MGUI aprovechan las
terminaciones normales de error de la controladora (disco protegido contra escritura, sector no encontrado, etc.)
para la detección de errores, aunque graban adicionalmente, en cada pista de datos, un checksum de la
información almacenada junto al número de pista y cabezal reales, para realizar el control de errores cuando la
controladora no puede devolver condiciones de error (debido a una serie de factores técnicos). De esta manera,
la información se graba y recupera con la seguridad de que es correcta -en caso contrario, se detectaría el fallo-.

Realizando pruebas, la capacidad admitida por diversas unidades se mostró directamente relacionada
con la velocidad de rotación de la misma. Por ejemplo, una unidad de 3½-HD que gire cada 199,9 ms admite
12405 bytes, mientras que otra que lo hace cada 199,1 ms sólo admite 12348 bytes. Ambas son casos realmente
extremos, ya que la inmensa mayoría se encuentra entre estas dos. Aún así, la capacidad finalmente adoptada
por 2MGUI serán 12329 bytes. El objetivo es permitir que los discos puedan ser intercambiados entre unidades.
EL HARDWARE DE APOYO AL MICROPROCESADOR 284

En lectura nunca hay problemas, ya que la peor unidad puede leer los datos de la mejor (la que más lentamente
gire) porque la señal de reloj la obtiene de los propios datos registrados en disco. Sin embargo, al escribir, la
señal de reloj la extrae de su base de tiempos propia (casi igual en todos los ordenadores) y al girar más deprisa
se le acaba la pista antes y sobreescribe el principio. Por tanto, los discos que apuren demasiado la capacidad de
una buena unidad serán estropeados al ser escritos (no leídos) en otra unidad peor.

┌───────────┬───────────┬────────────┐
│ Doble │ Alta │ Extraalta │
┌───────────────────────────────┼───────────┼───────────┼────────────┼──────┐
│ Récord absoluto previo a 2M │ 820.0 Kb │ 1394.0 Kb │ -- │ │
│ Capacidad máxima 2M (2MF /M) │ 902.0 Kb │ 1558.0 Kb │ -- │ 5.25 │
│ Capacidad mínima de 2MGUI │ 979.0 Kb │ 1642.4 Kb │ -- │ (5¼) │
│ Capacidad límite teórica (82p)│ 1001.0 Kb │ 1668.2 Kb │ -- │ │
├───────────────────────────────┼───────────┼───────────┼────────────┼──────┤
│ Récord absoluto previo a 2M │ 984.0 Kb │ 1722.0 Kb │ 2880.0 Kb │ │
│ Capacidad máxima 2M (2MF /M) │ 1066.0 Kb │ 1886.0 Kb │ 3772.0 Kb* │ 3.5 │
│ Capacidad mínima de 2MGUI │ 1178.3 Kb │ 1974.5 Kb │ 3949.0 Kb* │ (3½) │
│ Capacidad límite teórica (82p)│ 1201.2 Kb │ 2002.0 Kb │ 4003.9 Kb │ │
└───────────────────────────────┴───────────┴───────────┴────────────┴──────┘
(*) No probado. En esta lista están recogidos sólo los formatos soportados
por prácticamente todas las unidades y en casi todos los ordenadores.

Hay también otro pequeño problema técnico: si la capacidad de la pista es múltiplo del tamaño de
sector lógico empleado (aunque ese sector sea de 128 bytes en lugar de 512) se derrocha espacio al redondear
hacia abajo. La tentación era fuerte: permitir que un sector lógico pueda estar entre dos pistas. De esta manera,
la capacidad total de un disco no puede ser múltiplo entero del número de pistas y cabezales. Solución: crear un
controlador de dispositivo que trate al disco como un array de sectores (un dispositivo con un sector por pista,
un cabezal, y muchísimas pistas, igual que un disco virtual). Así, por ejemplo, los discos de 3½-HD con 12329
bytes por pista tienen en total (con las 82 pistas habituales) 2.021.956 bytes (que equivalen a 15.796 sectores de
128 bytes, totalizando 2.021.888 bytes con un desperdicio de sólo 68). Utilizando una sola FAT, un número
razonable de entradas al directorio y clusters de 2048 bytes (que en las pruebas han demostrado generar discos
notablemente más rápidos que los de 512 bytes) el espacio disponible para el usuario (visible con DIR) alcanza
los 2.015.232 bytes netos (1968K). Se trata de nuevo de 1968K... pero esta vez no son brutos, sino netos, y
además en todas las unidades (y no en casi ninguna).

En escritura, estos discos son 2 ó 3 veces más lentos que en lectura, aproximadamente. En lectura son
sin embargo algo más rápidos que los discos estándar optimizados. La lentitud escribiendo es obvia:
imaginemos que hay que escribir un sector ubicado entre dos pistas: primero habra que leer una pista, modificar
algunos bytes finales y volverla a escribir, luego leer la siguiente para cambiar unos bytes al principio y
escribirla de nuevo...¡todo eso para cambiar un sector lógico de 128 bytes!. Sin embargo, tampoco
es para tanto, ya que por lo general el tampoco al extremo del viejo 1968 de reservar 240 Kb de XMS.
DOS envía bloques grandes a los
dispositivos y esto supone la escritura El programa (un único fichero EXE que se carga en el
directa e inmediata de las pistas CONFIG.SYS y luego se puede ejecutar desde la línea de
completas... que además utilizan la comandos para formatear) es totalmente flexible tanto a nivel
técnica de Sector Sliding (la posición lógico (posibilidad de reprogramar el tamaño de cluster, el número
inicial del sector-pista está desplazada de entradas al directorio y el número de FATs) como físico
según la ubicación en el disco). De (posibilidad de elegir número de pistas, densidades, Sector Sliding
hecho, cacheando las áreas del sistema, X e Y (expresado además en grados angulares) e incluso un
la velocidad de escritura seria parámetro nada menos que para indicar los bytes por pista (por si el
probablemente muy superior, al agilizar usuario tiene una unidad que admite más). Dispone también de una
el cuello de botella que supone el acceso opción para medir con precisión la velocidad de rotación de la
a la FAT. Sin embargo, el consumo de unidad y para calcular qué capacidad máxima soporta. La
memoria del programa (unos 17 Kb) ya flexibilidad de un disco virtual... pero en un disquete; el número
es respetable sin caché, y no se llega de formatos
284 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

│ C:\AUXI>dir f: │
┌──────────────────────────────── │ │
───────────────────┐ │ Volume in drive F is unlabeled │
│ C:\AUXI>2mgui │ File not found "F:\*.*" │
│ │ 0 bytes in 0 file(s) │
│ │ 2.015.232 bytes free │
│ │ │
│ 2MGUI instalado en memoria. │ C:\AUXI>chkdsk f: │
│ │ │
│ - Nueva unidad E: 1.2M │ 2.015.232 bytes total disk space │
(unidad física A:) │ │ 2.015.232 bytes available on disk │
│ - Nueva unidad F: 1.44M │ │
(unidad física B:) │ │ 2.048 bytes in each allocation unit │
│ Ejecute 2MGUI /? si desea │ 984 total allocation units on disk │
obtener ayuda. │ │ 984 available allocation units on disk │
│ │ │
│ │ 655.360 total bytes memory │
│ │ 649.776 bytes free │
│ │ │
│ C:\AUXI>dir e: │ C:\AUXI>_ │
│ └───────────────────────────────────────────────────┘
│ EJEMPLOS DE ACCESO A UN DISCO DE 360K
│ Y OTRO DE 1.44M FORMATEADOS CON 2MGUI
│ Volume in drive E is unlabeled

│ File not found "E:\*.*"

│ 0 bytes in 0 file(s)

│ 997.376 bytes free



│ C:\AUXI>chkdsk e:



│ 997.376 bytes total disk
space │
│ 997.376 bytes available
on disk │


│ 2.048 bytes in each
allocation unit │
│ 487 total allocation
units on disk │
│ 487 available
allocation units on disk │


│ 655.360 total bytes
memory │
│ 649.776 bytes free





EL HARDWARE DE APOYO AL MICROPROCESADOR 284

es prácticamente infinito, según la voluntad del usuario. Una de las opciones es formatear las 28 pistas más
externas en alta densidad y las 54 restantes en doble, en un disco de 360K, obteniéndose 1.2M bastante más
fiables de lo que se podría esperar.

Con QEMM, si se instala el driver en memoria superior hay que indicar DMA=13 (unidades 1.44M) ó
DMA=25 (unidades 2.88M) en las opciones del controlador de memoria, ya que el buffer para acceso directo a
memoria que establece por defecto es de sólo 12 Kbytes (EMM386 establece 32 Kb).

Las nuevas letras de unidad 2MGUI también soportan discos estándar e incluso 2M (teniendo instalado
también 2M). De hecho, estas nuevas unidades posibilitan el empleo de discos 2M en OS/2.

12.6.7.10 - USO DE 2M 3.0 EN OS/2 2.1

Veamos qué consideraciones hay que tener en cuenta para utilizar disquetes 2M en OS/2. Para empezar,
es necesario arrancar el DOS desde un disquete o desde un fichero imagen de disco, ya que en las ventanas DOS
ordinarias 2M no puede controlar los accesos a disco. Curiosamente, sí se puede formatear en estas ventanas,
pero no trabajar con el disco: lo que sucede es que el sistema de ficheros de la emulación DOS que incorpora
OS/2 está gestionado al parecer sin llamadas a la BIOS, precisamente las que intercepta 2M, que por tanto no se
da cuenta de los accesos a disco. Una vez arrancado desde un fichero imagen con, por ejemplo, MS-DOS 6
(creado con el VMDISK del OS/2) 2M funcionaría perfectamente. Pero lo más probable es que el usuario tenga
instalada la utilidad FSFILTER.SYS para poder acceder a las particiones HPFS y, sobre todo, para poder
escribir sobre las particiones FAT ordinarias, que serían de sólo lectura en caso contrario. Y aquí vuelven los
problemas: al instalar este driver que altera la INT 21h, 2M deja de nuevo de funcionar.

La solución más rápida consiste en crear un driver que implemente 2 nuevas unidades lógicas (como la
D: y la E: por ejemplo) que utilicen la BIOS para acceder a disco: en estas nuevas unidades ya no habrá
problemas para trabajar con los disquetes 2M. Este driver sería un programa enteramente DOS, que sin embargo
no se puede instalar en las ventanas DOS normales de OS/2, ya que en ellas están prohibidos los dispositivos de
bloque. Por tanto, su utilización queda restringida a las ventanas de DOS que incorporen una auténtica versión
de este sistema (obtenidas con VMDISK sobre un disquete de arranque, a menos que el usuario desee
arrancarlas desde disquete cada vez que vaya a emplearlas).

Pese a la solución de dicho driver (en nuestro caso 2MGUI), existe algún problema relativamente
importante que comentar. El más interesante consiste en que OS/2 comprueba periódicamente si ha habido un
cambio de disco en alguna unidad, accediendo a la misma en ese caso para comprobar su contenido -con
independencia de que el usuario esté haciendo otra cosa en ese momento, como jugar a los marcianitos mientras
espera los resultados de un programa de cálculo-. Si no hay disco introducido no sucede nada, pero si lo hay y
es de tipo 2M, OS/2 se queda intentando leerlo de manera obsesiva hasta el punto de colapsar la ventana DOS,
que queda literalmente colgada (aunque no el resto de las ventanas ni el sistema en conjunto). La solución, si se
estaba trabajando en esta ventana, es retirar el disquete de la unidad y esperar un segundo o dos. Ah, y no volver
a introducirlo hasta que no se vaya a utilizar, para evitar nuevas molestias. Por fortuna, OS/2 suele tener cuidado
de no fisgar por las disqueteras cuando están siendo usadas. La solución ideal sería un driver que integrara en
OS/2 el soporte de estos disquetes, pero eso requiere saber construir controladores para OS/2.

Las primeras versiones de 2M venían acompañadas de un driver DOS que realizaba la tarea descrita;
sin embargo, desde 2M 1.3+ fue sustituido incorrectamente por una recomendación al usuario acerca de la
instalación de DRIVER.SYS, programa que no llama a la BIOS (sino al propio DOS; por tanto, con efectos
nulos). Por consiguiente, con 2M 3.0+ aparece de nuevo soporte oficial para este sistema.
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

12.7. - EL DISCO DURO DEL AT (IDE, MFM, BUS LOCAL).

La información aquí vertida se aplica al tradicional controlador de disco duro ST506, que ha equipado a
los discos duros MFM/RLL de los AT, con el que es compatible en líneas generales tanto el interface de los
ESDI como el de los IDE (ISA, PCI o Bus Local). Sin embargo, los discos SCSI no son compatibles con la
información que aquí se expone, ni tampoco la controladora de los PC/XT.

12.7.1 - EL INTERFACE.

El disco duro se conecta a la controladora a través de dos cables: uno con las señales de control y otro
con las de datos. El de señales de control consta de 34 conectores, y el de datos de 20.

┌──────────────────┬───────────┬──────────┐
│ Nombre señal │ Pin señal │ Pin masa │
├──────────────────┼───────────┼──────────┤
│ - HEAD SELECT 3 │ 2 │ 1 │
│ - HEAD SELECT 2 │ 4 │ 3 │
│ - WRITE GATE │ 6 │ 5 │
│ - SEEK COMPLETE │ 8 │ 7 │ ┌──────────────────────┬────────────────────────────────┐
│ - TRACK 000 │ 10 │ 9 │ │ Nombre señal │ Pin señal │
│ - WRITE FAULT │ 12 │ 11 │ ├──────────────────────┼────────────────────────────────┤
│ - HEAD SELECT 0 │ 14 │ 13 │ │ -Unidad seleccionada │ 1 │
│ RESERVADO │ 16 │ 15 │ │ +MFM Escribir datos │ 13 │
│ - HEAD SELECT 1 │ 18 │ 17 │ │ -MFM Escribir datos │ 14 │
│ - INDEX │ 20 │ 19 │ │ +MFM Leer datos │ 17 │
│ - READY │ 22 │ 21 │ │ -MFM Leer datos │ 18 │
│ - STEP │ 24 │ 23 │ │ Masa │ 2, 4, 6, 8, 11, 12, 15, 16, 19 │
│ - DRIVE SELECT 1 │ 26 │ 25 │ └──────────────────────┴────────────────────────────────┘
│ - DRIVE SELECT 2 │ 28 │ 27 │ SEÑALES PARA TRANSFERENCIA DE DATOS
│ - DRIVE SELECT 3 │ 30 │ 29 │
│ - DRIVE SELECT 4 │ 32 │ 31 │
│ - DIRECTION IN │ 34 │ 33 │
└──────────────────┴───────────┴──────────┘
SEÑALES DE CONTROL

Significado de las señales de control (entrada):


-Write Gate:Un nivel activo permite a los datos ser escritos en disco. Inactivo implica una lectura y permite mover los cabezales con el
pulso de Step.
-Head Select:Estas señales codifican un número de 4 bits para referenciar a un cabezal.
-Direction In:Esta señal define la dirección en que se mueven los cabezales con la señal Step. Un valor inactivo indica dirección Out y los
pulsos Step alejan los cabezales del centro del disco; un valor activo se interpreta como In y dichos pulsos acercan
los cabezales al centro.
-Step:Esta señal provoca un movimiento de los cabezales en la dirección que indica la anterior.
-Drive Select:Líneas de selección de unidad. Las unidades poseen jumpers para dejarse seleccionar con ciertas combinaciones.
Significado de las señales de control (salida):
-Seek Complete:Informan del final del posicionamiento de los cabezales. Si esta señal no indica que está terminada dicha operación, no
se puede leer ni escribir.
-Track 000:Indica que los cabezales están sobre la pista 0.
-Write Fault:Señala una condición que está provocando una operación no correcta del disco.
-Index:La unidad indica aquí al exterior el comienzo de una pista (a cada revolución).
-Ready:Señal necesaria junto con Seek Complete para poder leer/escribir/posicionar cabezales.

12.7.2 - PROGRAMACIÓN DE LA CONTROLADORA.

El disco duro trabaja rebasando fronteras de pista y cilindro y funciones de autodiagnóstico. El


con sectores de 512 bytes; límite de capacidad está en 1024 cilindros y 16 cabezales. Los registros de
soporta corrección de errores operación son los mostrados en
ECC, operaciones multisector
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

┌─────────────────────┬─────
────────────────────────────
──────┐
│ Dirección E/S hex. │
Significado

├──────────┬──────────┼─────
──────────────┬─────────────
──────┤
│ Primaria │ Secund. │
Lectura │
Escritura │
├──────────┼──────────┼─────
──────────────┼─────────────
──────┤
│ 1F0 │ 170 │ Data
registers │ Data register

│ 1F1 │ 171 │
Error register │ Write
precomp │
│ 1F2 │ 172 │
Sector count │ Sector
count │
│ 1F3 │ 173 │
Sector number │ Sector
number │
│ 1F4 │ 174 │
Cylinder low │ Cylinder
low │
│ 1F5 │ 175 │
Cylinder high │ Cylinder
high │
│ 1F6 │ 176 │
Drive/Head │
Drive/Head │
│ 1F7 │ 177 │
Status register │ Command
register │
└──────────┴──────────┴─────
──────────────┴─────────────
──────┘
la figura, estando la controladora ubicada normalmente en la localización E/S primaria.

Significado de los registros:

Data Register:Permite acceder al buffer donde está almacenado el sector para leer y escribir en el modo
PIO (esto es, sin DMA). No debería ser accedido a menos que haya una operación de
lectura o escritura en curso. Implementa una dirección de 16 bits dentro del buffer de
la controladora que contiene al sector para las operaciones de lectura y escritura
normales. Para una lectura/escritura largas 4 bytes ECC son transferidos por byte con
al menos 2 microsegundos entre transferencias (la línea DRQ debe estar activa antes de
transferir los bytes ECC).
Error Register:De sólo lectura, contiene información sobre el comando previo. El dato es válido sólo
cuando el bit de error en el registro de estado está activo.
n Tras conectar el disco duro a la corriente o tras enviar el comando apropiado, se encuentra en modo
diagnóstico: en esos casos, el registro debe ser comprobado diga lo que diga el bit del
registro de estado (con el significado en estos casos de 01-No hay error, 02-Fallo del
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

controlador, 03-Error en el buffer del sector, 04-Error en el dispositivo ECC, 05-Error


en el procesador de control).
n Cuando no está en modo diagnóstico, caso más común, significado de sus bits:
bit 0:Data Address Mark (DAM) no encontrada en los 16 bytes del campo ID.
bit 1:Error TR 000. Se activa si tras un comando Restore, la señal Track 000 no se activa después de 1023
pulsos de retroceso.
bit 2:Comando abortado. En estos casos, se debe mirar los registros de Status y Error para determinar con
precisión la causa (que estar en Write Fault, Seek Complete, Drive ready -- o
comando inválido en otro caso).
bit 3:No usado.
bit 4:ID no encontrada. La marca ID que identifica al cilindro, cabezal y sector no ha sido encontrada. Si están
activos los reintentos, el controlador lo reintenta 16 veces antes de dar error,
en caso contrario sólo explora la pista como mucho 2 veces antes de dar el
error.
bit 5:No usado.
bit 6:Error ECC. Indica si se ha producido un error ECC incorregible durante una lectura.
bit 7:Bad Block detected. Indica que se ha encontrado un sector marcado como defectuoso en la ID; no se
intentarán en él ni lecturas ni escrituras.
Write
Precompensation:El valor almacenado es el cilindro de comienzo para la escritura precompensada dividido
por 4.
Sector Count:Indica el número de sectores a transferir durante la lectura, escritura, verificación o formateo.
En las operaciones multisector, este registro se decrementa y el Sector Number se
incrementa; al formatear, antes de enviar cada comando de formateo debe cargarse
aquí el número de sectores en la pista. Se soportan operaciones multisector que crucen
fronteras de pista y cilindro. Las características de la unidad deben establecerse con el
comando Set Parameters antes de una transferencia multisector. Este registro debe
cargarse con el número de sectores antes de cualquier comando relacionado con datos.
Un valor 0 representa 256 sectores.
Sector Number:Número de sector para la lectura, escritura y verificación. El sector inicial se carga aquí en
las operaciones multisector.
Cylinder Number:Número de cilindro para los comandos de lectura, escritura, verificación y
posicionamiento de cabezales. Entre el registro que almacena la parte baja y el de la
parte alta (low y high respectivamente) se guarda un número entre 0 y 1023.
Drive/Head:Bits 7 y 5 puestos a 1, el 6 puesto a 0. El bit 4 indica la unidad seleccionada (0 el primer disco
duro y 1 el segundo) y los bits 0-3 el número de cabezal de lectura/escritura deseado.
Para acceder a las cabezas 8-15, es necesario además activar el bit 3 del puerto 3F6h.
Importante: este registro debe cargarse con el número máximo de cabezales antes de
enviar un comando Set Parameters.
Status register:Se actualiza tras ejecutar los comandos. El programa debe mirar este registro para conocer
el resultado. Si el bit busy (7) está activo, los demás bits no son válidos. Una lectura de
este registro borra la petición de interrupción IRQ 14. Si write-fault (bit 5) o error (bit
0) están activos, o si seek-complete (bit 4) o drive-ready (bit 6) están inactivos, la
operación multisector es abortada. Significado de los bits:
bit 7:Busy. Un 1 indica que el controlador está ejecutando un comando; por tanto, este bit debe ser examinado
antes de leer cualquier registro.
bit 6:Drive-ready. Un 0 indica que la lectura, escritura y seek están inhibidas; para poder ejecutarlas debe estar a
1 junto con el bit seek-complete (4).
bit 5:Write-fault. Un 1 indica funcionamiento incorrecto de la unidad; la lectura, escritura o seek están
inhibidos.
bit 4:Seek-complete. Un 1 indica que los cabezales han terminado el seek.
bit 3:Data-request. Este bit indica que el buffer del sector necesita ser atendido en un comando de lectura o
escritura: si este bit o el busy (7) están activos, hay un comando en ejecución.
Hasta recibir algún comando, este bit está a 0.
bit 2:Corrected-data. Un 1 indica que los datos leídos del disco fueron corregidos de error ECC con éxito.
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

Errores suaves no abortan la operación multisector.


bit 1:Index. Este bit se pone a 1 tras cada revolución del disco.
bit 0:Error. Un 1 indica que el comando previo terminó en error, y uno o más bits del Error register están
activos. El próximo comando enviado al controlador borra este bit. Si este bit
se activa, la operación multisector es abortada.
Command Register:Acepta 8 diferentes comandos. Los comandos se programan cargando primero los demás
registros necesarios y escribiendo después el comando en éste mientras el registro de
estado devuelve una condición de no busy. Un comando no legal provoca un error de
comando abortado. La solicitud de interrupción IRQ 14 se borra al escribir un
comando. Los comandos soportados son:

┌────────────────┬─────────────────────────────┐
│ Comando │ bit 7 6 5 4 3 2 1 0 │
├────────────────┼─────────────────────────────┤
│ Restore │ 0 0 0 1 R3 R2 R1 R0 │
│ Seek │ 0 1 1 1 R3 R2 R1 R0 │
│ Read sector │ 0 0 1 0 0 0 L T │
│ Write sector │ 0 0 1 1 0 0 L T │
│ Format track │ 0 1 0 1 0 0 0 0 │
│ Read verify │ 0 1 0 0 0 0 0 T │
│ Diagnose │ 1 0 0 1 0 0 0 0 │
│ Set Parameters │ 1 0 0 1 0 0 0 1 │
└────────────────┴─────────────────────────────┘
┌────┬────┬────┬────┬───────────────┐

│ R3 │ R2 │ R1 │ R0 │ Stepping rate │

├────┴────┴────┴────┼───────────────┤

│ 0 0 0 0 │ 35 µs │

│ 0 0 0 1 │ 0.5 ms │

│ 0 0 1 0 │ 1.0 ms │

│ 0 0 1 1 │ 1.5 ms │

│ 0 1 0 0 │ 2.0 ms │

│ 0 1 0 1 │ 2.5 ms │

│ 0 1 1 0 │ 3.0 ms │

│ 0 1 1 1 │ 3.5 ms │

│ 1 0 0 0 │ 4.0 ms │

│ 1 0 0 1 │ 4.5 ms │

│ 1 0 1 0 │ 5.0 ms │

│ 1 0 1 1 │ 5.5 ms │

│ 1 1 0 0 │ 6.0 ms │

│ 1 1 0 1 │ 6.5 ms │

│ 1 1 1 0 │ 7.0 ms │

│ 1 1 1 1 │ 7.5 ms │

└───────────────────┴───────────────┘

┌─────┐ ┌────────────────────────┬──────────────────────┐
│ Bit │ │ 0 │ 1 │
├─────┼────────────────────┼────────────────────────┼──────────────────────┤
│ L │ Modo de datos │ Sólo datos │ Datos y 4 bytes ECC │
│ T │ Modo de reintentos │ Reintentos habilitados │ Reintentos inhibidos │
└─────┴────────────────────┴────────────────────────┴──────────────────────┘

Nota:Después de un reset o un comando Diagnose, el step rate queda en 7.5 ms. Por otro lado, el sistema
verifica la operación ECC leyendo y escribiendo estos bytes: cuando los
reintentos están deshabilitados, los reintentos de ECC e ID están limitados a
menos de dos vueltas completas del disco.

Explicación de los comandos.


346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

n Restore:Envía los cabezales a la pista 0 (hasta que la señal Track 000 es activa). Si Track 000 no se activa tras
1023 pulsos de step activa el bit de error en el registro de estado y deja el error TR 000 en el
registro error. El step rate es establecido por el propio comando.
n Seek:Mueve los cabezales al cilindro indicado. Está soportado un seek simultáneo en dos unidades. Al final
del comando se produce una interrupción.
n Read sector:Cierto número de sectores (1-256) pueden ser leídos del disco duro con o sin el campo ECC
añadido, en el modo PIO (entrada-salida programada, sin DMA). Si los cabezales no están
sobre la pista necesaria, el controlador envía pulsos step para posicionarlo, utilizando el step
rate del último seek o restore. Los errores de datos de hasta 5 bits son corregidos
automáticamente en los comandos de lectura corta. Si un error no corregible tiene lugar, se
continúa leyendo el sector donde apareció pero ya no se leen más sectores en el caso de los
accesos multisector. Se produce una interrupción por cada sector cuando está preparado para
ser transferido, pero no al final del comando.
n Write sector:Cierto número de sectores (1-256) pueden ser escritos a disco duro con o sin el campo ECC
añadido, en el modo PIO (entrada-salida programada, sin DMA). Realiza los seeks que sea
necesario hacer. Las interrupciones suceden cada vez que es transferido un sector al buffer
(salvo el primero) y al final del comando. El primer sector debería ser escrito en el buffer
inmediatamente después de que el comando ha sido enviado y "Data-request" es activo.
n Format track:Se formatea la pista indicada según la tabla de interleave que se transfiere. Hay 2 bytes por cada
sector: 0, Nº sector. Así se puede elegir la numeración deseada. Hay que enviar 512 bytes con
independencia de que sean menos en la tabla (por ej. 34 bytes para 17 sectores). El sector count
debe cargarse con el nº de sectores por pista antes de cada comando de estos. Se genera una
interrupción al final del comando de formateo. Los sectores defectuosos se marcan
sustituyendo el 0 que les precede por 80. Cuando se conmuta entre dos unidades, antes de
formatear hay que hacer un restore.
n Read Verify:Similar al comando read sector con la diferencia de que no se envían datos al ordenador; de esta
manera simplemente se verifica la integridad de los mismos. Una única interrupción se genera
al completarse el comando o en caso de error.
n Diagnose:El adaptador ejecuta su auto-test y devuelve el resultado en el error register. Se produce una
interrupción cuando completa el comando.
n Set Parameters:Establece los parámetros de la unidad: máximo número de cabezales y sectores/pista. El registro
drive/head indica qué unidad es afectada. Hay que actualizar los registros sector count y
drive/head antes de enviar este comando. Estos parámetros serán empleados para cruzar los
cilindros en las operaciones multisector. Se genera una interrupción cuando se completa el
comando. Este comando debe ser enviado antes de intentar alguna operación multisector. Se
soportan dos discos duros, con diferentes características cada uno, definidas por este comando.

Registro del controlador de disco duro (3F6h) y Registro de entrada digital (3F7h).

Además de informar de la línea de cambio de disco en los disquetes, los bits 0-5 del registro de entrada
digital (3F7h) están relacionados con el disco duro.

3F6h -bits 7-4:Reservados3F7h -bit 7:Línea de cambio de disquetes.


bit 3:0 - Reduce Write Current)bit 6:Write gate
1 - Head 3 select enable)bit 5:Head select 3 / Reduced Write Current
bit 2:1 - Disk reset enablebit 4:Head select 2
0 - Disk reset disablebit 3:Head select 1
bit 1:0 - Disk initialization enablebit 2:Head select 0
1 - Disk initialization disablebit 1:Drive select 1
bit 0:Reservadobit 0:Drive select 0

12.7.3 - EJEMPLO PRACTICO DE PROGRAMACIÓN.

En los AT la interrupción de disco duro es la IRQ 14 (INT 76h). La BIOS, en caso de producirse esta
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

interrupción, almacena un valor 0FFh en 40h:8Eh con el gestor que tiene por defecto. Las transferencias con el
disco duro tienen lugar sin DMA por regla general. Esto se comprende mejor teniendo en cuenta que la
controladora tiene un buffer interno con capacidad para algún sector y, por tanto, cuando hay que transferirlo,
no hay que esperar a que venga del disco mientras este gira lentamente (como en el caso de los disquetes): una
transferencia con el DMA ordinario aquí sería más lenta que a través de la CPU.

Parte de la documentación vista con anterioridad es sólo oficial. Por ejemplo, los discos IDE suelen
venir formateados de fábrica a bajo nivel e ignoran el comando de formateo: estas unidades son bastante
inteligentes y llevan su propia gestión de sectores defectuosos (reemplazándolos por otros que tienen libres para
simular que todo está correcto) así como de interleaves (generalmente 1:1, valores peores se deben a
controladoras obsoletas que no tenían un buffer con capacidad para una pista) y skews óptimos.

El programa de ejemplo es el embrión de una utilidad para acceder directamente a la controladora de


disco duro. La función operahd() solo implementa realmente el comando de leer sector. En el ejemplo, se lee el
primer sector absoluto del disco, donde suele ser fácil reconocer la tabla de particiones. En nuestro caso, en
lectura, justo antes de enviar el comando se borra el flag de interrupción. Una vez enviado, cuando llegue la
interrupción se procede a leer el sector. Para ello se utiliza la rápida instrucción REP INSW del 286 y
procesadores superiores, para E/S repetitiva, que lo trae a gran velocidad desde la memoria de la controladora a
la del sistema.

Un acceso directo a bajo nivel puede tener mucho interés para ciertas aplicaciones. Por ejemplo, un
antivirus puede asegurarse de que ha reparado la tabla de particiones (o cualquier otra zona del disco) sin temor
a que en su llamada a INT 13h el virus residente le haya estropeado el trabajo (aunque si el virus trabaja en
modo protegido y controla el acceso a los puertos E/S del disco duro...).

HDIRECT.C
/********************************************************************* #define HDR_WRITEP 0x1F1

* * #define HDR_SECNT 0x1F2

* ACCESO A DISCO DURO ESTANDAR AT (IDE, MFM, BUS LOCAL, ETC) * #define HDR_SEC 0x1F3

* PROGRAMANDO DIRECTAMENTE LA CONTROLADORA * #define HDR_LCYL 0x1F4

* * #define HDR_HCYL 0x1F5

* - Compilar en modelo Large. * #define HDR_DRVHD 0x1F6

* - Este programa sólo implementa la función de leer sector. * #define HDR_STATUS 0x1F7

* - No soportadas controladoras de XT, SCSI u otras. * #define HDR_CMD 0x1F7

* *

*********************************************************************/ #define HD_ECC 2

#define HD_NORETRY 1

#include <dos.h> #define HD_BUSY 0x80

#include <alloc.h> #define HD_DATA_REQ 8

#include <conio.h>

#include <stdio.h>

#include <stdlib.h> int operahd (int unidad, int cabeza, int cilindro, int sector,

int operacion, char huge *direccion, int numsect)

#define HD_RESTORE 0x10 /* comandos del controlador */ int i;

#define HD_SEEK 0x70

#define HD_READ 0x20 if (operacion==HD_SETPARAM) {

#define HD_WRITE 0x30 }

#define HD_FORMAT 0x50 else if (operacion==HD_DIAGNOSE) {

#define HD_READVERIFY 0x40 }

#define HD_DIAGNOSE 0x90 else if (operacion==HD_FORMAT) {

#define HD_SETPARAM 0x91 }

else if ((operacion & 0xFE) == HD_READVERIFY) {

#define HDR_MAIN 0x3F6 }

#define HDR_DATA 0x1F0 /* registros del controlador */ else if ((operacion & 0xFC) == HD_READ) {

#define HDR_ERROR 0x1F1 outportb (HDR_SECNT, numsect); /* nº sectores */


346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

outportb (HDR_SEC, sector); /* primer sector */ outportb (HDR_CMD, operacion); /* comando */

outportb (HDR_LCYL, cilindro & 0xFF); /* nº cilindro 0..7 */

outportb (HDR_HCYL, cilindro >> 8); /* nº cilindro 8..9 */ while (!peekb(0x40, 0x8e)); /* esperar interrupción 76h */

outportb (HDR_DRVHD, unidad << 4 | cabeza | 0xC0); /* (convendría poner un timeout) */

outportb (HDR_MAIN, cabeza & 8);

/* por eficiencia, el siguiente código está en ensamblador */

pokeb (0x40, 0x8e, 0); /* flag de interrupción a 0 */

asm {

push es /* máxima lectura soportada: casi 64 Kb */

push cx

push dx

push di

mov cx,numsect

xchg ch,cl /* CX = numsect * 256 = nº palabras */

les di,direccion

cld

mov dx,HDR_DATA

db 0F3h, 6Dh /* instrucción 286+ 'rep insw' */

pop di

pop dx

pop cx

pop es

else if ((operacion & 0xFC) == HD_WRITE) {

else if ((operacion & 0xF0) == HD_SEEK) {

else if ((operacion & 0xF0) == HD_RESTORE) {

void main()

/* el puntero huge comienza en XXXX:0004 */

unsigned char huge *buffer, huge *p;

unsigned i, j, k;

if ((buffer=farmalloc(0xFFFC))==NULL) {

printf("\nMemoria insuficiente.\n");

exit(1);

/* leer sector de tabla de partición */

operahd (0, 0, 0, 1, HD_READ | HD_NORETRY, buffer, 1);

/* imprimir sector de 512 bytes */

p=buffer;

for (i=0; i<2; i++) {

clrscr();

for (j=0; j<256; j+=16) {

for (k=0; k<16; k++) printf("%02X ", *p++); p-=16;

printf(" ");

for (k=0; k<16; k++) {

if (*p<' ') printf("."); else printf("%c", *p);


EL HARDWARE DE APOYO AL MICROPROCESADOR 346

p++;

printf("\n");

printf("\n- Estás viendo 256 bytes del sector.\n");

printf("- Pulsa una tecla para continuar.");

getch();

}
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

12.8. - EL CONTROLADOR DEL TECLADO: 8042.

En este apartado se estudiará a fondo el funcionamiento a bajo nivel del teclado en los ordenadores
compatibles, si bien es poco frecuente que sea necesario acceder al mismo de esta manera.

12.8.1 - EL 8042.

Un microordenador llamado teclado.

El teclado se conecta al ordenador por medio de un cable que contiene 4 hilos hábiles: dos que
conducen la corriente, uno para datos y otro para reloj. El teclado es en realidad un pequeño microordenador; de
hecho muchos teclados llevan en su interior el chip 8049 de Intel (el microprocesador esclavo del viejo QL de
Sinclair) que consta de unos 2 Kb de memoria ROM y 128 bytes de RAM (las 8 primeras posiciones son
empleadas como registros). Este procesador se encarga de detectar la pulsación de las teclas, generando unos
bytes que las identifican y enviándolos a continuación por el cable a través de un protocolo de comunicación en
serie que en el AT consta de 11 bits por cada dato (1 de inicio, 8 de datos, 1 de paridad y otro de stop) y 9 en los
XT (entre otras razones, porque no se controla la paridad). Los teclados de AT y de XT generan códigos
diferentes para las mismas teclas. Además, al soltar una tecla, los teclados de XT generan el mismo código que
al pulsarla pero con el bit 7 activo; sin embargo, en AT se generan dos códigos que se envían consecutivamente
(0F0h y después el mismo código que al pulsarla). El teclado se encarga de repetir los códigos de una tecla
cuando ésta lleva cierto tiempo pulsada, en el conocido mecanismo autorepeat de la mayoría de los teclados.
Muchos teclados tienen debajo un interruptor que permite seleccionar su modo de funcionamiento (XT o AT).

El teclado en los PC y XT.

Los datos, cuando llegan al ordenador, reciben un tratamiento diferente en función de si el ordenador es
un XT o un AT, mucho más sencillo en el primero. En los XT se van colocando los bits que llegan en un simple
registro de desplazamiento conectado al puerto 60h; al completarse los 8 se produce una interrupción de tipo
IRQ 1 (INT 9), la segunda de mayor prioridad después de la del temporizador. No obstante, el teclado es capaz
de memorizar hasta 8 pulsaciones cuando la CPU no tiene tiempo para atenderle. Después de leer el código de
la tecla, el programa que la gestione habrá de enviar una señal de reconocimiento a la circuitería del ordenador
para permitir que continúe la recepción de datos.

El controlador del teclado del AT: el 8042.

En los AT hay un circuito integrado encargado de interpretar los datos procedentes del teclado y,
después de traducirles adecuadamente para compatibilizar con los XT si así ha sido programado, enviarles a la
CPU: el 8042 de Intel. También sirve de intermediario a las transmisiones de datos de la CPU al teclado, que en
el AT es un periférico bidireccional que puede recibir comandos para configurar los LEDs, entre otras tareas.
Cuando el 8042 recibe un byte entero del teclado, inhibe la comunicación hasta que la CPU lo acepta. Si el dato
se recibe con error de paridad, automáticamente el 8042 lo solicita de nuevo al teclado enviando un comando de
reenvío al mismo y un byte 0FFh a la CPU indicando esta circunstancia, activando también el bit 7 del registro
de estado del 8042. Además, chequea que no pasen más de 2 milisegundos durante la recepción: si se excede
este límite se envía también un 0FFh a la CPU y se activa el bit 6 en el registro de estado. Cuando la CPU envía
algo al teclado, el 8042 inserta el bit de paridad automáticamente. Si el teclado no empieza la comunicación en
menos de 15 milisegundos o tarda en recibir el dato más de 2 milisegundos, se envía un 0FEh a la CPU y se
activa el bit 5 en el registro de estado. Además, el teclado ha de responder a todas las transmisiones con un byte
de reconocimiento, si en esta operación hay un error de paridad se activarán los bits 5 y 7 en el registro de
estado; si tarda más de 25 milisegundos en responder también se envía el byte 0FEh a la CPU y se activan los
bits 5 y 6 del registro de estado.

La comunicación teclado-CPU puede ser inhibida por hardware por medio de la llave que incorpora la
unidad central, aunque la comunicación CPU-teclado sigue habilitada. El 8042 se apoya en tres registros
básicos: uno de estado, uno de salida y otro de entrada. El registro de estado, del que ya se ha explicado parte de
su funcionalidad, se encuentra en el puerto de E/S 64h y puede ser leído en cualquier momento. El significado
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

de sus bits se explica en el cuadro 1.

El registro de salida está ubicado en el puerto 60h y es de sólo lectura; el 8042 lo usa para enviar los
códigos de las teclas a la CPU y los bytes de datos de los comandos que los soliciten. Debería ser leído sólo
cuando el bit 0 del registro de estado está activo.

El registro de entrada del 8042 es de sólo escritura y puede ser accedido por los puertos 60h y 64h
según que lo que se quieran enviar sean datos o comandos al 8042, respectivamente; los datos serán reenviados
por el 8042 hacia el teclado a menos que el propio 8042 esté esperando un dato de la CPU a consecuencia de un
comando previo enviado por ésta. Los datos deben ser escritos en este registro sólo cuando el bit 1 del registro
de estado esté inactivo. En el cuadro 2 se listan los comandos que admite el 8042 (enviados al puerto 64h).
Debe darse cuenta el lector de la particularidad de que los registros de salida y entrada son accedidos por el
mismo puerto (60h), siendo la lectura y escritura las que seleccionan el acceso a uno u otro respectivamente.

┌─────┬───────────────────────────────────────────────────────────────────────────────────────────┐
│ BIT │ SIGNIFICADO │
├─────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ │ Registro de salida lleno. Un 1 indica que el 8042 ha colocado un dato en el registro de │
│ 0 │ salida y la CPU aún no lo ha leído. Este bit se pone a 0 cuando la CPU lee el puerto 60h. │
├─────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ 1 │ Registro de entrada lleno. Un 1 significa que ha sido colocado un dato en el registro de │
│ │ entrada y el 8042 aún no lo ha leído. │
├─────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ 2 │ Banderín del sistema: asignado con un comando del 8042. 0 al arrancar. │
├─────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ │ Comando/dato. Se pone a 1 o a 0 al enviar algo al puerto 60h o al 64h respectivamente: de │
│ 3 │ esta manera, el 8042 sabe si lo que se le envía son órdenes o datos (órdenes= 1). Ambos │
│ │ puertos conectan con el registro de entrada. │
├─────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ 4 │ Bit de inhibición. Este bit se actualiza siempre que se coloca un dato en el registro de │
│ │ salida, un 0 indica teclado inhibido. │
├─────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ 5 │ Transmisión fuera de tiempo. Indica que la transmisión de un dato hacia el teclado no ha │
│ │ sido respondida en los márgenes de tiempo adecuados. │
├─────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ 6 │ Recepción fuera de tiempo. Indica si el teclado ha enviado un dato y sigue enviando más │
│ │ después del tiempo esperado. │
├─────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ 7 │ Error de paridad. Indica la paridad del dato recibido: 0 la correcta. │
└─────┴───────────────────────────────────────────────────────────────────────────────────────────┘
CUADRO 1: REGISTRO DE ESTADO

12.8.2 - EL TECLADO DEL AT

Como se dijo en el apartado anterior, el teclado del AT es bidireccional y admite comandos por parte
del ordenador. Estudiaremos ahora cuáles son esos comandos. En primer lugar, tras el arranque del ordenador y
al recibir la alimentación el teclado, éste realiza un autotest denominado BAT (Basic Assurance Test) donde
chequea su ROM, RAM y enciende y apaga todos los LED. Esta operación emplea entre 600 y 900
milisegundos; al acabar el BAT y cuando sea posible establecer la comunicación con el ordenador (líneas de
reloj y datos en alto) envía un byte 0AAh si todo ha ido bien y un 0FCh si ha habido fallos; inicializando
después los parámetros de autorepetición de las teclas.

El teclado tiene un buffer interno con capacidad para 17 bytes (unas 8 teclas) con objeto de almacenar
las últimas teclas pulsadas cuando no puede enviarlas al 8042. Cuando este buffer se llena, su última posición
(17ª) se rellena con 0 y se ignoran las siguientes pulsaciones.

12.8.3 - COMUNICACIÓN CPU ─Ψ TECLADO


346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

Los comandos al teclado pueden ser enviados en cualquier momento al puerto 60h: a menos que el
8042 esté esperando por un byte de datos en el registro de entrada, como consecuencia de un comando previo,
redireccionará todo lo que se le envíe por el puerto 60h hacia el teclado. El teclado responderá en menos de 20
milisegundos, devolviendo una señal de reconocimiento por medio de un byte 0FAh. Los principales comandos
(diferenciados de los datos por tener el bit 7 activo) son:

- Reset (0FFh): Al recibirlo envía una señal de reconocimiento y se asegura de que la CPU se de por enterada
poniendo en alto las líneas de reloj y datos un mínimo de 500 microsegundos; el teclado permanece inhibido
hasta que la CPU acepta la señal de reconocimiento o envía otro comando que sobreescribe y anula éste.
Llegados a este punto, el teclado ejecuta de nuevo el BAT, estableciendo valores por defecto para la
autorepetición y limpiando su registro de salida.

- Reenvío (0FEh): El sistema puede enviar este comando al teclado cuando detecta un fallo en la recepción
desde el teclado. Este comando sólo puede ser enviado después de una transmisión del teclado y antes de
habilitar la comunicación para la siguiente recepción. El teclado responde enviando de nuevo el dato anterior (si
ya era un 0FEh, el último dato que envió que no fuera 0FEh).

┌─────────┬─────────────────────────────────────────────────────────────────────────────────────────┐
│ COMANDO │ SIGNIFICADO │
├─────┬───┴─────────────────────────────────────────────────────────────────────────────────────────┤
│ 20h │ Leer el byte de comando del 8042 (ver cuadro 3). Esta orden envía al registro de salida (en │
│ │ el puerto 60h) dicho byte para que sea leído. │
├─────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 60h │ Escribir el byte de comando del 8042. El siguiente byte que se envíe al registro de entrada │
│ │ (puerto 60h) será el byte de comando del 8042. │
├─────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ AAh │ Autotest. El 8042 realiza un diagnóstico interno y coloca un 55h en el registro de salida │
│ │ si todo va bien. │
├─────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ │ Test del interface. El controlador chequea las líneas de reloj y datos devolviendo: 0 si no │
│ ABh │ hay errores; 1: el reloj está demasiado en bajo, 2: está demasiado en alto; 3: la línea de │
│ │ datos está demasiado en bajo y 4: la línea de datos está demasiado en alto. │
├─────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ ACh │ Volcado de diagnóstico. Envía al registro de salida, sucesivamente, 16 bytes de la RAM del │
│ │ 8042, el estado de los registros de entrada y salida y la palabra de estado del controlador.│
├─────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ ADh │ Inhibir teclado. Esto activa el bit 4 del byte de comando del 8042. │
├─────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ AEh │ Habilitar teclado. Esto baja el bit 4 del byte de comando del 8042. │
├─────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ │ Leer el puerto de entrada (véase cuadro 4). Esto obliga al 8042 a leer el puerto de entrada │
│ C0h │ y colocar lo que lee en el registro de salida; sólo ha de emplearse este comando cuando el │
│ │ registro de salida está vacío. │
├─────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ D0h │ Leer el puerto de salida. El 8042 lee el puerto de salida y lo coloca en el registro de sa- │
│ │ lida; sólo debe emplearse este comando si dicho registro está vacío. │
├─────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ D1h │ Escribir el puerto de salida (ver cuadro 5). El siguiente byte que se envíe al registro de │
│ │ entrada (puerto 60h) se colocará en el puerto de salida. │
├─────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ E0h │ Leer entradas de testeo. El 8042 coloca en el registro de salida los bits de reloj (bit 0) │
│ │ y datos (bit 1) para permitir la comunicación directa con el teclado. │
├─────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ │ Los bits 0 al 3 de este comando (la parte baja de este mismo comando) se relacionan con los │
│ Fxh │ bits 0 al 3 del puerto de salida del 8042; un 0 indica bit pulsado durante 6 microsegundos │
│ │ (apróx.) y un 1 que el bit no resulta modificado; ¡cuidado con el reset!. │
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

└─────┴─────────────────────────────────────────────────────────────────────────────────────────────┘
CUADRO 2: COMANDOS DEL 8042

- Establecer valores por defecto (0F6h): Devuelve la autorepetición a los valores habituales, limpia su registro
de salida y continúa rastreando las teclas si no estaba inhibido; es una especie de reset en caliente.

- Establecer valores por defecto y parar (0F5h): Similar al comando anterior, pero dejando de rastrear las teclas
y permaneciendo inhibido hasta recibir más instrucciones.

- Habilitar (0F4): Reanuda el funcionamiento interrumpido por el comando anterior o algún otro.

- Establecer ratio y retardo de autorepetición (0F3h): Tras este comando debe enviarse otro inmediatamente a
continuación, que se interpretará como dato, estableciendo los valores de autorepetición. De este segundo byte,
el bit 7 estará siempre a cero; el valor de los bits 5 y 6, sumándole una unidad, indica el tiempo que ha de pasar
desde que se pulsa una tecla hasta que comience a autorepetirse, en unidades de 0,25 segundos (±20%). Los bits
2, 1 y 0 forman un número A; los bits 4 y 3 forman otro número B; por medio de la siguiente fórmula se obtiene
la tasa o ratio de autorepetición en «teclas por segundo»:

1
──────────────────────────────
(8 + A) * ( 2 ^ B) * 0.00417

Una vez recibido este comando, el teclado envía la acostumbrada señal de reconocimiento, deja de rastrear las
teclas y espera por el parámetro de autorepetición, respondiendo al mismo con otra señal de reconocimiento y
volviendo a rastrear las teclas. Si en lugar de recibir el parámetro recibe otro comando (bit 7 activo) dejará
inalterados los valores de autorepetición y procesará dicho comando, aunque ¡cuidado!: permanecerá inhibido
hasta que se le habilite con el comando 0F4h. Por defecto, el sistema establece una tasa de 10 caracteres por
segundo y 0,5 segundos de espera (parámetro 4Ch).

┌─────┬──────────────────────────────────────────────────────────────────────────────────────┐
│ BIT │ SIGNIFICADO │
├─────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 0 │ Activar la interrupción del registro de salida lleno: un 1 indica que el 8042 genere │
│ │ una IRQ1 (INT 9) tras colocar un dato en el registro de salida (esto es lo normal). │
├─────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 1 │ Reservado (escribir 0). │
├─────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 2 │ Banderín del sistema. Este bit define el bit 2 del registro de estado. │
├─────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 3 │ Ignorar inhibición: con 1 se ignorará la función de inhibir el teclado. │
├─────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 4 │ Deshabilitar el teclado: un 1 baja la línea de reloj inhibiendo la comunicación del │
│ │ 8042 con el teclado. │
├─────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 5 │ Modo IBM PC. Con 1 no se traducen los códigos del teclado ni se controla la paridad. │
├─────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ │ IBM PC compatibilidad. Un 1 selecciona la conversión de los códigos del teclado para │
│ 6 │ emular los del PC y XT, traduciendo los códigos de rastreo y generando un único byte │
│ │ al soltar las teclas. Puesto a 1 por la BIOS antes de cargar el DOS (compatibilidad).│
├─────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ 7 │ Reservado (escribir 0). │
└─────┴──────────────────────────────────────────────────────────────────────────────────────┘
CUADRO 3: BYTE DE COMANDO DEL 8042

┌─────┬────────────────────────────────┐ ┌─────┬────────────────────────────────────────────────────────────┐
│ BIT │ SIGNIFICADO │ │ BIT │ SIGNIFICADO │
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

├─────┼────────────────────────────────┤ ├─────┼────────────────────────────────────────────────────────────┤
│ 0-3 │ Indefinidos │ │ 0 │ Reset del sistema (como Ctrl-Alt-Del). │
├─────┼────────────────────────────────┤ ├─────┼────────────────────────────────────────────────────────────┤
│ 4 │ RAM del sistema. A 1 si insta- │ │ │ Línea A20: 0 fuerza la línea A20 de la CPU a 0, con lo que │
│ │ lada la extensión de 256 Kb. │ │ 1 │ se prohíbe acceder a la memoria por encima de 1 Mb lo cual │
├─────┼────────────────────────────────┤ │ │ emula el direccionamiento de los PC/XT; un 1 deja que A20 │
│ 5 │ A 0 si presente el puente (o │ │ │ la controle la CPU aunque hay PC's en que esto no basta. │
│ │ «jumper») del fabricante. │ ├─────┼────────────────────────────────────────────────────────────┤
├─────┼────────────────────────────────┤ │ 2-3 │ Indefinidos. │
│ │ Tipo de pantalla. 0 si la pan- │ ├─────┼────────────────────────────────────────────────────────────┤
│ 6 │ talla principal es de color y │ │ 4 │ Registro de salida lleno. │
│ │ 1 si es monocroma. │ ├─────┼────────────────────────────────────────────────────────────┤
├─────┼────────────────────────────────┤ │ 5 │ Registro de entrada vacío. │
│ │ 0: el teclado ha sido bloquea- │ ├─────┼────────────────────────────────────────────────────────────┤
│ 7 │ do con la llave externa de la │ │ 6 │ Línea de reloj (comunicación directa con el teclado). │
│ │ unidad central. │ ├─────┼────────────────────────────────────────────────────────────┤
└─────┴────────────────────────────────┘ │ 7 │ Línea de datos (comunicación directa con el teclado). │
CUADRO 4: BYTE RECIBIDO POR EL └─────┴────────────────────────────────────────────────────────────┘
PUERTO DE ENTRADA CUADRO 5: BYTE A ENVIAR AL PUERTO DE SALIDA

- No operación (0F7h a 0FDh y 0EFh al 0F2h): Son códigos reservados; el teclado al recibirlos envía la señal de
reconocimiento de siempre y no realiza ninguna acción.

- Eco (0EEh): Si el teclado recibe este comando, lo reenvía a continuación. Es una ayuda al diagnóstico.

- Encender/apagar los LED (0EDh). Tras este comando se ha de enviar otro byte de datos, cuyos bits 0, 1 y 2
están ligados al estado de los LED de Scroll Lock, Num Lock y Caps Lock, respectivamente; los demás están
reservados. Al recibir el comando envía la correspondiente señal de reconocimiento y deja de rastrear las teclas,
esperando por el dato. Si en vez de un dato recibe otro comando, dejará intactos los LED, procesará dicho
comando y continuará rastreando las teclas (sin quedar inhibido en esta ocasión). El siguiente ejemplo muestra
cómo establecer los LED configurados en AH:

CLI
MOV AL,0EDh
OUT 60h,AL ; enviar comando
XOR CX,CX
espera: JMP SHORT $+2 ; insertar estados de espera para AT obsoleto
JMP SHORT $+2
IN AL,64h
TEST AL,2
LOOPNZ espera ; esperar que reciba comando
MOV AL,AH
OUT 60h,AL ; establecer los LED
STI

En general, este será el procedimiento a seguir para cualquier comando que requiera parámetros: hay
que esperar el momento adecuado para enviarlos; el LOOPNZ evita que la CPU se quede colgada si por
cualquier motivo fallara el teclado o el 8042. Como se ve, se establecen los 3 LED a la vez, aunque si sólo se
desea cambiar uno habrá que consultar el estado actual de los otros en las variables de la BIOS. No obstante,
este cambio es sólo puntual ya que al pulsar las teclas que actúan sobre los LED, la BIOS o el KEYB los
reajustarán anulando el cambio, siendo necesario reprogramar parcialmente la interrupción del teclado si se
desea evitarlo.

12.8.4 - COMUNICACIÓN TECLADO ─Ψ CPU

Más bien cabría llamarla la comunicación teclado ─Ψ 8042: aunque muchos de estos códigos acaben
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

siendo interpretados por la CPU, algunos se los queda el 8042 que siempre es el primero en enterarse. A
continuación se listan los valores que el teclado puede enviar a la CPU o al 8042 en un momento dado.

- Reenvío (0FEh): El teclado puede enviar este comando a la CPU para solicitar el reenvío cuando detecta un
fallo en la recepción (normalmente de paridad) o una entrada incorrecta.

- Reconocimiento ó ACK (0FAh): El teclado devuelte este valor cada vez que la CPU le envía algo, para indicar
que lo ha recibido (excepto en el caso de los comandos Eco y Reenvío de la CPU).

- Desbordamiento (0): Cuando la CPU intenta leer el teclado directamente sin haber códigos en el buffer del
teclado (el buffer interno del propio teclado, se entiende) accederá a la posición 17ª del mismo, encontrándose
este valor.

- Fallo en el diagnóstico (0FDh): El teclado periódicamente se autochequea y envía este código si detecta algún
fallo. Si el fallo sucede durante el BAT, dejará de rastrear las teclas en espera de un comando de la CPU; en
cualquier otro momento continuará rastreando las teclas.

- Código de tecla soltada ó break code (0F0h): El teclado envía este código a la CPU para indicar que el
siguiente código que enviará a continuación corresponderá a una tecla soltada. Bajo MS-DOS este código lo
intercepta el 8042 y se lo oculta a la CPU, con objeto de emular el código de tecla soltada de los PC/XT.

- BAT completado (0AAh): Después de realizar el BAT el teclado envía un 0AAh para indicar que ha salido
bien, o un 0FCh (u otro valor) si ha habido fallos.

- Respuesta al eco (0EEh): El teclado envía este valor a la CPU si ésta se lo ha enviado a él.

la comunicación directa CPU ─Ψ teclado.

Debido a la presencia del 8042, normalmente no será preciso que la CPU se comunique directamente
con el teclado a través de las líneas de reloj y datos. No obstante, este capítulo está explicado en el manual de
referencia técnico del IBM AT, al menos en la edición de 1984; por tanto, aquellos aficionados que estén
pensando construirse su propio ordenador y acoplarle un teclado ordinario de PC podrían consultar ese libro.
Por cierto, en los PC y XT no es preciso tampoco realizar esta tarea, ya que el teclado con el conmutador de
selección de la parte inferior en modo XT no es realmente bidireccional (de hecho, lleva un control autónomo
de los LED) por lo que no tiene sentido intentar enviar nada. Y a la hora de recibir, hay métodos mucho más
cómodos...
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

12.9. - EL PUERTO SERIE: UART 8250.

La transmisión de datos en serie es una de las más comunes para aquellas aplicaciones en las que la
velocidad no es demasiado importante, o no es posible conseguirla (por ejemplo, vía red telefónica). Para
simplificar el proceso de enviar los bits uno por uno han surgido circuitos integrados que realizan la función,
teniendo en cuenta todos los tiempos necesarios para lograr una correcta comunicación y aliviando a la CPU de
esta pesada tarea. El circuito que estudiaremos es el 8250 de National, fabricado también por Intel, aunque las
diferencias respecto al 16550 serán brevemente señaladas. Esta última UART es más reciente y mucho más
potente -aunque solo sea por unos pequeños detalles- y cada vez está más extendida, en particular en las
actuales placas base.

La línea que transmite los datos en serie está inicialmente en estado alto. Al comenzar la transferencia,
se envía un bit a 0 ó bit de inicio. Tras él irán los 8 bits de datos a transmitir (en ocasiones son 7, 6 ó 5): estos
bits están espaciados con un intervalo temporal fijo y preciso, ligado a la velocidad de transmisión que se esté
empleando. Tras ellos podría venir o no un bit de paridad generado automáticamente por la UART. Al final,
aparecerá un bit (a veces un bit y medio ó dos bits) a 1, que son los bits de parada o bits de stop. Lo de medio
bit significa que la señal correspondiente en el tiempo a un bit dura la mitad; realmente, en comunicaciones se
utiliza el término baudio para hacer referencia a las velocidades, y normalmente un baudio equivale a un bit. La
presencia de bits de inicio y parada permite sincronizar la estación emisora con la receptora, haciendo que los
relojes de ambas vayan a la par. A la hora de transmitir los bytes de datos unos tras otros, existe flexibilidad en
los tiempos, de ahí que este tipo de comunicaciones se consideren asíncronas. La transmisión de los 8 bits de
datos de un byte realmente es síncrona, pero las comunicaciones en serie siempre han sido consideradas
asíncronas.

Para una transmisión en serie básica bastan tres hilos. Sin embargo, el software que controla el puerto
serie a través de la interfaz RS-232-C podría requerir más señales de control para establecer la comunicación, al
igual que para controlar un modem telefónico pueden hacer falta más líneas (de control, no telefónicas...).
Bromas aparte, sobre comunicaciones en serie existe todo un mundo; acerca de este tema se han escrito muchos
libros completos. Lógicamente, aquí no vamos a dar ningún curso de comunicaciones en serie. Sin embargo, los
menos introducidos en la materia no deben temer: ¿qué mejor manera de aprender sobre las comunicaciones en
serie que examinar cómo funciona un chip que las soporta?. Desde luego, también se podría partir desde el
punto de vista contrario, pero como entendido en sistemas digitales, el lector puede que tenga menos problemas
con este interesante enfoque.

12.9.1. - DESCRIPCIÓN DEL INTEGRADO.

▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ SIN ██▌ 10 31 ▐██ -OUT2

▌ ▐ ▌ ▐

D0 ██▌ 1 40 ▐██ Vcc SOUT ██▌ 11 30 ▐██ INTRPT

▌ ▐ ▌ ▐

D1 ██▌ 2 39 ▐██ -RI CS0 ██▌ 12 29 ▐██ NC

▌ ▐ ▌ ▐

D2 ██▌ 3 38 ▐██ -DCD CS1 ██▌ 13 28 ▐██ A0

▌ ▐ ▌ ▐

D3 ██▌ 4 37 ▐██ -DSR -CS2 ██▌ 14 27 ▐██ A1

▌ ▐ ▌ ▐

D4 ██▌ 5 36 ▐██ -CTS -BAUDOUT ██▌ 15 26 ▐██ A2

▌ ▐ ▌ ▐

D5 ██▌ 6 35 ▐██ MR XTAL1 ██▌ 16 25 ▐██ -ADS

▌ ▐ ▌ ▐

D6 ██▌ 7 34 ▐██ -OUT1 XTAL2 ██▌ 17 24 ▐██ CSOUT

▌ ▐ ▌ ▐

D7 ██▌ 8 33 ▐██ -DTR -DOSTR ██▌ 18 23 ▐██ DDIS

▌ ▐ ▌ ▐

RCLK ██▌ 9 32 ▐██ -RTS DOSTR ██▌ 19 22 ▐██ DISTR

▌ ▐ ▌ ▐
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

GND ██▌ 20 21 ▐██ -DISTR El ACE 8250 (Asynchronous Communication Element) integra en
▌ '8250 ▐ un solo chip una UART (Universal Asynchronous Receiver/Transmitter) y
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ un BRG (Baud Rate Generator). Soporta velocidades de hasta 625000
baudios con relojes de hasta 10 MHz. El BRG incorporado divide la
frecuencia base para conseguir las velocidades estándar de la RS-232-C.

SIGNIFICADO DE LAS LÍNEAS DEL 8250

DISTR:Data In Strobe. Línea de entrada que indica al 8250 que deje los datos en el bus (D0..D7), los
datos dejados dependen del registro seleccionado con A0..A2. Son necesarias CS0..CS2 para
habilitar DISTR. En vez de DISTR se puede usar -DISTR, pero sólo una de las dos.
DOSTR:Data Out Strobe. Idéntico a DISTR pero en salida.
D0..D7:Data Bits 0..7: Bus triestado bidireccional de 8 líneas para transmitir datos, información de
control y de estado entre la CPU y el 8250. El primer bit enviado/recibido es D0.
A0..A2:Register Select. Líneas de entrada que indican el registro del 8250 usado en la operación.
XTALx:Crystal/Clock: Conexiones para el cristal del cuarzo del BRG. XTAL1 puede actuar como
entrada de reloj externa, en cuyo caso XTAL2 debería quedar abierto.
SOUT:Serial Data Output: Salida de datos en serie del 8250. Una marca es un '1' y un espacio es un '0'.
SOUT está en marca cuando el transmisor está inhibido, MR está a 1, el registro de
transmisión está vacío o en el modo lazo (LOOP) del 8250. No es afectado por -CTS.
-CTS:Clear To Send: Línea de entrada. El estado lógico de esta señal puede consultarse en el bit CTS del
Modem Status Register (MSR) -como el bit CTS es el bit 4 del MSR se
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

referencia MSR(4)-. Un cambio en el estado de -CTS desde la última lectura del MSR provoca que se active DCTS (bit MSR(0)). Cuando -CTS está activo
(a 0) el modem indica que el dato en SOUT puede ser transmitido. -CTS no afecta al modo lazo (LOOP) del 8250.
-DSR:Data Set Ready: Línea de entrada. El estado lógico de esta señal puede consultarse en MSR(5). DDSR (bit MSR(1)) indica si -DSR ha cambiado
desde la última lectura del MSR. Cuando -DSR está activo el modem indica que está listo para intercambiar datos con el 8250; ello depende del
estado del DCE (Data Communications Equipment) local y no implica que haya comunicación con la estación remota.
-DTR:Data Terminal Ready. Línea de salida que puede activarse (poner a 0) escribiendo un 1 en MCR(0), y desactivarse escribiendo un 0 en dicho bit o
ante la activación del pin MR. Con -DTR activo se indica al DCE que el 8250 puede recibir datos. En algunas circunstancias, esta señal se usa
como LED de 'power on'. Si está inactivo, el DCE desconecta el modem del circuito de telecomunicaciones.
-RTS:Request To Send. Línea de salida que habilita el modem. Se activa (poner a 0) escribiendo un 1 en MCR(1). Esta señal se pone en alto en respuesta a
MR. -RTS indica al DCE que el 8250 tiene un dato listo para transmitir. En la modalidad half-duplex, esta señal se utiliza para controlar la
dirección de la línea.
-BAUDOUT:Esta línea de salida contiene una señal de reloj 16 veces mayor que la frecuencia usada para transmitir. Equivale a la frecuencia de entrada en el
oscilador dividida por el BRG. La estación receptora podría emplear esta señal conectándola a RCLK (para compartir el mismo reloj).
-OUTx:Estas dos salidas de propósito general se pueden activar (poner a 0) escribiendo un 1 en MCR(2) y MCR(3). Son desactivadas por la señal MR. En
el modo lazo (LOOP o bucle), están también inactivas.
-RI:Ring Indicator. Esta línea de entrada indica si el modem ha detectado que llaman por la línea y puede consultarse en MSR(6). El bit TERI (MSR(2))
indica si esta línea ha cambiado desde la última lectura del MSR. Si las interrupciones están habilitadas (IER(3) activo) esta patilla provoca una
interrupción al activarse. -RI permanece activo durante el mismo intervalo de tiempo que la zona activa del ciclo de llamada e inactivo en los
intervalos de la zona inactiva (o cuando el DCE no detecta la llamada). El circuito no se corta por culpa de -DTR.
-DCD:Data Carrier Detect. Línea de entrada que indica si el modem ha detectado portadora. Se puede consultar su estado lógico en MSR(7). El bit MSR(3)
indica si esta línea ha cambiado desde la última lectura del MSR. Esta línea no tiene efecto sobre el receptor. Si las interrupciones están
permitidas, una interrupción será generada ante el cambio de esta línea.
MR:Master Reset. Esta línea de entrada lleva el 8250 a un estado inactivo interrumpiendo su posible actividad. El MCR y las salidas ligadas al mismo son
borradas. El LSR es borrado en todos sus bits salvo THRE y TEMT (que son activados). El 8250 permanece en este estado hasta volver a ser
programado.
INTRPT:Interrupt Request. Línea de salida que se activa cuando se produce una interrupción de alguno de estos tipos y está permitida: Recepción de
banderín de error, dato recibido disponible, registro de retención de transmisión vacío, y estado del modem. Esta línea se desactiva con el
apropiado servicio de la interrupción o ante MR.
SIN:Serial Data Input. Es la línea de entrada de datos desde el modem. En el modo lazo (LOOP o bucle) están inhibidas las entradas en SIN.
CS0..2:Chip Select. Estas entradas actúan como líneas de habilitación para las señales de escritura (DOSTR, -DOSTR) y lectura (DISTR, -DISTR).
CSOUT:Chip Select Out. Esta línea de salida se activa cuando el chip ha sido seleccionado con CS0..2. No comenzará transferencia de datos alguna hasta
que CSOUT se active.
DDIS:Driver Disable. Esta salida está inactiva cuando la CPU lee datos del 8250. Una salida activa puede emplearse para inhibir un transceiver externo
cuando la CPU está leyendo datos.
-ADS:Address Strobe. Cuando esta línea de entrada está activa se enclavan las líneas A0..A2 y CS0..2; esto puede ser necesario si los pines de selección de
registro no son estables durante la duración de la operación de lectura o escritura (modo multiplexado). Si esto no es preciso, esta señal se puede
mantener inactiva (modo no-multiplexado).
RCLK:Esta línea se corresponde con la entrada de reloj para la sección receptora, equivalente a 16 veces la frecuencia empleada en la transmisión y puede
proceder del BAUDOUT de la estación remota o de un reloj externo.

REGISTROS DEL 8250

El 8250 dispone de 11 registros (uno más el 16550) pero sólo 3 líneas de dirección para seleccionarlos.
Lo que permita distinguir unos de otros será, aparte de las líneas de direcciones, el sentido del acceso (en lectura
o escritura) y el valor de un bit de uno de los registros: el bit DLAB del registro LCR, que es el bit 7 de dicho
registro. La notación para hacer referencia a un bit de un registro se escribe REG(i); en este ejemplo, el bit
DLAB sería LCR(7). Realmente, DLAB se emplea sólo puntualmente para poder acceder y programar los
registros que almacenan el divisor de velocidad; el resto del tiempo, DLAB estará a 0 para acceder a otros
registros más importantes.

┌────┬────┬────┬──────┬──────┬────────┬───────────────────────────────────────────────────────────────────────────────────┐
│ A2 │ A1 │ A0 │ DLAB │ MODO │ NOMBRE │ SIGNIFICADO │
├────┼────┼────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ 0 │ 0 │ 0 │ 0 │ R │ RBR │ Receiver Buffer Register (Registro buffer de recepción) │
├────┼────┼────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ 0 │ 0 │ 0 │ 1 │ R/W │ DLL │ Divisor Latch LSB (Divisor de velocidad, parte baja) │
├────┼────┼────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ 0 │ 0 │ 0 │ 0 │ W │ THR │ Transmitter Holding Register (Registro de retención de transmisión) │
├────┼────┼────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────┤
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

│ 0 │ 0 │ 1 │ 0 │ R/W │ IER │ Interrupt Enable Register (Registro de habilitación de interrupciones) │


├────┼────┼────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ 0 │ 0 │ 1 │ 1 │ R/W │ DLM │ Divisor latch MSB (Divisor de velocidad, parte alta) │
├────┼────┼────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ 0 │ 1 │ 0 │ X │ R │ IIR │ Interrupt Identification Register (Registro de identificación de interrupciones) │
├────┼────┼────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ 0 │ 1 │ 0 │ X │ W │ FCR │ FIFO Control Register (Registro de control FIFO) - SOLO 16550 - │
├────┼────┼────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ 0 │ 1 │ 1 │ X │ R/W │ LCR │ Line Control Register (Registro de control de línea) ¡¡EL BIT 7 ES DLAB!! │
├────┼────┼────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ 1 │ 0 │ 0 │ X │ R/W │ MCR │ Modem Control Register (Registro de control del modem) │
├────┼────┼────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ 1 │ 0 │ 1 │ X │ R/W │ LSR │ Line Status Register (Registro de estado de la línea) │
├────┼────┼────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ 1 │ 1 │ 0 │ X │ R/W │ MSR │ Modem Status Register (Registro de estado del modem) │
├────┼────┼────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────┤
│ 1 │ 1 │ 1 │ X │ R/W │ SCR │ Scratch Register (Registro residual) │
└────┴────┴────┴──────┴──────┴────────┴───────────────────────────────────────────────────────────────────────────────────┘
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

1) LCR (Line Control Register). Controla el formato del carácter de datos.

┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐
│ │ Break │ Stick │ │ │ │ │ │
│ DLAB │ Control │ Parity │ EPS │ PEN │ STB │ WLS1 │ WLS0 │
│ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
└─────────┴─────────┴────┬────┴────┬────┼────┬────┴─────────┴────┬────┴────┬────┘
└───────┐ │ ┌───────┘ │ ┌───────┘
0 0 0 Sin paridad │ │ Word Length Select
0 0 1 Paridad impar 0 0 Datos de 5 bits
0 1 1 Paridad par 0 1 Datos de 6 bits
1 0 1 Marca ('1') 1 0 Datos de 7 bits
1 1 1 Espacio ('0') 1 1 Datos de 8 bits

Los bits WLS seleccionan el tamaño del dato empleado. STB indica el número de bits de stop, que
pueden ser 1 (STB=0) ó 2 (STB=1), al trabajar con datos de 5 bits STB=1 implica 1.5 bits de stop. PEN (Parity
Enable) permite habilitar o no la generación de bit de paridad, EPS (Even Parity Select) selecciona paridad par
si está a 1 (o impar en caso contrario). Stick Parity permite forzar el bit de paridad a un estado conocido según el
valor de EPS. Cuando Break Control es puesto a 1, la salida SOUT se pone en estado espacio (a 0), sólo afecta a
SOUT y no a la lógica de transmisión. Esto permite a la CPU alertar a un terminal del sistema sin transmitir
caracteres erróneos o extraños si se siguen estas fases: 1) cargar un carácter 0 en respuesta a THRE, 2) activar
Break Control en respuesta al próximo THRE, 3) esperar a que el transmisor esté inactivo (TEMT=1) y bajar
Break Control. Durante el Break, el transmisor puede usarse como un preciso temporizador de carácter.

El bit DLAB (Divisor Latch Access Bit) puesto a 1 permite acceder a los Latches divisores DLL y
DLM del BRG en lectura y escritura. Para acceder al RBR, THR y al IER debe ser puesto a 0.

2) LSR (Line Status Register). Este suele ser el primer registro consultado tras una interrupción.

┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐
│ │ │ │ │ │ │ │ │
│ 0 │ TEMT │ THRE │ BI │ FE │ PE │ OE │ DR │
│ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
└─────────┴────┬────┴────┬────┴────┬────┼────┬────┴────┬────┴────┬────┴────┬────┘
│ │ │ │ │ │ Data Ready
Transmitter Transmitter │ │ │ Overrun Error
Empty Holding │ │ Parity Error
Register │ Framing Error
Empty Break Interrupt

DR está activo cuando hay un carácter listo en el RBR y es puesto a 0 cuando se lee el RBR. Los bits 1
al 4 de este registro (OE, PE, FE y BI) son puestos a 0 al consultarlos -cuando se lee el LSR- y al activarse
pueden generar una interrupción de prioridad 1 si ésta interrupción está habilitada. OE se activa para indicar que
el dato en el RBR no ha sido leído por la CPU y acaba de llegar otro que lo ha sobreescrito. PE indica si hay un
error de paridad. FE indica si el carácter recibido no tiene los bit de stop correctos. BI se activa cuando la
entrada de datos es mantenida en espacio (a 0) durante un tiempo superior al de transmisión de un carácter (bit
de inicio + bits de datos + bit de paridad + bit de parada).

THRE indica que el 8250 puede aceptar un nuevo carácter para la transmisión: este bit se activa cuando
el THR queda libre y se desactiva escribiendo un nuevo carácter en el THR. Se puede producir, si está
habilitada; la interrupción THRE (prioridad 3); INTRPT se borra leyendo el IIR. El 8250 emplea un registro
interno para ir desplazando los bit y mandarles en serie (el Transmitter Shift Register), dicho registro se carga
desde el THR. Cuando ambos registros (THR y el Transmitter Shift) están vacíos, TEMT se activa; volverá a
desactivarse cuando se deje otro dato en el THR hasta que el último bit salga por SOUT.

3) MCR (Modem Control Register). Controla el interface con el modem.


EL HARDWARE DE APOYO AL MICROPROCESADOR 346

┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐
│ │ │ │ │ │ │ │ │
│ 0 │ 0 │ 0 │ LOOP │ OUT2 │ OUT1 │ RTS │ DTR │
│ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
└─────────┴─────────┴─────────┴─────────┼─────────┴─────────┴────┬────┴────┬────┘
│ Data Terminal Ready
Request To Send

Las líneas de salida -DTR, -RTS, -OUT1 y -OUT2 están directamente controladas por estos bits; como
se activan a nivel bajo, son puestas a 0 escribiendo un 1 en estos bits y viceversa. Estas líneas sirven para
establecer diversos protocolos de comunicaciones.

El bit LOOP introduce el 8250 en un modo lazo (o bucle) de autodiagnóstico. Con LOOP activo,
SOUT pasa a estado de marca (a 1) y la entrada SIN es desconectada. Los registros de desplazamiento
empleados en la transmisión y la recepción son conectados entre sí. Las cuatro entradas de control del modem (-
CTS, -DSR, DC y -RI) son desconectadas y en su lugar son internamente conectadas las cuatro salidas de
control del modem (-DTR, -RTS, -OUT1 y -OUT2) cuyos pines son puestos en estado inactivo (alto). En esta
modalidad de operación (modo lazo o bucle), los datos transmitidos son inmediatamente recibidos, lo que
permite comprobar el correcto funcionamiento del integrado. Las interrupciones son completamente operativas
en este modo, pero la fuente de estas interrupciones son ahora los 4 bits bajos del MCR en lugar de las cuatro
entradas de control. Estas interrupciones están aún controladas por el IER.

4) MSR (Modem Status Register).

┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐
│ │ │ │ │ │ │ │ │
│ DCD │ RI │ DSR │ CTS │ DDCD │ TERI │ DDSR │ DCTS │
│ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
└────┬────┴────┬────┴────┬────┴────┬────┼────┬────┴────┬────┴────┬────┴────┬────┘
│ │ │ │ │ │ │ │
│ │ │ │ Delta Trailing Delta Delta
Data │ Data Clear Data Edge Data Clear
Carrier Ring Set To Carrier of Ring Set To
Detect Indicator Ready Send Detect Indicator Ready Send

Además de la información de estado del modem, los 4 bits bajos (DDCD, TERI, DDSR, DCTS)
indican si la línea correspondiente, en los 4 bits superiores, ha cambiado de estado desde la última lectura del
MSR; en el caso de TERI sólo indica transiciones bajo-Ψalto en -RI (y no las de sentido contrario). La línea
CTS del modem indica si está listo para recibir datos del 8250 a través de SOUT (en el modo lazo este bit
equivale al bit RTS del MCR). La línea DSR del modem indica que está listo para dar datos al 8250 (en el modo
lazo -o LOOP- equivale al bit DTR del MCR). RI y DCD indican el estado de ambas líneas (en el modo lazo se
corresponden con OUT1 y OUT2 respectivamente). Al leer el MSR, se borran los 4 bits inferiores (que en una
lectura posterior estarían a 0) pero no los bits de estado (los 4 más significativos).

Los bits de estado (DCD, RI, DSR y CTS) reflejan siempre la situación de los pines físicos respectivos
(estado del modem). Si DDCD, TERI, DDSR ó DCTS están a 1 y se produce un cambio de estado durante la
lectura, dicho cambio no será reflejado en el MSR; pero si están a 0 el cambio será reflejado después de la
lectura. Tanto en el LSR como en el MSR, la asignación de bits de estado está inhibida durante la lectura del
registro: si se produce un cambio de estado durante la lectura, el bit correspondiente será activado después de la
misma; pero si el bit ya estaba activado y la misma condición se produce, el bit será borrado tras la lectura en
lugar de volver a ser activado.

5) y 6) BRSR (Baud Rate Select Register). Son los registros DLL (parte baja) y DLM (parte alta).

Estos dos registros de 8 bits constituyen un valor de 16 bits que será el divisor que se aplicará a la
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

frecuencia base para seleccionar la velocidad a emplear. Dicha frecuencia base (por ejemplo, 1.8432 MHz) será
dividida por 16 veces el valor almacenado aquí. Por ejemplo, para obtener 2400 baudios:

1843200
───────── = 48 -Ψ DLL=48, DLM=0
16 * 2400

7) RBR (Receiver Buffer Register).

El circuito receptor del 8250 es programable para 5, 6, 7 u 8 bits de datos. En el caso de emplear menos
de 8, los bits superiores de este registro quedan a 0. Los datos entran en serie por SIN (comenzando por el bit
D0) en un registro de desplazamiento gobernado por el reloj de RCLK, sincronizado con el bit de inicio.
Cuando un carácter completa el registro de desplazamiento de recepción, sus bits son volcados al RBR y el bit
DR del LSR es activado para indicar a la CPU que puede leer el RBR. El diseño del 8250 permite la recepción
continua de datos sin pérdidas: el RBR almacena siempre el último carácter recibido dando tiempo suficiente a
la CPU para leerlo mientras simultáneamente está cargando el registro de desplazamiento con el siguiente; si la
CPU tarda demasiado un nuevo dato podría aparecer en el RBR antes de haber leído el anterior (condición de
overrun, bit OE del LSR).

8) THR (Transmitter Holding Register).

El registro de retención de transmisión almacena el siguiente carácter que va a ser transmitido en serie
mientras el registro de desplazamiento de transmisión está enviando el carácter actual. Cuando el registro de
desplazamiento se vacíe, será cargado desde el THR para transmitir el nuevo carácter. Al quedar vacío THR, el
bit THRE del LSR se activa. Cuando estén vacíos tanto el THR como el registro de desplazamiento de
transmisión, el bit TEMT del LSR se activa.

9) SCR (Scratchpad Register).

Este registro no es empleado por el 8250, y de hecho no existía en las primeras versiones del integrado.
Puede ser empleado por el programador como una celdilla de memoria.

10) IIR (Interrupt Identification Register).

Existen 4 niveles de prioridad en las interrupciones generables por el 8250, por este orden:

1) Estado de la línea de recepción.


2) Dato recibido disponible.
3) Registro de retención de transmisión vacío.
4) Estado del modem.

La información que indica que hay una interrupción pendiente y el tipo de la misma es almacenada en
el IIR. El IIR indica la interrupción de mayor prioridad pendiente. No serán reconocidas otras interrupciones
hasta que la CPU envíe la señal de reconocimiento apropiada. En el registro IIR, el bit 0 indica si hay una
interrupción pendiente (bit 0=0) o si no la hay (bit 0=1), esto permite tratar las interrupciones en modo polled
consultando este bit. Los bits 1 y 2 indican el tipo de interrupción. Los restantes están a 0 en el 8250, pero el
16550 utiliza alguno más.

┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐
│ │ │ │ │ │ │ │ │
│ DCD │ RI │ DSR │ CTS │ DDCD │ │ │ │
│ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
└────┬────┴────┬────┴─────────┴─────────┼────┬────┴────┬────┴────┬────┴────┬────┘
│ ┌───────┘ │ │ ┌───────┘ 1 - Interrupción pendiente
1 1 - Colas FIFO activadas en 16550 │ X X - Identificación de la Interrupción
0 0 - Colas FIFO no activadas A 1 en el 16550 si pendiente la interrupción TIMEOUT
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

┌───────────────────────────────────┬─────────────────────────────────────────────────────────────────┐
│ IDENTIFICACIÓN DE LA INTERRUPCIÓN │ ACTIVACIÓN / RECONOCIMIENTO (RESET) DE LA INTERRUPCIÓN │
├───────┬───────┬───────┬───────────┼──────────────────┬───────────────┬──────────────────────────────┤
│ Bit 2 │ Bit 1 │ Bit 0 │ Prioridad │ Flag │ Fuente │ Reconocimiento │
├───────┼───────┼───────┼───────────┼──────────────────┼───────────────┼──────────────────────────────┤
│ X │ X │ 1 │ │ Ninguno │ Ninguna │ │
│ │ │ │ │ │ │ │
├───────┼───────┼───────┼───────────┼──────────────────┼───────────────┼──────────────────────────────┤
│ 1 │ 1 │ 0 │ Primera │ Línea de estado │ OE, PE, │ Leer LSR │
│ │ │ │ │ del receptor │ FE ó BI │ │
├───────┼───────┼───────┼───────────┼──────────────────┼───────────────┼──────────────────────────────┤
│ 1 │ 0 │ 0 │ Segunda │ Recibido dato │ Recibido dato │ Leer RBR │
│ │ │ │ │ disponible │ disponible │ │
├───────┼───────┼───────┼───────────┼──────────────────┼───────────────┼──────────────────────────────┤
│ 0 │ 1 │ 0 │ Tercera │ THRE │ THRE │ Leer IIR si es la fuente de │
│ │ │ │ │ │ │ interrupción, o escribir THR │
├───────┼───────┼───────┼───────────┼──────────────────┼───────────────┼──────────────────────────────┤
│ 0 │ 0 │ 0 │ Cuarta │ Estado del modem │ -CTS, -DSR │ Leer MSR │
│ │ │ │ │ │ -RI, -DCD │ │
└───────┴───────┴───────┴───────────┴──────────────────┴───────────────┴──────────────────────────────┘

11) IER (Interrupt Enable Register).

Este registro de escritura se utiliza para seleccionar qué interrupciones activan INTRPT y, por
consiguiente, van a ser solicitadas a la CPU. Deshabilitar el sistema de interrupciones inhibe el IIR y desactiva
la salida INTRPT.

┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐
│ │ │ │ │ │ │ │ │
│ 0 │ 0 │ 0 │ 0 │ IER(3) │ IER(2) │ IER(1) │ IER(0) │
│ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
└─────────┴─────────┴─────────┴─────────┼─────────┴─────────┴─────────┴─────────┘

IER0: A 1 si habilitar interrupción de dato disponible.


IER1: A 1 si habilitar interrupción de registro de retención de transmisión vacío.
IER2: A 1 si habilitar interrupción de error de recepción (bits 1 al 4 del LSR).
IER3: A 1 si habilitar interrupción ante el cambio del MSR (Registro de estado del modem).

El 16550 genera también una interrupción de TIMEOUT (prioridad 1) si hay datos en la cola FIFO y
no son leídos dentro del tiempo que dura la recepción de 4 bytes o si no se reciben datos durante el tiempo que
tomaría recibir 4 bytes.

12) FCR (FIFO Control Register). Sólo disponible en el 16550, no en el 8250.

┌─────────┬─────────┬─────────┬─────────┼─────────┬─────────┬─────────┬─────────┐
│ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │
│ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
└────┬────┴────┬────┴─────────┴─────────┼────┬────┴────┬────┴────┬────┴────┬────┘
│ ┌───────┘ │ │ │ 1 - Habilita el
│ │ Tamaño cola A 1 si cambiar los │ │ borrado de colas
0 0 - 1 byte pines RXRDY y TXRDY │ │ FIFO XMIT y RCVR.
0 1 - 4 bytes del modo 0 al modo 1 │ │
1 0 - 8 bytes │ 1 - Borrar cola RCVR
1 1 - 14 bytes 1 - Borrar cola XMIT
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

El bit 0 debe estar a 1 para escribir los bits 1 ó 2. Cuando el bit 1 ó el 2 son activados, la cola afectada
es borrada y el bit es devuelto a 0. Los registros de desplazamiento de la transmisión y la recepción, en cada
caso, no resultan afectados.

LA TRANSMISIÓN Y LA RECEPCIÓN EN EL 8250

La sección de transmisión del 8250 consiste en el Registro de Retención de transmisión (THR), el


Registro de Desplazamiento de la Transmisión (TSR) y en la lógica de control asociada. Dos bits en el LSR
indican si está vacío el THR (bit THRE) o el TSR (bit TEMT). El carácter de 5-8 bits a ser transmitido es
escrito en el THR; la CPU debería realizar esta operación sólo si THRE está activo: este bit es activado cuando
el carácter es copiado del THR al TSR durante la transmisión del bit de inicio.

Cuando el transmisor está inactivo, tanto THRE como TEMT están activos. El primer carácter escrito
provoca que THRE baje; tras completarse la transferencia vuelve a subir aunque TEMT permanecerá bajo
mientras dure la transferencia en serie del carácter a través de TSR. Si un segundo carácter es escrito en THR,
THRE vuelve a bajar y permanecerá bajo hasta que el TSR termine la transmisión, porque no es posible volcar
el contenido de THR en TSR hasta que este último no acabe con el carácter que estaba transmitiendo. Cuando el
último carácter ha sido transmitido fuera del TSR, TEMT vuelve a activarse y THRE también lo hará tras un
cierto tiempo (el que tarda en escribirse THR en TSR).

En la recepción, los datos en serie asíncronos entran por la patilla SIN. El estado inactivo de la línea se
considera el '1' lógico. Un circuito de detección de bit de inicio está continuamente buscando una transición
alto─Ψbajo que interrumpa el estado inactivo. Cuando la detecta, se resetea un contador interno y cuenta 7½
pulsos de reloj (tener en cuenta que la frecuencia base es dividida por 16), posicionándose en el centro del bit de
inicio. El bit de inicio se considera válido si SIN continúa aún bajo en ese momento. La validación del bit de
inicio evita que un ruido espúreo en la línea sea confundido con un nuevo carácter.

El LCR tiene toda la información necesaria para la recepción: tamaño del carácter (5-8 bits), número de
bits de stop, si hay paridad o no... la información de estado que se genere será depositada en el LSR. Cuando un
carácter es transmitido desde el Registro de Desplazamiento de la Recepción (RSR) al Registro Buffer de
Recepción (RBR), el bit DR del LSR se activa. La CPU lee entonces el RBR, lo que hace bajar de nuevo DR. Si
el carácter no es leído antes de que el siguiente carácter que se está formando pase del RSR al RBR, el bit OE
(overrun) del LSR se activa. También se puede activar PE en el LSR si hay un error de paridad. Finalmente, la
circuitería que chequea la validez del bit de stop podría activar el bit FE del LSR en caso de error.

El centro del bit de inicio se define como 7½ pulsos de reloj; si los datos que entran por SIN
constituyen una onda cuadrada simétrica, el centro de las celdas que contienen los bits se desviará a lo sumo un
±3.125% del centro real, lo que deja un margen de error del 46.875%; el bit de inicio puede comenzar, como
mucho, 1 ciclo de reloj (de los 16) antes de ser detectado.

EL B.R.G. (BAUD RATE GENERATOR)

El BRG genera las señales de reloj para el funcionamiento de la UART, permitiendo los ratios de
transferencia del estándar ANSI/CCITT. Se puede conectar un cristal a XTAL1 y XTAL2 ó una señal de reloj a
XTAL1. La salida -BAUDOUT puede excitar la línea XTAL1 de otro 8250.

La velocidad es determinada por los registros DLL y DLM almacenando un valor divisor de la
frecuencia del reloj conectado al 8250. El resultado debe ser 16 veces mayor que la frecuencia en baudios
deseada, ya que el 8250 utiliza 16 pulsos de reloj para cada bit. El siguiente cuadro resume los valores que hay
que asignar al divisor para lograr las frecuencias más usuales con los cristales más comunes.

┌────────────────────────────┬────────────────────────────┬────────────────────────────┐
│ Cristal de 1.8432 MHz │ Cristal de 2.4576 MHz │ Cristal de 3.072 MHz │
┌─────────┼───────────────┬────────────┼───────────────┬────────────┼───────────────┬────────────┤
│ Baudios │ Divisor usado │ % error │ Divisor usado │ % error │ Divisor usado │ % error │
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

│ finales │ para 16xReloj │ si lo hay │ para 16xReloj │ si lo hay │ para 16xReloj │ si lo hay │
├─────────┼───────────────┼────────────┼───────────────┼────────────┼───────────────┼────────────┤
│ 50 │ 2304 │ │ 3072 │ │ 3840 │ │
│ 75 │ 1536 │ │ 2048 │ │ 2560 │ │
│ 110 │ 1047 │ 0.026 │ 1396 │ 0.026 │ 1745 │ 0.026 │
│ 134.5 │ 857 │ 0.058 │ 1142 │ 0.0007 │ 1428 │ 0.034 │
│ 150 │ 768 │ │ 1024 │ │ 1280 │ │
│ 300 │ 384 │ │ 512 │ │ 640 │ │
│ 600 │ 192 │ │ 256 │ │ 320 │ │
│ 1200 │ 96 │ │ 128 │ │ 160 │ │
│ 1800 │ 64 │ │ 85 │ 0.392 │ 107 │ 0.315 │
│ 2000 │ 58 │ 0.69 │ 77 │ 0.260 │ 96 │ │
│ 2400 │ 48 │ │ 64 │ │ 80 │ │
│ 3600 │ 32 │ │ 43 │ 0.775 │ 53 │ 0.628 │
│ 4800 │ 24 │ │ 32 │ │ 40 │ │
│ 7200 │ 16 │ │ 21 │ 1.587 │ 27 │ 1.23 │
│ 9600 │ 12 │ │ 16 │ │ 20 │ │
│ 19200 │ 6 │ │ 8 │ │ 10 │ │
│ 38400 │ 3 │ │ 4 │ │ 5 │ │
│ 56000 │ 2 │ 2.86 │ - │ │ - │ │
└─────────┴───────────────┴────────────┴───────────────┴────────────┴───────────────┴────────────┘

RESET DEL 8250

Tras dar corriente al 8250 hay que tenerlo unos 500 ns con MR alto para resetearlo. Un nivel alto en
MR provoca:

1)Se inicializan los contadores internos de transmisión y recepción.


2)Se limpia el LSR salvo en sus bits TEMT y THRE (que son puestos a 1).
MCR, todas las líneas discretas, elementos de memoria y demás son puestos a 0.
DLL y DLM, RBR y THR no son afectados.

Tras el reset (MR llevado a estado bajo) el 8250 permanece en estado inactivo hasta ser programado.
Un reset hardware activa THRE y TEMT: cuando las interrupciones sean habilitadas, THRE provocará una.

Por software se puede forzar al 8250 a retornar a un estado totalmente conocido. Dicho reset consiste en
escribir el LCR, DLL y DLM, así como MCR. LSR y RBR deberían ser leídos antes de habilitar las
interrupciones para borrar cualquier información residual (datos o estado) de las operaciones anteriores.

┌─────────────────────────────────────────┬──────────────────────────────┬──────────────────────────────────────────────┐
│ REGISTRO / SEÑAL │ CONTROL DEL RESET │ EFECTO DEL RESET EN EL 8250 │
├─────────────────────────────────────────┼──────────────────────────────┼──────────────────────────────────────────────┤
│ IER │ MR │ Todos los bits a 0 (4..7 ya lo estaban) │
│ IIR │ MR │ Bit 0 a 1, Bits 1 y 2 a 0, demás siempre a 0 │
│ LCR │ MR │ Todos los bits a 0 │
│ MCR │ MR │ Todos los bits a 0 │
│ LSR │ MR │ Todos los bits a 0, salvo el 5 y el 6 (a 1) │
│ MSR │ MR │ Bits 0..3 a 0, bits 4..7 señal de entrada │
│ SOUT │ MR │ En alto │
│ INTRPT (RCVR error) │ Leer LSR / MR │ En bajo │
│ INTRPT (RCVR dato listo) │ Leer RBR / MR │ En bajo │
│ INTRPT (THRE) │ Leer IIR / Escribir THR / MR │ En bajo │
│ INTRPT (Cambios en el estado del modem) │ Leer MSR / MR │ En bajo │
│ -OUT2 │ MR │ En alto │
│ -RTS │ MR │ En alto │
│ -DTR │ MR │ En alto │
│ -OUT1 │ MR │ En alto │
└─────────────────────────────────────────┴──────────────────────────────┴──────────────────────────────────────────────┘
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

PROGRAMACIÓN DEL 8250

El 8250 se programa a través de los registros de control LCR, IER, DLL, DLM y MCR. Aunque los
registros de control pueden ser escritos en cualquier orden, IER debe ser escrito al final porque controla la
habilitación de las interrupciones. Una vez que el 8250 ha sido programado, los registros pueden ser
actualizados en cualquier momento en que el 8250 no se encuentre enviando o recibiendo datos.

12.9.2. - EL 8250 EN EL ORDENADOR.

Los ordenadores compatibles pueden tener conectados, de manera normal, hasta 4 puertos serie,
nombrados COM1-COM4. En el área de datos de la BIOS (segmento 40h) y justo al principio de la misma, hay
4 palabras con la dirección de memoria base de los puertos serie. A esta dirección de memoria base habrá que
sumar el desplazamiento relativo del número de registro a ser accedido.

El principal problema reside en que sólo están previstas 2 interrupciones para los puertos serie. Ello
implica que generalmente sólo 2 de los puertos podrán emplear interrupciones a un tiempo, debido a la
arquitectura del bus ISA. Generalmente COM1 y COM3 compartirán la IRQ4 (INT 0Ch) y COM2/COM4 la
IRQ3 (INT 0Bh). Estas asignaciones pueden ser cambiadas por el usuario actuando sobre los switches de
configuración de las tarjetas (que en ocasiones permiten incluso elegir la IRQ5). Por tanto, no está de más tener
cuidado en los programas y permitir un cierto grado de configuración en estas cuestiones.

┌────────┬──────┬──────┬────────┬───────────────────────────────────────────────────────────────────────────────────

│ OFFSET │ DLAB │ MODO │ NOMBRE │ SIGNIFICADO

├────────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────

│ 0 │ 0 │ R │ RBR │ Receiver Buffer Register (Registro buffer de recepción)

├────────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────

│ 0 │ 1 │ R/W │ DLL │ Divisor Latch LSB (Divisor de velocidad, parte baja)

├────────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────

│ 0 │ 0 │ W │ THR │ Transmitter Holding Register (Registro de retención de transmisión)

├────────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────

│ 1 │ 0 │ R/W │ IER │ Interrupt Enable Register (Registro de habilitación de interrupciones)

├────────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────

│ 1 │ 1 │ R/W │ DLM │ Divisor latch MSB (Divisor de velocidad, parte alta)

├────────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────

│ 2 │ X │ R │ IIR │ Interrupt Identification Register (Registro de identificación de interrupciones)

├────────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────

│ 2 │ X │ W │ FCR │ FIFO Control Register (Registro de control FIFO) - SOLO 16550 -

├────────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────

│ 3 │ X │ R/W │ LCR │ Line Control Register (Registro de control de línea) ¡¡EL BIT 7 ES DLAB!!
EL HARDWARE DE APOYO AL MICROPROCESADOR 346


├────────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────

│ 4 │ X │ R/W │ MCR │ Modem Control Register (Registro de control del modem)

├────────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────

│ 5 │ X │ R/W │ LSR │ Line Status Register (Registro de estado de la línea)

├────────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────

│ 6 │ X │ R/W │ MSR │ Modem Status Register (Registro de estado del modem)

├────────┼──────┼──────┼────────┼───────────────────────────────────────────────────────────────────────────────────

│ 7 │ X │ R/W │ SCR │ Scratch Register (Registro residual)

└────────┴──────┴──────┴────────┴───────────────────────────────────────────────────────────────────────────────────

El cuadro superior muestra los desplazamientos (offsets) que hay que sumar a la dirección E/S base del
puerto serie para acceder a sus registros. COM1 suele estar en 3F8h, COM2 en 2F8h, COM3 en 3E8h y COM4
en 2E8h. Sin embargo, es mejor acceder a las variables de la BIOS para obtener la dirección.

La INT 14h de la BIOS se encarga de controlar el puerto serie. El trabajo del DOS a través de los
dispositivos COM1: (conocido también como AUX:) al COM4: se realiza también apoyándose en esta
interrupción. El comando MODE del sistema permite inicializar el puerto serie a alto nivel. Sin embargo, tanto
el DOS como la BIOS no permiten exceder los 9600 baudios, velocidad excesivamente baja para la transmisión
de datos entre dos ordenadores cercanos o el trabajo con un modem.

El cristal que gobierna el 8250 oscila a 1.8432 MHz. Nosotros debemos considerar esta frecuencia
dividida por 16 de cara a calcular el valor para el divisor. Por tanto, la velocidad máxima que puede alcanzar
el puerto serie de los PC es de 1843200/16 = 115200 baudios.
┌─────────┬────────────────────────────┐ Con datos de 8 bit se pueden empaquetar los bytes en 10 baudios
│ Baudios │ Divisor a emplear en el PC │ (1 bit de inicio, 8 de datos, 1 de stop), lo que permite alcanzar
│ más ├────────────┬───────┬───────┤ 11520 bytes/seg (11.25 Kb/seg). Para distancias de pocos metros
│ comunes │ Divisor │ DLM │ DLL │
(no decenas ni centenas) no habrá problemas, incluso para
├─────────┼────────────┼───────┼───────┤
distancias algo mayores si los cables se diseñan con cuidado. La
│ 50 │ 2304 │ 9 │ 0 │
programación del puerto serie en el PC a nivel de hardware es
│ 110 │ 1047 │ 4 │ 23 │
│ 150 │ 768 │ 3 │ 0 │
necesaria a menudo por dos razones de mucho peso: poder
│ 300 │ 384 │ 1 │ 128 │ utilizar interrupciones y emplear velocidades superiores a 9600
│ 1200 │ 96 │ 0 │ 96 │ baudios. Por supuesto, en estas transferencias los paquetes
│ 2400 │ 48 │ 0 │ 48 │ deberían llevar algún control de errores, aunque no precisamente
│ 4800 │ 24 │ 0 │ 24 │ basado en la paridad.
│ 9600 │ 12 │ 0 │ 12 │
│ 14400 │ 8 │ 0 │ 8 │
│ 19200 │ 6 │ 0 │ 6 │
│ 28800 │ 4 │ 0 │ 4 │
│ 38400 │ 3 │ 0 │ 3 │
│ 57600 │ 2 │ 0 │ 2 │
│ 115200 │ 1 │ 0 │ 1 │
└─────────┴────────────┴───────┴───────┘

Nota:El bit OUT2 del MCR controla en los PC la salida de la línea INTRPT. Esto significa que si dicho bit, por defecto
inicializado a 0, es puesto a 1, las interrupciones del puerto serie quedan inhibidas. El bit OUT1, por el contrario, debe
estar a 1 por motivos no muy claros. También se podría inhibir la INTRPT a través del 8259, por lo que este dato no es
muy importante, con la excepción de evitar que una involuntaria e incorrecta asignación de OUT1 y OUT2 inhiba las
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

interrupciones. La ventaja de inhibir las interrupciones en el 8250 radica en la posibilidad de utilizar plenamente todas
sus funciones incluso en el modo de no interrupciones: el olvido del diseñador de incluir esta característica obligó a IBM
a utilizar para este fin OUT2. Realmente, el 8250 está concebido para ser utilizado por medio de interrupciones, y hay
quien duda incluso de la veracidad de la afirmación del fabricante acerca del double buffering (buffers duplicados) que
son muy aconsejables al trabajar sin interrupciones.

12.9.3. - EJEMPLO: AUTODIAGNÓSTICO DEL 8250.

El siguiente programa de ejemplo coloca el 8250 en modo lazo (LOOP) y seguidamente comienza a
transmitir datos de 8 bits (desde 0 hasta 255) comprobando que le llegan los mismos datos que envía y sin que
se produzcan errores. Se permite elegir el puerto deseado así como la velocidad de transmisión.

/*********************************************************************

* * printf("- Elige divisor (1-65535): ");

* 8250T.C 1.0 - UTILIDAD DE AUTODIAGNOSTICO DEL 8250 EN TURBO C * scanf ("%d", &divisor); if (!divisor) divisor=1;

* *

* (c) 1993 Ciriaco García de Celis. * printf("\nComprobando 8250 en %03Xh a %lu baudios.\nEspera...",

* * base, 1843200L/divisor/16);

*********************************************************************/

outportb (LCR, 0x83); /* DLAB=1, 8 bits, 1 stop, sin paridad */

#include <dos.h> outportb (IER, 0);

#include <conio.h> outportb (DLL, divisor % 256);

outportb (DLM, divisor >> 8);

#define LCR (base+3) /* registro de control de línea */ outportb (MCR, 8+16); /* modo LOOP */

#define IER (base+1) /* registro de activación de interrupciones */ outportb (LCR, 0x03); /* DLAB=0, 8 bits, 1 stop, sin paridad */

#define DLL (base+0) /* parte baja del divisor */

#define DLM (base+1) /* parte alta del divisor */ for (dato=0; (dato<0x100) && !kbhit(); dato++) {

#define MCR (base+4) /* registro de control del modem */

#define LSR (base+5) /* registro de estado de línea */ do { /* esperar por THR vacío */

#define RBR (base+0) /* registro buffer de recepción */ lsr=inportb(LSR);

#define THR (base+0) /* registro de retención de transmisión */ if (lsr & (OE|PE|FE|BI)) error();

} while (!(lsr & THRE));

#define DR 1 /* bit dato disponible del LSR */

#define OE 2 /* bit de error de overrun del LSR */ outportb (THR, dato); /* enviar carácter */

#define PE 4 /* bit de error de paridad del LSR */

#define FE 8 /* bit de error en bits de stop del LSR */ do { /* esperar por RBR lleno */

#define BI 0x10 /* bit de error de break en el LSR */ lsr=inportb(LSR);

#define THRE 0x20 /* bit de THR vacío */ if (lsr & (OE|PE|FE|BI)) error();

} while (!(lsr & DR));

void error() entrada=inportb (RBR); /* recibir carácter */

printf ("\r ¡¡Fallo del puerto serie!!\n"); if (dato!=entrada) error();

exit (2); printf ("\rEnviado y recibido byte %d",dato);

} }

if (!kbhit())

void main() printf("\rAutodiagnóstico del 8250 en COM%d superado.\n", com);

{ else

unsigned com, base, divisor, dato, entrada, lsr; { getch(); printf("\rTecla pulsada - prueba abortada.\n");}

printf("\n8250 Test v1.0 - (c) 1993 Ciriaco García de Celis.\n");

printf("- Elige COM (1, 2, ...): "); scanf ("%d", &com);

base=peek(0x40, (com-1)*2);

if (base==0) {

printf("\n ¡El COM elegido no existe para la BIOS!.\n");

exit (1);

}
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

12.10. - EL PUERTO DE LA IMPRESORA.

La impresora se controla desde el DOS referenciándola como dispositivo LPT1 (PRN) ó LPT2. La
BIOS utiliza la INT 17h para los servicios de impresora. En ambos casos, el funcionamiento es realmente trivial
y la dificultad estriba en el modelo de impresora que se trate (IBM, Epson, HP-III, PostScript, etc.) de cara al
lenguaje que soporta. Eso no lo trataremos aquí, ya que todas las impresoras vienen acompañadas de un manual
técnico de programación (o en su defecto se puede adquirir opcionalmente). Lo que veremos a continuación son
los registros a bajo nivel del puerto paralelo, así como pistas para una utilización algo más allá de la impresora:
la comunicación entre ordenadores.

12.10.1. - LOS REGISTROS DEL PUERTO PARALELO.

La dirección base del puerto paralelo en los ordenadores compatibles depende del tipo de adaptador que
incorporen. Las primeras máquinas traían un puerto paralelo en el adaptador de vídeo monocromo, cuya
dirección base es 3BCh. Sin embargo, otros adaptadores utilizan la dirección base 378h para LPT1 y 278h para
LPT2. Por fortuna, la BIOS tiene en el área de datos una tabla con las direcciones base de los 4 posibles puertos
paralelos. Dicha tabla comienza en 40h:8 y consta de 1 palabra por puerto (a 0 si ese puerto no existe). La
asignación que realizan diversas BIOS puede ser un tanto discutible, pero si el usuario no ve salir los datos por
la impresora que desea, siempre puede cambiar los cables o configurar su programa...

Los registros de que consta el puerto paralelo son 3: el primero es el registro de datos, de 8 bits,
ubicado en la dirección base (3BCh, 378h, 278h, etc.). Este registro es de sólo escritura, para enviar los
caracteres a la impresora. El siguiente registro, de sólo lectura, es el registro de estado, inmediatamente a
continuación del anterior (3BDh, 379h, 279h). Finalmente, tras ellos hay un registro de sólo escritura, el
registro de control (en 3BEh, 37Ah, 27Ah). Aunque en los tres casos he indicado la dirección, hay que tener
en cuenta que lo correcto es consultar la variable de la BIOS y tomarla como punto de partida.

Los registros de estado y control están asociados a unas líneas físicas del puerto paralelo estándar, y
poseen un significado concreto que resumimos a continuación. En el valor pin se hace referencia al pin del
puerto paralelo del ordenador y al correspondiente en la impresora (ordenador/impresora). Las líneas o pines
que no aparecen aquí son las de datos (líneas 2 a la 9, conectadas también con las líneas 2 a la 9 del lado de la
impresora; las restantes están a masa).

n Registro de estado:
- Bits 0-2: no utilizados.
- Bit 3: pin 15/32 (-ERROR). A 0 si hay un error gordo (a revisar los cables).
- Bit 4: pin 13/13 (SLCT). A 1 si la impresora está ON LINE.
- Bit 5: pin 12/12 (PE). A 1 si la impresora no tiene papel (PAPER ERROR).
- Bit 6: pin 10/10 (-ACK). A 0 si la impresora confirma la recepción del carácter.
- Bit 7: pin 11/11 (-BUSY). A 0 si la impresora está ocupada.
n Registro de control:
- Bit 0: pin 1/1 (-STROBE). A 0 si hay un carácter en el registro de datos.
- Bit 1: pin 14/14 (-AUTO FEED). A 1 si la impresora debe saltar línea tras cada código 13 (CR).
- Bit 2: pin 16/31 (-INIT). A 0 para resetear la impresora.
- Bit 3: pin 17/36 (SLCT IN). A 1 para seleccionar la impresora (0 para OFF-LINE).
- Bit 4: no conectado al puerto de impresora. A 1 activa la interrupción de la impresora.
- Bits 5-7: no utilizados.

La posibilidad de emplear interrupciones es realmente interesante: cuando la señal -ACK se pone a


nivel 0 (esto es, se activa) viene una IRQ7 ó una IRQ5 (según cómo esté configurada la tarjeta). De todos
modos, habrá que mandar primero un carácter por el método tradicional para iniciar la transmisión. La BIOS,
sin embargo, no utiliza la interrupción de la impresora.

12.10.2. - ENVÍO DE CARACTERES.

Ante todo dejar claro que cuando digamos 0 ó 1 nos referimos al valor del bit en el registro del PC,
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

olvidando ya cuestiones como el nivel al que son activas las señales, para evitar lios: los nombres de las señales
les tomaremos como referencia, sin considerar su polaridad. Para enviar un carácter, primero se le coloca en el
registro de datos. A continuación se pone a 0 en el registro de control el bit de STROBE. Este bit debe estar
muy poco tiempo activo, para evitar que la impresora lea dos veces el mismo carácter (del orden de un
microsegundo). Como la impresora no tiene una capacidad de aguante ilimitada, se puede defender poniendo el
bit de BUSY en el registro de estado a 0 para poder leer con tranquilidad el STROBE que le llega. Cuando lo
haya leído, pondrá un 0 en ACK para indicar que ya ha recibido el carácter.

Este es el esquema básico del envío de caracteres. Sin embargo, hay que tener en cuenta que la
impresora puede devolver ciertas condiciones de error, tanto leves (falta de papel) como más graves, como el
caso de ERROR. También el ordenador puede provocar ciertos efectos en la impresora, a través del registro de
control, como vimos anteriormente. Quizá el más curioso es el del AUTO FEED: ya se podían haber puesto de
acuerdo el primer día, resulta triste que además de perder horas configurando impresoras y programas, hasta el
propio puerto pueda meter las narices en el control del salto de línea...

12.10.3. - CABLE NULL-MODEM PARA CONECTAR DOS ORDENADORES.

Anteriormente hemos visto una descripción de patillas del puerto paralelo suficiente para que
cualquiera se pueda construir su propio cable centronics. De todas formas, estos cables afortunadamente se
venden ya construidos por un precio poco aceptable. Los que no se venden, aunque sí acompañan a ciertas
aplicaciones software e incluso hardware (como disqueteras externas vía puerto de impresora) permiten una
comunicación bidireccional. El truco consiste en utilizar las líneas del registro de estado para recibir datos,
aunque esto limita la transferencia a 5 bits (realmente 4, más otro para el protocolo de transferencia).

Se toman dos conectores centronic 25-pin machos. Se unen los pins de la siguiente forma:

┌────┐ ┌────┐
│ 2 ├──────────────────┤ 15 │
│ 3 ├──────────────────┤ 13 │
│ 4 ├──────────────────┤ 12 │
│ 5 ├──────────────────┤ 10 │
│ 6 ├──────────────────┤ 11 │
│ 10 ├──────────────────┤ 5 │
│ 11 ├──────────────────┤ 6 │
│ 12 ├──────────────────┤ 4 │
│ 13 ├──────────────────┤ 3 │
│ 15 ├──────────────────┤ 2 │
│ 18 ├──────────────────┤ 18 │
└────┘ └────┘

El motivo de emplear esta asignación y no otra se debe a que es la ya utilizada por ciertas aplicaciones
comerciales, como LAPLINK. Es por razones de compatibilidad, para que no pase como con los saltos de línea.
La línea común (18) es masa, aunque valdría cualquier patilla entre la 18 y la 25; si se emplea un cable de 10
hilos más malla, esta última es la más adecuada para hacer de masa.

Con este cable, para enviar datos se utilizan las líneas D0 a D4 del registro de datos y para recibirlos las
5 líneas útiles del registro de estado. Como D0-D1-D2-D3-D4 están conectados en este mismo orden a
ERROR-SLCT-PE-ACK-BUSY, lo ideal es utilizar D0-D3 para transmitir datos y ERROR-SLCT-PE-ACK
para recibirlos. Las señales BUSY y D4 sirven para establecer el protocolo de transmisión. La transferencia
puede ser bidireccional y además de forma simultánea. En realidad, cuando se mande un dato y el ordenador
remoto indique con BUSY que ya lo tiene (a través de su línea D4), de paso nos puede haber reenviado el dato
en D0-D3 para que veamos si es correcto: un control de errores bastante fiable y rápido. Sin embargo, se podría
aprovechar quizá para enviar otro medio byte en sentido contrario en el caso de que las dos máquinas se estén
pasando información simultáneamente la una a la otra; el control de errores ya se haría de otra manera, a nivel
de bloques con checksum, etc. Conviene aprovechar y mandar otros 4 bits de datos cada vez que se envía un
reconocimiento (al informar al receptor de que ya se ha recibido su señal de "dato recibido"), lo que permite
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

transferir un byte completo en cada ciclo del protocolo de transferencia. Ah, no hay que olvidar la polaridad de
las líneas: al poner un 0 en D4 aparece un 1 en el -BUSY del otro extremo...

Si el cable no rebasa los 3 metros o poco más la transmisión será fiable, y además bastante rápida: 4 bits
en paralelo, a la velocidad que pueda alcanzar la CPU del ordenador más lento. No emplear el ensamblador
sería un acto imperdonable.
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

12.11. - EL RATÓN.

El ratón se controla normalmente a través de llamadas a la INT 33h. Existen toda suerte de funciones
para controlar su posición, el estado de los botones, el puntero que se visualiza... todas ellas son bastante
intuitivas y aptas para un programador en lenguajes de alto nivel. Aquí estudiaremos, sin embargo, el
funcionamiento a bajo nivel del ratón. En concreto, del ratón de Microsoft, el más extendido y con el que son
compatibles casi todos los demás (aunque sea accionando el correspondiente conmutador).

La mayoría de los ratones se conectan vía puerto serie a 1200 baudios, 7 bits y sin paridad. Para
detectar la presencia del ratón, hay que poner la línea DTR del puerto serie a 1. Al cabo de un rato, el ratón
devuelve el código ASCII de la letra M (¿será por lo de Mouse o por Microsoft?). Los controladores de
Microsoft son un poco estrictos en esta comprobación, y si el ratón no responde en unos márgenes de tiempo
muy concretos consideran que no existe, de ahí que en ocasiones haya que emplear otro controlador un poco
más flexible.

Llegados a este punto, el funcionamiento se establece a partir de interrupciones de puerto serie. Se


trasmiten 3 bytes cada vez que hay un envío: en ellos se indica cuánto se ha movido el ratón en los ejes X e Y
desde la última vez, así como el estado de los botones. La unidad de medida, cómo no, son los Mickeys, que
según la resolución del aparato serán 1/200 ó 1/400 pulgadas.

Los desplazamientos se toman en complemento a dos; como hay 8 bits por cada eje, el movimiento
puede oscilar en el rango +128 a -127. Hay además un bit por cada botón. De los 7 bits recibidos en cada
interrupción, el más significativo (bit 6) está a 1 en el primer envío y a 0 en los restantes, con objeto de evitar
malas interpretaciones de la secuencia si se pierde alguna interrupción por cualquier motivo. El formato
empleado para codificar la información es el siguiente:

┌────┬────┬────┬────┬────┬────┬────┐ ┌────┬────┬────┬────┬────┬────┬────┐ ┌────┬────┬────┬────┬────┬────┬────┐


│ 1 │ L │ R │ Y7 │ Y6 │ X7 │ X6 │ │ 0 │ X5 │ X4 │ X3 │ X2 │ X1 │ X0 │ │ 0 │ Y5 │ Y4 │ Y3 │ Y2 │ Y1 │ Y0 │
└────┴────┴────┴────┴────┴────┴────┘ └────┴────┴────┴────┴────┴────┴────┘ └────┴────┴────┴────┴────┴────┴────┘

El otro gran estándar de ratón, el Mouse Systems, permite trabajar hasta con tres botones. Estos ratones
envían (cuando están en modo Mouse) 5 bytes por cada evento. En el primero hay información sobre el estado
de los botones; los 4 siguientes parecen contener el desplazamiento relativo en los ejes X e Y. El
funcionamiento es, por tanto, similar, y al parecer quizá todavía con 7 bits. Curiosamente, al conmutar el
selector de modo (Microsoft-Mouse) aparece una secuencia de bytes un tanto especial, distinta según el sentido
de la conmutación, para ayudar al controlador de ratón a detectar el paso al nuevo protocolo con objeto de poder
adaptarse al mismo.
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

12.12. - EL RELOJ DE TIEMPO REAL DEL AT: MOTOROLA MC146818.

12.12.1. - DESCRIPCIÓN DEL INTEGRADO.

El MC146818 incorpora un completo reloj con alarma, calendario, interrupción periódica programable,
generador de onda cuadrada y 64 bytes libres de RAM estática de bajo consumo. Los primeros 10 bytes de esta
RAM son empleados para gestionar la fecha y la hora y los 4 siguientes son registros (A, B, C y D); los 50
restantes quedan a disposición del usuario.

▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ La línea OSC1 (de entrada) puede conectarse a señales


▌ ▐ cuadradas de 4.194304 Mhz, 1.048576 Mhz y 32768 Hz. La
NC ██▌ 1 24 ▐██ Vcc frecuencia de esta base de tiempos, como se verá, ha de indicarse
▌ ▐
en el registro A (bits DV0 a DV2). El chip provee una útil salida
OSC 1 ██▌ 2 23 ▐██ SQW
de reloj en CKOUT dependiente del nivel de la entrada CKFS,
▌ ▐
según la siguiente tabla:
OSC 2 ██▌ 3 22 ▐██ PS
▌ ▐
┌───────────────┬───────────────┬────────────────┐
AD 0 ██▌ 4 21 ▐██ CKOUT
│ Señal en OSC1 │ Nivel de CKFS │ Señal en CKOUT │
▌ ▐
├───────────────┼───────────────┼────────────────┤
AD 1 ██▌ 5 20 ▐██ CKFS
│ 4,194304 Mhz │ 1 │ 4,194304 Mhz │
▌ ▐
│ 4,194304 Mhz │ 0 │ 1,048576 Mhz │
AD 2 ██▌ 6 19 ▐██ -IRQ
│ 1,048576 Mhz │ 1 │ 1,048576 Mhz │
▌ ▐
│ 1,048576 Mhz │ 0 │ 262,144 KHz │
AD 3 ██▌ 7 18 ▐██ -RESET
│ 32,768 Khz │ 1 │ 32,768 Khz │
▌ ▐
│ 32,768 Khz │ 0 │ 8,192 Khz │
AD 4 ██▌ 8 17 ▐██ DS
└───────────────┴───────────────┴────────────────┘
▌ ▐
AD 5 ██▌ 9 16 ▐██ NC
▌ ▐ La salida SQW genera una onda cuadrada, cuya
AD 6 ██▌ 10 15 ▐██ R/-W frecuencia es programable (útil para alarmas). La línea -IRQ se
▌ ▐
AD 7 ██▌ 11 14 ▐██ AS
▌ ▐
GND ██▌ 12 13 ▐██ -CE
▌ '146818 ▐
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
encarga de solicitar las interrupciones periódicas si están habilitadas. La línea de entrada -RESET reinicializa
el integrado asignando valores por defecto a ciertos bits de los registros B y C,
aunque no afecta a la fecha/hora ni a la memoria. La entrada PS debe ┌──────────────────┐

mantenerse a nivel bajo cuando se alimenta el chip hasta que la tensión se 00 │ Segundos │

estabilice, poniéndose después en alto; esta entrada está asociada al bit VRT ├──────────────────┤

del registro D que indica si el integrado está en condiciones de operar. El bus 01 │ Segundos Alarma │

bidireccional de direcciones y datos está multiplexado (líneas AD0..AD7): en ├──────────────────┤

los flancos de bajada de la entrada de validación de direcciones (línea AS) 02 │ Minutos │

├──────────────────┤
contiene direcciones, y datos en los flancos de subida de la entrada de
03 │ Minutos alarma │
validación de datos (línea DS). La línea -R/-W indica si la operación es de ├──────────────────┤
entrada o salida; -CE permite habilitar el chip o desconectarlo de los buses. 04 │ Horas │

├──────────────────┤
El cuadro de la derecha refleja la estructura de la memoria del 05 │ Horas alarma │
MC146818. Los primeros 14 bytes son empleados para la fecha y hora. ├──────────────────┤

06 │ Dia de la semana │

├──────────────────┤

07 │ Dia del mes │

├──────────────────┤

08 │ Mes │

├──────────────────┤

09 │ Año │

├──────────────────┤
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

0A │ Registro A │

├──────────────────┤

0B │ Registro B │

├──────────────────┤

0C │ Registro C │

├──────────────────┤

0D │ Registro D │

├──────────────────┤

0E..3F │ 50 bytes libres │

└──────────────────┘

REGISTROS DEL MC146818

REGISTRO A (lectura/escritura, excepto UIP).


Este registro sirve para indicar al integrado qué tipo de reloj lo gobierna, así como elegir la frecuencia
de la interrupción periódica programable y la de la salida SQW. También contiene un bit que indica si hay una
actualización del reloj en curso, lo que sucede una vez cada segundo, ya que en ese preciso instante no se
pueden leer los registros con objeto de evitar lecturas incorrectas.

D7 D6 D5 D4 D3 D2 D1 D0
┌──────┬──────┬──────┬──────┼──────┬──────┬──────┬──────┐
│ UIP │ DV2 │ DV1 │ DV0 │ RS3 │ RS2 │ RS1 │ RS0 │
└──────┴──┬───┴──┬───┴──┬───┼──────┴──────┴──────┴──────┘
│ ┌────┘ │
│ │ ┌─────────┘
0 0 0 Reloj de 4,194304 MHz ─┐
0 0 1 Reloj de 1,048576 MHz │Ψ Tipo de reloj conectado
0 1 0 Reloj de 32768 Hz ─┘
El bit UIP (Update In Progress), de sólo lectura, se pone a 1 mientras se actualizan los primeros 14
bytes de la memoria y poco tiempo antes de que comience dicha actualización. Antes de acceder a estos bytes,
hay que esperar a que el bit UIP se ponga a cero (si no lo estaba ya): con el bit UIP a 0, es seguro que en un
intervalo de al menos 244 microsegundos no se va a producir ninguna actualización, por lo que hay tiempo
suficiente para acceder (sin prisas, pero tampoco con pausas). La actualización dura 248 microsegundos (1984
con relojes de 32768 Hz).

Los bits RS0..RS3, de selección de velocidad, definen la frecuencia de la onda cuadrada generada en
SQW y/o la de la interrupción periódica, como indica esta tabla:

┌────────────────────────────────┬────────────────────────────────┐
│ Reloj 1,048576 ó 4,194304 Mhz │ Reloj de 32768 Hz │
┌─────┬─────┬─────┬─────┼───────────────┬────────────────┼───────────────┬────────────────┤
│ RS3 │ RS2 │ RS1 │ RS0 │ Velocidad INT │ Frecuencia SQW │ Velocidad INT │ Frecuencia SQW │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 0 │ 0 │ 0 │ 0 │ (no actúa) │ (nula) │ (no actúa) │ (nula) │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 0 │ 0 │ 0 │ 1 │ 30,517 µs │ 32768 Hz │ 3,90625 ms │ 256 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 0 │ 0 │ 1 │ 0 │ 61,035 µs │ 16384 Hz │ 7,81250 ms │ 128 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 0 │ 0 │ 1 │ 1 │ 122,070 µs │ 8192 Hz │ 122,070 µs │ 8192 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 0 │ 1 │ 0 │ 0 │ 244,141 µs │ 4096 Hz │ 244,141 µs │ 4096 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 0 │ 1 │ 0 │ 1 │ 488,281 µs │ 2048 Hz │ 488,281 µs │ 2048 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 0 │ 1 │ 1 │ 0 │ 976,562 µs │ 1024 Hz │ 976,562 µs │ 1024 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 0 │ 1 │ 1 │ 1 │ 1,953125 ms │ 512 Hz │ 1,953125 ms │ 512 Hz │
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 1 │ 0 │ 0 │ 0 │ 3,90625 ms │ 256 Hz │ 3,90625 ms │ 256 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 1 │ 0 │ 0 │ 1 │ 7,8125 ms │ 128 Hz │ 7,8125 ms │ 128 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 1 │ 0 │ 1 │ 0 │ 15,625 ms │ 64 Hz │ 15,625 ms │ 64 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 1 │ 0 │ 1 │ 1 │ 31,25 ms │ 32 Hz │ 31,25 ms │ 32 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 1 │ 1 │ 0 │ 0 │ 62,5 ms │ 16 Hz │ 62,5 ms │ 16 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 1 │ 1 │ 0 │ 1 │ 125 ms │ 8 Hz │ 125 ms │ 8 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 1 │ 1 │ 1 │ 0 │ 250 ms │ 4 Hz │ 250 ms │ 4 Hz │
├─────┼─────┼─────┼─────┼───────────────┼────────────────┼───────────────┼────────────────┤
│ 1 │ 1 │ 1 │ 1 │ 500 ms │ 2 Hz │ 500 ms │ 2 Hz │
└─────┴─────┴─────┴─────┴───────────────┴────────────────┴───────────────┴────────────────┘

REGISTRO B (lectura/escritura).
En este registro hay bits útiles, entre otros, para controlar la inicialización de la fecha y hora, para
habilitar o inhibir las diversas interrupciones y para establecer ciertas características de operación.

D7 D6 D5 D4 D3 D2 D1 D0
┌──────┬──────┬──────┬──────┼──────┬──────┬──────┬──────┐
│ SET │ PIE │ AIE │ UIE │ SQWE │ DM │24/12 │ DSE │
└──────┴──────┴──────┴──────┼──────┴──────┴──────┴──────┘

El bit SET puede ser establecido a 1, con lo que cualquier ciclo de actualización de los primeros 14
bytes de la RAM resulta abortado: de este modo, es factible proceder a inicializar la fecha y la hora sin el riesgo
de que se produzca en medio una actualización. Este bit no se ve afectado por la señal -RESET.

El bit PIE (Periodic Interrupt Enable) sirve para permitir la interrupción periódica cuando es puesto a 1;
tras una señal -RESET es puesto a 0. El bit AIE (Alarm Interrupt Enable) ha de estar a 1 para habilitar la
interrupción de alarma; también es puesto a cero tras un -RESET. El bit UIE (Update Interrupt Enable) sirve
para habilitar o inhibir la interrupción de fin de actualización, que se produciría tras cada actualización del reloj;
la señal -RESET baja el bit UIE. Por último, el bit SQWE (Square Wave Enable) permite habilitar o inhibir la
señal de onda cuadrada de la salida SQW; también es borrado ante una señal -RESET.

El bit DM (Data Mode) permite seleccionar datos en binario (1) o BCD (0) en los bytes de fecha y
hora; la señal -RESET no afecta a este bit. El bit 24/12 sirve para elegir entre el modo 12 horas del reloj (bit a 0)
o el de 24 (bit a 1): en el modo de 12 horas, el bit más significativo del byte de la hora estará activo para indicar
"PM". Si bit DSE está activo, el último domingo de abril la hora pasa de 1:59:59 AM a 3:00:00 AM; en el
último domingo de octubre pasa de 1:59:59 AM a 1:00:00 AM (sólo la primera vez, claro) para ajustarse al
cambio de hora oficial; este bit no es afectado por -RESET.

REGISTRO C (sólo lectura).


Este registro contiene bits que informan de las interrupciones que se producen. Permite identificar al
ordenador qué o cuáles interrupción(es) se ha(n) producido.

D7 D6 D5 D4 D3 D2 D1 D0
┌──────┬──────┬──────┬──────┼──────┬──────┬──────┬──────┐
│ IRQF │ PF │ AF │ UF │ 0 │ 0 │ 0 │ 0 │
└──────┴──────┴──────┴──────┼──────┴──────┴──────┴──────┘

El bit IRQF (Interrupt ReQuest Flag) se activa cuando el bit PF y el PIE (registro B) están activos, o
bien cuando el bit AF y el AIE (registro B) están activos, o bien cuando UF y el bit UIE (registro B) están
activos. Es decir, IRQF se pone en alto cuando es necesario que se produzca una interrupción: la línea -IRQ se
346 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

encarga de pedirla entonces. Por su parte: PF (Periodic Flag), AF (Alarm Flag) y UF (Update Flag) indican si
es necesario que se produzca la interrupción correspondiente. Todos los bits de este registro son borrados ante
una señal -RESET, pero también ante una lectura por software del registro C.

REGISTRO D (sólo lectura).


Este registro contiene sólo el bit VRT (Valid RAM and Time). Este bit está a cero cuando la patilla PS
está a cero (PS se eleva a 1 cuando la tensión de alimentación es correcta). Por software, el bit VRT puede ser
puesto a 1 mediante una simple lectura del registro D (si la patilla PS=1), con objeto de indicar que la fecha y
hora establecidas son correctas; si fallara la alimentación, al caer la tensión en la patilla PS este bit pasaría de
nuevo a cero. VRT no es afectado por -RESET.

D7 D6 D5 D4 D3 D2 D1 D0
┌──────┬──────┬──────┬──────┼──────┬──────┬──────┬──────┐
│ VRT │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │
└──────┴──────┴──────┴──────┼──────┴──────┴──────┴──────┘

FUNCIONAMIENTO DE LA ALARMA

La interrupción de alarma se produce todos los días cuando llega la hora en que ha sido programada y
el bit que permite esta interrupción está habilitado. Existe un método alternativo para programar la alarma,
basado en los códigos indiferentes almacenables en los bytes de la alarma. Un código indiferente es cualquier
valor comprendido entre 0C0h y 0FFh. Si la hora de alarma es un código indiferente, la alarma se producirá
cada hora. Si la hora y minuto de alarma son códigos indiferentes, ésta se producirá cada minuto. Si tanto la
hora como el minuto y segundo de la alarma son códigos indiferentes, la alarma se producirá cada segundo.

12.12.2. - EL MC146818 DENTRO DEL ORDENADOR.

El MC146818 es por lo general exclusivo de los AT y PS/2. En muchos ordenadores, la


implementación física se realiza con circuitos totalmente compatibles que incluyen 128 bytes de RAM en lugar
de 64. En la RAM que sobra por encima de los primeros 14 bytes se almacenan parámetros de la configuración
del sistema, modificables con el programa SETUP durante el arranque.

Por defecto, la BIOS inicializa el chip para trabajar con un reloj de 32768 Hz y a un ritmo de 1024
interrupciones periódicas por segundo (cuando están habilitadas), al escribir el valor 26h en el registro A. De la
misma manera, el registro B se carga con 2 (modo 24 horas, datos en BCD y sin horario verano/invierno).

El MC146818 está diseñado para ser conectado a un bus multiplexado, por lo que la circuitería de
apoyo de los AT se encarga de gestionar la comunicación con el microprocesador, estableciendo dos puertos de
entrada/salida en las direcciones 70h y 71h. Para leer o escribir cualquier registro de la RAM CMOS, basta con
enviar al puerto 70h el número de registro y, a continuación, leer o escribir del puerto 71h. Entre los accesos a
ambos puertos debe mediar un tiempo mínimo; de lo contrario la operación fallará. En particular, las últimas
versiones de los compiladores de Borland no permiten acceder al reloj de tiempo real en la mayoría de las
máquinas a través de las funciones outportb() e inportb(). La razón es que esas funciones están en una librería y
es preciso llamarlas con paso de parámetros a través de la pila, lo que ralentiza excesivamente el proceso. Desde
el lenguaje ensamblador, nunca hay problemas, aunque como es costumbre es conveniente insertar algún estado
de espera (JMP SHORT $+2) entre dos operaciones E/S consecutivas, precaución necesaria en los ordenadores
más antiguos.

A nivel de interrupciones, la salida -IRQ del MC146818 está conectada a IRQ8 (INT 70h) a través del
segundo controlador de interrupciones (véase la documentación del mismo).

Desde la interrupción 1Ah, la BIOS implementa una serie de servicios para acceder al reloj de tiempo
real, incluyendo la posibilidad de programar la alarma (que invoque una INT 4Ah cuando llegue la hora). Las
funciones de retardo de la INT 15h se apoyan también en el reloj de tiempo real.
EL HARDWARE DE APOYO AL MICROPROCESADOR 346

Conviene tener presente que es de vital importancia acceder a los primeros 14 bytes de la CMOS sólo si
el bit UIP del registro A (bit 7) está a cero. También es necesario poner a 1 el bit SET del registro B (bit 7) antes
de modificar dichos bytes, devolviéndolo a 1 después. No respetar este principio puede provocar la lectura de
fechas u horas incorrectas o una errónea asignación de valores. Para los demás bytes de la CMOS no es
necesario tomar esta precaución.

12.12.3. - UN MÉTODO PARA AVERIGUAR LA CONFIGURACIÓN DEL AT Y PS/2.

Como se dijo antes, los AT y superiores almacenan en los 50 ó 114 últimos bytes de RAM libres de la
CMOS información relativa a la configuración del sistema. Los bytes más importantes y comunes a todas las
máquinas se muestran a continuación.

Byte 0Eh:Diagnostics Status Byte. El bit 7 indica (si vale 1) que el MC146818 tiene un déficit de corriente eléctrica. El bit 6
indica (si es 1) que el chechsum o suma de comprobación de la CMOS ha fallado. El bit 5 indica (si vale 1) que
la configuración del sistema es incorrecta (no hay al menos una disquetera presente o el modo de vídeo de la
configuración no coincide con el detectado en el hardware). El bit 4 es puesto a 1 si el tamaño de la memoria
detectado no coincide con el indicado en la configuración. El bit 3 activo indica que el adaptador o el disco fijo
C: falló en la inicialización, siendo imposible botar desde él. El bit 2 activo indica que la hora del reloj es
incorrecta. Los bits 1 y 0 están reservados.
Byte 0Fh:Shutdown Status Byte. Los bits de este byte son asignados durante la inicialización del sistema por parte de la BIOS,
informando de su desarrollo (véase listado de la BIOS).
Byte 10h:Diskette Drive Type Byte. Los bits 7..4 indican el tipo de la disquetera A y los bits 3..0 el tipo de la disquetera B. Los
valores posibles son 0 (no existe esa disquetera), 1 (5¼-360K), 2 (5¼-1.2M), 3 (3½-720K), 4 (3½-1.44M) y 5
(3½-2.88M en BIOS AMI) ó 6 (3½-2.88M en BIOS IBM).
Byte 11h:Reservado.
Byte 12h:Fixed Disk Type Byte. Los bits 7..4 indican el tipo del primer disco fijo y los bits 3..0 el tipo del segundo. Existe una
tabla definida por IBM cuando lanzó el AT con 14 tipos de disco; ninguno que se vende hoy en dia está en la
tabla, por lo que es frecuente que estos campos estén inicializados con el valor 1111b (ó 0 si no hay disco duro
instalado) para indicar simplemente la presencia de disco duro.
Byte 13h:Reservado.
Byte 14h:Equipment Byte. Los bits 7 y 6 indican el número de disquetes instalados; los bits 5 y 4 el tipo de adaptador de vídeo
primario (00: EGA/VGA, 01: CGA-80, 10: CGA-40, 11: MDA); los bits 3 y 2 no se emplean. El bit 1 indica si
hay coprocesador aritmético y el bit 0 está activo para confirmar que hay disqueteras.
Byte 15h-16h:Low and High Base Memory Bytes. El 15h es el bajo y el 16h el alto. Entre ambos forman una palabra de 16 bits
que indica la cantidad de memoria convencional (típicamente 640 Kb).
Byte 17h-18h:Low and High Memory Expansion Bytes. El 17h es el bajo y el 18h el alto. Entre ambos forman una palabra de
16 bits que indica la cantidad de memoria extendida, en Kbytes.
Byte 19h:Número del primer disco duro. Número de identificación que la BIOS asigna al primer disco duro instalado.
Byte 1Ah-2Dh:Reservados.
Byte 2Eh-2Fh:Checksum. El 2Eh es el alto y el 2Fh el bajo. Entre ambos forman una palabra de 16 bytes que constituye el
checksum o suma de comprobación de los bytes 10h-20h.
Byte 30h-31h:Low and High Memory Expansion Bytes. Habitualmente es el mismo valor que el almacenado en los bytes 17h y
18h; esta variable refleja sólo la memoria extendida ubicada por encima del primer megabyte que detecta la
BIOS en el momento de arrancar.
Byte 32h:Date Century Byte. Valor BCD del siglo actual-1. Para 1992, por ejemplo, es 19h.
Byte 33h:Information Flag. El bit 7 indica si está instalada la vieja opción de ampliación de 128 Kb (hasta los 640 Kb) del IBM
AT original: hoy en día suele estar siempre activo. El bit 6 es empleado por el programa SETUP para eliminar
el mensaje inicial al usuario tras el primer SETUP. Los demás bits están reservados.
Byte 34h-3Fh:Reservados.
EL ENSAMBLADOR Y EL LENGUAJE C 373

Capítulo XIII: EL ENSAMBLADOR Y EL LENGUAJE C

El lenguaje C es sin duda el más apropiado para la programación de sistemas, pudiendo sustituir al
ensamblador en muchos casos. Sin embargo, hay ocasiones en que es necesario acceder a un nivel más bajo por
razones de operatividad e incluso de necesidad (programas residentes que economicen memoria, algoritmos
rápidos para operaciones críticas, etc.). Es entonces cuando resulta evidente la necesidad de poder emplear el
ensamblador y el C a la vez.

Para comprender este capítulo, basta tener unos conocimientos razonables de C estándar. Aquí se
explicarán las funciones de librería necesarias para acceder al más bajo nivel, así como la manera de integrar el
ensamblador y el C.

13.1 - USO DEL TURBO C y BORLAND C A BAJO NIVEL.

A continuación veremos algunas funciones, macros y estructuras de la librería DOS.H del Turbo C.

13.1.1 - ACCESO A LOS PUERTOS DE E/S.

int inp (int puerto); /* leer del puerto E/S una palabra (16 bits) */
int inport (int puerto); /* leer del puerto E/S una palabra (16 bits) */
unsigned char inportb (int puerto); /* leer del puerto E/S un byte (8 bits) */
int outp (int puerto, int valor); /* enviar al puerto E/S una palabra (16 bits) */
void outport (int puerto, int valor); /* enviar al puerto E/S una palabra (16 bits) */
void outportb (int puerto, unsigned char valor); /* enviar al puerto E/S un byte (8 bits) */

Aunque pueden parecer demasiadas, algunas son idénticas (caso de inp() e inport()) y otras se
diferencian sólo ligeramente en el tipo de los datos devueltos, lo cual es irrelevante si se tiene en cuenta que el
dato devuelto es descartado (caso de outp() y outport()). En general, lo normal es emplear inport() e inportb()
para la entrada, así como outport() y outportb() para la salida. Por ejemplo, para enviar el EOI al final de una
interrupción hardware se puede ejecutar: outportb(0x20, 0x20);

13.1.2 - ACCESO A LA MEMORIA.

int peek (unsigned seg, unsigned off); /* leer la palabra (16 bits) en seg:off */
char peekb (unsigned seg, unsigned off); /* leer el byte (8 bits) en seg:off */
void poke (unsigned seg, unsigned off, int valor); /* poner palabra valor (16 bits) en seg:off */
void pokeb (unsigned seg, unsigned off, char valor); /* poner byte valor (8 bits) en seg:off */
unsigned FP_OFF (void far *puntero); /* obtener offset de variable tipo far */
unsigned FP_SEG (void far *puntero); /* obtener segmento de variable tipo far */
void far *MK_FP (unsigned seg, unsigned off); /* convertir seg:off en puntero tipo far */

Las funciones peek(), peekb(), poke() y pokeb() tienen una utilidad evidente de cara a consultar y
modificar las posiciones de memoria. Cuando se necesita saber el segmento y/o el offset de una variable del
programa, las macros FP_OFF y FP_SEG devuelven dicha información. Por último, con MK_FP es posible
asignar una dirección de memoria absoluta a un puntero far. Por ejemplo, si se declara una variable:

char far *pantalla_color;

se puede hacer que apunte a la memoria de vídeo del modo texto de los adaptadores de color con:
373 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

pantalla_color = MK_FP (0xB800, 0);

y después se podría limpiar la pantalla con un bucle: for (i=0; i<4000; i++) *pantalla_color++=0;

13.1.3 - CONTROL DE INTERRUPCIONES.

void enable(void); /* habilitar interrupciones hardware, equivalente a STI */


void disable(void); /* inhibir interrupciones hardware, equivalente a CLI */

13.1.4 - LLAMADA A INTERRUPCIONES.

Para llamar a las interrupciones es conveniente conocer antes ciertas estructuras y uniones.

struct WORDREGS {
unsigned int ax, bx, cx, dx, si, di, cflag, flags;
};

struct BYTEREGS {
unsigned char al, ah, bl, bh, cl, ch, dl, dh;
};

union REGS {
struct WORDREGS x;
struct BYTEREGS h;
};

struct SREGS {
unsigned int es; unsigned int cs; unsigned int ss; unsigned int ds;
};

struct REGPACK {
unsigned r_ax, r_bx, r_cx, r_dx;
unsigned r_bp, r_si, r_di, r_ds, r_es, r_flags;
};

A continuación, se listan las funciones que permiten invocar las interrupciones:

int int86(int interrupción, union REGS *entrada, union REGS *salida);


int int86x(int interrupción, union REGS *entrada, union REGS *salida, struct REGS *rsegmento);
void intr(int interrupción, struct REGPACK *registros);

Las dos primeras funciones se basan en la declaración de dos uniones: una para entrada y otra para
salida, que simbolizan los valores iniciales (antes de llamar a la interrupción) y finales (tras la llamada) en los
registros. Si se desea que la misma unión que indica los valores iniciales devuelva los finales, se puede indicar
por duplicado:

union REGS regs;

regs.h.ah = 0;
regs.h.al = 0x13; /* VGA 320x200 - 256 colores */
int86 (0x10, &regs, &regs); /* cambiar modo de vídeo */

La diferencia entre int86() e int86x() reside en que la última permite trabajar con los registros de
segmento (la estructura SREGS se puede inicializar con los valores que tienen que tener los registros de
segmento antes de llamar a la interrupción; a la vuelta, dicha estructura habrá sido modificada para indicar el
valor devuelto en los registros de segmento tras la interrupción).
EL ENSAMBLADOR Y EL LENGUAJE C 373

Hay quien prefiere trabajar con REGPACK, que con una sola estructura permite también operar con
los registros de segmento y la emplea tanto para enviar como para recibir los resultados. El inconveniente, poco
relevante, es que sólo admite registros de 16 bits, lo que suele obligar a hacer desplazamientos y forzar el
empleo de máscaras para trabajar con las mitades necesarias:

struct REGPACK bios;

bios.r_ax = 0x13; /* VGA 320x200 - 256 colores */


intr (0x10, &bios); /* cambiar modo de vídeo */

13.1.5 - CAMBIO DE VECTORES DE INTERRUPCIÓN.

void interrupt (*getvect(int interrupción))(); /* obtener vector de interrupción */


void setvect (int interrupción, void interrupt (*rutina)()); /* establecer vector de interrupción */

La función getvect() devuelve un puntero con la dirección del vector de interrupción indicado. La
función setvect() permite desviar un vector hacia la rutina de tipo interrupt que se indica. Interrupt es una
palabra clave del Turbo C que será explicada en el futuro. Por ahora, baste el siguiente programa de ejemplo:

void interrupt nueva_rutina(); /* nuestra función de interrupción */


void interrupt (*vieja_rutina)(); /* variable para almacenar el vector inicial */

int main()
{
vieja_rutina = getvect (5); /* almacenar dirección de INT 5 (activada con Print Screen) */
setvect (5, nueva_rutina); /* desviar INT 5 a nuestra propia rutina de control */
. . .
. . . /* resto del programa */
. . .
setvect (5, vieja_rutina); /* restaurar rutina inicial de INT 5 */
}

void interrupt nueva_rutina() /* rutina de control de INT 5 */


{
. . .
}

13.1.6 - PROGRAMAS RESIDENTES.

void keep (unsigned char errorlevel, unsigned tamaño);

La función anterior, basada en el servicio 31h del DOS, permite a un programa realizado en C quedar
residente en la memoria. Además del código de retorno, es preciso indicar el tamaño del área residente (en
párrafos). Es difícil determinar con precisión la memoria que ocupa un programa en C. Sin embargo, en muchos
casos la siguiente fórmula puede ser válida:

keep (0, (_SS + ((_SP + area_de_seguridad)/16) - _psp));

En los casos en que no lo sea, se le puede hacer que vuelva a serlo aumentando el tamaño del área de
seguridad (que en los programas menos conflictivos será 0). Tanto _psp como _SS y _SP están definidas ya por
el compilador, por lo que la línea anterior es perfectamente válida (sin más) al final de un programa.

13.1.7 - VARIABLES GLOBALES PREDEFINIDAS INTERESANTES.

_version /* devuelve la versión del DOS de manera completa */


_osmajor /* devuelve el número principal de versión del DOS: ej., 5 en el DOS 5.0 */
_osminor /* devuelve el número secundario de versión del DOS: ej., 0 en el DOS 5.0 */
_psp /* segmento del PSP */
373 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

_stklen /* contiene el tamaño de la pila, en bytes */


_heaplen /* almacena el tamaño inicial del heap, en bytes (0 para maximizarlo) */

De estas variables predefinidas, las más útiles son quizá las que devuelven la versión del DOS, lo que
ahorra el esfuerzo que supone averiguarlo llamando al DOS o empleando la función de librería correspondiente.
También es útil _psp, que permite un acceso a este área del programa de manera inmediata.

13.1.8 - INSERCIÓN DE CÓDIGO EN LÍNEA.

void _ _emit_ _ (argumento,...);


void geninterrupt (int interrupción);

Por medio de _ _emit_ _() se puede colocar código máquina de manera directa dentro del programa en
C. No es conveniente hacerlo así porque así, ya que alterar directamente los registros de la CPU acabará
alterando el funcionamiento esperado del compilador y haciendo fallar el programa. Sin embargo, en un
procedimiento dedicado exclusivamente a almacenar código inline (en línea), es seguro este método, sobre todo
si se tiene cuidado de no alterar los registros SI y DI (empleados muy a menudo por el compilador como
variables de tipo register). Por medio de geninterrupt() se puede llamar directamente a una interrupción:
geninterrupt (interr) es exactamente lo mismo que _ _emit_ _(0xCD, interr) ya que 0xCD es el código de
operación de INT. Por ejemplo, para volcar la pantalla por impresora se puede ejecutar geninterrupt(5). Con los
símbolos _AX, _AL, _AH, _BX, _BL, _BH, _CX, _CL, _CH, _DX, _DL, _DH, _SI, _DI, _BP, _SP, _CS,
_DS, _ES, _SS y _FLAGS se puede acceder directamente a los registros de la CPU. Hay que tomar también
precauciones para evitar efectos laterales (una asignación tipo _DS=0x40 no afectará sólo a DS).

13.1.9 - LAS PALABRAS CLAVE INTERRUPT Y ASM.

Con interrupt <declaración_de_función>; se declara una determinada función como de tipo


interrupción. En estas funciones, el compilador preserva y restaura todos los registros al comienzo y final de las
mismas; finalmente, retorna con IRET. Por tanto, es útil para funciones que controlan interrupciones. Para
emplear esto, se debería compilar el programa con la opción test stack overflow y las variables tipo registro
desactivadas. Con asm se pueden insertar instrucciones en ensamblador, como se verá más adelante.

13.2 - INTERFAZ C (BORLAND/MICROSOFT) - ENSAMBLADOR.

13.2.1 - MODELOS DE MEMORIA.

Los modelos de memoria constituyen las diversas maneras de acceder a la memoria por parte de los
compiladores de C. En el caso del Turbo C se pueden distinguir los siguientes:

TINY: Se emplea en los programas donde ╔══════════════════════════╦═════════════════╗


es preciso apurar el consumo de memoria ║ Segmentos ║ Punteros ║
hasta el último byte. Los 4 registros de ╔═════════╣────────┬────────┬────────╫────────┬────────╢
segmento (CS, DS, ES, SS) están asignados ║ Modelo ║ Código │ Datos │ Pila ║ Código │ Datos ║
╠═════════╬════════╧════════╧════════╬════════╪════════╣
a la misma dirección, por lo que existe un
║ Tiny ║ 64 Kb ║ near │ near ║
total de 64 Kb donde se mezclan código,
╟─────────╫────────┬─────────────────╫────────┼────────╢
datos y pila. Los programas de este tipo ║ Small ║ 64 Kb │ 64 Kb ║ near │ near ║
pueden convertirse a formato COM. ╟─────────╫────────┼─────────────────╫────────┼────────╢
║ Medium ║ 1 Mb │ 64 Kb ║ far │ near ║
SMALL: Se utiliza en aplicaciones ╟─────────╫────────┼─────────────────╫────────┼────────╢
pequeñas. Los segmentos de código y datos ║ Compact ║ 64 Kb │ 1 Mb ║ near │ far ║
son diferentes y no se solapan. Por ello, ╟─────────╫────────┼─────────────────╫────────┼────────╢
║ Large ║ 1 Mb │ 1 Mb ║ far │ far ║
╟─────────╫────────┼─────────────────╫────────┼────────╢
║ Huge ║ 1 Mb │ 1 Mb ║ far │ far ║
║ ║ │(bloques > 64 Kb)║ │ ║
╚═════════╩════════╧═════════════════╩════════╧════════╝
EL ENSAMBLADOR Y EL LENGUAJE C 373

hay 64 kb para código y otros 64 Kb a repartir entre datos y pila.

MEDIUM: Este modelo es ideal para programas largos que no manejan demasiados datos. Se utilizan punteros
largos para el código (que puede extenderse hasta 1 Mb) y cortos para los datos: la pila y los datos juntos no
pueden exceder de 64 Kb.

COMPACT: Al contrario que el anterior, este modelo es el apropiado para los programas pequeños que
emplean muchos datos. Por ello, el programa no puede exceder de 64 Kb aunque los datos que controla pueden
alcanzar el Mb, ya que los punteros de datos son de tipo far por defecto.

LARGE: Empleado en las aplicaciones grandes y también por los programadores de sistemas que no tienen
paciencia para andar forzando continuamente el tipo de los punteros (para rebasar el límite de 64 Kb). Tanto los
datos como el código pueden alcanzar el Mb, aunque no se admite que los datos estáticos ocupen más de 64 Kb.
Este modo es el que menos problemas da para manejar la memoria, no siendo quizá tan lento y pesado como
indica el fabricante.

HUGE: Similar al anterior, pero con algunas ventajas: por un lado, todos los punteros son normalizados
automáticamente y se admiten datos estáticos de más de 64 Kb. Por otro, y gracias a esto último, es factible
manipular bloques de datos de más de 64 Kb cada uno, ya que los segmentos de los punteros se actualizan
correctamente. Sin embargo, este modelo es el más costoso en tiempo de ejecución de los programas.

13.2.2 - INTEGRACIÓN DE MÓDULOS EN ENSAMBLADOR.

LA SENTENCIA ASM

La sentencia asm permite incluir código ensamblador dentro del programa C, utilizando los
mnemónicos normales del ensamblador. Sin embargo, el uso de esta posibilidad está más o menos limitado
según la versión del compilador. En Turbo C 2.0, los programas que utilizan este método es necesario salir a la
línea de comandos para compilarlos con el tradicional compilador de línea, lo cual resulta poco atractivo. En
Turbo C++ 1.0, se puede configurar adecuadamente el compilador para que localice el Turbo Assembler y lo
utilice automáticamente para ensamblar, sin necesidad de salir del entorno integrado. Sin embargo, es a partir
del Borland C++ cuando se puede trabajar a gusto: en concreto, la versión Borland C++ 2.0 permite ensamblar
sin rodeos código ensamblador incluido dentro del listado C. El único inconveniente es la limitación del
hardware disponible: para un PC/XT, el Turbo C 2.0 es el único compilador aceptablemente rápido. Sin
embargo, en un 286 es más recomendable el Turbo C++, mientras que en un 386 modesto (o incluso en un 286
potente) resulta más interesante emplear el Borland C++ 2.0: las versiones 3.X de este compilador son las más
adecuadas para un 486 o superior (bajo DOS).

La sintaxis de asm se puede entender fácilmente con un ejemplo:

main()
{
int dato1, dato2, resultado;

printf("Dame dos números: "); scanf("%d %d", &dato1, &dato2);

asm push ax; push cx;


asm mov cx,dato1
asm mov ax,0h
mult:
asm add ax,dato2
asm loop mult
asm mov resultado,ax
asm pop cx; pop ax;

printf("Su producto por el peor método da: %d", resultado);


373 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

Como se ve en el ejemplo, los registros utilizados son convenientemente preservados para no alterar el
valor que puedan tener en ese momento (importante para el compilador). También puede observarse lo fácil que
resulta acceder a las variables. Ah, cuidado con BP: el registro BP es empleado mucho por el compilador y no
conviene tocarlo (ni siquiera guardándolo en la pila). De hecho, la instrucción MOV CX,DATO1 será
compilada como MOV CX,[BP-algo] al ser una variable local de main().

Esta es la única sintaxis soportada por el Turbo C 2.0; sin embargo, en las versiones más modernas del
compilador se admiten las llaves '{' y '}' para agrupar varias sentencias asm:

asm {
push ax; push cx;
mov cx,dato1
mov ax,0h }
mult: asm {
add ax,dato2
loop mult
mov resultado,ax
pop cx; pop ax;
}

SUBRUTINAS EN ENSAMBLADOR

Cuando las rutinas a incluir son excesivamente largas, resulta más conveniente escribirlas como
ficheros independientes y ensamblarlas por separado, incluyéndolas en un fichero de proyecto (*.PRJ)
seleccionable en los menús del compilador.

Para escribir este tipo de rutinas hay que respetar las mismas definiciones de segmentos que realiza el
compilador. Hoy en día existe algo más de flexibilidad; sin embargo, aquí se expone el método general para
mezclar código de ensamblador con C.

Veamos el siguiente programa en C:

int variable;
extern dato;
extern funcion();

main()
{
int a=21930; char b='Z';

variable = funcion (a, b, 0x12345678);

La variable variable es una variable global del programa a la que no se asigna valor alguno en el
momento de definirla. Tanto a como b son variables locales del procedimiento main() y son asignadas con un
cierto valor inicial; funcion() no aparece por ningún sitio, ya que será codificada en ensamblador en un fichero
independiente. A dicha función se le pasan 3 parámetros. La manera de hacerlo es colocándolos en la pila
(empezando por el último y acabando por el primero). Por ello, el compilador meterá primero en la pila el valor
1234h y luego el 5678h (necesita dos palabras de pila porque es un dato de tipo long). Luego coloca en la pila el
carácter almacenado en la variable b: como los valores que se apilan son siempre de 16 bits, la parte alta está a
0. Finalmente, deposita el dato entero a. Seguidamente, llama a la función funcion() con un CALL que puede
ser de dos tipos: corto (CALL/RET en el mismo segmento) o largo (CALL/RETF entre distintos segmentos).
Esta llamada a la función, por tanto, provoca un almacenamiento adicional de 2 bytes (modelos TINY, SMALL
y COMPACT) o 4 (en los restantes modelos de memoria, que podríamos llamar largos).
EL ENSAMBLADOR Y EL LENGUAJE C 373

El esqueleto de la subrutina en ensamblador que ha de recibir esos datos y, tras procesarlos, devolver un
resultado de tipo int es el siguiente:

DGROUP GROUP _DATA, _BSS

_DATA SEGMENT WORD PUBLIC 'DATA'


PUBLIC _dato ; _dato será accesible desde el programa C
_dato DW 0 ; valor inicial a 0
_DATA ENDS

_BSS SEGMENT WORD PUBLIC 'BSS'


EXTRN _variable:WORD ; variable externa
_info DW ? ; sin valor inicial
_BSS ENDS

_TEXT SEGMENT BYTE PUBLIC 'CODE'


ASSUME CS:_TEXT,DS:DGROUP,SS:DGROUP

PUBLIC _funcion ; _funcion será accesible desde el programa C

_funcion PROC NEAR ; funcion() del C


PUSH BP
MOV BP,SP
MOV BX,[BP+4] ; recuperar variable 'a'
MOV CX,[BP+6] ; recuperar variable 'b'
MOV AX,[BP+8] ; AX = 5678h
MOV DX,[BP+10] ; DX = 1234h -> DX:AX = 12345678h
; ...
; ...
ADD CX,BX ; cuerpo de la función
ADD CX,AX
SUB CX,DX
; ...
; ...
MOV AX,CX ; resultado (tipo int)
MOV SP,BP
POP BP
RET
_funcion ENDP

_TEXT ENDS
END

Como se puede observar, se respetan ciertas convenciones en cuanto a los nombres de los segmentos y
grupos. En el segmento _DATA se definen las variables inicializadas (las que tienen un valor inicial): _dato
podría haber sido accedida perfectamente desde el programa en C, ya que es declarada como pública. Por otro
lado, en el segmento _BSS se definen o declaran las variables que no son inicializadas con un valor inicial
(como es el caso de la variable _variable del programa C, que fue definida simplemente como int variable: en el
listado ensamblador se la declara como externa ya que está definida en el programa C). El compilador de C
precede siempre de un subrayado a todas las variables y funciones cuando compila, motivo por el cual hay que
hacer lo propio en el listado ensamblador. Al tratarse de un modelo de memoria pequeño, _BSS y _DATA están
agrupados. En el segmento _TEXT se almacena el código, es decir, las funciones definidas: en nuestro caso,
373 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

sólo una (el procedimiento _funcion). Como es de tipo NEAR, sólo se podrá emplear con programas C
compilados en un modelo de memoria TINY, SMALL o COMPACT (para los demás modelos hay que poner
FAR en lugar de NEAR). Esta función de ejemplo en ensamblador no utiliza ninguna variable, pero tanto
_variable (la variable del programa C) como, por supuesto, _info o _dato son plenamente accesibles.

A la hora de acceder a las variables, hay que tener en cuenta el modelo de memoria: como no emplea
más de 64 Kb para código (modelos TINY, SMALL o COMPACT), el compilador sólo ha colocado en la pila
el offset de la dirección de retorno (registro IP). Nosotros apilamos después BP (ya que lo vamos a manchar)
por lo que el último dato que apiló el programa C antes de llamar a la rutina en ensamblador habrá de ser
accedido en [BP+4]. La ventaja de inicializar BP es que luego se pueden introducir datos en la pila sin perder la
posibilidad de acceder a los parámetros de la rutina que llama. Si el procedimiento fuera de tipo FAR (modelos
MEDIUM, LARGE y HUGE), todos los accesos indexados sobre la pila se incrementarían en dos unidades (por
ejemplo, [BP+6] en vez de [BP+4] para acceder a la variable a) debido a que también se habría almacenado CS
en la llamada. Como se puede observar, la rutina no preserva ni restaura todos los registros que va a emplear:
sólo es necesario devolver intactos DS, SS, BP y (por si se emplean variables register) SI y DI; los demás
registros pueden ser libremente alterados. Como la función es de tipo entero, devuelve el resultado en AX; si
fuera de tipo long lo devolvería en DX:AX.

El modelo de memoria también cuenta en los parámetros que son pasados a la rutina en ensamblador
cuando no son pasados por valor (es decir, cuando se pasan punteros). En el ejemplo, podríamos haber pasado
un puntero que podría ser de tipo corto (para cargarlo en BX, por ejemplo, y efectuar operaciones tipo [BX]).
Sin embargo, si se pasan punteros a variables de tipo far (o si se emplea un modelo de memoria COMPACT,
LARGE o HUGE) es necesario cargar la dirección con una instrucción LES de 32 bits.

Esta rutina de ejemplo en ensamblador es sólo demostrativa, por lo que no debe el lector intentar
encontrar alguna utilidad práctica, de ahí que incluso ni siquiera emplee todas las variables que define.

Evidentemente, cuando el programa C retome el control, habrá de equilibrar la pila sumando 8 unidades
a SP (para compensar las 4 palabras que apiló antes de llamar a la función en ensamblador). En general, el
funcionamiento general del C en las llamadas a procedimientos se basa en apilar los parámetros empezando por
el último y llamar al procedimiento: éste, a su vez, preserva BP y lo hace apuntar a dichos parámetros (a los que
accederá con [BP+desp]); a continuación, le resta a SP una cantidad suficiente para que quepan en la pila todas
las variables locales (a las que accederá con [BP-desp]); antes de retornar restaura el valor inicial de SP y
recupera BP de la pila. Es entonces cuando el procedimiento que llamó, al recuperar el control, se encarga de
sumar el valor adecuado a SP para equilibrar la pila (devolverla al estado previo a la introducción de los
parámetros).

Desde las rutinas en ensamblador también se puede llamar a las funciones del compilador, apilando
adecuadamente los parámetros en la pila (empezando por el último) y haciendo un CALL al nombre de la
función precedido de un subrayado: no olvidar nunca al final sumar a SP la cantidad necesaria para reequilibrar
la pila.

AVISO IMPORTANTE: Algo a tener en cuenta es que el compilador de C es sensible a las


mayúsculas: funcion() no es lo mismo que FUNCION(). Por ello, al ensamblar, es obligatorio emplear como
mínimo el parámetro /mx del ensamblador con objeto de que no ponga todos los símbolos automáticamente en
mayúsculas (con /mx se respetan las minúsculas en los símbolos globales y con /ml en todos los símbolos). En
MASM 6.0, el equivalente a /mx es /Cx y la opción /Cp se corresponde con /ml.
APÉNDICES 381

Apéndice I - MAPA DE MEMORIA BAJO MS-DOS y DR-DOS 6.0

┌─ FFFFFFFF ──┬──────────────────────────────────────────────────┐
│ │ 3,98 Gb Memoria extendida (386) │
Memoria extendida │ FFFFFF ──┼──────────────────────────────────────────────────┤
│ │ 14,9 Mb Memoria extendida (286/386) │
└─ 110000 ──┼──────────────────────────────────────────────────┤
┌─ │ 64 Kb HMA (286/386) para el núcleo del DOS (AT) │
Memoria alta │ │ - zona más alta accesible por el DOS - │
(64 Kb) └─ 100000 ──┼──────────────────────────────────────────────────┤
┌─ │ 64 Kb ROM BIOS (y/o memoria superior 386) │
│ F0000 ──┼──────────────────────────────────────────────────┤
│ │ 64 Kb EMS (PC/XT/AT) (o memoria superior 386) │
│ E0000 ──┼──────────────────────────────────────────────────┤
Memoria superior │ │ 64 Kb EMS (PC/XT/AT) (o memoria superior 386) │
(máximo 384 Kb) │ D0000 ──┼──────────────────────────────────────────────────┤
│ │ 64 Kb extensiones ROM (y/o memoria superior 386) │
│ C0000 ──┼──────────────────────────────────────────────────┤
│ │ 128 Kb memoria máxima de vídeo direccionable │
└─ A0000 ──┼──────────────────────────────────────────────────┤
┌─ │ 638,5 Kb RAM de usuario (y núcleo del DOS PC/XT) │
│ 00600 ──┼──────────────────────────────────────────────────┤
Memoria Convencional │ │ Area de datos del DOS y del BASIC │
(640 Kb) │ 00500 ──┼──────────────────────────────────────────────────┤
│ │ Variables de la BIOS y de las extensiones ROM │
│ 00400 ──┼──────────────────────────────────────────────────┤
│ │ Vectores de interrupción │
└─ 00000 ──┴──────────────────────────────────────────────────┘

La memoria convencional en las máquinas más potentes está casi enteramente a


disposición del usuario, aunque en los PC/XT el núcleo del sistema operativo ocupa un buen
fragmento de la misma (unos 45 Kb). En los 286 y superiores, el núcleo del sistema se ubica en
el HMA (primeros 64 Kb de la memoria extendida). La memoria de vídeo está dividida en
dos bloques de 64 Kb: el ubicado entre A0000-AFFFF lo emplean la EGA, VGA y SuperVga en
modo gráfico. El segundo, entre B0000-BFFFF es usado por la CGA y la Hércules, también en
modo gráfico. En modo de texto, el adaptador monocromo de IBM (primeros PC sin gráficos)
emplea 4 Kb a partir de B0000; el adaptador de color utiliza 16 kb a partir de B8000. Las
EGA/VGA soportan ambos tipos de pantallas de texto; las tarjetas «bifrecuencia» también.
Entre C0000 y CFFFF puede estar ubicada la BIOS de la VGA (normalmente entre C0000 y
C7FFF) o las BIOS de discos duros de XT, el resto de este segmento (en 386) es memoria
superior donde cargar los programas residentes con HILOAD (o LOADHIGH en MS-DOS)
que así no ocupan memoria convencional. Los segmentos de 64 Kb que comienzan en D0000
y E0000 pueden contener extensiones de la BIOS (normalmente discos duros de XT) o
también memoria superior. Uno de los dos puede ser empleado para la «ventana» de
memoria expandida EMS (PC/XT/AT), normalmente el primero. En F0000 está colocada la
ROM BIOS (aunque en PC/XT es frecuente que sólo estén ocupados los últimos 8 Kb; en los
AT suele ubicarse un programa SETUP que permite al usuario definir la configuración de la
máquina). Por encima, los primeros 64 Kb de memoria extendida son accesibles incluso desde
el modo real del 286 y 386, siempre que la línea de direcciones A20 esté habilitada (lo que
sucede a partir del DR-DOS y del MS-DOS 5.0). Para ello, con CS=FFFF se puede acceder a
65520 bytes (casi 64Kb) de RAM adicionales donde se puede cargar el núcleo del sistema
381 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

operativo y quizá algún que otro programa residente (DR-DOS 6.0). El resto de la memoria en
máquinas 286/386 es memoria extendida, que puede ser direccionada por controladores de
disco virtual o cachés de disco duro, e incluso -en 386- puede ser convertida por software en
memoria expandida paginable en el segmento (dentro del primer mega) habilitado al efecto.
APÉNDICES 381
Apéndice II - TABLA DE INTERRUPCIONES DEL SISTEMA

INT 00: División por cero


INT 01: Ejecución paso a paso
INT 02: No Enmascarable (NMI)
INT 03: Puntos de ruptura
INT 04: Desbordamiento (INTO)
INT 05: Volcar pantalla por impresora (BIOS)
INT 06: Código de operación incorrecto
INT 07: Reservada
INT 08: IRQ 0: Contador de hora del sistema (BIOS)
INT 09: IRQ 1: Interrupción de teclado (BIOS)
INT 0A: IRQ 2: canal E/S, segundo 8259 del AT
INT 0B: IRQ 3: COM2
INT 0C: IRQ 4: COM1
INT 0D: IRQ 5: disco duro XT, LPT2 en AT, retrazo vertical PCjr
INT 0E: IRQ 6: Controlador del disquete
INT 0F: IRQ 7: LPT1
INT 10: Servicios de vídeo (BIOS)
INT 11: Listado del equipo (BIOS)
INT 12: Tamaño de memoria (BIOS)
INT 13: Servicios de disco (BIOS)
INT 14: Comunicaciones en serie (BIOS)
INT 15: Servicios del sistema (BIOS)
INT 16: Servicios de teclado (BIOS)
INT 17: Servicios de impresora (BIOS)
INT 18: IBM Basic (ROM del BASIC)
INT 19: Arranque del sistema (BIOS)
INT 1A: Fecha/hora del sistema
INT 1B: Acción de CTRL-BREAK (BIOS)
INT 1C: Proceso periódico del usuario (Usuario)
INT 1D: Parámetros de vídeo (BIOS)
INT 1E: Parámetros del disquete (BIOS)
INT 1F: Tabla de caracteres gráficos (BIOS)
INT 20: Fin de programa (DOS)
INT 21: Servicio del sistema operativo (DOS)
INT 22: Dirección de terminación (DOS)
INT 23: DOS CTRL-BREAK (DOS)
INT 24: Manipulador de errores críticos (DOS)
INT 25: Lectura absoluta de disco (DOS)
INT 26: Escritura absoluta en disco (DOS)
INT 27: Terminar permaneciendo residente (DOS)
INT 28: DOS Idle (programas residentes que usan funciones DOS)
INT 29: DOS TTY (impresión en pantalla)
INT 2A: Red local MS net
INT 2B-2D: Uso interno del DOS
INT 2E: Procesos Batch (DOS)
INT 2F: Multiplex (DOS)
INT 30: Compatibilidad CP/M-80 (xx:YYyy en JMP XXxx:YYyy)
INT 31: Compatibilidad CP/M-80 (XX en JMP XXxx:YYyy)
INT 32: Reservada
INT 33: Controlador del ratón
INT 34-3F: Reservadas
INT 40: Interrupción de disquete (BIOS)
INT 41: Parámetros del disco duro 1 (BIOS)
INT 42: Apunta a la INT 10h original del BIOS si existe VGA
INT 43: Caracteres gráficos EGA (BIOS)
INT 44-45: Reservadas
INT 46: Parámetros del disco duro 2 (BIOS)
INT 47-49: Reservadas
INT 4A: Alarma del usuario
INT 4B-5F: Reservadas
APÉNDICES 382

INT 60-66: Para uso de los programas


INT 67: Interrupción de EMS (controlador EMS)
INT 68-6F: Reservadas
INT 70: IRQ 8: Reloj de tiempo real AT (2º chip 8259-AT)
INT 71: IRQ 9: IRQ 2 redireccionada (2º chip 8259-AT)
INT 72: IRQ 10: reservada (2º chip 8259-AT)
INT 73: IRQ 11: reservada (2º chip 8259-AT)
INT 74: IRQ 12: interrupción de ratón IBM (2º chip 8259-AT)
INT 75: IRQ 13: error de coprocesador matemático (2º chip 8259-AT)
INT 76: IRQ 14: controlador disco fijo (2º chip 8259-AT)
INT 77: IRQ 15: reservada (2º chip 8259-AT)
INT 78-7F: Reservadas
INT 80-85: Reservadas para el Basic
INT 86-F0: Usadas por el Basic
INT F1-FF: Para uso de los programas
382 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Apéndice III - TABLA DE VARIABLES DE LA BIOS

La siguiente información procede del fichero MEMORY.LST de Robin Walker, incluido en el mismo
paquete del INTERRUP.LST. La información está actualizada mayoritariamente al 24/8/92. Se han eliminado
aspectos demasiado técnicos sobre las tarjetas EGA/VGA y alguna información sobre hardware no estándar.

Las variables de la BIOS comienzan en el segmento de memoria 40h, justo después de la tabla de
vectores de interrupción. Son empleadas por los programas de control ubicados en las memorias ROM del
ordenador. En general, siempre es preferible utilizar una función de la BIOS que modificar directamente sus
variables, aunque a veces ello no es posible o puede no resultar conveniente. Los campos colocados entre llaves
('{' y '}') no están documentados por IBM y podrían cambiar en el futuro. Los códigos entre corchetes indican a
qué máquinas o configuraciones, en exclusiva, se aplica la información.

Offset Tamaño Descripción


00h WORD Dirección E/S base del primer puerto serie (0 si no instalado)
02h WORD Dirección E/S base del segundo puerto serie (0 si no instalado)
04h WORD Dirección E/S base del tercer puerto serie (0 si no instalado)
06h WORD Dirección E/S base del cuarto puerto serie (0 si no instalado)
Nota: Los campos de arriba son rellenados en estricto orden por
el programa POST de la BIOS que inicializa el sistema, sin
dejar huecos. Los puertos serie del DOS y de la BIOS pueden
redefinirse modificando estos campos.
08h WORD Dirección E/S base del primer puerto paralelo (0 si no instalado)
0Ah WORD Dirección E/S base del segundo puerto paralelo (0 si no instalado)
0Ch WORD Dirección E/S base del tercer puerto paralelo (0 si no instalado)
0Eh WORD [Máquinas no PS]:
Dirección E/S base del cuarto puerto paralelo (0 si no instalado)
[Máquinas PS]:
Segmento del área de datos extendida de la BIOS
Nota: Los campos de arriba son rellenados en estricto orden por
el programa POST de la BIOS que inicializa el sistema, sin
dejar huecos. Los puertos paralelo del DOS y de la BIOS
pueden redefinirse modificando estos campos.
10h WORD Hardware instalado:
bits 15-14: número de puertos paralelos
bit 13: [PC Convertible] = 1 si hay modem interno
bit 12: reservado
bits 11- 9: número de puertos serie
bit 8: reservado
bits 7- 6: número de disqueteras - 1
bits 5- 4: modo de vídeo inicial
00b = EGA,VGA,PGA
01b = 40 x 25 color
10b = 80 x 25 color
11b = 80 x 25 mono
bit 3: reservado
bit 2: [máquinas PS] = 1 si hay dispositivo apuntador
[máquinas no PS] reservado
bit 1: = 1 si hay coprocesador
bit 0: = 1 si hay disquete disponible para arrancar
12h BYTE [PC Convertible] estado del POST
[AT] {Banderines de inicialización de los test de fabricación}
13h WORD Tamaño de memoria convencional en kbytes (0-640)
15h BYTE [AT] {Usado en los test de fabricación}
16h BYTE [AT] {Usado en los test de fabricación}
[PS/2 Mod 30] Banderines de control de la BIOS
17h BYTE Banderines de estado del teclado 1:
bit 7 =1 INSert activo
383 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

bit 6 =1 Caps Lock activo


bit 5 =1 Num Lock activo
bit 4 =1 Scroll Lock activo
bit 3 =1 cualquier Alt pulsado
bit 2 =1 cualquier Ctrl pulsado
bit 1 =1 Shift izquierdo pulsado
bit 0 =1 Shift derecho pulsado
18h BYTE Banderines de estado del teclado 2:
bit 7 = 1 INSert pulsado
bit 6 = 1 Caps Lock pulsado
bit 5 = 1 Num Lock pulsado
bit 4 = 1 Scroll Lock pulsado
bit 3 = 1 Estado de pausa activo
bit 2 = 1 Sys Req pulsada
bit 1 = 1 Alt izquierdo pulsado
bit 0 = 1 Ctrl izquierdo pulsado
19h BYTE Teclado: Area de trabajo para Alt-nnn (nnn=teclado numérico)
1Ah WORD Teclado: puntero al próximo carácter en el buffer
1Ch WORD Teclado: puntero a la primera entrada vacía en el buffer
1Eh 16 WORDs Buffer del teclado (cola circular, ver offsets 80h y 82h para moverlo)
3Eh BYTE Estado de recalibración del disquete:
bit 7 = 1 Se ha producido interrupción hardware del disquete
bits 6-4 reservados
bit 3 = 1 Recalibrada disquetera 3
bit 2 = 1 Recalibrada disquetera 2
bit 1 = 1 Recalibrada disquetera 1
bit 0 = 1 Recalibrada disquetera 0
3Fh BYTE Estado del motor del disquete:
bit 7 = 1 la operación en curso es escritura o formateo
= 0 la operación en curso es lectura o verificación
bit 6 reservado
bits 5-4 número de disquetera seleccionada (0-3)
bit 3 = 1 motor de la disquetera 3 en marcha
bit 2 = 1 motor de la disquetera 2 en marcha
bit 1 = 1 motor de la disquetera 1 en marcha
bit 0 = 1 motor de la disquetera 0 en marcha
40h BYTE Contador de tics de reloj que faltan para parar motor de la disquetera
41h BYTE Estado de la última operación de disco (0 = correcta)
bit 7 = 1 unidad no preparada
bit 6 = 1 error de posicionamiento del cabezal
bit 5 = 1 fallo general del controlador
bits 4-0:
00h no hay error
01h solicitud incorrecta
02h no encontrada la marca de direcciones
03h error de protección contra escritura
04h sector no encontrado
06h línea de disco cambiado activa
08h el DMA se ha desbordado
09h el DMA ha cruzado una frontera de 64k
0Ch medio físico desconocido
10h fallo de CRC al leer
42h 7 BYTEs Bytes de estado/comandos de la Disquetera/Disco fijo
49h BYTE Modo de vídeo activo
4Ah WORD Número de columnas en pantalla
4Ch WORD Tamaño del buffer de vídeo de la página activa en bytes
4Eh WORD Desplazamiento sobre la memoria de pantalla de la página activa
50h 16 BYTEs Posición del cursor (columna, fila) para las 8 páginas
60h WORD Tipo de cursor, compatible 6845, byte alto=línea inicial, bajo=final
APÉNDICES 383

62h BYTE Página activa


63h WORD Dirección E/S base del controlador de vídeo: color=03D4h, mono=03B4h
65h BYTE Valor actual del registro de selección de modo 03D8h/03B8h
66h BYTE Valor actual almacenado en el registro de paleta de la CGA 03D9h
67h DWORD Punto de retorno al modo real tras ciertos resets del POST
6Bh BYTE Ultima interrupción no esperada por el POST
6Ch DWORD Tics de reloj (1/18,2 segundos) ocurridos desde medianoche
70h BYTE Flag de medianoche, <> 0 si el contador pasa de las 23:59:59.99
71h BYTE Banderín de Ctrl-Break: bit 7=1
72h WORD Banderín de reset del POST:
= 1234h si no realizar chequeo de memoria (arranque caliente)
= 4321h [solo PS/2 MCA] si preservar la memoria al arrancar
= 5678h [PC Convertible] sistema detenido
= 9ABCh [PC Convertible] test de fabricación
= ABCDh [PC Convertible] bucle del POST
= 64h modo «Burn-in»
74h BYTE Estado de la última operación del disco fijo: {salvo unidades ESDI}
00h no hubo error
01h función solicitada incorrecta
02h no encontrada marca de direcciones
03h error de protección contra escritura
04h sector no encontrado
05h fallo en el reset
07h fallo en la actividad de los parámetros del disco
08h el DMA se ha desbordado
09h alineamiento de datos incorrecto para el DMA
0Ah detectado banderín de sector erróneo
0Bh detectada pista errónea
0Dh número incorrecto de sectores para el formateo
0Eh detectada marca de direcciones de control
0Fh nivel de arbitrio del DMA fuera de rango
10h error ECC o CRC incorregible
11h error de datos ECC corregido
20h fallo general del controlador
40h fallo en el posicionamiento del cabezal
80h fuera de tiempo, no responde
AAh disco no preparado
BBh error indefinido
CCh fallo de escritura en el disco seleccionado
E0h el registro de errores es cero
FFh fallo de sentido
75h BYTE Disco fijo: número de discos fijos
76h BYTE Disco fijo: byte de control {IBM lo documenta sólo en el XT}
77h BYTE Disco fijo: offset del puerto E/S {IBM lo documenta sólo en el XT}
78h 3 BYTEs Contadores de «time-out» para los puertos paralelos 1-3
7Bh BYTE Contador «time-out» para puerto paralelo 4 [máquinas no PS]
bit 5 = 1 si especificación de DMA virtual soportada [PS] (ver INT
4B)
7Ch 4 BYTEs Contadores de «time-out» para los puertos serie 1-4
80h WORD Offset de inicio del buffer del teclado respecto al segmento 40h
(normalmente 1Eh)
82h WORD Offset del fin del buffer del teclado+1 respecto al segmento 40h
(normalmente 3Eh)

[La BIOS del XT con fecha 8/11/82 acaba aquí]

84h BYTE Vídeo: líneas en pantalla menos 1 en EGA/MCGA/VGA


85h WORD Video: altura del carácter, en pixels, en EGA/MCGA/VGA
87h BYTE Vídeo: control de EGA/VGA.
383 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

bit 7: = 1 si no limpiar RAM (ver INT 10h, AH=0)


88h BYTE Vídeo: switches EGA/VGA [MCGA: reservado]
89h BYTE Vídeo: MCGA/VGA opción de control del modo
8Ah BYTE Vídeo [MCGA/VGA]: índice en tabla Códigos de Combinaciones de Pantalla
8Bh BYTE Control del medio físico del disco [no XT]:
bits 7-6: Ultima tasa de transferencia fijada por el controlador:
00=500kbps, 01=300kbps, 10=250kbps, 11=1Mbps
bits 5-4:Ultimo «step rate» seleccionado en el disquete:
00-0Ch, 01=0Dh, 10=0Eh, 11=0Ah
bits 3-2: {Tasa de transferencia al inicio de la operación}
bits 1-0: reservado
8Ch BYTE Estado del controlador del disco fijo [no XT]
8Dh BYTE Estado de error del controlador de disco fijo [no XT]
8Eh BYTE Control de interrupciones del disco fijo [no XT]
8Fh BYTE Información del controlador de disquete [no XT]:
bit 7: reservado
bit 6: = 1 si disco 1 determinado
bit 5: = 1 si disco 1 es multi-ratio, válido si disco determinado
bit 4: = 1 si disco 1 soporta 80 pistas, siempre válido
bit 3: reservado
bit 2: = 1 si disco 0 determinado
bit 1: = 1 si disco 0 es multi-ratio, válido si disco determinado
bit 0: = 1 si disco 0 soporta 80 pistas, siempre válido
90h BYTE Estado físico de la disquetera 0
91h BYTE Estado físico de la disquetera 1
bits 7-6:tasa de transferencia a disquete: 00=500kbps, 01=300kbps,
10=250kbps, 11=1Mbps
bit 5: = 1 si doble salto de pista requerido (e.g. 360Kb en
1.2Mb)
bit 4: = 1 si superficie ya determinada
bit 3: reservado
bits 2-0: a la salida de la BIOS, contiene:
000 intentando 360Kb en 360Kb
001 intentado 360Kb en 1.2Mb
010 intentando 1.2MB en 1.2Mb
011 determinado 360Kb en 360Kb
100 determinado 360Kb en 1.2Mb
101 determinado 1.2Mb en 1.2Mb (continúa en pág siguiente)
110 reservado
111 todos los demás formatos
92h BYTE Estado físico de la disquetera 0 al inicio de la operación
93h BYTE Estado físico de la disquetera 1 al inicio de la operación
94h BYTE Número de pista en curso de la disquetera 0
95h BYTE Número de pista en curso de la disquetera 1
96h BYTE Estado del teclado, byte 1
bit 7 = 1 proceso de lectura de ID en marcha
bit 6 = 1 el último código leído fue el primero de dos códigos ID
bit 5 = 1 forzar Num Lock si se lee el ID y es un teclado expandido
bit 4 = 1 teclado expandido instalado
bit 3 = 1 Alt derecho pulsado
bit 2 = 1 Ctrl derecho pulsado
bit 1 = 1 último código leído fue E0h
bit 0 = 1 último código leído fue E1h
97h BYTE Estado del teclado, byte 2
bit 7 = 1 error de transmisión del teclado
bit 6 = 1 actualización de LEDs en curso
bit 5 = 1 código RESEND recibido del teclado
bit 4 = 1 código ACK recibido del teclado
bit 3 reservado, debe ser cero
APÉNDICES 383

bit 2 LED de Caps Lock


bit 1 LED de Num Lock
bit 0 LED de Scroll Lock
98h DWORD Timer2: [AT, PS excepto Mod 30] puntero al banderín de espera de
usuario completada (ver INT 15, AX=8300h)
9Ch DWORD Timer2: [AT, PS exc Mod 30] contador de espera del usuario (microseg.)
A0h BYTE Timer2: [AT, PS exc Mod 30] banderín de espera activa:
bit 7 = 1 tiempo de espera transcurrido
bits 6-1 reservados
bit 0 = 1 INT 15h, AH=86h ha sucedido
A1h 7 BYTEs Reservado para adaptadores de red local (¿será verdad?)
A4h DWORD [PS/2 Mod 30] Vector de la interrupción del disco duro preservada
A8h DWORD Video: En EGA/MCGA/VGA, puntero al «Video Save Pointer Table»
ACh-AFh Reservados
B0h BYTE (Phoenix 386 BIOS 1.10 10a) contador para retardo LOOP cuando se pita
ante un buffer de teclado lleno
B0h DWORD Puntero al controlador de disco óptico 3363.
B4h WORD Reservado
B6h 3 BYTEs ¿Reservado para el POST?
B9h 7 BYTEs ???
C0h 14 BYTEs Reservado
CEh WORD ¿¿¿Cuenta de días desde el último arranque???
D0h-EFh Reservado
D0h-DCh Usado por Digiboard MV/4
F0h-FFh Reservado para el usuario
100h BYTE Byte de estado de Print Screen
10Eh BYTE Estado de BREAK al inicio de la ejecución de BASICA.COM
10Fh BYTE Banderín: 02h si BASICA v2.10 está ejecutándose
116h DWORD INT 1Bh al inicio de la ejecución de BASICA.COM
11Ah DWORD INT 24h al inicio de la ejecución de BASICA.COM
383 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
APÉNDICES 383

Apéndice IV - PUERTOS DE ENTRADA Y SALIDA

PC/XT 000 - 00F Controlador de DMA (8237)


020 - 021 Controlador de interrupciones (8259)
040 - 043 Temporizador (8253)
060 - 063 Interface programable de periféricos (PPI, 8255)
081 - 083 Registros de página del DMA (74LS612)
0A0 - 0AF Registro de máscara de la NMI (0A0)
200 - 20F Joystick
210 - 217 Unidad de expansión
2F8 - 2FF 2º puerto serie
300 - 31F Tarjetas prototipo
320 - 32F Disco duro
378 - 37F 1º puerto paralelo
380 - 38C SDLC
3B0 - 3BF Adaptador monocromo/impresora
3D0 - 3D7 Adaptador CGA
3F0 - 3F7Controlador de disquete (NEC 765)
3F8 - 3FF 1º Puerto serie
790 - 793 Bloques (adaptador 1)
B90 - B93 Bloques (adaptador 2)
1390 - 1393 Bloques (adaptador 3)
2390 - 2393 Bloques (adaptador 4)

PC/AT 000 - 01F 1º Controlador de DMA (8237)


020 - 021 1º Controlador de interrupciones (8259)
040 - 05F Temporizador (8254)
060 - 06F Controlador del teclado (8042)
070 - 07F Registro de máscara de la NMI; reloj de tiempo real
080 - 09F Registros de página del DMA (74LS612)
0A0 - 0A1 2º Controlador de interrupciones (8259)
0C0 - 0DF 2º Controlador de DMA (8237)
0F0 - 0FF Coprocesador matemático
1F0 - 1F8Disco duro
200 - 207 Joystick
258 - 25F Intel «Above Board»
278 - 27F 2º puerto paralelo
2E1 GPIB (adaptador 0)
2E2 - 2E3 Adquisición de datos (adaptador 0)
2F8 - 2FF 2º puerto serie
300 - 31F Tarjetas prototipo
360 - 36F Reservados
378 - 37F 1º puerto paralelo
380 - 38C 2º SDLC o comunicación bisíncrona
3A0 - 3AF 1º SDLC
3B0 - 3BF Adaptador monocromo/impresora
3C0 - 3CF EGA/VGA
3D0 - 3DF Adaptador CGA
3F0 - 3F7Controlador de disquete (NEC 765)
3F8 - 3FF 1º Puerto serie
6E2 - 6E3 Adquisición de datos (Adaptador 1)
790 - 793 Bloques (adaptador 1)
AE2 - AE3 Adquisición de datos (Adaptador 2)
B90 - B93 Bloques (adaptador 2)
EE2 - EE3 Adquisición de datos (Adaptador 3)
1390 - 1393 Bloques (adaptador 3)
22E1 GPIB (Adaptador 1)
383 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

2390 - 2393 Bloques (adaptador 4)


42E1 GPIB (Adaptador 2)
62E1 GPIB (Adaptador 3)
82E1 GPIB (Adaptador 4)
A2E1 GPIB (Adaptador 5)
C2E1 GPIB (Adaptador 6)
E2E1 GPIB (Adaptador 7)
APÉNDICES 383
Apéndice V - CÓDIGOS DE RASTREO DEL TECLADO. CÓDIGOS SECUNDARIOS.

01 3B 3C 3D 3E 3F 40 41 42 43 44 57 58 Ex 46 Ex
┌───┐┌──┬──┬──┬──┐┌──┬──┬──┬──┐┌──┬───┬───┬───┐ ┌───┬───┬───┐ ┌───┬───┬───┐
│ESC││F1│F2│F3│F4││F5│F6│F7│F8││F9│F10│F11│F12│ │Ipt│Bdp│Pau│ │ │ │ │
└───┘└──┴──┴──┴──┘└──┴──┴──┴──┘└──┴───┴───┴───┘ └───┴───┴───┘ └───┴───┴───┘

29 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E Ex Ex Ex 45 Ex 37 4A
┌───┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬─────┐ ┌───┬───┬───┐ ┌──┬──┬──┬──┐
│ºª\│ 1│ 2│ 3│ 4│ 5│ 6│ 7│ 8│ 9│ 0│'?│¡¿│ ── │ │Ins│Ini│Rpg│ │Bn│ /│ *│ -│
└───┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴─────┘ └───┴───┴───┘ └──┴──┴──┴──┘

0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C Ex Ex Ex 47 48 49 4E
┌────┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬────┐ ┌───┬───┬───┐ ┌──┬──┬──┬──┐
│ TAB│ Q│ W│ E│ R│ T│ Y│ U│ I│ O│ P│`[│+]│ │ │Sup│Fin│Apg│ │ 7│↑8│ 9│ │
└────┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴┐ │ │ └───┴───┴───┘ └──┴──┴──┤ +│
│ ┘ │ │ │
3A 1E 1F 20 21 22 23 24 25 26 27 28 2B└───┘ 4B 4C 4D└──┘
┌─────┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ ┌──┬──┬──┐
│B.May│ A│ S│ D│ F│ G│ H│ J│ K│ L│ Ñ│'{│Ç}│ │←4│ 5│→6│
└─────┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘ └──┴──┴──┘

2A 56 2C 2D 2E 2F 30 31 32 33 34 35 36 Ex 4F 50 51 Ex
┌────┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬───────┐ ┌───┐ ┌──┬──┬──┬──┐
│ │<>│ Z│ X│ C│ V│ B│ N│ M│,;│.:│-_│ │ │ ↑│ │ 1│↓2│ 3│ │
└────┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴───────┘ └───┘ └──┴──┴──┤ │
│ ┘│
1D 38 39 Ex Ex Ex Ex Ex 52 53 └──┘
┌────┐ ┌─────┬───────────────────┬─────┐ ┌────┐ ┌───┬───┬───┐ ┌─────┬──┐
│Ctrl│ │ Alt │ │AltGr│ │Ctrl│ │ ← │ ↓ │ → │ │ 0 │ .│
└────┘ └─────┴───────────────────┴─────┘ └────┘ └───┴───┴───┘ └─────┴──┘

Las teclas marcadas con 'Ex' son exclusivas de teclados expandidos; generan los mismos códigos de
rastreo que sus correspondientes teclas «no expandidas», aunque precedidos de un código de rastreo adicional
0E0h como mínimo, por lo general (consultar el apartado 5.2 del capítulo 7 para más detalles).

Códigos secundarios.

A continuación se listan los códigos secundarios. Estos se producen al pulsar ciertas combinaciones
especiales de teclas, a las que el controlador de INT 9 responde colocando un código ASCII 0 en el buffer, a
menudo junto al código de rastreo, para identificarlas; las teclas expandidas provocan frecuentemente la
inserción de un ASCII 0E0h o bien 0F0h. Estos códigos secundarios son el valor devuelto en AH por las
funciones 0, 1, 10h y 11h de la BIOS, cuando éstas devuelven un carácter ASCII 0 ó 0E0h en AL.

Ha de tenerse en cuenta que la BIOS modifica en ocasiones el valor leído del buffer del teclado, aunque
en la siguiente tabla hay pautas para detectar esta circunstancia si fuera necesario. En primer lugar, cuando se
invoca a la BIOS con las funciones 0 y 1, éste se encarga de simular las teclas normales con las expandidas, así
como de ocultar las combinaciones exclusivamente expandidas. Aquellos códigos precedidos de (*) en la tabla
son ocultados por la BIOS (como si no se hubiera pulsado las teclas) al emplear las funciones 0 y 1, sacándolos
del buffer e ignorándolos. En concreto, estos códigos son almacenados con un código ASCII 0F0h en el buffer
del teclado. Lógicamente, para las funciones 10h y 11h sí existen, aunque la BIOS devuelve un 0 en AL (y no
un 0F0h). A los códigos precedidos por (#) les sucede lo mismo: sólo existen para las funciones 10h y 11h, al
emplear dichas funciones la BIOS devuelve en AL el valor 0 (el auténtico contenido del buffer en esta ocasión,
sin necesidad de transformarlo). Por último, los códigos precedidos por (@) existen tanto para las funciones 0 y
1 como para la 10h y la 11h: la ventaja de usar las dos últimas es que devuelven en AL el auténtico código
ASCII del buffer (0E0h), permitiendo diferenciar entre la pulsación de una tecla normal y su correspondiente
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

expandida.

En general, quien no desee complicarse la vida con este galimatías (debido a una evidente falta de
previsión en el diseño del primer teclado) puede limitarse a emplear las combinaciones normales (las no
marcadas con #, # ni *). Por otra parte, para emplear las combinaciones señaladas con (#), (@) o (*) hay que
asegurarse previamente de que la BIOS soporta teclado expandido (véase capítulo 7, apartado 5.3).

Para diferenciar las teclas repetidas, en la tabla siguiente, las teclas entrecomilladas se suponen
expandidas o, en su defecto, ubicadas en el teclado numérico. Por ejemplo: "5" es el 5 del teclado numérico,
"←" es el cursor izquierdo expandido y ← a secas el normal (esto es, la tecla 4 del teclado numérico con Num
Lock inactivo). Se emplea la notación anglosajona: Ctrl (Control), Alt (Alt o AltGr), Shift (Mays), Ins (Insert),
Del (Supr), Home (Inicio), End (Fin), PgUp (RePág), PgDn (AvPág).

┌──────────────────┬──────────────────┬──────────────────┬──────────────────┐
│ * 01 Alt ESC │ * 35 Alt - │ 58 Shift F5 │ 7F Alt 8 │
│ 03 Ctrl 2, NUL│ * 37 Alt "*" │ 59 Shift F6 │ 80 Alt 9 │
│ * 0E Alt ── │ 3B F1 │ 5A Shift F7 │ 81 Alt 0 │
│ 0F Shift Tab │ 3C F2 │ 5B Shift F8 │ 82 Alt ' │
│ 10 Alt Q │ 3D F3 │ 5C Shift F9 │ 83 Alt ¡ │
│ 11 Alt W │ 3E F4 │ 5D Shift F10 │ 84 Ctrl PgUp │
│ 12 Alt E │ 3F F5 │ 5E Ctrl F1 │ # 85 F11 │
│ 13 Alt R │ 40 F6 │ 5F Ctrl F2 │ # 86 F12 │
│ 14 Alt T │ 41 F7 │ 60 Ctrl F3 │ # 87 Shift F11 │
│ 15 Alt Y │ 42 F8 │ 61 Ctrl F4 │ # 88 Shift F12 │
│ 16 Alt U │ 43 F9 │ 62 Ctrl F5 │ # 89 Ctrl F11 │
│ 17 Alt I │ 44 F10 │ 63 Ctrl F6 │ # 8A Ctrl F12 │
│ 18 Alt O │ 47 Home │ 64 Ctrl F7 │ # 8B Alt F11 │
│ 19 Alt P │ @ 47 "Home" │ 65 Ctrl F8 │ # 8C Alt F12 │
│ * 1A Alt [ │ 48 ↑ │ 66 Ctrl F9 │ # 8D Ctrl "↑" │
│ * 1B Alt ] │ @ 48 "↑" │ 67 Ctrl F10 │ # 8E Ctrl "-" │
│ * 1C Alt Intro │ 49 PgUp │ 68 Alt F1 │ # 8F Ctrl "5" │
│ 1E Alt A │ @ 49 "PgUp" │ 69 Alt F2 │ # 90 Ctrl "+" │
│ 1F Alt S │ * 4A Alt "-" │ 6A Alt F3 │ # 91 Ctrl "↓" │
│ 20 Alt D │ 4B ← │ 6B Alt F4 │ # 92 Ctrl "Ins" │
│ 21 Alt F │ @ 4B "←" │ 6C Alt F5 │ # 93 Ctrl "Del" │
│ 22 Alt G │ * 4C "5" │ 6D Alt F6 │ # 94 Ctrl Tab │
│ 23 Alt H │ 4D → │ 6E Alt F7 │ # 95 Ctrl "/" │
│ 24 Alt J │ @ 4D "→" │ 6F Alt F8 │ # 96 Ctrl "*" │
│ 25 Alt K │ * 4E Alt "+" │ 70 Alt F9 │ # 97 Alt "Home" │
│ 26 Alt L │ 4F End │ 71 Alt F10 │ # 98 Alt "↑" │
│ * 27 Alt Ñ │ @ 4F "End" │ 72 Ctrl Ptr │ # 99 Alt "PgUp" │
│ * 28 Alt { │ 50 ↓ │ 73 Ctrl ← │ # 9B Alt "←" │
│ * 29 Alt \ │ @ 50 "↓" │ 74 Ctrl → │ # 9D Alt "→" │
│ * 2B Alt } │ 51 PgDn │ 75 Ctrl End │ # 9F Alt "End" │
│ 2C Alt Z │ @ 51 "PgDn" │ 76 Ctrl PgDn │ # A0 Alt "↓" │
│ 2D Alt X │ 52 Ins │ 77 Ctrl Home │ # A1 Alt "PgDn" │
│ 2E Alt C │ @ 52 "Ins" │ 78 Alt 1 │ # A2 Alt "Ins" │
│ 2F Alt V │ 53 Del │ 79 Alt 2 │ # A3 Alt "Del" │
│ 30 Alt B │ @ 53 "Del" │ 7A Alt 3 │ # A4 Alt "/" │
│ 31 Alt N │ 54 Shift F1 │ 7B Alt 4 │ # A5 Alt Tab │
│ 32 Alt M │ 55 Shift F2 │ 7C Alt 5 │ # A6 Alt "Intro"│
│ * 33 Alt , │ 56 Shift F3 │ 7D Alt 6 │ │
│ * 34 Alt . │ 57 Shift F4 │ 7E Alt 7 │ │
└──────────────────┴──────────────────┴──────────────────┴──────────────────┘
APÉNDICES 389

Excepciones:

Hay un par de teclas que sin tener un código ASCII 0, 0E0h ni 0F0h reciben un tratamiento especial por
parte de la BIOS, que provoca que el código secundario no sea el de rastreo acostumbrado: el Intro del teclado
numérico genera un código ASCII 0Dh, como cabría esperar, pero su código secundario es 0E0h; lo mismo
sucede con el '/' del teclado numérico. Las funciones 0 y 1 de la BIOS traducen este 0E0h al valor
correspondiente a la tecla Intro principal y al '-' del teclado principal (tecla que ocupa la posición del '/' en los
teclados norteamericanos), para compatibilizar con los teclados no expandidos.
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

Apéndice VI - TAMAÑOS Y TIEMPOS DE EJECUCIÓN DE LAS INSTRUCCIONES

En la tabla de las siguientes páginas se listan las instrucciones del ensamblador por orden alfabético,
indicándose el número de bytes consumidos al ser ensambladas así como los tiempos teóricos de ejecución en
8088, 286, 386 y 486. Estos tiempos son teóricos y no deberían ser utilizados para temporizaciones exactas. Por
otra parte son diferentes de un procesador a otro. Los tiempos se expresan en estados de máquina (1 MHz
equivale a 1.000.000 de estados o ciclos de reloj) estando la capacidad de ejecución de instrucciones
lógicamente en función de los MHz del equipo que se trate. Estos tiempos se aplican suponiendo que se
cumplen las siguientes hipótesis:

- La instrucción ya ha sido extraída de la memoria y decodificada.


- Los datos, si los hay, están alineados (a palabra o doble palabra).
- No hay estados de espera en la placa principal del ordenador.
- Nadie ha sustraido el control del bus a la CPU (el DMA no debe estar actuando y no han de producirse
ciclos de refresco de la memoria).
- No se produce ninguna interrupción o excepción durante la ejecución.

Evidentemente, es casi imposible que los tiempos teóricos sean los reales, teniendo en cuenta todos
estos factores. Cuanto menos potente es la máquina, mucho más lentos son los tiempos reales; por el contrario,
en ordenadores con caché y procesador avanzado ¡los tiempos efectivos pueden ser en ocasiones mejores que
los teóricos!. Por ejemplo, el 486 emplea ya la tecnología pipeline, lo que le permite simultanear la ejecución de
una instrucción con la decodificación de la siguiente y la lectura de memoria de la posterior así como almacenar
el resultado de la anterior. Esto, con las lógicas limitaciones de un procesador CISC, permite en la práctica
ejecutar un alto número de instrucciones en un solo ciclo (cada una de ellas, claro). Por tanto, para lo que sí
sirven las tablas es para decidir qué instrucciones emplear en ciertos procesos en que el tiempo de ejecución o la
memoria consumida son críticos, especialmente en las máquinas menos potentes. Como muestra de lo
sumamente teóricos que son estos tiempos, a continuación se listan dos rutinas con las que he probado
experimentalmente los tiempos de ejecución en diversos microprocesadores. Ambas rutinas constan de un bucle
que se repite cierto número de veces; mientras tanto las interrupciones están inhibidas, por lo que se
cronometran a mano:

Ciclos teóricos

8088 286 386 486

RutinaA: CLI
MOV AX,1000h
bucle: XOR CX,CX 3 2 2 1
repite: LOOP repite 17 ó 5 8+m ó 4 (m=2) 11+m (m=2) 6 ó 2
DEC AX 2 2 2 1
JNZ bucle 16 ó 4 7+m ó 3 (m=2) 7+m ó 3 (m=2) 3 ó 1
STI

RutinaB: CLI
XOR CX,CX
bucle1: MOV AX,BX 2 2 2 1
bucle2: MOV AX,BX 2 2 2 1
... . . . .
bucle16384: MOV AX,BX 2 2 2 .
DEC CX 2 2 2 1
JNZ fin 16 ó 4 7+m ó 3 7+m ó 3 (m=1) 3 ó 1
APÉNDICES 389

JMP bucle1 15 7+m 7+m (m=2) 3


fin: STI

Por ejemplo, la rutina B ejecuta 16384 instrucciones del tipo MOV AX,BX (2 ciclos cada una) así
como un decremento (2 ciclos) un salto que no se realiza -salvo al final del todo- (4 ciclos en 8088) y otro salto
absoluto (15 ciclos en 8088). Se emplea este rodeo ya que los saltos condicionales, como conocerá el lector,
sólo pueden desviar algo más de 100 bytes el flujo del programa (y este bucle ocupa nada menos que 32 Kb).
En total, 32787 ciclos que, repetidos 65536 veces, suponen 2.148.728.832 ciclos. Con un 8088 corriendo a 8
MHz (8 millones de ciclos) cabría esperar una demora de 268,59 segundos. Sin embargo, mi reloj de pulsera
dice que son nada menos que ¡1194!, unas 4,44 veces más de lo que los tiempos teóricos de Intel sugieren. De
hecho, esto implica que cada MOV tarda casi 9 ciclos reales en un 8088, y no 2. Sin embargo, en el caso de la
rutina A apenas hay diferencia entre el tiempo teórico y el real: el tiempo que emplea la instrucción LOOP es
bastante alto en comparación con lo que se tarda en traer dicha instrucción de la memoria, por lo que la
diferencia porcentual se reduce notablemente.

╔═══════════════════════╦═══════════════════════╗
║ RUTINA A ║ RUTINA B ║
╠═══════════╦═══════════╬═══════════╦═══════════╣
║ Teórico ║ Efectivo ║ Teórico ║ Efectivo ║
╔═══════════╬═══════════╬═══════════╬═══════════╬═══════════╣
║ 8088-4.77 ║ 956,71 ║ 1014,00 ║ 450,47 ║ 1946,00 ║
╠═══════════╬═══════════╬═══════════╬═══════════╬═══════════╣
║ V20-8 ║ 570,43 ║ 623,30 ║ 268,59 ║ 1194,00 ║
╠═══════════╬═══════════╬═══════════╬═══════════╬═══════════╣
║ 286-12 ║ 223,70 ║ 254,00 ║ 179,02 ║ 188,25 ║
╠═══════════╬═══════════╬═══════════╬═══════════╬═══════════╣
║ 386-25* ║ 139,59 ║ 135,20 ║ 85,93 ║ 93,50 ║
╠═══════════╬═══════════╬═══════════╬═══════════╬═══════════╣
║ 486-25* ║ 64,42 ║ 75,50 ║ 42,96 ║ 69,10 ║
╚═══════════╩═══════════╩═══════════╩═══════════╩═══════════╝

(*) El 386 carecía de memoria caché y el 486 sólo poseía los 8 Kb de caché incluidos en el chip.

Pautas para interpretar la tabla de las siguientes páginas.

El 8088, bastante menos potente que el 286, varía enormemente la velocidad de ejecución de las
instrucciones en función del modo de direccionamiento, hay que añadir además dos ciclos de reloj en este
procesador cuando se usa un prefijo de registro de segmento. En la siguiente tabla se indica el número de ciclos
de reloj adicionales que deben considerarse en el 8086/8088 para calcular la dirección de memoria efectiva (EA,
Efective Address) en la tabla de tiempos, según el tipo de direccionamiento:

Componentes Operandos valor EA


────────────────────────── ──────────────────────────────────────── ──────────
(a) Base o índice [BX], [BP], [SI], [DI] 5
(b) Desplazamiento desp 6
(c) Base + índice [BX+SI], [BX+DI] 7
[BP+SI], [BP+DI] 8
(d) Desplaz.+ base/índice [BX+desp], [BP+desp], [DI+desp], [SI+desp] 9
(e) Desplaz.+ base + índice [BX+SI+desp], [BX+DI+desp] 11
[BP+SI+desp], [BP+DI+desp] 12

Los datos entre paréntesis en el 8088 indican el tiempo empleado por las palabras de 16 bits, fuera del
paréntesis hacen referencia a 8 bits (los 8086 y superiores no son más lentos con datos de 16 que con los de 8
bits, siempre lógicamente que éstos estén en una posición de memoria par). Aunque el 286 y 386 no penalizan
tanto los modos de direccionamiento complejos, a los tiempos marcados con (#) hay que añadir un ciclo si en el
offset participan tres elementos (ej., BP+DI+desp). La letra «m» se refiere al número de bytes totales de la
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

siguiente instrucción que se va a ejecutar. Cuando aparecen dos opciones en las instrucciones de salto
condicional, el menor tiempo de ejecución se verifica cuando el salto no se realiza. Todas las instrucciones
específicas de 386 ocupan, bajo DOS, un byte más de lo que indican las tablas debido a que se utiliza un prefijo
para forzar el modo 32 bit en segmentos de 16. En los tiempos del 386, los datos entre paréntesis se aplican
cuando la CPU está en modo virtual 86; en general, los tiempos de ejecución corresponden al modo real (en
modo protegido, podrían variar).
APÉNDICES 389

Inst. Operandos Bytes Ciclos 8088 Ciclos 286 Ciclos 386 Ciclos 486
────── ─────────────────────────────────── ─────── ─────────────── ──────────── ──────────── ────────────
AAA 1 8 3 4 3
AAD 2 60 14 19 14
AAM 2 83 16 17 15
AAS 1 8 3 4 3
ADC registro, registro 2 3 2 2 1
ADC registro, memoria 2-4 9(13)+EA 7 # 6 2
ADC memoria, registro 2-4 16(24)+EA 7 # 7 3
ADC registro, inmediato 3-4 4 3 2 1
ADC memoria, inmediato 3-6 17(25)+EA 7 # 7 3
ADC acumulador, inmediato 2-3 4 3 2 1
ADD registro, registro 2 3 2 2 1
ADD registro, memoria 2-4 9(13)+EA 7 # 6 2
ADD memoria, registro 2-4 16(24)+EA 7 # 7 3
ADD registro, inmediato 3-4 4 3 2 1
ADD memoria, inmediato 3-6 17(25)+EA 7 # 7 3
ADD acumulador, inmediato 2-3 4 3 2 1
AND registro, registro 2 3 2 2 1
AND registro, memoria 2-4 9(13)+EA 7 # 6 2
AND memoria, registro 2-4 16(24)+EA 7 # 7 3
AND registro, inmediato 3-4 4 3 2 1
AND memoria, inmediato 3-6 17(25)+EA 7 # 7 3
AND acumulador, inmediato 2-3 4 3 2 1
BOUND registro16, memoria16 2-4 (no existe) 13 # 10 7
BOUND registro32, memoria32 2-6 (no existe) (no existe) 10 7
BSF registro16, registro16 3 (no existe) (no existe) 10+3*n 6-42
BSF registro16, memoria16 5-7 (no existe) (no existe) 10+3*n 7-43
BSF registro32, registro32 3 (no existe) (no existe) 10+3*n 6-42
BSF registro32, memoria32 5-7 (no existe) (no existe) 10+3*n 7-43
BSR registro16, registro16 3 (no existe) (no existe) 10+3*n 6-42
BSR registro16, memoria16 5-7 (no existe) (no existe) 10+3*n 7-43
BSR registro32, registro32 3 (no existe) (no existe) 10+3*n 6-42
BSR registro32, memoria32 5-7 (no existe) (no existe) 10+3*n 7-43
BT registro16, registro16 3 (no existe) (no existe) 3 3
BT memoria16, registro16 5-7 (no existe) (no existe) 12 8
BT registro32, registro32 3 (no existe) (no existe) 3 3
BT memoria32, registro32 5-7 (no existe) (no existe) 12 8
BT registro16, inmediato8 4 (no existe) (no existe) 3 3
BT memoria16, inmediato8 6-8 (no existe) (no existe) 6 3
BT registro32, inmediato8 4 (no existe) (no existe) 3 3
BT memoria32, inmediato8 6-8 (no existe) (no existe) 6 3
BTC registro16, registro16 3 (no existe) (no existe) 6 6
BTC memoria16, registro16 5-7 (no existe) (no existe) 13 13
BTC registro32, registro32 3 (no existe) (no existe) 6 6
BTC memoria32, registro32 5-7 (no existe) (no existe) 13 13
BTC registro16, inmediato8 4 (no existe) (no existe) 6 6
BTC memoria16, inmediato8 6-8 (no existe) (no existe) 8 8
BTC registro32, inmediato8 4 (no existe) (no existe) 6 6
BTC memoria32, inmediato8 6-8 (no existe) (no existe) 8 8
BTR registro16, registro16 3 (no existe) (no existe) 6 6
BTR memoria16, registro16 5-7 (no existe) (no existe) 13 13
BTR registro32, registro32 3 (no existe) (no existe) 6 6
BTR memoria32, registro32 5-7 (no existe) (no existe) 13 13
BTR registro16, inmediato8 4 (no existe) (no existe) 6 6
BTR memoria16, inmediato8 6-8 (no existe) (no existe) 8 8
BTR registro32, inmediato8 4 (no existe) (no existe) 6 6
BTR memoria32, inmediato8 6-8 (no existe) (no existe) 8 8
BTS registro16, registro16 3 (no existe) (no existe) 6 6
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

BTS memoria16, registro16 5-7 (no existe) (no existe) 13 13


BTS registro32, registro32 3 (no existe) (no existe) 6 6
BTS memoria32, registro32 5-7 (no existe) (no existe) 13 13
BTS registro16, inmediato8 4 (no existe) (no existe) 6 6
BTS memoria16, inmediato8 6-8 (no existe) (no existe) 8 8
BTS registro32, inmediato8 4 (no existe) (no existe) 6 6
BTS memoria32, inmediato8 6-8 (no existe) (no existe) 8 8
CALL procedimiento near (intrasegmento) 3 23 7+m 7+m 3
CALL procedimiento far (intersegmento) 5 36 13+m 17+m 18
CALL intrasegmento indirecto a memoria 2-4 29+EA 11+m 10+m 5
CALL intrasegmento indirecto a registro 2 24 7+m 7+m 5
CALL intersegmento indirecto a memoria 2-4 57+EA 16+m 22+m 17
CBW 1 2 2 3 3
CDQ 1 (no existe) (no existe) 2 3
CLC 1 2 2 2 2
CLD 1 2 2 2 2
CLI 1 2 3 3 5
CMC 1 2 2 2 2
CMP registro, registro 2 3 2 2 1
CMP registro, memoria 2-4 9(13)+EA 6 # 6 2
CMP memoria, registro 2-4 9(13)+EA 7 # 5 2
CMP registro, inmediato 3-4 4 3 2 1
CMP memoria, inmediato 3-6 10(14)+EA 6 # 5 2
CMP acumulador, inmediato 2-3 4 3 2 1
CMPS 1 22(30) 8 10 8
CMPS (REP) 1 9+22(30)*n 5+9*n 5+9*n 5 (CX=0) ó 7+7*n
CWD 1 5 2 2 3
CWDE 1 (no existe) (no existe) 3 3
DAA 1 4 3 4 2
DAS 1 4 3 4 2
DEC registro byte 2 3 2 2 1
DEC registro palabra 1 2 2 2 1
DEC memoria 2-4 15(23)+EA 7 # 6 3
DIV registro byte 2 80-90 14 14 16
DIV registro palabra 2 144-162 22 22 24
DIV registro32 2 (no existe) (no existe) 38 40
DIV byte de memoria 2-4 86-96+EA 17 # 17 16
DIV palabra de memoria 2-4 154-172+EA 25 # 25 24
DIV palabra32 de memoria 2-6 (no existe) (no existe) 41 40
ENTER constante16, 0 4 (no existe) 11 10 14
ENTER constante16, 1 4 (no existe) 15 12 17
ENTER constante16, nivel 4 (no existe) 12+4*(n-1) 15+4*(n-1) 17+3*n
Inst. Operandos Bytes Ciclos 8088 Ciclos 286 Ciclos 386 Ciclos 486
────── ─────────────────────────────────── ─────── ─────────────── ──────────── ──────────── ────────────
ESC inmediato, memoria 2-4 8(12)+EA 9-20 # (ver coproc.) (ver coproc.)
ESC inmediato, registro 2 2 2 (ver coproc.) (ver coproc.)
HLT 1 2 2 5 4
IDIV registro byte 2 101-112 17 19 19
IDIV registro palabra 2 165-185 25 27 27
IDIV registro32 2 (no existe) (no existe) 43 43
IDIV byte de memoria 2-4 107-118+EA 20 # 19 20
IDIV palabra de memoria 2-4 175-194+EA 28 # 27 28
IDIV palabra32 de memoria 2-6 (no existe) (no existe) 43 44
IMUL registro byte 2 80-98 13 9-14 13-18
IMUL registro palabra 2 128-154 21 9-22 13-26
IMUL registro32 2 (no existe) (no existe) 9-38 13-42
IMUL byte de memoria 2-4 86-104+EA 16 12-17 13-18
IMUL palabra de memoria 2-4 138-164+EA 24 # 12-25 13-26
IMUL palabra32 de memoria 2-6 (no existe) (no existe) 12-41 13-42
APÉNDICES 389

IMUL registro16 destino, constante 3-4 (no existe) 21 9-22 13-26


IMUL registro16 destino, memoria 5-7 (no existe) (no existe) 12-25 13-26
IMUL registro32 destino, memoria 5-7 (no existe) (no existe) 12-41 13-42
IMUL registro destino, registro, cte. 2-4 (no existe) 21 9-22 13-26
IMUL registro destino, memoria, cte. 3-4 (no existe) 24 # 12-25 13-26
IN acumulador, puerto fijo 2 10(14) 5 12(26) 14(27)
IN acumulador, DX 1 8(12) 5 13(27) 14(27)
INC registro byte 2 3 2 2 1
INC registro palabra 1 2 2 2 1
INC memoria 2-4 15(23)+EA 7 # 6 3
INS 1 (no existe) 5 15(29) 17(30)
INS (REP) 2 (no existe) 5+4*n 13(27)+6*n 16(29)+8*n
INT 3 1 52 23+m 33 26
INT inmediato 2 51 23+m 37 30
INTO 1 53 ó 4 24+m ó 3 35 ó 3 28 ó 3
IRET 1 32 17+m 22 15
JCXZ 2 18 ó 6 8+m ó 4 9+m ó 5 3 ó 1
JECXZ 2 (no existe) (no existe) 9+m ó 5 3 ó 1
JMP short 2 15 7+m 7+m 3
JMP near (intrasegmento) 3 15 7+m 7+m 3
JMP far (intersegmento) 5 15 11+m 12+m 17
JMP intrasegmento indirecto a memoria 2-4 18+EA 11+m # 10+m 5
JMP intrasegmento indirecto a registro 2 11 7+m 7+m 5
JMP intersegmento indirecto a memoria 2-4 24+EA 15+m 17+m 13
Jxxx inmediato8 2 16 ó 4 7+m ó 3 7+m ó 3 3 ó 1
Jxxx inmediato32 6 (no existe) (no existe) 7+m ó 3 3 ó 1
LAHF 1 4 2 2 3
LDS 2-4 24+EA 7 # 7 6
LEA 2-4 2+EA 3 # 2 1
LEAVE 1 (no existe) 5 4 5
LES 2-4 24+EA 7 # 7 6
LFS 2-4 (no existe) (no existe) 7 6
LGS 2-4 (no existe) (no existe) 7 6
LSS 2-4 (no existe) (no existe) 7 6
LOCK 1 2 0 0 1
LODS 1 12(16) 5 5 5
LODS (REP) 1 9+13(17)*n 5+4*n 5+6*n 5 (CX=0) ó 7+4*n
LOOP 2 17 ó 5 8+m ó 4 11+m 2 ó 6
LOOPE 2 18 ó 6 8+m ó 4 11+m 9 ó 6
LOOPNE 2 19 ó 5 8+m ó 4 11+m 9 ó 6
LOOPZ 2 18 ó 6 8+m ó 4 11+m 9 ó 6
LOOPNZ 2 19 ó 5 8+m ó 4 11+m 9 ó 6
MOV memoria, acumulador 3 10(14) 3 2 1
MOV acumulador, memoria 3 10(14) 5 4 1
MOV registro, registro 2 2 2 2 1
MOV registro, memoria 2-4 8(12)+EA 5 # 4 1
MOV memoria, registro 2-4 9(13)+EA 3 # 2 1
MOV registro, inmediato 2-3 4 2 2 1
MOV memoria, inmediato 3-6 10(14)+EA 3 # 2 1
MOV registro de segmento, registro 2 2 2 2 3
MOV registro, registro de segmento 2 2 2 2 3
MOV registro de segmento, memoria 2-4 8(12)+EA 5 # 5 9
MOV memoria, registro de segmento 2-4 9(13)+EA 3 # 2 3
MOVS 1 18(26) 5 7 7
MOVS (REP) 1 9+17(25)*n 5+4*n 5+4*n 5 (CX=0) ó 12+3*n
MOVSX registro16, registro8 3 (no existe) (no existe) 3 3
MOVSX registro16, memoria8 5-7 (no existe) (no existe) 6 3
MOVSX registro32, registro8 3 (no existe) (no existe) 3 3
MOVSX registro32, memoria8 5-7 (no existe) (no existe) 6 3
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

MOVSX registro32, registro16 3 (no existe) (no existe) 3 3


MOVSX registro32, memoria16 5-7 (no existe) (no existe) 6 3
MOVZX registro16, registro8 3 (no existe) (no existe) 3 3
MOVZX registro16, memoria8 5-7 (no existe) (no existe) 6 3
MOVZX registro32, registro8 3 (no existe) (no existe) 3 3
MOVZX registro32, memoria8 5-7 (no existe) (no existe) 6 3
MOVZX registro32, registro16 3 (no existe) (no existe) 3 3
MOVZX registro32, memoria16 5-7 (no existe) (no existe) 6 3
MUL registro byte 2 70-77 13 9-14 13
MUL registro palabra 2 118-133 21 9-22 13
MUL registro32 2 (no existe) (no existe) 9-38 13
MUL byte de memoria 2-4 76-83+EA 16 # 12-27 18
MUL palabra de memoria 2-4 128-143+EA 24 # 12-25 26
MUL palabra32 de memoria 2-6 (no existe) (no existe) 12-41 42
NEG registro 2 3 2 2 1
NEG memoria 2-4 16(24)+EA 7 # 6 3
NOP 1 3 3 3 1
NOT registro 2 3 2 2 1
NOT memoria 2-4 16(24)+EA 7 # 6 3
OR registro, registro 2 3 2 2 1
OR registro, memoria 2-4 9(13)+EA 7 # 6 3
OR memoria, registro 2-4 16(24)+EA 7 # 7 3
OR registro, inmediato 3-4 4 3 2 1
OR memoria, inmediato 3-6 17(25)+EA 7 # 7 3
OR acumulador, inmediato 2-3 4 3 2 1
Inst. Operandos Bytes Ciclos 8088 Ciclos 286 Ciclos 386 Ciclos 486
────── ─────────────────────────────────── ─────── ─────────────── ──────────── ──────────── ────────────
OUT puerto fijo, acumulador 2 10(14) 3 10(24) 16(29)
OUT DX, acumulador 1 8(12) 3 11(25) 16(29)
OUTS byte o palabra 1 (no existe) 5 14(28) 17(30)
OUTS (REP) 2 (no existe) 5+4*n 12(26)+5*n 17(31)+5*n
POP registro normal 1 12 5 4 4
POP registro de segmento 1 12 5 7 3
POP memoria 2-4 25+EA 5 # 5 6
POPA 1 (no existe) 19 24 9
POPAD 1 (no existe) (no existe) 24 9
POPF 1 12 5 5 9
POPFD 1 (no existe) (no existe) 5 9
PUSH registro 1 14 3 2 1
PUSH memoria 2-4 24+EA 5 # 5 4
PUSH inmediato 2-3 (no existe) 3 2 1
PUSHA 1 (no existe) 17 18 11
PUSHAD 1 (no existe) (no existe) 18 11
PUSHF 1 14 3 4 4
PUSHFD 1 (no existe) (no existe) 4 4
RCL registro,1 2 2 2 9 3
RCL registro,CL 2 8+4*bits 5 9 8-30
RCL registro, contador 3 (no existe) 5 9 8-30
RCL memoria, contador 3-6 (no existe) 8 # 10 9-31
RCL memoria,1 2-4 15(23)+EA 7 # 10 4
RCL memoria,CL 2-4 20(28)+EA+4*bits 8 # 10 9-31
RCR registro,1 2 2 2 9 3
RCR registro,CL 2 8+4*bits 5 9 8-30
RCR registro, contador 3 (no existe) 5 9 8-30
RCR memoria, contador 3-6 (no existe) 8 # 10 9-31
RCR memoria,1 2-4 15(23)+EA 7 # 10 4
RCR memoria,CL 2-4 20(28)+EA+4*bits 8 # 10 9-31
REP 1 2 0 0 0
REPE 1 2 0 0 0
APÉNDICES 389

REPNE 1 2 0 0 0
REPZ 1 2 0 0 0
REPNZ 1 2 0 0 0
RET intrasegmento 1 20 11+m 10+m 5
RET intrasegmento con SP+inmediato 3 24 11+m 10+m 5
RET intersegmento 1 32 15+m 18+m 13
RET intersegmento con SP+inmediato 3 31 15+m 18+m 14
ROL registro,1 2 2 2 3 3
ROL registro,CL 2 8+4*bits 5 3 3
ROL registro, contador 3 (no existe) 5 3 2
ROL memoria, contador 3-6 (no existe) 8 # 7 4
ROL memoria,1 2-4 15(23)+EA 7 # 7 4
ROL memoria,CL 2-4 20(28)+EA+4*bits 8 # 7 4
ROR registro,1 2 2 2 3 3
ROR registro,CL 2 8+4*bits 5 3 3
ROR registro, contador 3 (no existe) 5 3 2
ROR memoria, contador 3-6 (no existe) 8 # 7 4
ROR memoria,1 2-4 15(23)+EA 7 # 7 4
ROR memoria,CL 2-4 20(28)+EA+4*bits 8 # 7 4
SAHF 1 4 2 3 2
SAL registro,1 2 2 2 3 3
SAL registro,CL 2 8+4*bits 5 3 3
SAL registro, contador 3 (no existe) 5 3 2
SAL memoria, contador 3-6 (no existe) 8 # 7 4
SAL memoria,1 2-4 15(23)+EA 7 # 7 4
SAL memoria,CL 2-4 20(28)+EA+4*bits 8 # 7 4
SAR registro,1 2 2 2 3 3
SAR registro,CL 2 8+4*bits 5 3 3
SAR registro, contador 3 (no existe) 5 3 2
SAR memoria, contador 3-6 (no existe) 8 # 7 4
SAR memoria,1 2-4 15(23)+EA 7 # 7 4
SAR memoria,CL 2-4 20(28)+EA+4*bits 8 # 7 4
SBB registro, registro 2 3 2 2 1
SBB registro, memoria 2-4 9(13)+EA 7 # 6 2
SBB memoria, registro 2-4 16(24)+EA 7 # 7 3
SBB registro, inmediato 3-4 4 3 2 1
SBB memoria, inmediato 3-6 17(25)+EA 7 # 7 3
SBB acumulador, inmediato 2-3 4 3 2 1
SCAS 1 15(19) 7 7 6
SCAS (REP) 1 9+15(19)*n 5+8*n 5+8*n 5 (CX=0) ó 7+5*n
SETcc registro8 2 (no existe) (no existe) 4 4
SETcc memoria8 4-6 (no existe) (no existe) 5 3
SHL registro,1 2 2 2 3 3
SHL registro,CL 2 8+4*bits 5 3 3
SHL registro, contador 3 (no existe) 5 3 2
SHL memoria, contador 3-6 (no existe) 8 # 7 4
SHL memoria,1 2-4 15(23)+EA 7 # 7 4
SHL memoria,CL 2-4 20(28)+EA+4*bits 8 # 7 4
SHLD registro16, registro16, inmediato8 4 (no existe) (no existe) 3 2
SHLD memoria16, registro16, inmediato8 6-8 (no existe) (no existe) 7 3
SHLD registro32, registro32, inmediato8 4 (no existe) (no existe) 3 2
SHLD memoria32, registro32, inmediato8 6-8 (no existe) (no existe) 7 3
SHLD registro16, registro16, CL 3 (no existe) (no existe) 3 2
SHLD memoria16, registro16, CL 5-7 (no existe) (no existe) 7 3
SHLD registro32, registro32, CL 3 (no existe) (no existe) 3 2
SHLD memoria32, registro32, CL 5-7 (no existe) (no existe) 7 3
SHR registro,1 2 2 2 3 3
SHR registro,CL 2 8+4*bits 5 3 3
SHR registro, contador 3 (no existe) 5 3 2
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

SHR memoria, contador 3-6 (no existe) 8 # 7 4


SHR memoria,1 2-4 15(23)+EA 7 # 7 4
SHR memoria,CL 2-4 20(28)+EA+4*bits 8 # 7 4
SHRD registro16, registro16, inmediato8 4 (no existe) (no existe) 3 2
SHRD memoria16, registro16, inmediato8 6-8 (no existe) (no existe) 7 3
SHRD registro32, registro32, inmediato8 4 (no existe) (no existe) 3 2
SHRD memoria32, registro32, inmediato8 6-8 (no existe) (no existe) 7 3
SHRD registro16, registro16, CL 3 (no existe) (no existe) 3 2
Inst. Operandos Bytes Ciclos 8088 Ciclos 286 Ciclos 386 Ciclos 486
────── ─────────────────────────────────── ─────── ─────────────── ──────────── ──────────── ────────────
SHRD memoria16, registro16, CL 5-7 (no existe) (no existe) 7 3
SHRD registro32, registro32, CL 3 (no existe) (no existe) 3 2
SHRD memoria32, registro32, CL 5-7 (no existe) (no existe) 7 3
STC 1 2 2 2 2
STD 1 2 2 2 2
STI 1 2 2 3 5
STOS 1 11(15) 3 4 5
STOS (REP) 1 9+10(14)*n 4+3*n 5+5*n 5 (CX=0) ó 7+4*n
SUB registro, registro 2 3 2 2 1
SUB registro, memoria 2-4 9(13)+EA 7 # 6 2
SUB memoria, registro 2-4 16(24)+EA 7 # 7 3
SUB registro, inmediato 3-4 4 3 2 1
SUB memoria, inmediato 3-6 17(25)+EA 7 # 7 3
SUB acumulador, inmediato 2-3 4 3 2 1
TEST registro, registro 2 3 2 2 1
TEST registro, memoria 2-4 9(13)+EA 6 # 5 2
TEST memoria, registro 2-4 16(24)+EA 6 # 5 2
TEST registro, inmediato 3-4 4 3 2 1
TEST memoria, inmediato 3-6 17(25)+EA 6 # 5 2
TEST acumulador, inmediato 2-3 4 3 2 1
WAIT 1 3 3 6 1-3
XCHG AX,registro16 1 3 3 3 3
XCHG registro, registro 2 4 3 3 3
XCHG memoria, registro 2-4 17(25)+EA 5 # 5 5
XLAT 1 11 5 5 4
XOR registro, registro 2 3 2 2 1
XOR registro, memoria 2-4 9(13)+EA 7 # 6 2
XOR memoria, registro 2-4 16(24)+EA 7 # 7 3
XOR registro, inmediato 3-4 4 3 2 1
XOR memoria, inmediato 3-6 17(25)+EA 7 # 7 3
XOR acumulador, inmediato 2-3 4 3 2 1
APÉNDICES 389

Apéndice VII - SEÑALES DEL SLOT DE EXPANSIÓN ISA

El slot de expansión del XT, de 8 bits, consta de 62 terminales en un conector hembra, 31 por cada cara.
La cara A es la de los componentes; por la B sólo hay pistas. Viendo las tarjetas por arriba (por la cara de
componentes) y con los conectores exteriores a la derecha, la numeración comienza de derecha a izquierda. En
los AT el slot de 16 bits consta de 36 terminales más, distribuidos en grupos de 18 en dos nuevas caras (C y D).
La mayoría de las máquinas AT poseen slots de 8 y 16 bits, aunque lo ideal sería que todos fueran de 16 (en los
de 16 bits se pueden insertar también tarjetas de 8 bits, dejando la otra mitad al aire).

Las señales en la parte de 8 bits son idénticas en XT y AT, si se exceptúa la línea IRQ2 que en los AT
es realmente IRQ9 (IRQ2 es empleada en la placa base para conectar en cascada el segundo controlador de
interrupciones; por compatibilidad con los XT, cuando se produce una IRQ9 -normalmente una INT 71h- se
invoca por software la INT 0Ah).

En el siguiente esquema, las líneas activas en alto van precedidas de un signo (+); las activas en estado
lógico bajo (-). Los símbolos I (Input) y O (Output) indican si las líneas son de entrada, salida o bidireccionales.
┌───────────────────────────┐
GND ──┤ B1 I A1 ├── -I/O CH CK
RESET DRV ──┤ B2 O I/O A2 ├── +D7
+5v ──┤ B3 I/O A3 ├── +D6
+IRQ2/+IRQ9 ──┤ B4 I I/O A4 ├── +D5
-5v ──┤ B5 I/O A5 ├── +D4
+DRQ2 ──┤ B6 I I/O A6 ├── +D3
-12v ──┤ B7 I/O A7 ├── +D2
RESERVADO (0 WS) ──┤ B8 I I/O A8 ├── +D1
+12v ──┤ B9 I/O A9 ├── +D0
GND ──┤ B10 I A10 ├── +I/O CH RDY
-MEMW ──┤ B11 O O A11 ├── +AEN
-MEMR ──┤ B12 O I/O A12 ├── +A19
-IOW ──┤ B13 O I/O A13 ├── +A18
-IOR ──┤ B14 O I/O A14 ├── +A17
-DACK3 ──┤ B15 O I/O A15 ├── +A16
+DRQ3 ──┤ B16 I I/O A16 ├── +A15
-DACK1 ──┤ B17 O I/O A17 ├── +A14
+DRQ1 ──┤ B18 I I/O A18 ├── +A13
-MEMREF ──┤ B19 I/O I/O A19 ├── +A12
CLOCK ──┤ B20 O I/O A20 ├── +A11
+IRQ7 ──┤ B21 I I/O A21 ├── +A10
+IRQ6 ──┤ B22 I I/O A22 ├── +A9
+IRQ5 ──┤ B23 I I/O A23 ├── +A8
+IRQ4 ──┤ B24 I I/O A24 ├── +A7
+IRQ3 ──┤ B25 I I/O A25 ├── +A6
-DACK2 ──┤ B26 O I/O A26 ├── +A5
+TC ──┤ B27 O I/O A27 ├── +A4
+ALE ──┤ B28 O I/O A28 ├── +A3
+5v ──┤ B29 I/O A29 ├── +A2
+OSC ──┤ B30 O I/O A30 ├── +A1
GND ──┤ B31 I/O A31 ├── +A0
├───────────────────────────┤
-MEM CS 16 ──┤ D1 I I/O C1 ├── +BHE
-I/O CS 16 ──┤ D2 I I/O C2 ├── +A23
+IRQ10 ──┤ D3 I I/O C3 ├── +A22
+IRQ11 ──┤ D4 I I/O C4 ├── +A21
+IRQ12 ──┤ D5 I I/O C5 ├── +A20
+IRQ15 ──┤ D6 I I/O C6 ├── +A19
+IRQ14 ──┤ D7 I I/O C7 ├── +A18
-DACK0 ──┤ D8 O O C8 ├── +A17
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

+DRQ0 ──┤ D9 I O C9 ├── -MEMR


-DACK5 ──┤ D10 O I/O C10 ├── -MEMW
+DRQ5 ──┤ D11 I I/O C11 ├── +D8
-DACK6 ──┤ D12 O I/O C12 ├── +D9
+DRQ6 ──┤ D13 I I/O C13 ├── +D10
-DACK7 ──┤ D14 O I/O C14 ├── +D11
+DRQ7 ──┤ D15 I I/O C15 ├── +D12
+5v ──┤ D16 I/O C16 ├── +D13
-MASTER ──┤ D17 I I/O C17 ├── +D14
GND ──┤ D18 I/O C18 ├── +D15
└───────────────────────────┘

El slot de expansión de los PC contiene básicamente las principales señales del 8086 demultiplexadas,
así como otras de interrupciones, DMA, control de E/S, etc. Las señales presentes en el slot de expansión de 8
bits son:

OSC:(Oscilator) Señal de reloj de casi 70 ns (14,31818 MHz) que está la mitad del período en estado alto y la
otra mitad en estado bajo.
ALE:(Address Latch Enable) Indica en su flanco de bajada que el latch de direcciones se ha cargado con una
dirección válida procedente del microprocesador.
TC: (Terminal Count) Indica el final de la cuenta en algún canal de DMA.
DRQ1-DRQ3:(DMA Request) Líneas asíncronas de petición de DMA (1 mayor prioridad, 3 menor). Esta línea
debe activarse hasta que DACK (activo a nivel bajo) suba.
DACK1-DACK3:(DMA Acknowledge) Indica que ha sido atendida la petición de DMA y que debe bajarse el
correspondiente DRQ.
IRQ2-IRQ7:(Interrupt request) Indica una petición de interrupción (2 mayor prioridad, 7 menor). La señal debe
mantenerse activa hasta que la interrupción acabe de ser procesada.
IOR:(Input/Output Read) Señala al dispositivo de E/S que se va a leer el bus de datos; esta línea la controla la
CPU o el DMA.
IOW:(Input/Output Write) Señala al dispositivo de E/S que se va a escribir en el bus de datos; esta línea la
controla también la CPU o el DMA.
MEMR:(Memory Read) Indica que se va a efectuar una lectura de la memoria en la dirección contenida en el
bus de direcciones. La activa la CPU o el DMA.
MEMW:(Memory Write) Indica que se va a efectuar una escritura en memoria en la dirección contenida en el
bus de direcciones. La activa la CPU o el DMA.
RESET DRV:(Reset drive) Avisa de que el sistema está en proceso de reinicialización, para que todos los
dispositivos conectados se inicialicen. Se activa en el flanco de bajada de la señal del
reloj.
A0-A19:(Address) Bus de direcciones común a la memoria y a la E/S, controlado por la CPU o el DMA.
D0-D7:(Data) Bus de datos que conecta el microprocesador y los demás componentes.
AEN:(Address Enable) Valida la dirección almacenada en A0-A19. Esto permite inhibir la CPU y los demás
dispositivos, pudiendo el DMA tomar el control. Los periféricos deben decodificar la
dirección comprobando que AEN está en estado bajo.
I/O CH RDY:(I/O Channel Ready) Esta línea se pone momentáneamente en estado bajo por los periféricos
lentos (no durante más de 10 ciclos de reloj) cuando detectan una dirección válida en
una operación de E/S, con objeto de poder sincronizarse con la CPU, que genera
estados de espera.
I/O CH CK:(I/O Channel Check) Indica si se ha producido un error de paridad en la memoria o en los
dispositivos E/S.

En los AT, las líneas adicionales completan fundamentalmente la nueva longitud de los buses de datos
y direcciones, permitiendo acceder también al resto del nuevo hardware:

DRQ y DACK:Nuevas líneas de petición/reconocimiento de DMA para los canales 5, 6 y 7, así como el 0
(realmente el 4) que en los XT no estaba disponible al ser empleado por el refresco de
memoria.
APÉNDICES 389

IRQ:Nuevos niveles de interrupción: 10, 11, 12, 13, 14 y 15. IRQ8 es interna a la placa base y no está presente
en el slot; IRQ9 se utiliza para emular IRQ2.
I/O CS 16:Indica un acceso de 16 bits en los puertos E/S.
MEM CS 16:Indica un acceso de 16 bits en la memoria.
D8-D15:Parte alta del bus de datos.
A17-A23:Parte alta del bus de direcciones.
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
APÉNDICES 389

Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO

Lógicamente, las funciones del DOS y la BIOS podrían llenar varios libros de mayor tamaño que éste. Por
ello, se listarán exclusivamente las funciones que se utilizan en los programas ejemplo y en las explicaciones. Toda
la información ha sido obtenida del INTERRUPT.LST, en su mayoría de la versión 39 del mismo (ver bibliografía),
en este libro se recoge menos de un 8% de las líneas de dicho fichero. Todas las funciones recogidas en el
INTERRUPT tienen el siguiente formato:

--------V-1000-------------------------------
INT 10 - VIDEO - SET VIDEO MODE
AH = 00h
AL = mode (see below)
Return: AL = video mode flag (Phoenix BIOS)
20h mode > 7
30h modes 0-5 and 7
3Fh mode 6
AL = CRT controller mode byte (Phoenix 386 BIOS v1.10)

Al principio de la función, en la línea de guiones, suele haber uno, dos o tres números hexadecimales de 8
bits (pegados unos a otros) que indican, por orden de aparición: número de la interrupción, valor de llamada en AH,
valor de llamada en AL. En el ejemplo superior se trata de la INT 10h, a la que hay que llamar con AH=0. Si fueran
necesarios más valores en otros registros normalmente se indicará de manera explícita en la cabecera. Esta cabecera
es útil, ya que un fichero de varios megas no es operativo consultarlo con TYPE (y muchos editores de texto no
pueden cargarlo): lo normal es emplear una de esas pequeñas utilidades para ver ficheros de texto, que permiten
moverse arriba y abajo con las teclas de los cursores (como README.COM que acompaña a los compiladores de
Borland): esos programas suelen tener opciones de búsqueda de texto; de esta manera, buscando la cadena "-210A"
se podría encontrar rápidamente la función 0Ah del DOS (INT 21h).

-------- P - printer enhancements, p - power management,

!---FLAGS------------------------------------------------- Q - DESQview/TopView and Quarterdeck programs,

The use of -> instead of = signifies that the indicated register or register R - remote control/file access, r - runtime support,

pair contains a pointer to the specified item, rather than the item itself. S - serial I/O, s - sound/speech,

One or more letters may follow the interrupt number; they have the following T - DOS-based task switchers/multitaskers, t - TSR libraries

meanings: U - undocumented function, u - partially documented function, U - resident utilities, u - emulators,

P - available only in protected mode, R - available only in real or V86 mode, V - video, v - virus/antivirus,

C - callout or callback (usually hooked rather than called), W - MS Windows, X - expansion bus BIOSes,

O - obsolete (no longer present in current versions) y - security, * - reserved (and not otherwise classified)

-------- --------
!---CATEGORIES-------------------------------------------- C-00------------------------------------------------------
The ninth column of the divider line preceding an entry usually contains a INT 00 - CPU-generated - DIVIDE ERROR
classification code (the entry has not been classified if that character is Desc: generated if the divisor of a DIV or IDIV instruction is zero or the

a dash). The codes currently in use are: quotient overflows the result register; DX and AX will be

A - applications, a - access software (screen readers, etc), unchanged.

B - BIOS, b - vendor-specific BIOS extensions, Notes: on an 8086/8088, the return address points to the following

C - CPU-generated, c - caches/spoolers, instruction

D - DOS kernel, d - disk I/O enhancements, on an 80286+, the return address points to the divide instruction

E - DOS extenders, e - electronic mail, F - FAX, an 8086/8088 will generate this interrupt if the result of a division

f - file manipulation, G - debuggers/debugging tools, is 80h (byte) or 8000h (word)

H - hardware, h - vendor-specific hardware, SeeAlso: INT 04

I - IBM workstation/terminal emulators, i - system info/monitoring --------


J - Japanese, j - joke programs, C-01------------------------------------------------------
K - keyboard enhancers, k - file compression, INT 01 - CPU-generated - SINGLE STEP
l - shells/command interpreters, Desc: generated after each instruction if TF (trap flag) is set; TF is

M - mouse/pointing device, m - memory management, cleared on invoking the single-step interrupt handler

N - network, n - non-traditional input devices, Notes: interrupts are prioritized such that external interrupts are invoked

O - other operating systems, after the INT 01 pushes CS:IP/FLAGS and clears TF, but before the
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

first instruction of the handler executes BIOSes

used by debuggers for single-instruction execution tracing, such as SeeAlso: INT 10/AH=12h/BL=20h

MS-DOS DEBUG's T command --------


SeeAlso: INT 03 C-05------------------------------------------------------
-------- INT 05 - CPU-generated (80186+) - BOUND RANGE EXCEEDED
H-02------------------------------------------------------ Desc: generated by BOUND instruction when the value to be tested is less

INT 02 - external hardware - NON-MASKABLE INTERRUPT than

Desc: generated by the CPU when the input to the NMI pin is asserted the indicated lower bound or greater than the indicated upper

Notes: return address points to start of interrupted instruction on 80286+ bound.

on the 80286+, further NMIs are disabled until the next IRET Note: returning from this interrupt re-executes the failing BOUND

instruction, but one additional NMI is remembered by the hardware instruction

and will be serviced after the IRET instruction reenables NMIs --------
maskable interrupts may interrupt the NMI handler if interrupts are C-06------------------------------------------------------
enabled INT 06 - CPU-generated (80286+) - INVALID OPCODE
although the Intel documentation states that this interrupt is Desc: this interrupt is generated when the CPU attempts to execute an

typically used for power-failure procedures, it has many other uses invalid opcode (most protected-mode instructions are considered

on IBM-compatible machines: invalid in real mode) or a BOUND, LDS, LES, or LIDT instruction

Memory parity error: all except Jr, CONV, and some machines which specifies a register rather than a memory address

without memory parity Notes: return address points to beginning of invalid instruction

Breakout switch on hardware debuggers with proper programming, this interrupt may be used to emulate

Coprocessor interrupt: all except Jr and CONV instructions which do not exist; many 386 BIOSes emulate the 80286

Keyboard interrupt: Jr, CONV undocumented LOADALL instruction which was removed from the 80386+

I/O channel check: CONV, PS50+ generated by the 80386+ when the LOCK prefix is used with

Disk-controller power-on request: CONV instructions

System suspend: CONV other than BTS, BTR, BTC, XCHG, XADD (486), CMPXCHG (486), INC,

Real-time clock: CONV DEC,

System watch-dog timer, time-out interrupt: PS50+ NOT, NEG, ADD, ADC, SUB, SBB, AND, OR, or XOR, or any instruction

DMA timer time-out interrupt: PS50+ not accessing memory.

Low battery: HP 95LX SeeAlso: INT 0C"CPU",INT 0D"CPU"

Module pulled: HP 95LX --------


-------- C-07------------------------------------------------------
C-03------------------------------------------------------ INT 07 - CPU-generated (80286+) - PROCESSOR EXTENSION NOT
INT 03 - CPU-generated - BREAKPOINT AVAILABLE
Desc: generated by the one-byte breakpoint instruction (opcode CCh) Desc: this interrupt is automatically called if a coprocessor instruction

Notes: used by debuggers to implement breakpoints, such as MS-DOS DEBUG's G is

command encountered when no coprocessor is installed

also used by Turbo Pascal versions 1,2,3 when {$U+} specified Note: can be used to emulate a numeric coprocessor in software

return address points to byte following the breakpoint instruction SeeAlso: INT 09"MATH UNIT PROTECTION"

SeeAlso: INT 01 --------


-------- H-08------------------------------------------------------
C-04------------------------------------------------------ INT 08 - IRQ0 - SYSTEM TIMER
INT 04 - CPU-generated - INTO DETECTED OVERFLOW Desc: generated 18.2 times per second by channel 0 of the 8254 system

Desc: the INTO instruction will generate this interrupt if OF (Overflow timer,

Flag) this interrupt is used to keep the time-of-day clock updated

is set; otherwise, INTO is effectively a NOP Notes: programs which need to be invoked regularly should use INT 1C unless

Note: may be used for convenient overflow testing (to prevent errors from they need to reprogram the timer while still keeping the

propagating) instead of JO or a JNO/JMP combination time-of-day

SeeAlso: INT 00 clock running at the proper rate

-------- default handler is at F000h:FEA5h in IBM PC and 100%-compatible

B-05------------------------------------------------------ BIOSes

INT 05 - PRINT SCREEN may be masked by setting bit 0 on I/O port 21h

Desc: dump the current text screen to the first printer SeeAlso: INT 1C,INT 4A,INT 50"DESQview",INT 58"DoubleDOS",INT 70,INT 78"GO32"

Notes: normally invoked by the INT 09 handler when PrtSc key is pressed, but SeeAlso: INT D8"Screen Thief"

may be invoked directly by applications --------


byte at 0050h:0000h contains status used by default handler C-08------------------------------------------------------
00h not active INT 08 - CPU-generated (80286+) - DOUBLE EXCEPTION DETECTED
01h PrtSc in progress Desc: called when multiple exceptions occur on one instruction, or an

FFh last PrtSc encountered error exception occurs in an exception handler

default handler is at F000h:FF54h in IBM PC and 100%-compatible Notes: called in protected mode if an interrupt above the defined limit of
APÉNDICES 389

the interrupt vector table occurs 14h T 44h F10 74h ExSel

return address points at beginning of instruction with errors or the 15h Y 45h NumLock 75h --

beginning of the instruction which was about to execute when the 16h U 46h ScrollLock 76h Clear

external interrupt caused the exception 17h I 47h Home

if an exception occurs in the double fault handler, the CPU goes into 18h O 48h UpArrow

SHUTDOWN mode (which circuitry in the PC/AT converts to a reset); 19h P 49h PgUp

this "triple fault" is a faster way of returning to real mode on 1Ah [ { 4Ah Grey-

many 80286 machines than the standard keyboard controller reset 1Bh ] } 4Bh LeftArrow

-------- 1Ch Enter 4Ch Keypad 5

H-09------------------------------------------------------ 1Dh Ctrl 4Dh RightArrow

INT 09 - IRQ1 - KEYBOARD DATA READY 1Eh A 4Eh Grey+

Desc: this interrupt is generated when data is received from the keyboard. 1Fh S 4Fh End

This is normally a scan code (from either a keypress *or* a key 20h D 50h DownArrow E0h prefix code

release), but may also be an ACK or NAK of a command on AT-class 21h F 51h PgDn E1h prefix code

keyboards. 22h G 52h Ins FAh ACK

Notes: this IRQ may be masked by setting bit 1 on I/O port 21h 23h H 53h Del FEh RESEND

if the BIOS supports an enhanced (101/102-key) keyboard, it calls 24h J 54h SysRq FFh kbd error/buffer full

INT 15/AH=4Fh after reading the scan code from the keyboard and 25h K

before further processing; all further processing uses the scan 26h L 56h left \| (102-key)

code returned from INT 15/AH=4Fh 27h ; : 57h F11

the default interrupt handler is at F000h:E987h in 100%-compatible 28h ' " 58h F12

BIOSes 29h ` ~

the interrupt handler performs the following actions for certain 2Ah Left Shift 5Ah PA1

special keystrokes: 2Bh \ | 5Bh F13

Ctrl-Break clear keyboard buffer, place word 0000h in buffer, 2Ch Z 5Ch F14

invoke INT 1B, and set flag at 0040h:0071h 2Dh X 5Dh F15

SysRq invoke INT 15/AH=85h 2Eh C

Ctrl-Numlock place system in a tight wait loop until next INT 09 2Fh V

Ctrl-Alt-Del jump to BIOS startup code (either F000h:FFF0h or the 30h B

destination of the jump at that address) Note: scan codes 56h-E1h are only available on the extended (101/102-key)

Shift-PrtSc invoke INT 05 keyboard and Host Connected (122-key) keyboard; scan codes 5Ah-76h

DRDOS hooks this interrupt to control the cursor shape (underscore/ are only available on the 122-key keyboard

half block) for overwrite/insert mode --------


DR Multiuser DOS hooks this interrupt for cursor shape control and to C-09------------------------------------------------------
control whether Ctrl-Alt-Del reboots the current session or the INT 09 - CPU-generated (80286,80386) - PROCESSOR EXTENSION
entire system PROTECTION ERROR
SeeAlso: INT 05,INT 0B"HP 95LX",INT 15/AH=4Fh,INT 15/AH=85h,INT 16,INT 1B Desc: called if the coprocessor attempts to access memory outside a segment

SeeAlso: INT 2F/AX=A901h,INT 51"DESQview",INT 59"DoubleDOS",INT 79"GO32" boundary; it may occur at an arbitrary time after the coprocessor

instruction was issued

Values for scan code: Note: until the condition is cleared or the coprocessor is reset, the only

01h Esc 31h N coprocessor instruction which may be used is FNINIT; WAIT or other

02h 1 ! 32h M coprocessor instructions will cause a deadlock because the

03h 2 @ 33h , < 63h F16 coprocessor is still busy waiting for data

04h 3 # 34h . > 64h F17 SeeAlso: INT 07"CPU"

05h 4 $ 35h / ? 65h F18 --------


06h 5 % 36h Right Shift 66h F19 H-0A------------------------------------------------------
07h 6 ^ 37h Grey* 67h F20 INT 0A - IRQ2 - LPT2 (PC), VERTICAL RETRACE INTERRUPT (EGA,VGA)
08h 7 & 38h Alt 68h F21 Notes: the TOPS and PCnet adapters use this interrupt request line by

09h 8 * 39h SpaceBar 69h F22 default

0Ah 9 ( 3Ah CapsLock 6Ah F23 DOS 3.2 revectors IRQ2 to a stack-switching routine

0Bh 0 ) 3Bh F1 6Bh F24 on ATs and above, the physical data line for IRQ2 is labeled IRQ9 and

0Ch - _ 3Ch F2 6Ch -- connects to the slave 8259. The BIOS redirects the interrupt for

0Dh = + 3Dh F3 6Dh EraseEOF IRQ9 back here.

0Eh Backspace 3Eh F4 under DESQview, only the INT 15h vector and BASIC segment address

0Fh Tab 3Fh F5 6Fh Copy/Play (the

10h Q 40h F6 word at 0000h:0510h) may be assumed to be valid for the handler's

11h W 41h F7 process

12h E 42h F8 72h CrSel many VGA boards do not implement the vertical retrace interrupt,

13h R 43h F9 including the IBM VGA Adapter where the traces are either cut or
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

removed SeeAlso: INT 0D"LPT2",INT 57"DESQview",INT 5F"DoubleDOS",INT 7F"GO32"

SeeAlso: INT 52"DESQview",INT 5A"DoubleDOS",INT 71,INT 7A"GO32" --------


-------- V-1000----------------------------------------------------
H-0B------------------------------------------------------ INT 10 - VIDEO - SET VIDEO MODE
INT 0B - IRQ3 - SERIAL COMMUNICATIONS (COM2) AH = 00h

Desc: automatically asserted by the UART when COM2 needs attention, if the AL = mode (see below)

UART has been programmed to generate interrupts Return: AL = video mode flag (Phoenix BIOS)

Notes: the TOPS and PCnet adapters use this interrupt request line as an 20h mode > 7

alternate 30h modes 0-5 and 7

on PS/2s, COM2 through COM8 share this interrupt; on many PC's, COM4 3Fh mode 6

shares this interrupt AL = CRT controller mode byte (Phoenix 386 BIOS v1.10)

may be masked by setting bit 3 on I/O port 21h Desc: specify the display mode for the currently active display adapter

SeeAlso: INT 0C"COM1",INT 53"DESQview",INT 5B"DoubleDOS",INT 7B"GO32" Notes: IBM standard modes do not clear the screen if the high bit of AL is

-------- set

H-0C------------------------------------------------------ (EGA or higher only)

INT 0C - IRQ4 - SERIAL COMMUNICATIONS (COM1) Values for video mode:

Desc: automatically asserted by the UART when COM1 needs attention, if the text/ text pixel pixel colors disply scrn system

UART has been programmed to generate interrupts grph resol box resoltn pages addr

BUG: this vector is modified but not restored by Direct Access v4.0, and 00h = T 40x25 8x8 16gray 8 B800 CGA,PCjr,Tandy

may be left dangling by other programs written with the same = T 40x25 8x14 16gray 8 B800 EGA

version = T 40x25 8x16 16 8 B800 MCGA

of compiled BASIC = T 40x25 9x16 16 8 B800 VGA

Notes: on many PC's, COM3 shares this interrupt 01h = T 40x25 8x8 16 8 B800 CGA,PCjr,Tandy

may be masked by setting bit 4 on I/O port 21h = T 40x25 8x14 16 8 B800 EGA

SeeAlso: INT 0B"COM2",INT 54"DESQview",INT 5C"DoubleDOS",INT 7C"GO32" = T 40x25 8x16 16 8 B800 MCGA

-------- = T 40x25 9x16 16 8 B800 VGA

H-0D------------------------------------------------------ 02h = T 80x25 8x8 16gray 4 B800 CGA,PCjr,Tandy

INT 0D - IRQ5 - FIXED DISK (PC,XT), LPT2 (AT), reserved (PS/2) = T 80x25 8x14 16gray 4 B800 EGA

Notes: under DESQview, only the INT 15h vector and BASIC segment address = T 80x25 8x16 16 4 B800 MCGA

(the = T 80x25 9x16 16 4 B800 VGA

word at 0000h:0510h) may be assumed to be valid for the handler's 03h = T 80x25 8x8 16 4 B800 CGA,PCjr,Tandy

process = T 80x25 8x14 16 4 B800 EGA

the Tandy 1000, 1000A, and 1000HD use IRQ2 for the hard disk; the = T 80x25 8x16 16 4 B800 MCGA

1000EX, HX, RLX, RLX-HD, RLX-B, RLX-HD-B use IRQ5 instead; the = T 80x25 9x16 16 4 B800 VGA

1000RL, RL-HD, SL, SL/2, TL, TL/2, and TL/3 are jumper-selectable 04h = G 40x25 8x8 320x200 4 B800 CGA,PCjr,EGA,MCGA,VGA

for either IRQ2 or IRQ5 (default IRQ5); the 1000SX and TX are 05h = G 40x25 8x8 320x200 4gray B800 CGA,PCjr,EGA

DIP-switch selectable for IRQ2 or IRQ5 (default IRQ2); the RSX and = G 40x25 8x8 320x200 4 B800 MCGA,VGA

RSX-HD use IRQ14. Tandy systems which use IRQ2 for the hard disk 06h = G 80x25 8x8 640x200 2 B800 CGA,PCjr,EGA,MCGA,VGA

interrupt use IRQ5 for vertical retrace. 07h = T 80x25 9x14 mono var B000 MDA,Hercules,EGA

may be masked by setting bit 5 on I/O port 21h = T 80x25 9x16 mono B000 VGA

SeeAlso: INT 0E"IRQ6",INT 0F"IRQ7",INT 55"DESQview",INT 5D"DoubleDOS" 08h = T 132x25 8x8 16 B800 ATI EGA/VGA Wonder [2]

SeeAlso: INT 7D"GO32" = T 132x25 8x8 mono B000 ATI EGA/VGA Wonder [2]

-------- = G 20x25 8x8 160x200 16 PCjr, Tandy 1000

H-0E------------------------------------------------------ = G 90x43 8x8 720x352 mono B000 Hercules + MSHERC.COM

INT 0E - IRQ6 - DISKETTE CONTROLLER = G 90x45 8x8 mono B000 Hercules + HERKULES [11]

Desc: this interrupt is generated by the floppy disk controller on 09h = G 40x25 8x8 320x200 16 PCjr, Tandy 1000

completion of an operation 0Ah = G 80x25 8x8 640x200 4 PCjr, Tandy 1000

Notes: default handler is at F000h:EF57h in IBM PC and 100%-compatible 0Bh = reserved (used internally by EGA BIOS)

BIOSes = G 80x25 8x8 640x200 16 Tandy 1000 SL/TL [13]

may be masked by setting bit 6 on I/O port 21h 0Ch = reserved (used internally by EGA BIOS)

SeeAlso: INT 0D"IRQ5",INT 56"DESQview",INT 5E"DoubleDOS",INT 7E"GO32" 0Dh = G 40x25 8x8 320x200 16 8 A000 EGA,VGA

-------- 0Eh = G 80x25 8x8 640x200 16 4 A000 EGA,VGA

H-0F------------------------------------------------------ 0Fh = G 80x25 8x14 640x350 mono 2 A000 EGA,VGA

INT 0F - IRQ7 - PARALLEL PRINTER 10h = G 80x25 8x14 640x350 4 2 A000 64k EGA

Desc: this interrupt is generated by the LPT1 printer adapter when the = G 640x350 16 A000 256k EGA,VGA

printer becomes ready 11h = G 80x30 8x16 640x480 mono A000 VGA,MCGA,ATI EGA,ATI VIP

Notes: most printer adapters do not reliably generate this interrupt 12h = G 80x30 8x16 640x480 16/256k A000 VGA,ATI VIP

the 8259 interrupt controller generates an interrupt corresponding to = G 80x30 8x16 640x480 16/64 A000 ATI EGA Wonder

IRQ7 when an error condition occurs = G 640x480 16 UltraVision+256K EGA


APÉNDICES 389

13h = G 40x25 8x8 320x200 256/256k A000 VGA,MCGA,ATI VIP AL = character

Index: video modes Notes: for monochrome displays, a foreground of 1 with background 0 is

Index: installation check|HERKULES underlined

-------- the blink bit may be reprogrammed to enable intense background colors

V-1002---------------------------------------------------- using AX=1003h or by programming the CRT controller

INT 10 - VIDEO - SET CURSOR POSITION the foreground intensity bit (3) can be programmed to switch between

AH = 02h character sets A and B on EGA and VGA cards, thus enabling 512

BH = page number simultaneous characters on screen. In this case the bit's usual

0-3 in modes 2&3 function (intensity) is regularly turned off.

0-7 in modes 0&1 SeeAlso: AH=09h,AX=1003h,AX=5001h

0 in graphics modes

DH = row (00h is top) Bitfields for character's attribute:

DL = column (00h is left) bit 7 blink

SeeAlso: AH=03h,AH=05h,INT 60/DI=030Bh bits 6-4 background color

-------- 000 black 100 red

V-1003---------------------------------------------------- 001 blue 101 magenta

INT 10 - VIDEO - GET CURSOR POSITION AND SIZE 010 green 110 brown

AH = 03h 011 cyan 111 white

BH = page number bits 3-0 foreground color

0-3 in modes 2&3 0000 black 1000 dark gray

0-7 in modes 0&1 0001 blue 1001 light blue

0 in graphics modes 0010 green 1010 light green

Return: AX = 0000h (Phoenix BIOS) 0011 cyan 1011 light cyan

CH = start scan line 0100 red 1100 light red

CL = end scan line 0101 magenta 1101 light magenta

DH = row (00h is top) 0110 brown 1110 yellow

DL = column (00h is left) 0111 light gray 1111 white

Notes: a separate cursor is maintained for each of up to 8 display pages --------


many ROM BIOSes incorrectly return the default size for a color V-1009----------------------------------------------------
display INT 10 - VIDEO - WRITE CHARACTER AND ATTRIBUTE AT CURSOR POSITION
(start 06h, end 07h) when a monochrome display is attached AH = 09h

SeeAlso: AH=01h,AH=02h,AH=12h/BL=34h AL = character to display

-------- BH = page number (00h to number of pages - 1) (see AH=00h)

V-1005---------------------------------------------------- BL = attribute (text mode) or color (graphics mode)

INT 10 - VIDEO - SELECT ACTIVE DISPLAY PAGE if bit 7 set in graphics mode, character is xor'ed onto screen

AH = 05h CX = number of times to write character

AL = new page number (00h to number of pages - 1) (see AH=00h) Notes: all characters are displayed, including CR, LF, and BS

Desc: specify which of possibly multiple display pages will be visible replication count in CX may produce an unpredictable result in

Note: to determine whether the requested page actually exists, use AH=0Fh graphics

to query the current page after making this call modes if it is greater than the number of positions remaining in

SeeAlso: AH=0Fh,AH=43h,AH=45h the

-------- current row

V-1006---------------------------------------------------- SeeAlso: AH=08h,AH=0Ah,AH=4Bh"GRAFIX",INT 17/AH=60h,INT 1F,INT 43,INT 44

INT 10 - VIDEO - SCROLL UP WINDOW --------


AH = 06h V-100C----------------------------------------------------
AL = number of lines by which to scroll up (00h = clear entire INT 10 - VIDEO - WRITE GRAPHICS PIXEL
window) AH = 0Ch

BH = attribute used to write blank lines at bottom of window BH = page number

CH,CL = row,column of window's upper left corner AL = pixel color (if bit 7 set, value is xor'ed onto screen)

DH,DL = row,column of window's lower right corner CX = column

Note: affects only the currently active page (see AH=05h) DX = row

Warning: some implementations have a bug which destroys BP Desc: set a single pixel on the display in graphics modes

SeeAlso: AH=07h,AH=72h,AH=73h,AX=7F07h,INT 50/AX=0014h Notes: valid only in graphics modes

-------- BH is ignored if the current video mode supports only one page

V-1008---------------------------------------------------- SeeAlso: AH=0Dh,AH=46h

INT 10 - VIDEO - READ CHARACTER AND ATTRIBUTE AT CURSOR POSITION --------


AH = 08h V-100E----------------------------------------------------
BH = page number (00h to number of pages - 1) (see AH=00h) INT 10 - VIDEO - TELETYPE OUTPUT
Return: AH = charater's attribute (see below) AH = 0Eh
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

AL = character to write V-101A00--------------------------------------------------


BH = page number INT 10 - VIDEO - GET DISPLAY COMBINATION CODE (PS,VGA/MCGA)
BL = foreground color (graphics modes only) AX = 1A00h

Desc: display a character on the screen, advancing the cursor and scrolling Return: AL = 1Ah if function was supported

the screen as necessary BL = active display code (see below)

Notes: characters 07h (BEL), 08h (BS), 0Ah (LF), and 0Dh (CR) are BH = alternate display code

interpreted SeeAlso: AH=12h/BL=35h,AX=1A01h,AH=1Bh

and do the expected things

IBM PC ROMs dated 4/24/81 and 10/19/81 require that BH be the same as Values for display combination code:

the current active page 00h no display

SeeAlso: AH=02h,AH=0Ah 01h monochrome adapter w/ monochrome display

-------- 02h CGA w/ color display

V-100F---------------------------------------------------- 03h reserved

INT 10 - VIDEO - GET CURRENT VIDEO MODE 04h EGA w/ color display

AH = 0Fh 05h EGA w/ monochrome display

Return: AH = number of character columns 06h PGA w/ color display

AL = display mode (see AH=00h) 07h VGA w/ monochrome analog display

BH = active page (see AH=05h) 08h VGA w/ color analog display

Notes: if mode was set with bit 7 set ("no blanking"), the returned mode 09h reserved

will 0Ah MCGA w/ digital color display

also have bit 7 set 0Bh MCGA w/ monochrome analog display

EGA, VGA, and UltraVision return either AL=03h (color) or AL=07h 0Ch MCGA w/ color analog display

(monochrome) in all extended-row text modes FFh unknown display type

SeeAlso: AH=00h,AH=05h,AX=10F2h/BL=00h,AX=1130h,AX=CD04h --------V-104F00-----------------------------


-------- INT 10 - VESA SuperVGA BIOS - GET SuperVGA INFORMATION
V-101002-------------------------------------------------- AX = 4F00h

INT 10 - VIDEO - SET ALL PALETTE REGISTERS (PCjr,Tandy,EGA,VGA) ES:DI -> 256-byte buffer for SuperVGA information (see #0063)

AX = 1002h Return: AL = 4Fh if function supported

ES:DX -> palette register list AH = status

Note: under UltraVision, the palette locking status (see AX=CD01h) 00h successful

determines the outcome ES:DI buffer filled

SeeAlso: AX=1000h,AX=1001h,AX=1009h,AX=CD01h 01h failed

Desc: determine whether VESA BIOS extensions are present and the

Format of palette register list: capabilities

Offset Size Description supported by the display adapter

00h 16 BYTEs colors for palette registers 00h through 0Fh SeeAlso: AX=4E00h,AX=4F01h,AX=7F00h,AX=A00Ch

10h BYTE border color Index: installation check;VESA SuperVGA

--------
V-101012-------------------------------------------------- Format of SuperVGA information:

INT 10 - VIDEO - SET BLOCK OF DAC REGISTERS (VGA/MCGA) Offset Size Description (Table 0063)

AX = 1012h 00h 4 BYTEs signature ("VESA")

BX = starting color register 04h WORD VESA version number

CX = number of registers to set 06h DWORD pointer to OEM name

ES:DX -> table of 3*CX bytes where each 3 byte group represents one "761295520" for ATI

byte each of red, green and blue (0-63) 0Ah 4 BYTEs capabilities

SeeAlso: AX=1010h,AX=1017h,INT 62/AX=00A5h 0Eh DWORD pointer to list of supported VESA and OEM video modes

-------- (list of words terminated with FFFFh)

V-101013-------------------------------------------------- 12h WORD total amount of video memory in 64K blocks

INT 10 - VIDEO - SELECT VIDEO DAC COLOR PAGE (VGA) 14h 236 BYTEs reserved

AX = 1013h Notes: the list of supported video modes is stored in the reserved portion

BL = subfunction of

00h select paging mode the SuperVGA information record by some implementations, and it may

BH = 00h select 4 blocks of 64 thus be necessary to either copy the mode list or use a different

BH = 01h select 16 blocks of 16 buffer for all subsequent VESA calls

01h select page the 1.1 VESA document specifies 242 reserved bytes at the end, so the

BH = page number (00h to 03h) or (00h to 0Fh) buffer should be 262 bytes to ensure that it is not overrun

Note: this function is not valid in mode 13h --------V-104F01-----------------------------


SeeAlso: AX=101Ah INT 10 - VESA SuperVGA BIOS - GET SuperVGA MODE INFORMATION
-------- AX = 4F01h
APÉNDICES 389

CX = SuperVGA video mode

ES:DI -> 256-byte buffer for mode information (see #0064) (Table 0067)

Return: AL = 4Fh function supported Values for VESA SuperVGA memory model type:

AH = status 00h text

00h successful 01h CGA graphics

ES:DI buffer filled 02h HGC graphics

01h failed 03h 16-color (EGA) graphics

Desc: determine the attributes of the specified video mode 04h packed pixel graphics

SeeAlso: AX=4F00h,AX=4F02h 05h "sequ 256" (non-chain 4) graphics

06h direct color (HiColor, 24-bit color)

Format of VESA SuperVGA mode information: 07h YUV (luminance-chrominance, also called YIQ)

Offset Size Description (Table 0064) 08h-0Fh reserved for VESA

00h WORD mode attributes (see #0065) 10h-FFh OEM memory models

02h BYTE window attributes, window A (see #0066) --------V-104F02-----------------------------


03h BYTE window attributes, window B (see #0066) INT 10 - VESA SuperVGA BIOS - SET SuperVGA VIDEO MODE
04h WORD window granularity in KB AX = 4F02h

06h WORD window size in KB BX = mode

08h WORD start segment of window A bit 15 set means don't clear video memory

0Ah WORD start segment of window B Return: AL = 4Fh function supported

0Ch DWORD -> FAR window positioning function (equivalent to AX=4F05h) AH = status

10h WORD bytes per scan line 00h successful

---remainder is optional for VESA modes in v1.0/1.1, needed for OEM modes--- 01h failed

12h WORD width in pixels (graphics) or characters (text) SeeAlso: AX=4E03h,AX=4F01h,AX=4F03h

14h WORD height in pixels (graphics) or characters (text)

16h BYTE width of character cell in pixels (Table 0068)

17h BYTE height of character cell in pixels Values for VESA video mode:

18h BYTE number of memory planes 00h-FFh OEM video modes (see #0009 at AH=00h)

19h BYTE number of bits per pixel 100h 640x400x256

1Ah BYTE number of banks 101h 640x480x256

1Bh BYTE memory model type (see #0067) 102h 800x600x16

1Ch BYTE size of bank in KB 103h 800x600x256

1Dh BYTE number of image pages 104h 1024x768x16

1Eh BYTE reserved (0) 105h 1024x768x256

---VBE v1.2+--- 106h 1280x1024x16

1Fh BYTE red mask size 107h 1280x1024x256

20h BYTE red field position 108h 80x60 text

21h BYTE green mask size 109h 132x25 text

22h BYTE green field size 10Ah 132x43 text

23h BYTE blue mask size 10Bh 132x50 text

24h BYTE blue field size 10Ch 132x60 text

25h BYTE reserved mask size ---VBE v1.2---

26h BYTE reserved mask position 10Dh 320x200x32K

27h BYTE direct color mode info 10Eh 320x200x64K

28h 216 BYTEs reserved (0) 10Fh 320x200x16M

110h 640x480x32K

Bitfields for VESA SuperVGA mode attributes: 111h 640x480x64K

Bit(s) Description (Table 0065) 112h 640x480x16M

0 mode supported 113h 800x600x32K

1 optional information available 114h 800x600x64K

2 BIOS output supported 115h 800x600x16M

3 set if color, clear if monochrome 116h 1024x768x32K

4 set if graphics mode, clear if text mode 117h 1024x768x64K

118h 1024x768x16M

Bitfields for VESA SuperVGA window attributes: 119h 1280x1024x32K

Bit(s) Description (Table 0066) 11Ah 1280x1024x64K

0 exists 11Bh 1280x1024x16M

1 readable Index: video modes

2 writable

3-7 reserved (Table 0069)


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

Values for S3 OEM video mode: SeeAlso: AX=4F01h,AX=4F06h,AX=4F07h,AX=7000h/BX=0004h

201h 640x480x256 --------V-104F06-----------------------------


202h 800x600x16 INT 10 - VESA SuperVGA BIOS v1.1+ - GET/SET LOGICAL SCAN LINE
203h 800x600x256 LENGTH
204h 1024x768x16 AX = 4F06h

205h 1024x768x256 BL = function

206h 1280x960x16 00h set scan line length

208h 1280x1024x16 CX = desired width in pixels

211h 640x480x64K (Diamond Stealth 24) 01h get scan line length

212h 640x480x16M (Diamond Stealth 24) Return: AL = 4Fh if function supported

301h 640x480x32K AH = status

Note: these modes are only available on video cards using S3's VESA driver 00h successful

Index: video modes 01h failed

--------V-104F03----------------------------- BX = bytes per scan line

INT 10 - VESA SuperVGA BIOS - GET CURRENT VIDEO MODE CX = number of pixels per scan line

AX = 4F03h DX = maximum number of scan lines

Return: AL = 4Fh function supported Notes: if the desired width is not achievable, the next larger width will be

AH = status set

00h successful the scan line may be wider than the visible area of the screen

BX = video mode (see #0068,#0069) this function is valid in text modes, provided that values are

01h failed multiplied by the character cell width/height

SeeAlso: AH=0Fh,AX=4E04h,AX=4F02h SeeAlso: AX=4F01h,AX=4F05h,AX=4F07h

--------V-104F04----------------------------- --------V-104F07BH00-------------------------
INT 10 - VESA SuperVGA BIOS - SAVE/RESTORE SuperVGA VIDEO STATE INT 10 - VESA SuperVGA BIOS v1.1+ - GET/SET DISPLAY START
AX = 4F04h AX = 4F07h

DL = subfunction BH = 00h (reserved)

00h get state buffer size BL = function

Return: BX = number of 64-byte blocks needed 00h set display start

01h save video states CX = leftmost displayed pixel in scan line

ES:BX -> buffer DX = first displayed scan line

02h restore video states 01h get display start

ES:BX -> buffer Return: BH = 00h

CX = states to save/restore (see #0070) CX = leftmost displayed pixel in scan line

Return: AL = 4Fh function supported DX = first displayed scan line

AH = status Return: AL = 4Fh if function supported

00h successful AH = status

01h failed 00h successful

01h failed

Bitfields for VESA SuperVGA states to save/restore: Note: this function is valid in text modes, provided that values are

Bit(s) Description (Table 0070) multiplied by the character cell width/height

0 video hardware state SeeAlso: AX=4F01h,AX=4F05h,AX=4F06h

1 video BIOS data state --------V-104F08-----------------------------


2 video DAC state INT 10 - VESA SuperVGA BIOS v1.2+ - GET/SET DAC PALETTE CONTROL
3 SuperVGA state AX = 4F08h

--------V-104F05----------------------------- BL = function

INT 10 - VESA SuperVGA BIOS - CPU VIDEO MEMORY CONTROL 00h set DAC palette width

AX = 4F05h BH = desired number of bits per primary color

BH = subfunction 01h get DAC palette width

00h select video memory window Return: AL = 4Fh if function supported

DX = window address in video memory (in granularity units) AH = status

01h get video memory window BH = current number of bits per primary (06h = standard VGA)

Return: DX = window address in video memory (in gran. units) --------


BL = window number B-11------------------------------------------------------
00h window A INT 11 - BIOS - GET EQUIPMENT LIST
01h window B Return: (E)AX = BIOS equipment list word (see below)

Return: AL = 4Fh function supported Note: since older BIOSes do not know of the existence of EAX, the high word

AH = status of EAX should be cleared before this call if any of the high bits

00h successful will be tested

01h failed
APÉNDICES 389

Bitfields for BIOS equipment list: AH = status of previous operation (see below)

bit 0 floppy disk(s) installed (see bits 6-7) Note: some BIOSes return the status in AL; the PS/2 Model 30/286 returns

bit 1 80x87 coprocessor installed the

bits 2,3 number of 16K banks of RAM on motherboard (PC only) status in both AH and AL

number of 64K banks of RAM on motherboard (XT only)

bit 2 pointing device installed (PS) Values for status:

bit 3 unused (PS) 00h successful completion

bits 4-5 initial video mode 01h invalid function in AH or invalid parameter

00 EGA, VGA, or PGA 02h address mark not found

01 40x25 color 03h disk write-protected

10 80x25 color 04h sector not found/read error

11 80x25 monochrome 05h reset failed (hard disk)

bits 6-7 number of floppies installed less 1 (if bit 0 set) 06h disk changed (floppy)

bit 8 DMA support installed (PCjr, Tandy 1400LT) 07h drive parameter activity failed (hard disk)

DMA support *not* installed (Tandy 1000's) 08h DMA overrun

bits 9-11 number of serial ports installed 09h attempted DMA across 64K boundary

bit 12 game port installed 0Ah bad sector detected (hard disk)

bit 13 serial printer attached (PCjr) 0Bh bad track detected (hard disk)

internal modem installed (PC/Convertible) 0Ch unsupported track or invalid media

bits 14-15 number of parallel ports installed 0Dh invalid number of sectors on format (hard disk)

---Compaq, Dell, and many other 386/486 machines-- 0Eh control data address mark detected (hard disk)

bit 23: page tables set so that Weitek coprocessor addressable in real mode 0Fh DMA arbitration level out of range (hard disk)

bit 24: Weitek math coprocessor present 10h uncorrectable CRC or ECC error on read

---Compaq Systempro--- 11h data ECC corrected (hard disk)

bit 25: internal DMA parallel port available 20h controller failure

bit 26: IRQ for internal DMA parallel port (if bit 25 set) 31h no such drive (Compaq)

0 = IRQ5 32h incorrect drive type stored in CMOS (Compaq)

1 = IRQ7 40h seek failed

bits 27,28: parallel port DMA channel 80h timeout (not ready)

00 DMA channel 0 AAh drive not ready (hard disk)

01 DMA channel 0 ??? BBh undefined error (hard disk)

10 reserved CCh write fault (hard disk)

11 DMA channel 3 E0h status register error (hard disk)

SeeAlso: INT 12 FFh sense operation failed (hard disk)

-------- --------
B-12------------------------------------------------------ B-1302----------------------------------------------------
INT 12 - BIOS - GET MEMORY SIZE INT 13 - DISK - READ SECTOR(S) INTO MEMORY
Return: AX = kilobytes of contiguous memory starting at absolute address AH = 02h

00000h AL = number of sectors to read (must be nonzero)

Note: this call returns the contents of the word at 0040h:0013h; in PC and CH = low eight bits of cylinder number

XT, this value is set from the switches on the motherboard CL = sector number 1-63 (bits 0-5)

SeeAlso: INT 11,INT 2F/AX=4A06h high two bits of cylinder (bits 6-7, hard disk only)

-------- DH = head number

B-1300---------------------------------------------------- DL = drive number (bit 7 set for hard disk)

INT 13 - DISK - RESET DISK SYSTEM ES:BX -> data buffer

AH = 00h Return: CF set on error

DL = drive (if bit 7 is set both hard disks and floppy disks reset) if AH = 11h (corrected ECC error), AL = burst length

Return: AH = status (see AH=01h) CF clear if successful

CF clear if successful (returned AH=00h) AH = status (see AH=01h)

CF set on error AL = number of sectors transferred

Note: forces controller to recalibrate drive heads (seek to track 0) Notes: errors on a floppy may be due to the motor failing to spin up quickly

SeeAlso: AH=0Dh,AH=11h,INT 21/AH=0Dh,INT 4E"TI Professional" enough; the read should be retried at least three times, resetting

-------- the disk with AH=00h between attempts

B-1301---------------------------------------------------- the IBM AT BIOS and many other BIOSes use only the low four bits of

INT 13 - DISK - GET STATUS OF LAST OPERATION DH (head number) since the WD-1003 controller which is the standard

AH = 01h AT controller (and the controller that IDE emulates) only supports

DL = drive (bit 7 set for hard disk) 16 heads

Return: CF clear if successful (returned status 00h) AWARD AT BIOS and AMI 386sx BIOS have been extended to handle more

CF set on error than 1024 cylinders by placing bits 10 and 11 of the cylinder
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

number SeeAlso: AH=02h

into bits 6 and 7 of DH --------


SeeAlso: AH=03h,AH=0Ah B-1305----------------------------------------------------
-------- INT 13 - FLOPPY - FORMAT TRACK
B-1303---------------------------------------------------- AH = 05h

INT 13 - DISK - WRITE DISK SECTOR(S) AL = number of sectors to format

AH = 03h CH = track number

AL = number of sectors to write (must be nonzero) DH = head number

CH = low eight bits of cylinder number DL = drive number

CL = sector number 1-63 (bits 0-5) ES:BX -> address field buffer (see below)

high two bits of cylinder (bits 6-7, hard disk only) Return: CF set on error

DH = head number CF clear if successful

DL = drive number (bit 7 set for hard disk) AH = status (see AH=01h)

ES:BX -> data buffer Notes: on AT or higher, call AH=17h first

Return: CF set on error the number of sectors per track is read from the diskette parameter

CF clear if successful table pointed at by INT 1E

AH = status (see AH=01h) SeeAlso: AH=05h"FIXED",AH=17h,AH=18h,INT 1E

AL = number of sectors transferred

Notes: errors on a floppy may be due to the motor failing to spin up quickly Format of address field buffer entry (one per sector in track):

enough; the write should be retried at least three times, resetting Offset Size Description

the disk with AH=00h between attempts 00h BYTE track number

the IBM AT BIOS and many other BIOSes use only the low four bits of 01h BYTE head number (0-based)

DH (head number) since the WD-1003 controller which is the standard 02h BYTE sector number

AT controller (and the controller that IDE emulates) only supports 03h BYTE sector size (00h=128 bytes, 01h=256 bytes, 02h=512, 03h=1024)

16 heads --------B-13057FSI324D-----------------------
AWARD AT BIOS and AMI 386sx BIOS have been extended to handle more INT 13 - 2M - FORMAT TRACK
than 1024 cylinders by placing bits 10 and 11 of the cylinder AX = 057Fh

number SI = 324Dh ("2M")

into bits 6 and 7 of DH CH = track number

SeeAlso: AH=02h,AH=0Bh DH = head number

-------- DL = drive number

B-1304---------------------------------------------------- ES:BX -> boot sector of future 2M diskette

INT 13 - DISK - VERIFY DISK SECTOR(S) Return: CF set on error

AH = 04h CF clear if successful

AL = number of sectors to verify (must be nonzero) AH = status (see AH=01h)

CH = low eight bits of cylinder number Program: 2M is a TSR developed by Ciriaco Garcia de Celis to support

CL = sector number 1-63 (bits 0-5) non standard diskettes with 820-902/1476-1558K (5.25 DD/HD)

high two bits of cylinder (bits 6-7, hard disk only) and 984-1066/1804-1886K/3608-3772K (3.5 DD/HD/ED)

DH = head number Notes: it is not necessary to call AH=17h/AH=18h first (will be ignored)

DL = drive number (bit 7 set for hard disk) diskette format must begin always on cylinder 0 head 0

ES:BX -> data buffer (PC,XT,AT with BIOS prior to 11/15/85) the installation check for 2M must search a "CiriSOFT:2M:3.0" or

Return: CF set on error "CiriSOFT:2MX:3.0" or similar (recomended ":2M:" or ":2MX:"

CF clear if successful substrings) in CiriSOFT TSR interface

AH = status (see AH=01h) the boot sector can be obtained from a 2M diskette already formatted

AL = number of sectors verified if

Notes: errors on a floppy may be due to the motor failing to spin up quickly reading (AH=02h) with normal head number in 2M 1.x and with head

enough; the write should be retried at least three times, resetting 80h

the disk with AH=00h between attempts in 2M 2.0+

this function does not compare the disk with memory, it merely since 2M 2.0+ release, the BOOT sector is emulated using first

checks whether the sector's stored CRC matches the data's actual physical

CRC sector of FAT2; the second-sixth physical sectors of FAT2 in HD or

the IBM AT BIOS and many other BIOSes use only the low four bits of ED

DH (head number) since the WD-1003 controller which is the standard diskettes store the SuperBOOT code. To skip the FAT2 emulation

AT controller (and the controller that IDE emulates) only supports (using

16 heads FAT1) of 2M, in order to read the SuperBOOT code, in 2M 2.0+ the

AWARD AT BIOS and AMI 386sx BIOS have been extended to handle more head

than 1024 cylinders by placing bits 10 and 11 of the cylinder number must be 80h instead 0 (bit 7 on) in read/write functions,

number and

into bits 6 and 7 of DH the number of sectors must be 7+FT in HD and 2+FT in DD, being FT
APÉNDICES 389

the 06h change line active or not supported

number of sectors ocupied by one FAT. This lets diskcopy programs 80h drive not ready or not present

to Note: call AH=15h first to determine whether the drive supports a change

format 2M target disks copying also the SuperBOOT code. If target line

diskette is already 2MF formatted (provided of boot code) this SeeAlso: AH=15h

trick --------
it is not necessary B-154F----------------------------------------------------
when using STV technology (offset 65 of boot sector equal to 1) it is INT 15 C - KEYBOARD - KEYBOARD INTERCEPT (AT model
necessary to write the full track before formatting (except track 0 3x9,XT2,XT286,CONV,PS)
side 0) to complete the format and skip future CRC errors on read; AH = 4Fh

with 2M 2.0+ in track 0 side 1 the head used must be 81h instead 1. AL = hardware scan code

Optimized diskcopy programs may do a format-write-verify secuential CF set

phases to improve performance Return: CF set

SeeAlso: AH=05h"FLOPPY",INT 2F"CiriSOFT TSR interface" AL = hardware scan code

-------- CF clear

B-1308---------------------------------------------------- scan code should be ignored

INT 13 - DISK - GET DRIVE PARAMETERS (PC,XT286,CONV,PS,ESDI,SCSI) Note: called by INT 09 handler to translate scan codes; the INT 09 code

AH = 08h does

DL = drive (bit 7 set for hard disk) not examine the scan code it reads from the keyboard until after

Return: CF set on error this function returns. This permits software to rearrange the

AH = status (07h) (see AH=01h) keyboard; for example, swapping the CapsLock and Control keys, or

CF clear if successful turning the right Shift key into Enter.

AH = 00h SeeAlso: INT 09,INT 15/AH=C0h

BL = drive type (AT/PS2 floppies only) (see below) --------


CH = low eight bits of maximum cylinder number B-1585----------------------------------------------------
CL = maximum sector number (bits 5-0) INT 15 C - OS HOOK - SysRq KEY ACTIVITY (AT,PS)
high two bits of maximum cylinder number (bits 7-6) AH = 85h

DH = maximum head number AL = SysRq key action (00h pressed, 01h released)

DL = number of drives CF clear

ES:DI -> drive parameter table (floppies only) Return: CF clear if successful

Notes: may return successful even though specified drive is greater than the AH = 00h

number of attached drives of that type (floppy/hard); check DL to CF set on error

ensure validity AH = status (see AH=84h)

for systems predating the IBM AT, this call is only valid for hard Notes: called by keyboard decode routine

disks, as it is implemented by the hard disk BIOS rather than the the default handler simply returns successfully; programs which wish

ROM BIOS to monitor the SysRq key must hook this call

Toshiba laptops with HardRAM return DL=02h when called with DL=80h, SeeAlso: INT 09

but fail on DL=81h. The BIOS data at 40h:75h correctly reports --------
01h. B-1586----------------------------------------------------
SeeAlso: AH=06h"Adaptec",AH=15h,INT 1E,INT 41 INT 15 - BIOS - WAIT (AT,PS)
AH = 86h

Values for drive type: CX:DX = interval in microseconds

01h 360K Return: CF clear if successful (wait interval elapsed)

02h 1.2M CF set on error or AH=83h wait already in progress

03h 720K AH = status (see AH=84h)

04h 1.44M Note: the resolution of the wait period is 977 microseconds on most systems

05h ??? (reportedly an obscure drive type shipped on some IBM machines) because most BIOSes use the 1/1024 second fast interrupt from the

2.88M on some machines (at least AMI 486 BIOS) AT

06h 2.88M real-time clock chip which is available on INT 70

-------- SeeAlso: AH=41h,AH=83h,INT 1A/AX=FF01h,INT 70

B-1316---------------------------------------------------- --------
INT 13 - FLOPPY DISK - DETECT DISK CHANGE (XT 1/10/86 or B-1590----------------------------------------------------
later,XT286,AT,PS) INT 15 - OS HOOK - DEVICE BUSY (AT,PS)
AH = 16h AH = 90h

DL = drive number AL = device type (see below)

Return: CF clear if change line inactive ES:BX -> request block for type codes 80h through BFh

AH = 00h (disk not changed) CF clear

CF set if change line active Return: CF set if wait time satisfied

AH = status CF clear if driver must perform wait


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

AH = 00h Tandy 1000 machines contain 21h in the byte at F000h:C000h and FFh in

Notes: type codes are allocated as follows: the byte at FFFFh:000Eh; Tandy 1000SL/TL machines only provide the

00-7F non-reentrant devices; OS must arbitrate access first three data bytes (model/submodel/revision) in the returned

80-BF reentrant devices; ES:BX points to a unique control block table

C0-FF wait-only calls, no complementary INT 15/AH=91h call some AST machines contain the string "COPYRIGHT AST RESEARCH" one

floppy and hard disk BIOS code uses this call to implement a timeout; byte

for device types 00h and 01h, a return of CF set means that the past the end of the configuration table

timeout expired before the disk responded. the Phoenix 386 BIOS contains a second version and date string

this function should be hooked by a multitasker to allow other tasks (presumably the last modification for that OEM version) beginning

to execute while the BIOS is waiting for I/O completion; the at

default F000h:FFD8h, with each byte doubled (so that both ROM chips contain

handler merely returns with AH=00h and CF clear the complete information)

SeeAlso: AH=91h,INT 13/AH=00h,INT 17/AH=00h,INT 1A/AH=83h SeeAlso: AH=C7h,AH=C9h,AH=D1h

Values for device type: Format of ROM configuration table:

00h disk Offset Size Description

01h diskette 00h WORD number of bytes following

02h keyboard 02h BYTE model (see below)

03h PS/2 pointing device 03h BYTE submodel (see below)

21h waiting for keyboard input (Phoenix BIOS) 04h BYTE BIOS revision: 0 for first release, 1 for 2nd, etc.

80h network 05h BYTE feature byte 1 (see below)

FBh digital sound (Tandy) 06h BYTE feature byte 2 (see below)

FCh disk reset (PS) 07h BYTE feature byte 3 (see below)

FDh diskette motor start 08h BYTE feature byte 4:

FEh printer bit 7: ??? (set on N51SX, CL57SX)

-------- bits 6-4: reserved

B-1591---------------------------------------------------- bit 3: ??? (set on some 1992 PS/1's, 35SX, 40SX)

INT 15 - OS HOOK - DEVICE POST (AT,PS) bits 2-1: reserved

AH = 91h bit 0: ??? (set on N51SX, CL57SX, 57SX)

AL = device type (see AH=90h) 09h BYTE feature byte 5:

ES:BX -> request block for type codes 80h through BFh reserved (0) (IBM)

CF clear ??? (08h) (Phoenix 386 v1.10)

Return: AH = 00h ---AWARD BIOS---

Note: this function should be hooked by a multitasker to allow other tasks 0Ah N BYTEs AWARD copyright notice

to execute while the BIOS is waiting for I/O completion; the ---Phoenix BIOS---

default 0Ah BYTE ??? (00h)

handler merely returns with AH=00h and CF clear 0Bh BYTE major version

SeeAlso: AH=90h 0Ch BYTE minor version (BCD)

-------- 0Dh 4 BYTEs ASCIZ string "PTL" (Phoenix Technologies Ltd)

B-15C0---------------------------------------------------- ---Quadram Quad386---

INT 15 - SYSTEM - GET CONFIGURATION (XT after 1/10/86,AT mdl 0Ah 17 BYTEs ASCII signature string "Quadram Quad386XT"

3x9,CONV,XT286,PS)
AH = C0h Bitfields for feature byte 1:

Return: CF set if BIOS doesn't support call bit 7 DMA channel 3 used by hard disk BIOS

CF clear on success bit 6 2nd 8259 installed

ES:BX -> ROM table (see below) bit 5 Real-Time Clock installed

AH = status bit 4 INT 15/AH=4Fh called upon INT 09h

00h successful bit 3 wait for external event (INT 15/AH=41h) supported

86h unsupported function bit 2 extended BIOS area allocated (usually at top of RAM)

Notes: the 1/10/86 XT BIOS returns an incorrect value for the feature byte bit 1 bus is Micro Channel instead of ISA

the configuration table is at F000h:E6F5h in 100% compatible BIOSes bit 0 system has dual bus (Micro Channel + ISA)

Dell machines contain the signature "DELL" or "Dell" at absolute

FE076h Bitfields for feature byte 2:

and a model byte at absolute address FE845h bit 7 reserved

Hewlett-Packard machines contain the signature "HP" at F000h:00F8h bit 6 INT 16/AH=09h (keyboard functionality) supported

and bit 5 INT 15/AH=C6h (get POS data) supported

a product identifier at F000h:00FAh (see below) bit 4 INT 15/AH=C7h (return memory map info) supported

Compaq machines can be identified by the signature string "COMPAQ" at bit 3 INT 15/AH=C8h (en/disable CPU functions) supported

F000h:FFEAh, and is preceded by additional information (see below) bit 2 non-8042 keyboard controller
APÉNDICES 389

bit 1 data streaming supported FCh 30h *** ??? Epson, unknown model

bit 0 reserved FCh 31h *** ??? Epson, unknown model

FCh 33h *** ??? Epson, unknown model

Bitfields for feature byte 3: FCh 42h *** ??? Olivetti M280

bits 7-5 reserved FCh 45h *** ??? Olivetti M380 (XP 1, XP3, XP 5)

bit 4 ??? (set on 1992 PS/1's, N51SX, CL57SX, 35SX?, 40SX?) FCh 48h *** ??? Olivetti M290

bit 3 SCSI subsystem supported on system board FCh 4Fh *** ??? Olivetti M250

bit 2 information panel installed FCh 50h *** ??? Olivetti M380 (XP 7)

bit 1 IML (Initial Machine Load) system FCh 51h *** ??? Olivetti PCS286

bit 0 SCSI supported in IML FCh 52h *** ??? Olivetti M300

FCh 81h 00h 01/15/88 Phoenix 386 BIOS v1.10 10a

Values for model/submodel/revision: FCh 81h 01h ??? "OEM machine"

Model Submdl Rev BIOS date System FCh 82h 01h ??? "OEM machine"

FFh * * 04/24/81 PC (original) FCh 94h 00h ??? Zenith 386

FFh * * 10/19/81 PC (some bugfixes) FBh 00h 01h 01/10/86 PC XT-089, Enh Keyb, 3.5" support

FFh * * 10/27/82 PC (HD, 640K, EGA support) FBh 00h 02h 05/09/86 PC XT

FFh 00h rev ??? Tandy 1000SL FBh 4Ch *** ??? Olivetti M200

FFh 01h rev ??? Tandy 1000TL FAh 00h 00h 09/02/86 PS/2 Model 30 (8 MHz 8086)

FFh 46h *** ??? Olivetti M15 FAh 00h 01h 12/12/86 PS/2 Model 30

FEh * * 08/16/82 PC XT FAh 01h 00h ??? PS/2 Model 25/25L (8 MHz 8086)

FEh * * 11/08/82 PC XT and Portable FAh 30h 00h ??? IBM Restaurant Terminal

FEh 43h *** ??? Olivetti M240 FAh 4Eh *** ??? Olivetti M111

FEh A6h ??? ??? Quadram Quad386 FAh FEh 00h ??? IBM PCradio 9075

FDh * * 06/01/83 PCjr F9h 00h 00h 09/13/85 PC Convertible

FCh * * 01/10/84 AT models 068,099 6 MHz 20MB F9h FFh 00h ??? PC Convertible

FCh 00h 00h ??? PC3270/AT F8h 00h 00h 03/30/87 ** PS/2 Model 80 (16MHz 386)

FCh 00h 01h 06/10/85 AT model 239 6 MHz 30MB F8h 01h 00h 10/07/87 PS/2 Model 80 (20MHz 386)

FCh 00h > 01h ??? 7531/2 Industrial AT F8h 02h 00h ??? PS/2 Model 55-5571

FCh 01h 00h 11/15/85 AT models 319,339 8 MHz, Enh Keyb, F8h 04h 00h ??? PS/2 Model 70

3.5" F8h 04h 02h 04/11/88 PS/2 Model 70 20MHz, type 2 system

FCh 01h 00h 09/17/87 Tandy 3000 brd

FCh 01h 00h 01/15&88 Toshiba T5200/100 F8h 04h 03h 03/17/89 PS/2 Model 70 20MHz, type 2 system

FCh 01h 00h 12/26*89 Toshiba T1200/XE brd

FCh 01h 00h 04/05A92 Toshiba T4500SX-C F8h 05h 00h ??? IBM PC 7568

FCh 01h 00h 07/17o92 Toshiba T1800SX F8h 06h 00h ??? PS/2 Model 55-5571

FCh 01h 00h 12/25n92 Toshiba T1850SX F8h 07h 00h ??? IBM PC 7561/2

FCh 01h 00h 01/13E93 Toshiba T4400C F8h 07h 01h ??? PS/2 Model 55-5551

(Those date characters are not typos) F8h 07h 02h ??? IBM PC 7561/2

FCh 01h 00h 03/08/93 Compaq DESKPRO/i F8h 07h 03h ??? PS/2 Model 55-5551

FCh 01h 00h various Compaq DESKPRO, SystemPro, ProSignia F8h 09h 00h ??? PS/2 Model 70 16MHz, type 1 system

FCh 01h 20h 06/10/92 AST brd

FCh 01h 30h ??? Tandy 3000NL F8h 09h 02h 04/11/88 PS/2 Model 70 some models

FCh 01h ??? ??? Compaq 286/386 F8h 09h 03h 03/17/89 PS/2 Model 70 some models

FCh 02h 00h 04/21/86 PC XT-286 F8h 0Bh 00h 01/18/89 PS/2 Model P70 (8573-121) typ 2 sys

FCh 02h 00h various Compaq LTE Lite brd

FCh 02h 00h 08/05/93 Compaq Contura 486/486c/486cx F8h 0Bh 02h 12/16/89 PS/2 Model P70 ??

FCh 04h 00h 02/13/87 ** PS/2 Model 50 (10 MHz/1 ws 286) F8h 0Ch 00h 11/02/88 PS/2 Model 55SX (16 MHz 386SX)

FCh 04h 02h ??? PS/2 Model 50 F8h 0Dh 00h ??? PS/2 Model 70 25MHz, type 3 system

FCh 04h 03h 04/18/88 PS/2 Model 50Z (10 MHz/0 ws 286) brd

FCh 04h 04h ??? PS/2 Model 50Z F8h 0Eh 00h ??? PS/1 486SX

FCh 05h 00h 02/13/87 ** PS/2 Model 60 (10 MHz 286) F8h 0Fh 00h ??? PS/1 486DX

FCh 06h 00h ??? IBM 7552-140 "Gearbox" F8h 10h 00h ??? PS/2 Model 55-5551

FCh 06h 01h ??? IBM 7552-540 "Gearbox" F8h 11h 00h 10/01/90 PS/2 Model 90 XP (25 MHz 486)

FCh 08h *** ??? Epson, unknown model F8h 12h 00h ??? PS/2 Model 95 XP

FCh 08h 00h ??? PS/2 Model 25/286 F8h 13h 00h 10/01/90 PS/2 Model 90 XP (33 MHz 486)

FCh 09h 00h ??? PS/2 Model 25 (10 MHz 286) F8h 14h 00h 10/01/90 PS/2 Model 90-AK9 (25 MHz 486), 95 XP

FCh 09h 02h 06/28/89 PS/2 Model 30-286 F8h 15h 00h ??? PS/2 Model 90 XP

FCh 0Bh 00h 02/16/90 PS/1 Model 2011 (10 MHz 286) F8h 16h 00h 10/01/90 PS/2 Model 90-AKD (33 MHz 486)

FCh 20h 00h 02/18/93 Compaq ProLinea F8h 17h 00h ??? PS/2 Model 90 XP
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

F8h 19h 05h ??? PS/2 Model 35/35LS or 40 (20 MHz F8h 88h 00h ??? PS/2 Model 55-5530T

386SX) F8h 97h 00h ??? PS/2 Model 55 Note N23SX

F8h 1Ah 00h ??? PS/2 Model 95 XP F8h 99h 00h ??? PS/2 Model N51 SX

F8h 1Bh 00h 10/02/89 PS/2 Model 70-486 (25 MHz 486) F8h F2h 30h ??? Reply Model 32

F8h 1Ch 00h 02/08/90 PS/2 Model 65-121 (16 MHz 386SX) F8h F6h 30h ??? Memorex Telex

F8h 1Eh 00h 02/08/90 PS/2 Model 55LS (16 MHz 386SX) F8h FDh 00h ??? IBM Processor Complex (with VPD)

F8h 23h 00h ??? PS/2 Model L40 SX F8h ??? ??? ??? PS/2 Model 90 (25 MHz 486SX)

F8h 23h 01h ??? PS/2 Model L40 SX (20 MHz 386SX) F8h ??? ??? ??? PS/2 Model 95 (25 MHz 486SX)

F8h 25h 00h ??? PS/2 Model 57 SLC F8h ??? ??? ??? PS/2 Model 90 (25 MHz 486SX + 487SX)

F8h 25h 06h ??? PS/2 Model M57 (20 MHz 386SLC) F8h ??? ??? ??? PS/2 Model 95 (25 MHz 486SX + 487SX)

F8h 26h 00h ??? PS/2 Model 57 SX E1h ??? ??? ??? ??? (checked for by DOS4GW.EXE)

F8h 26h 01h ??? PS/2 Model 57 (20 MHz 386SX) E1h 00h 00h ??? PS/2 Model 55-5530 Laptop

F8h 28h 00h ??? PS/2 Model 95 XP 9Ah * * ??? Compaq XT/Compaq Plus

F8h 29h 00h ??? PS/2 Model 90 XP 30h ??? ??? ??? Sperry PC

F8h 2Ah 00h ??? PS/2 Model 95 XP (50 MHz 486) 2Dh * * ??? Compaq PC/Compaq Deskpro

F8h 2Bh 00h ??? PS/2 Model 90 (50 MHz 486) ??? 56h ??? ??? Olivetti, unknown model

F8h 2Ch 00h ??? PS/2 Model 95 XP ??? 74h ??? ??? Olivetti, unknown model

F8h 2Ch 01h ??? PS/2 Model 95 (20 MHz 486SX) * This BIOS call is not implemented in these early versions.

F8h 2Dh 00h ??? PS/2 Model 90 XP (20 MHz 486SX) Read Model byte at F000h:FFFEh and BIOS date at F000h:FFF5h.

F8h 2Eh 00h ??? PS/2 Model 95 XP ** These BIOS versions require the DASDDRVR.SYS patches.

F8h 2Eh 01h ??? PS/2 Model 95 (20 MHz 486SX + 487SX) *** These Olivetti and Epson machines store the submodel in the byte at

F8h 2Fh 00h ??? PS/2 Model 90 XP (20 MHz 486SX + F000h:FFFDh.

487SX)

F8h 30h 00h ??? PS/1 Model 2121 (16 MHz 386SX) Values for Dell model byte:

F8h 33h 00h ??? PS/2 Model 30-386 02h Dell 200

F8h 34h 00h ??? PS/2 Model 25-386 03h Dell 300

F8h 36h 00h ??? PS/2 Model 95 XP 05h Dell 220

F8h 37h 00h ??? PS/2 Model 90 XP 06h Dell 310

F8h 38h 00h ??? PS/2 Model 57 07h Dell 325

F8h 39h 00h ??? PS/2 Model 95 XP 09h Dell 310A

F8h 3Fh 00h ??? PS/2 Model 90 XP 0Ah Dell 316

F8h 40h 00h ??? PS/2 Model 95 XP 0Bh Dell 220E

F8h 41h 00h ??? PS/2 Model 77 0Ch Dell 210

F8h 45h 00h ??? PS/2 Model 90 XP (Pentium) 0Dh Dell 316SX

F8h 46h 00h ??? PS/2 Model 95 XP (Pentium) 0Eh Dell 316LT

F8h 47h 00h ??? PS/2 Model 90/95 E (Pentium) 0Fh Dell 320LX

F8h 48h 00h ??? PS/2 Model 85 11h Dell 425E

F8h 49h 00h ??? PS/ValuePoint 325T

F8h 4Ah 00h ??? PS/ValuePoint 425SX Format of Compaq product information:

F8h 4Bh 00h ??? PS/ValuePoint 433DX Address Size Description

F8h 4Eh 00h ??? PS/2 Model 295 F000h:FFE4h BYTE product family code (first byte)

F8h 50h 00h ??? PS/2 Model P70 (8573) (16 MHz 386) F000h:FFE4h BYTE Point release number

F8h 50h 01h 12/16/89 PS/2 Model P70 (8570-031) F000h:FFE4h BYTE ROM version code

F8h 52h 00h ??? PS/2 Model P75 (33 MHz 486) F000h:FFE4h BYTE product family code (second byte)

F8h 56h 00h ??? PS/2 Model CL57 SX F000h:FFE8h WORD BIOS type code

F8h 57h 00h ??? PS/2 Model 90 XP

F8h 58h 00h ??? PS/2 Model 95 XP Bitfields for Hewlett-Packard product identifier:

F8h 59h 00h ??? PS/2 Model 90 XP bits 4-0 machine code

F8h 5Ah 00h ??? PS/2 Model 95 XP 0 original Vectra

F8h 5Bh 00h ??? PS/2 Model 90 XP 1 ES/12

F8h 5Ch 00h ??? PS/2 Model 95 XP 2 RS/20

F8h 5Dh 00h ??? PS/2 Model N51 SLC 3 Portable/CS

F8h 5Eh 00h ??? IBM ThinkPad 700 4 ES

F8h 61h *** ??? Olivetti P500 5 CS

F8h 62h *** ??? Olivetti P800 6 RS/16

F8h 80h 00h ??? PS/2 Model 80 (25 MHz 386) other reserved

F8h 80h 01h 11/21/89 PS/2 Model 80-A21 bits 7-5 CPU type

F8h 81h 00h ??? PS/2 Model 55-5502 0 = 80286

F8h 87h 00h ??? PS/2 Model N33SX 1 = 8088


APÉNDICES 389

2 = 8086 actions when they are read from the keyboard buffer:

3 = 80386 38FBh or FB00h switch to next window (only if main menu

other reserved popped up)

-------- 38FCh or FC00h pop up DESQview main menu

B-1600---------------------------------------------------- 38FEh or FE00h close the current window

INT 16 - KEYBOARD - GET KEYSTROKE 38FFh or FF00h pop up DESQview learn menu

AH = 00h SeeAlso: AH=00h,AH=71h,AH=FFh,INT 15/AX=DE10h

Return: AH = BIOS scan code --------


AL = ASCII character B-1610----------------------------------------------------
Notes: on extended keyboards, this function discards any extended INT 16 - KEYBOARD - GET ENHANCED KEYSTROKE (enhanced kbd support
keystrokes, only)
returning only when a non-extended keystroke is available AH = 10h

the BIOS scan code is usually, but not always, the same as the Return: AH = BIOS scan code

hardware AL = ASCII character

scan code processed by INT 09. It is the same for ASCII keystrokes Notes: if no keystroke is available, this function waits until one is placed

and most unshifted special keys (F-keys, arrow keys, etc.), but in the keyboard buffer

differs for shifted special keys. the BIOS scan code is usually, but not always, the same as the

SeeAlso: AH=01h,AH=05h,AH=10h,AH=20h,INT 18/AH=00h hardware

-------- scan code processed by INT 09. It is the same for ASCII keystrokes

B-1601---------------------------------------------------- and most unshifted special keys (F-keys, arrow keys, etc.), but

INT 16 - KEYBOARD - CHECK FOR KEYSTROKE differs for shifted special keys.

AH = 01h unlike AH=00h, this function does not discard extended keystrokes

Return: ZF set if no keystroke available INT 16/AH=09h can be used to determine whether this function is

ZF clear if keystroke available supported, but only on later model PS/2s

AH = BIOS scan code SeeAlso: AH=00h,AH=09h,AH=11h,AH=20h

AL = ASCII character --------


Note: if a keystroke is present, it is not removed from the keyboard B-1611----------------------------------------------------
buffer; INT 16 - KEYBOARD - CHECK FOR ENHANCED KEYSTROKE (enh kbd support
however, any extended keystrokes which are not compatible with only)
83/84- AH = 11h

key keyboards are removed in the process of checking whether a Return: ZF set if no keystroke available

non-extended keystroke is available ZF clear if keystroke available

SeeAlso: AH=00h,AH=11h,AH=21h,INT 18/AH=01h AH = BIOS scan code

-------- AL = ASCII character

B-1602---------------------------------------------------- Notes: if a keystroke is available, it is not removed from the keyboard

INT 16 - KEYBOARD - GET SHIFT FLAGS buffer

AH = 02h unlike AH=01h, this function does not discard extended keystrokes

Return: AL = shift flags (see below) some versions of the IBM BIOS Technical Reference erroneously report

SeeAlso: AH=12h,AH=22h,INT 17/AH=0Dh,INT 18/AH=02h that CF is returned instead of ZF

INT 16/AH=09h can be used to determine whether this function is

Bitfields for shift flags: supported, but only on later model PS/2s

bit 7 Insert active SeeAlso: AH=01h,AH=09h,AH=10h,AH=21h

bit 6 CapsLock active --------


bit 5 NumLock active B-1612----------------------------------------------------
bit 4 ScrollLock active INT 16 - KEYBOARD - GET EXTENDED SHIFT STATES (enh kbd support
bit 3 Alt key pressed (either Alt on 101/102-key keyboards) only)
bit 2 Ctrl key pressed (either Ctrl on 101/102-key keyboards) AH = 12h

bit 1 left shift key pressed Return: AL = shift flags 1 (same as returned by AH=02h) (see below)

bit 0 right shift key pressed AH = shift flags 2 (see below)

-------- Notes: AL bit 3 set only for left Alt key on many machines

B-1605---------------------------------------------------- AH bits 7 through 4 always clear on a Compaq SLT/286

INT 16 - KEYBOARD - STORE KEYSTROKE IN KEYBOARD BUFFER (AT/PS w INT 16/AH=09h can be used to determine whether this function is

enh keybd only) supported, but only on later model PS/2s

AH = 05h SeeAlso: AH=02h,AH=09h,AH=22h,AH=51h,INT 17/AH=0Dh

CH = scan code

CL = ASCII character Bitfields for shift flags 1:

Return: AL = 00h if successful bit 7 Insert active

01h if keyboard buffer full bit 6 CapsLock active

Note: under DESQview, the following "keystrokes" invoke the following bit 5 NumLock active
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

bit 4 ScrollLock active MS-DOS 3.2+ hangs on booting (even from floppy) if the hard disk

bit 3 Alt key pressed (either Alt on 101/102-key keyboards) contains extended partitions which point at each other in a loop,

bit 2 Ctrl key pressed (either Ctrl on 101/102-key keyboards) since it will never find the end of the linked list of extended

bit 1 left shift key pressed partitions

bit 0 right shift key pressed SeeAlso: INT 14/AH=17h,INT 18

Bitfields for shift flags 2: Format of VDISK header block (at beginning of INT 19 handler's segment):

bit 7 SysRq key pressed Offset Size Description

bit 6 CapsLock pressed 00h 18 BYTEs n/a (for VDISK.SYS, the device driver header)

bit 5 NumLock pressed 12h 11 BYTEs signature string "VDISK Vn.m" for VDISK.SYS version n.m

bit 4 ScrollLock pressed 1Dh 15 BYTEs n/a

bit 3 right Alt key pressed 2Ch 3 BYTEs linear address of first byte of available extended memory

bit 2 right Ctrl key pressed

bit 1 left Alt key pressed Format of hard disk master boot sector:

bit 0 left Ctrl key pressed Offset Size Description

-------- 00h 446 BYTEs Master bootstrap loader code

B-18------------------------------------------------------ 1BEh 16 BYTEs partition record for partition 1 (see below)

INT 18 - DISKLESS BOOT HOOK (START CASSETTE BASIC) 1CEh 16 BYTEs partition record for partition 2

Desc: called when there is no bootable disk available to the system 1DEh 16 BYTEs partition record for partition 3

Notes: only PCs produced by IBM contain BASIC in ROM, so the action is 1EEh 16 BYTEs partition record for partition 4

unpredictable on compatibles; this interrupt often reboots the 1FEh WORD signature, AA55h indicates valid boot block

system, and often has no effect at all

network cards with their own BIOS can hook this interrupt to allow Format of partition record:

a diskless boot off the network (even when a hard disk is present Offset Size Description

if none of the partitions is marked as the boot partition) 00h BYTE boot indicator (80h = active partition)

SeeAlso: INT 86"NetBIOS" 01h BYTE partition start head

-------- 02h BYTE partition start sector (bits 0-5)

B-19------------------------------------------------------ 03h BYTE partition start track (bits 8,9 in bits 6,7 of sector)

INT 19 - SYSTEM - BOOTSTRAP LOADER 04h BYTE operating system indicator (see below)

Desc: This interrupt reboots the system without clearing memory or 05h BYTE partition end head

restoring 06h BYTE partition end sector (bits 0-5)

interrupt vectors. Because interrupt vectors are preserved, this 07h BYTE partition end track (bits 8,9 in bits 6,7 of sector)

interrupt usually causes a system hang if any TSRs have hooked 08h DWORD sectors preceding partition

vectors from 00h through 1Ch, particularly INT 08. 0Ch DWORD length of partition in sectors

Notes: Usually, the BIOS will try to read sector 1, head 0, track 0 from

drive Values for operating system indicator:

A: to 0000h:7C00h. If this fails, and a hard disk is installed, 00h empty

the 01h DOS 12-bit FAT

BIOS will read sector 1, head 0, track 0 of the first hard disk. 02h XENIX root file system

This sector should contain a master bootstrap loader and a 03h XENIX /usr file system (obsolete)

partition 04h DOS 16-bit FAT

table. After loading the master boot sector at 0000h:7C00h, the 05h DOS 3.3+ extended partition

master bootstrap loader is given control. It will scan the 06h DOS 3.31+ Large File System

partition 07h QNX

table for an active partition, and will then load the operating 07h OS/2 HPFS

system's bootstrap loader (contained in the first sector of the 07h Advanced Unix

active partition) and give it control. 08h AIX bootable partition, SplitDrive

true IBM PCs and most clones issue an INT 18 if neither floppy nor 09h AIX data partition

hard 09h Coherent filesystem

disk have a valid boot sector 0Ah OS/2 Boot Manager

to accomplish a warm boot equivalent to Ctrl-Alt-Del, store 1234h in 0Ah OPUS

0040h:0072h and jump to FFFFh:0000h. For a cold boot equivalent to 0Ah Coherent swap partition

a reset, store 0000h at 0040h:0072h before jumping. 10h OPUS

VDISK.SYS hooks this interrupt to allow applications to find out how 18h AST special Windows swap file

much extended memory has been used by VDISKs (see below). DOS 3.3+ 24h NEC MS-DOS 3.x

PRINT hooks INT 19 but does not set up a correct VDISK header block 40h VENIX 80286

at the beginning of its INT 19 handler segment, thus causing some 50h Disk Manager, read-only partition

programs to overwrite extended memory which is already in use. 51h Disk Manager, read/write partition

the default handler is at F000h:E6F2h for 100% compatible BIOSes 51h Novell???
APÉNDICES 389

52h CP/M bits 3-0: head unload time (0Fh = 240 ms)

52h Microport System V/386 01h BYTE second specify byte

56h GoldenBow VFeature bits 7-1: head load time (01h = 4 ms)

61h SpeedStor bit 0: non-DMA mode (always 0)

63h Unix SysV/386, 386/ix 02h BYTE delay until motor turned off (in clock ticks)

63h Mach, MtXinu BSD 4.3 on Mach 03h BYTE bytes per sector (00h = 128, 01h = 256, 02h = 512, 03h =

63h GNU HURD 1024)

64h Novell NetWare 04h BYTE sectors per track

65h Novell NetWare (3.11) 05h BYTE length of gap between sectors (2Ah for 5.25", 1Bh for 3.5")

70h DiskSecure Multi-Boot 06h BYTE data length (ignored if bytes-per-sector field nonzero)

75h PC/IX 07h BYTE gap length when formatting (50h for 5.25", 6Ch for 3.5")

80h Minix v1.1 - 1.4a 08h BYTE format filler byte (default F6h)

81h Minix v1.4b+ 09h BYTE head settle time in milliseconds

81h Linux 0Ah BYTE motor start time in 1/8 seconds

81h Mitac Advanced Disk Manager --------


82h Linux Swap partition (planned) B-1F------------------------------------------------------
84h OS/2-renumbered type 04h partition (related to hiding DOS C: drive) INT 1F - SYSTEM DATA - 8x8 GRAPHICS FONT
93h Amoeba file system Desc: this vector points at 1024 bytes of graphics data, 8 bytes for each

94h Amoeba bad block table character 80h-FFh

B7h BSDI file system (secondarily swap) Note: graphics data for characters 00h-7Fh stored at F000h:FA6Eh in 100%

B8h BSDI swap partition (secondarily file system) compatible BIOSes

C1h DR-DOS 6.0 LOGIN.EXE-secured 12-bit FAT partition SeeAlso: INT 10/AX=5000h,INT 43

C4h DR-DOS 6.0 LOGIN.EXE-secured 16-bit FAT partition --------


C6h DR-DOS 6.0 LOGIN.EXE-secured Huge partition D-20------------------------------------------------------
DBh CP/M, Concurrent CP/M, Concurrent DOS INT 20 - DOS 1+ - TERMINATE PROGRAM
DBh CTOS (Convergent Technologies OS) CS = PSP segment

E1h SpeedStor 12-bit FAT extended partition Return: never

E4h SpeedStor 16-bit FAT extended partition Note: (see INT 21/AH=00h)

F2h DOS 3.3+ secondary SeeAlso: INT 21/AH=00h,INT 21/AH=4Ch

FEh LANstep --------


FFh Xenix bad block table D-2102----------------------------------------------------
-------- INT 21 - DOS 1+ - WRITE CHARACTER TO STANDARD OUTPUT
B-1B------------------------------------------------------ AH = 02h

INT 1B C - KEYBOARD - CONTROL-BREAK HANDLER DL = character to write

Desc: this interrupt is automatically called when INT 09 determines that Return: AL = last character output (despite the official docs which state

Control-Break has been pressed nothing is returned) (at least DOS 3.3-5.0)

Note: normally points to a short routine in DOS which sets the Ctrl-C flag, Notes: ^C/^Break are checked, and INT 23 executed if pressed

thus invoking INT 23h the next time DOS checks for Ctrl-C. standard output is always the screen under DOS 1.x, but may be

SeeAlso: INT 23 redirected under DOS 2+

-------- the last character output will be the character in DL unless DL=09h

B-1C------------------------------------------------------ on entry, in which case AL=20h as tabs are expanded to blanks

INT 1C - TIME - SYSTEM TIMER TICK SeeAlso: AH=06h,AH=09h

Desc: this interrupt is automatically called on each clock tick by the INT --------
08 D-2109----------------------------------------------------
handler INT 21 - DOS 1+ - WRITE STRING TO STANDARD OUTPUT
Notes: this is the preferred interrupt to chain when a program needs to be AH = 09h

invoked regularly DS:DX -> '$'-terminated string

not available on NEC 9800-series PCs Return: AL = 24h (the '$' terminating the string, despite official docs which

SeeAlso: INT 08 state that nothing is returned) (at least DOS 3.3-5.0)

-------- Notes: ^C/^Break are checked, and INT 23 is called if either pressed

B-1E------------------------------------------------------ standard output is always the screen under DOS 1.x, but may be

INT 1E - SYSTEM DATA - DISKETTE PARAMETERS redirected under DOS 2+

Note: default parameter table at F000h:EFC7h for 100% compatible BIOSes under the FlashTek X-32 DOS extender, the pointer is in DS:EDX

SeeAlso: INT 13/AH=0Fh,INT 41 SeeAlso: AH=02h,AH=06h"OUTPUT"

--------
Format of diskette parameter table: D-210A----------------------------------------------------
Offset Size Description INT 21 - DOS 1+ - BUFFERED INPUT
00h BYTE first specify byte AH = 0Ah

bits 7-4: step rate DS:DX -> buffer (see below)


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

Return: buffer filled with user input DL = 1/100 seconds

Notes: ^C/^Break are checked, and INT 23 is called if either detected Note: on most systems, the resolution of the system clock is about

reads from standard input, which may be redirected under DOS 2+ 5/100sec,

if the maximum buffer size (see below) is set to 00h, this call so returned times generally do not increment by 1

returns on some systems, DL may always return 00h

immediately without reading any input SeeAlso: AH=2Ah,AH=2Dh,AH=E7h,INT 1A/AH=00h,INT 1A/AH=02h,INT 1A/AH=FEh

SeeAlso: AH=0Ch,INT 2F/AX=4810h SeeAlso: INT 2F/AX=120Dh

--------
Format of input buffer: D-212F----------------------------------------------------
Offset Size Description INT 21 - DOS 2+ - GET DISK TRANSFER AREA ADDRESS
00h BYTE maximum characters buffer can hold AH = 2Fh

01h BYTE (input) number of chars from last input which may be recalled Return: ES:BX -> current DTA

(return) number of characters actually read, excluding CR Note: under the FlashTek X-32 DOS extender, the pointer is in ES:EBX

02h N BYTEs actual characters read, including the final carriage return SeeAlso: AH=1Ah

-------- --------
D-211A---------------------------------------------------- D-2130----------------------------------------------------
INT 21 - DOS 1+ - SET DISK TRANSFER AREA ADDRESS INT 21 - DOS 2+ - GET DOS VERSION
AH = 1Ah AH = 30h

DS:DX -> Disk Transfer Area (DTA) ---DOS 5+ ---

Notes: the DTA is set to PSP:0080h when a program is started AL = what to return in BH

under the FlashTek X-32 DOS extender, the pointer is in DS:EDX 00h OEM number (as for DOS 2.0-4.0x)

SeeAlso: AH=11h,AH=12h,AH=2Fh,AH=4Eh,AH=4Fh 01h version flag

-------- Return: AL = major version number (00h if DOS 1.x)

D-2125---------------------------------------------------- AH = minor version number

INT 21 - DOS 1+ - SET INTERRUPT VECTOR BL:CX = 24-bit user serial number (most versions do not use this)

AH = 25h ---if DOS <5 or AL=00h---

AL = interrupt number BH = MS-DOS OEM number (see below)

DS:DX -> new interrupt handler ---if DOS 5+ and AL=01h---

Notes: this function is preferred over direct modification of the interrupt BH = version flag

vector table bit 3: DOS is in ROM

some DOS extenders place an API on this function, as it is not other: reserved (0)

directly meaningful in protected mode Notes: the OS/2 v1.x Compatibility Box returns major version 0Ah (10)

under DR-DOS 5.0+, this function does not use any of the DOS-internal the OS/2 v2.x Compatibility Box returns major version 14h (20)

stacks and may thus be called at any time the Windows/NT DOS box returns version 5.00, subject to SETVER

Novell NetWare (except the new DOS Requester) monitors the offset of DOS 4.01 and 4.02 identify themselves as version 4.00; use

any INT 24 set, and if equal to the value at startup, substitutes INT 21/AH=87h to distinguish between the original European MS-DOS

its own handler to allow handling of network errors; this 4.0

introduces and the later PC-DOS 4.0x and MS-DOS 4.0x

the potential bug that any program whose INT 24 handler offset IBM DOS 6.1 reports its version as 6.00; use the OEM number to

happens to be the same as COMMAND.COM's will not have its INT 24 distinguish between MS-DOS 6.00 and IBM DOS 6.1 (there was never an

handler installed IBM DOS 6.0)

SeeAlso: AX=2501h,AH=35h generic MS-DOS 3.30, Compaq MS-DOS 3.31, and others identify

-------- themselves

D-212A---------------------------------------------------- as PC-DOS by returning OEM number 00h

INT 21 - DOS 1+ - GET SYSTEM DATE the version returned under DOS 4.0x may be modified by entries in

AH = 2Ah the special program list (see AH=52h); the version returned under

Return: CX = year (1980-2099) DOS 5+ may be modified by SETVER--use AX=3306h to get the true

DH = month version number

DL = day SeeAlso: AX=3000h/BX=3000h,AX=3306h,AX=4452h,AH=87h,INT 15/AX=4900h

---DOS 1.10+--- SeeAlso: INT 2F/AX=122Fh,INT 2F/AX=E002h

AL = day of week (00h=Sunday)

SeeAlso: AH=2Bh"DOS",AH=2Ch,AH=E7h,INT 1A/AH=04h,INT 2F/AX=120Dh Values for DOS OEM number:

-------- 00h IBM

D-212C---------------------------------------------------- 01h Compaq

INT 21 - DOS 1+ - GET SYSTEM TIME 02h MS Packaged Product

AH = 2Ch 04h AT&T

Return: CH = hour 05h Zenith

CL = minute 06h Hewlett-Packard

DH = second 0Dh Packard-Bell


APÉNDICES 389

16h DEC --------


23h Olivetti D-2136----------------------------------------------------
29h Toshiba INT 21 - DOS 2+ - GET FREE DISK SPACE
33h Novell (Windows/386 device IDs only) AH = 36h

34h MS Multimedia Systems (Windows/386 device IDs only) DL = drive number (00h = default, 01h = A:, etc)

35h MS Multimedia Systems (Windows/386 device IDs only) Return: AX = FFFFh if invalid drive

4Dh Hewlett-Packard else

66h PhysTechSoft (PTS-DOS) AX = sectors per cluster

99h General Software's Embedded DOS BX = number of free clusters

EEh DR-DOS CX = bytes per sector

EFh Novell DOS DX = total clusters on drive

FFh Microsoft, Phoenix Notes: free space on drive in bytes is AX * BX * CX

-------- total space on drive in bytes is AX * CX * DX

D-2131---------------------------------------------------- "lost clusters" are considered to be in use

INT 21 - DOS 2+ - TERMINATE AND STAY RESIDENT according to Dave Williams' MS-DOS reference, the value in DX is

AH = 31h incorrect for non-default drives after ASSIGN is run

AL = return code SeeAlso: AH=1Bh,AH=1Ch

DX = number of paragraphs to keep resident --------


Return: never D-2138----------------------------------------------------
Notes: the value in DX only affects the memory block containing the PSP; INT 21 - DOS 2+ - GET COUNTRY-SPECIFIC INFORMATION
additional memory allocated via AH=48h is not affected AH = 38h

the minimum number of paragraphs which will remain resident is 11h --DOS 2.x--

for DOS 2.x and 06h for DOS 3+ AL = 00h get current-country info

most TSRs can save some memory by releasing their environment block DS:DX -> buffer for returned info (see below)

before terminating (see AH=26h,AH=49h) Return: CF set on error

SeeAlso: AH=00h,AH=4Ch,AH=4Dh,INT 20,INT 22,INT 27 AX = error code (02h)

-------- CF clear if successful

D-2134---------------------------------------------------- AX = country code (MS-DOS 2.11 only)

INT 21 - DOS 2+ - GET ADDRESS OF INDOS FLAG buffer at DS:DX filled

AH = 34h --DOS 3+--

Return: ES:BX -> one-byte InDOS flag AL = 00h for current country

Notes: the value of InDOS is incremented whenever an INT 21 function begins AL = 01h thru 0FEh for specific country with code <255

and decremented whenever one completes AL = 0FFh for specific country with code >= 255

during an INT 28 call, it is safe to call some INT 21 functions even BX = 16-bit country code

though InDOS may be 01h instead of zero DS:DX -> buffer for returned info (see below)

InDOS alone is not sufficient for determining when it is safe to Return: CF set on error

enter DOS, as the critical error handling decrements InDOS and AX = error code (02h)

increments the critical error flag for the duration of the critical CF clear if successful

error. Thus, it is possible for InDOS to be zero even if DOS is BX = country code

busy. DS:DX buffer filled

SMARTDRV 4.0 sets the InDOS flag while flushing its buffers to disk, Note: this function is not supported by the Borland DPMI host, but no error

then zeros it on completion is returned; as a workaround, one should allocate a buffer in

the critical error flag is the byte immediately following InDOS in conventional memory with INT 31/AX=0100h and simulate an INT 21

DOS 2.x, and the byte BEFORE the InDOS flag in DOS 3+ and with

DR-DOS 3.41+ (except COMPAQ DOS 3.0, where the critical error flag INT 31/AX=0300h

is located 1AAh bytes BEFORE the critical section flag) SeeAlso: AH=65h,INT 10/AX=5001h,INT 2F/AX=110Ch,INT 2F/AX=1404h

for DOS 3.1+, an undocumented call exists to get the address of the

critical error flag (see AX=5D06h) Format of DOS 2.00-2.10 country info:

this function was undocumented prior to the release of DOS 5.0. Offset Size Description

SeeAlso: AX=5D06h,AX=5D0Bh,INT 15/AX=DE1Fh,INT 28 00h WORD date format 0 = USA mm dd yy

-------- 1 = Europe dd mm yy

D-2135---------------------------------------------------- 2 = Japan yy mm dd

INT 21 - DOS 2+ - GET INTERRUPT VECTOR 02h BYTE currency symbol

AH = 35h 03h BYTE 00h

AL = interrupt number 04h BYTE thousands separator char

Return: ES:BX -> current interrupt handler 05h BYTE 00h

Note: under DR-DOS 5.0+, this function does not use any of the DOS-internal 06h BYTE decimal separator char

stacks and may thus be called at any time 07h BYTE 00h

SeeAlso: AH=25h,AX=2503h 08h 24 BYTEs reserved


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

CX = file attributes (see below)

Format of DOS 2.11+ country info: DS:DX -> ASCIZ filename

Offset Size Description Return: CF clear if successful

00h WORD date format (see above) AX = file handle

02h 5 BYTEs ASCIZ currency symbol string CF set on error

07h 2 BYTEs ASCIZ thousands separator AX = error code (03h,04h,05h) (see AH=59h)

09h 2 BYTEs ASCIZ decimal separator Notes: if a file with the given name exists, it is truncated to zero length

0Bh 2 BYTEs ASCIZ date separator under the FlashTek X-32 DOS extender, the pointer is in DS:EDX

0Dh 2 BYTEs ASCIZ time separator DR-DOS checks the system password or explicitly supplied password at

0Fh BYTE currency format the end of the filename against the reserved field in the directory

bit 2 = set if currency symbol replaces decimal point entry before allowing access

bit 1 = number of spaces between value and currency symbol SeeAlso: AH=16h,AH=3Dh,AH=5Ah,AH=5Bh,AH=93h,INT 2F/AX=1117h

bit 0 = 0 if currency symbol precedes value

1 if currency symbol follows value Bitfields for file attributes:

10h BYTE number of digits after decimal in currency bit 0 read-only

11h BYTE time format bit 1 hidden

bit 0 = 0 if 12-hour clock bit 2 system

1 if 24-hour clock bit 3 volume label (ignored)

12h DWORD address of case map routine bit 4 reserved, must be zero (directory)

(FAR CALL, AL = character to map to upper case [>= 80h]) bit 5 archive bit

16h 2 BYTEs ASCIZ data-list separator bit 7 if set, file is shareable under Novell NetWare

18h 10 BYTEs reserved --------


D-213D----------------------------------------------------
Values for country code: INT 21 - DOS 2+ - "OPEN" - OPEN EXISTING FILE
001h United States AH = 3Dh

002h Canadian-French AL = access and sharing modes (see below)

003h Latin America DS:DX -> ASCIZ filename

01Fh Netherlands CL = attribute mask of files to look for (server call only)

020h Belgium Return: CF clear if successful

021h France AX = file handle

022h Spain CF set on error

024h Hungary (not supported by DR-DOS 5.0) AX = error code (01h,02h,03h,04h,05h,0Ch,56h) (see AH=59h)

026h Yugoslavia (not supported by DR-DOS 5.0) Notes: file pointer is set to start of file

027h Italy file handles which are inherited from a parent also inherit sharing

029h Switzerland and access restrictions

02Ah Czechoslovakia/Tjekia (not supported by DR-DOS 5.0) files may be opened even if given the hidden or system attributes

02Bh Austria (DR-DOS 5.0) under the FlashTek X-32 DOS extender, the pointer is in DS:EDX

02Ch United Kingdom DR-DOS checks the system password or explicitly supplied password at

02Dh Denmark the end of the filename against the reserved field in the directory

02Eh Sweden entry before allowing access

02Fh Norway sharing modes are only effective on local drives if SHARE is loaded

030h Poland (not supported by DR-DOS 5.0) SeeAlso: AH=0Fh,AH=3Ch,AX=4301h,AX=5D00h,INT 2F/AX=1116h,INT 2F/AX=1226h

031h Germany

037h Brazil (not supported by DR-DOS 5.0) Bitfields for access and sharing modes:

03Dh International English [Australia in DR-DOS 5.0] bits 2-0 access mode

051h Japan (DR-DOS 5.0, MS-DOS 5.0+) 000 read only

052h Korea (DR-DOS 5.0) 001 write only

056h China (MS-DOS 5.0+) 010 read/write

058h Taiwan (MS-DOS 5.0+) 011 (DOS 5+ internal) passed to redirector on EXEC to allow

05Ah Turkey (MS-DOS 5.0+) case-sensitive filenames

15Fh Portugal bit 3 reserved (0)

162h Iceland bits 6-4 sharing mode (DOS 3+)

166h Finland 000 compatibility mode

311h Middle East/Saudi Arabia (DR-DOS 5.0,MS-DOS 5.0+) 001 "DENYALL" prohibit both read and write access by others

3CCh Israel (DR-DOS 5.0,MS-DOS 5.0+) 010 "DENYWRITE" prohibit write access by others

-------- 011 "DENYREAD" prohibit read access by others

D-213C---------------------------------------------------- 100 "DENYNONE" allow full access by others

INT 21 - DOS 2+ - "CREAT" - CREATE OR TRUNCATE FILE 111 network FCB (only available during server call)

AH = 3Ch bit 7 inheritance


APÉNDICES 389

if set, file is private to current process and will not be inherited is updated after a successful read

by child processes the returned AX may be smaller than the request in CX if a partial

read occurred

File sharing behavior: if reading from CON, read stops at first CR

| Second and subsequent Opens under the FlashTek X-32 DOS extender, the pointer is in DS:EDX

First |Compat Deny Deny Deny Deny SeeAlso: AH=27h,AH=40h,AH=93h,INT 2F/AX=1108h,INT 2F/AX=1229h

Open | All Write Read None --------


|R W RW R W RW R W RW R W RW R W RW D-2140----------------------------------------------------
- - - - -| - - - - - - - - - - - - - - - - - INT 21 - DOS 2+ - "WRITE" - WRITE TO FILE OR DEVICE
Compat R |Y Y Y N N N 1 N N N N N 1 N N AH = 40h

W |Y Y Y N N N N N N N N N N N N BX = file handle

RW|Y Y Y N N N N N N N N N N N N CX = number of bytes to write

- - - - -| DS:DX -> data to write

Deny R |C C C N N N N N N N N N N N N Return: CF clear if successful

All W |C C C N N N N N N N N N N N N AX = number of bytes actually written

RW|C C C N N N N N N N N N N N N CF set on error

- - - - -| AX = error code (05h,06h) (see AH=59h)

Deny R |2 C C N N N Y N N N N N Y N N Notes: if CX is zero, no data is written, and the file is truncated or

Write W |C C C N N N N N N Y N N Y N N extended to the current position

RW|C C C N N N N N N N N N Y N N data is written beginning at the current file position, and the file

- - - - -| position is updated after a successful write

Deny R |C C C N N N N Y N N N N N Y N the usual cause for AX < CX on return is a full disk

Read W |C C C N N N N N N N Y N N Y N BUG: a write of zero bytes will appear to succeed when it actually failed

RW|C C C N N N N N N N N N N Y N if the write is extending the file and there is not enough disk

- - - - -| space for the expanded file (DOS 5.0-6.0); one should therefore

Deny R |2 C C N N N Y Y Y N N N Y Y Y check

None W |C C C N N N N N N Y Y Y Y Y Y whether the file was in fact extended by seeking to 0 bytes from

RW|C C C N N N N N N N N N Y Y Y the end of the file (INT 21/AX=4202h/CX=0/DX=0)

Legend: Y = open succeeds, N = open fails with error code 05h under the FlashTek X-32 DOS extender, the pointer is in DS:EDX

C = open fails, INT 24 generated SeeAlso: AH=28h,AH=3Fh,AH=93h,INT 2F/AX=1109h

1 = open succeeds if file read-only, else fails with error code --------
2 = open succeeds if file read-only, else fails with INT 24 O-214452--------------------------------------------------
-------- INT 21 - DR-DOS 3.41+ - DETERMINE DOS TYPE/GET DR-DOS VERSION
D-213E---------------------------------------------------- AX = 4452h ("DR")

INT 21 - DOS 2+ - "CLOSE" - CLOSE FILE CF set

AH = 3Eh Return: CF set if not DR-DOS

BX = file handle AX = error code (see AH=59h)

Return: CF clear if successful CF clear if DR-DOS

AX destroyed DX = AX = version code

CF set on error AH = single-user/multiuser nature

AX = error code (06h) (see AH=59h) 10h single-user

Note: if the file was written to, any pending disk writes are performed, AL = operating system version ID (see below)

the 14h multiuser

time and date stamps are set to the current time, and the directory AL = operating system version ID (see AX=4451h)

entry is updated Notes: the DR-DOS version is stored in the environment variable VER

SeeAlso: AH=10h,AH=3Ch,AH=3Dh,INT 2F/AX=1106h,INT 2F/AX=1227h use this function if looking for single-user capabilities, AX=4451h

-------- if looking for multiuser; this call should never return multiuser

D-213F---------------------------------------------------- values

INT 21 - DOS 2+ - "READ" - READ FROM FILE OR DEVICE SeeAlso: AX=4412h,AX=4451h,AX=4459h

AH = 3Fh

BX = file handle Values for operating system version ID:

CX = number of bytes to read 60h DOS Plus

DS:DX -> buffer for data 63h DR-DOS 3.41

Return: CF clear if successful 64h DR-DOS 3.42

AX = number of bytes actually read (0 if at EOF before call) 65h DR-DOS 5.00

CF set on error 67h DR-DOS 6.00

AX = error code (05h,06h) (see AH=59h) 70h PalmDOS

Notes: data is read beginning at current file position, and the file 71h DR-DOS 6.0 March 1993 "business update"

position 72h Novell DOS 7.0


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

--------
O-214458-------------------------------------------------- Format of HMA Memory Block (DR-DOS 6.0 kernel loaded in HMA):

INT 21 U - DR-DOS 5.0+ internal - GET POINTER TO INTERNAL Offset Size Description

VARIABLE TABLE 00h WORD offset of next HMA Memory Block (0000h if last block)

AX = 4458h 02h WORD size of this block in bytes (at least 10h)

Return: ES:BX -> internal variable table (see below) 04h BYTE type of HMA Memory Block (interpreted by MEM)

AX = ??? (0B50h for DR-DOS 5.0, 0A56h for DR-DOS 6.0) 00h system

SeeAlso: AX=4452h 01h KEYB

02h NLSFUNC

Format of internal variable table: 03h SHARE

Offset Size Description 04h TaskMAX

00h WORD ??? 05h COMMAND

02h WORD segment of ??? 05h var TSR (or system) code and data. DR-DOS TSR's, such as KEYB,

04h 7 BYTEs ??? hooks interrupts using segment FFFEh instead FFFFh.

0Bh WORD KB of extended memory at startup --------


0Dh BYTE number of far jump entry points D-2148----------------------------------------------------
0Eh WORD segment containing far jumps to DR-DOS entry points (see INT 21 - DOS 2+ - ALLOCATE MEMORY
below) AH = 48h

10h WORD (only if kernel loaded in HMA) offset in HMA of first free BX = number of paragraphs to allocate

HMA Return: CF clear if successful

memory block (see below) or 0000h if none; segment is FFFFh AX = segment of allocated block

12h WORD pointer to segment of environment variables set in CONFIG, CF set on error

or 0000h if already used AX = error code (07h,08h) (see AH=59h)

---DR-DOS 6.0--- BX = size of largest available block

14h WORD (only if kernel loaded in HMA) offset in HMA of first used Notes: DOS 2.1-6.0 coalesces free blocks while scanning for a block to

HMA allocate

memory block (see below) or 0000h if none; segment is FFFFh .COM programs are initially allocated the largest available memory

Note: the segment used for the DR-DOS 6.0 CONFIG environment variables block, and should free some memory with AH=49h before attempting

(excluding COMSPEC, VER and OS) is only useful for programs/drivers any

called from CONFIG.SYS. The word is set to zero later when the area allocations

is copied to the COMMAND.COM environment space. This allows under the FlashTek X-32 DOS extender, EBX contains a protected-mode

CONFIG.SYS to pass information to AUTOEXEC.BAT. near pointer to the allocated block on a successful return

SeeAlso: AH=49h,AH=4Ah,AH=58h,AH=83h

Format of kernel entry jump table for DR-DOS 5.0-6.0: --------


Offset Size Description D-2149----------------------------------------------------
00h 5 BYTEs far jump to kernel entry point for CP/M CALL 5 INT 21 - DOS 2+ - FREE MEMORY
05h 5 BYTEs far jump to kernel entry point for INT 20 AH = 49h

0Ah 5 BYTEs far jump to kernel entry point for INT 21 ES = segment of block to free

0Fh 5 BYTEs far jump to kernel entry point for INT 22 (RETF) Return: CF clear if successful

14h 5 BYTEs far jump to kernel entry point for INT 23 (RETF) CF set on error

19h 5 BYTEs far jump to kernel entry point for INT 24 AX = error code (07h,09h) (see AH=59h)

1Eh 5 BYTEs far jump to kernel entry point for INT 25 Notes: apparently never returns an error 07h, despite official docs; DOS

23h 5 BYTEs far jump to kernel entry point for INT 26 2.1+

28h 5 BYTEs far jump to kernel entry point for INT 27 code contains only an error 09h exit

2Dh 5 BYTEs far jump to kernel entry point for INT 28 DOS 2.1-6.0 does not coalesce adjacent free blocks when a block is

32h 5 BYTEs far jump to kernel entry point for INT 2A (IRET) freed, only when a block is allocated or resized

37h 5 BYTEs far jump to kernel entry point for INT 2B (IRET) the code for this function is identical in DOS 2.1-6.0 except for

3Ch 5 BYTEs far jump to kernel entry point for INT 2C (IRET) calls to start/end a critical section in DOS 3+

41h 5 BYTEs far jump to kernel entry point for INT 2D (IRET) SeeAlso: AH=48h,AH=4Ah

46h 5 BYTEs far jump to kernel entry point for INT 2E (IRET) --------
4Bh 5 BYTEs far jump to kernel entry point for INT 2F D-214A----------------------------------------------------
Notes: all of these entry points are indirected through this jump table INT 21 - DOS 2+ - RESIZE MEMORY BLOCK
to allow the kernel to be relocated into high memory while leaving AH = 4Ah

the actual entry addresses in low memory for maximum compatibility BX = new size in paragraphs

some of these entry points (22h,23h,24h,2Eh,2Fh) are replaced as soon ES = segment of block to resize

as COMMAND.COM is loaded, and return immediately to the caller, Return: CF clear if successful

some CF set on error

returning an error code (the original handler for INT 2F returns AX = error code (07h,08h,09h) (see AH=59h)

AL=03h [fail]). BX = maximum paragraphs available for specified memory block


APÉNDICES 389

Notes: under DOS 2.1-6.0, if there is insufficient memory to expand the names for the various executable type understood by various

block environments:

as much as requested, the block will be made as large as possible MZ old-style DOS executable

DOS 2.1-6.0 coalesces any free blocks immediately following the block NE Windows or OS/2 1.x segmented ("new") executable

to be resized LE Windows virtual device driver (VxD) linear executable

SeeAlso: AH=48h,AH=49h,AH=83h LX variant of LE used in OS/2 2.x

-------- W3 Windows WIN386.EXE file; a collection of LE files

D-214B---------------------------------------------------- PE Win32 (Windows NT and Win32s) portable executable based

INT 21 - DOS 2+ - "EXEC" - LOAD AND/OR EXECUTE PROGRAM on

AH = 4Bh Unix COFF

AL = type of load BUGS: DOS 2.00 assumes that DS points at the current program's PSP

00h load and execute Load Overlay (subfunction 03h) loads up to 512 bytes too many if the

01h load but do not execute file contains additional data after the actual overlay

03h load overlay SeeAlso: AX=4B05h,AH=4Ch,AH=4Dh,AH=64h"OS/2",AH=8Ah,INT 2E

04h load and execute in background (European MS-DOS 4.0 only)

"Exec & Go" (see also AH=80h) Format of EXEC parameter block for AL=00h,01h,04h:

DS:DX -> ASCIZ program name (must include extension) Offset Size Description

ES:BX -> parameter block (see below) 00h WORD segment of environment to copy for child process (copy

CX = mode (subfunction 04h only) caller's

0000h child placed in zombie mode after termination environment if 0000h)

0001h child's return code discarded on termination 02h DWORD pointer to command tail to be copied into child's PSP

Return: CF clear if successful 06h DWORD pointer to first FCB to be copied into child's PSP

BX,DX destroyed 0Ah DWORD pointer to second FCB to be copied into child's PSP

if subfunction 01h, process ID set to new program's PSP; get with 0Eh DWORD (AL=01h) will hold subprogram's initial SS:SP on return

INT 21/AH=62h 12h DWORD (AL=01h) will hold entry point (CS:IP) on return

CF set on error

AX = error code (01h,02h,05h,08h,0Ah,0Bh) (see AH=59h) Format of EXEC parameter block for AL=03h:

Notes: DOS 2.x destroys all registers, including SS:SP Offset Size Description

under ROM-based DOS, if no disk path characters (colons or slashes) 00h WORD segment at which to load overlay

are included in the program name, the name is searched for in the 02h WORD relocation factor to apply to overlay if in .EXE format

ROM module headers (see below) before searching on disk

for functions 00h and 01h, the calling process must ensure that there Format of EXEC parameter block for FlashTek X-32:

is enough unallocated memory available; if necessary, by releasing Offset Size Description

memory with AH=49h or AH=4Ah 00h PWORD 48-bit far pointer to environment string

for function 01h, the AX value to be passed to the child program is 06h PWORD 48-bit far pointer to command tail string

put

on top of the child's stack Format of .EXE file header:

for function 03h, DOS assumes that the overlay is being loaded into Offset Size Description

memory allocated by the caller 00h 2 BYTEs .EXE signature, either "MZ" or "ZM" (5A4Dh or 4D5Ah)

function 01h was undocumented prior to the release of DOS 5.0 02h WORD number of bytes in last 512-byte page of executable

some versions (such as DR-DOS 6.0) check the parameters and parameter 04h WORD total number of 512-byte pages in executable (includes any

block and return an error if an invalid value (such as an offset of partial last page)

FFFFh) is found 06h WORD number of relocation entries

background programs under European MS-DOS 4.0 must use the new 08h WORD header size in paragraphs

executable format 0Ah WORD minimum paragraphs of memory to allocation in addition to

new executables begin running with the following register values executable's size

AX = environment segment 0Ch WORD maximum paragraphs to allocate in addition to executable's

BX = offset of command tail in environment segment size

CX = size of automatic data segment (0000h = 64K) 0Eh WORD initial SS relative to start of executable

ES,BP = 0000h 10h WORD initial SP

DS = automatic data segment 12h WORD checksum (one's complement of sum of all words in executable)

SS:SP = initial stack 14h DWORD initial CS:IP relative to start of executable

the command tail corresponds to an old executable's PSP:0081h and 18h WORD offset within header of relocation table

following, except that the 0Dh is turned into a NUL (00h); new 40h or greater for new-format (NE,LE,LX,W3,PE,etc.)

format executables have no PSP executable

under the FlashTek X-32 DOS extender, only function 00h is supported 1Ah WORD overlay number (normally 0000h = main program)

and the pointers are passed in DS:EDX and ES:EBX ---new executable---

DR-DOS 6 always loads .EXE-format programs with no fixups above the 1Ch 4 BYTEs ???

64K mark to avoid the EXEPACK bug 20h WORD behavior bits
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

22h 26 BYTEs reserved for additional behavior info 00h 2 BYTEs ROM signature 55h, AAh

3Ch DWORD offset of new executable (NE,LE,etc) header within disk file, 02h BYTE size of ROM in 512-byte blocks

or 00000000h if plain MZ executable 03h 3 BYTEs POST initialization entry point (near JMP instruction)

---Borland TLINK--- 06h ROM Program Name List [array]

1Ch 2 BYTEs ??? (apparently always 01h 00h) Offset Size Description

1Eh BYTE signature FBh 00h BYTE length of ROM program's name (00h if end of name

1Fh BYTE TLINK version (major in high nybble, minor in low nybble) list)

20h 2 BYTEs ??? (v2.0 apparently always 72h 6Ah, v3.0+ seems always 6Ah 01h N BYTEs program name

72h) N+1 3 BYTEs program entry point (near JMP instruction)

---ARJ self-extracting archive---

1Ch 4 BYTEs signature "RJSX" (older versions, new signature is "aRJsfX" Format of new executable header:

in Offset Size Description

the first 1000 bytes of the file) 00h 2 BYTEs "NE" (4Eh 45h) signature

---LZEXE 0.90 compressed executable--- 02h 2 BYTEs linker version (major, then minor)

1Ch 4 BYTEs signature "LZ09" 04h WORD offset from start of this header to entry table (see below)

---LZEXE 0.91 compressed executable--- 06h WORD length of entry table in bytes

1Ch 4 BYTEs signature "LZ91" 08h DWORD file load CRC (0 in Borland's TPW)

---PKLITE compressed executable--- 0Ch BYTE program flags

1Ch BYTE minor version number bits 0-1 DGROUP type

1Dh BYTE bits 0-3: major version 0 = none

bit 4: extra compression 1 = single shared

bit 5: huge (multi-segment) file 2 = multiple (unshared)

1Eh 6 BYTEs signature "PKLITE" (followed by copyright message) 3 = (null)

---LHarc 1.x self-extracting archive--- bit 2: global initialization

1Ch 4 BYTEs unused??? bit 3: protected mode only

20h 3 BYTEs jump to start of extraction code bit 4: 8086 instructions

23h 2 BYTEs ??? bit 5: 80286 instructions

25h 12 BYTEs signature "LHarc's SFX " bit 6: 80386 instructions

---LHA 2.x self-extracting archive--- bit 7: 80x87 instructions

1Ch 8 BYTEs ??? 0Dh BYTE application flags

24h 10 BYTEs signature "LHa's SFX " (v2.10) or "LHA's SFX " (v2.13) bits 0-2: application type

---TopSpeed C 3.0 CRUNCH compressed file--- 001 full screen (not aware of Windows/P.M. API)

1Ch DWORD 018A0001h 010 compatible with Windows/P.M. API

20h WORD 1565h 011 uses Windows/P.M. API

---PKARCK 3.5 self-extracting archive--- bit 3: is a Family Application (OS/2)

1Ch DWORD 00020001h bit 5: 0=executable, 1=errors in image

20h WORD 0700h bit 6: non-conforming program (valid stack is not maintained)

---BSA (Soviet archiver) self-extracting archive--- bit 7: DLL or driver rather than application

1Ch WORD 000Fh (SS:SP info invalid, CS:IP points at FAR init routine

1Eh BYTE A7h called with AX=module handle which returns AX=0000h

---LARC self-extracting archive--- on failure, AX nonzero on successful initialization)

1Ch 4 BYTEs ??? 0Eh WORD auto data segment index

20h 11 BYTEs "SFX by LARC " 10h WORD initial local heap size

---LH self-extracting archive--- 12h WORD initial stack size (added to data seg, 0000h if SS <> DS)

1Ch 8 BYTEs ??? 14h DWORD program entry point (CS:IP), "CS" is index into segment table

24h 8 BYTEs "LH's SFX " 18h DWORD initial stack pointer (SS:SP), "SS" is segment index

---other linkers--- if SS=automatic data segment and SP=0000h, the stack pointer

1Ch var optional information is

--- set to the top of the automatic data segment, just below

N N DWORDs relocation items the

Notes: if word at offset 02h is 4, it should be treated as 00h, since local heap

pre-1.10 1Ch WORD segment count

versions of the MS linker set it that way 1Eh WORD module reference count

if both minimum and maximum allocation (offset 0Ah/0Ch) are zero, the 20h WORD length of nonresident names table in bytes

program is loaded as high in memory as possible 22h WORD offset from start of this header to segment table (see below)

the maximum allocation is set to FFFFh by default 24h WORD offset from start of this header to resource table

26h WORD offset from start of this header to resident names table

Format of ROM Module Header: 28h WORD offset from start of this header to module reference table

Offset Size Description 2Ah WORD offset from start of this header to imported names table
APÉNDICES 389

(array of counted strings, terminated with a string of length 01h BYTE segment number (00h if end of entry table list)

00h) 02h 3N BYTEs entry records

2Ch DWORD offset from start of file to nonresident names table Offset Size Description

30h WORD count of moveable entry point listed in entry table 00h BYTE flags

32h WORD file alignment size shift count bit 0: exported

0 is equivalent to 9 (default 512-byte pages) bit 1: single data

34h WORD number of resource table entries bits 2-7: unused???

36h BYTE target operating system 01h WORD offset within segment

00h unknown

01h OS/2 Format of new executable relocation data (immediately follows segment image):

02h Windows Offset Size Description

03h European MS-DOS 4.x 00h WORD number of relocation items

04h Windows 386 02h 8N BYTEs relocation items

05h BOSS (Borland Operating System Services) Offset Size Description

37h BYTE other EXE flags 00h BYTE relocation type

bit 0: supports long filenames 00h LOBYTE

bit 1: 2.X protected mode 02h BASE

bit 2: 2.X proportional font 03h PTR

bit 3: gangload area 05h OFFS

38h WORD offset to return thunks or start of gangload area 0Bh PTR48

3Ah WORD offset to segment reference thunks or length of gangload area 0Dh OFFS32

3Ch WORD minimum code swap area size 01h BYTE flags

3Eh 2 BYTEs expected Windows version (minor version first) bit 2: additive

Note: this header is documented in detail in the Windows 3.1 SDK 02h WORD offset within segment

Programmer's 04h WORD target address segment

Reference, Vol 4. 06h WORD target address offset

Format of Codeview trailer (at end of executable): Format of new executable resource data:

Offset Size Description Offset Size Description

00h WORD signature 4E42h ('NB') 00h WORD alignment shift count for resource data

02h WORD Microsoft debug info version number 02h N RECORDs resources

04h DWORD Codeview header offset Format of resource record:

Offset Size Description

Format of new executable segment table record: 00h WORD type ID

00h WORD offset in file (shift left by alignment shift to get byte 0000h if end of resource records

offs) >= 8000h if integer type

02h WORD length of image in file (0000h = 64K) else offset from start of resource table to type

04h WORD segment attributes (see below) string

06h WORD number of bytes to allocate for segment (0000h = 64K) 02h WORD number of resources of this type

Note: the first segment table entry is entry number 1 04h DWORD reserved for runtime use

08h N Resources (see below)

Bitfields for segment attributes: Note: resource type and name strings are stored immediately following the

bit 0 data segment rather than code segment resource table, and are not null-terminated

bit 1 unused???

bit 2 real mode Format of new executable resource entry:

bit 3 iterated Offset Size Description

bit 4 movable 00h WORD offset in alignment units from start of file to contents of

bit 5 sharable the resource data

bit 6 preloaded rather than demand-loaded 02h WORD length of resource image in bytes

bit 7 execute-only (code) or read-only (data) 04h WORD flags

bit 8 relocations (directly following code for this segment) bit 4: moveable

bit 9 debug info present bit 5: shareable

bits 10,11 80286 DPL bits bit 6: preloaded

bit 12 discardable 06h WORD resource ID

bits 13-15 discard priority >= 8000h if integer resource

else offset from start of resource table to resource string

Format of new executable entry table item (list): 08h DWORD reserved for runtime use

Offset Size Description Notes: resource type and name strings are stored immediately following the

00h BYTE number of entry points (00h if end of entry table list) resource table, and are not null-terminated
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

strings are counted strings, with a string of length 0 indicating the bit 2: initialization (only for DLLs)

end of the resource table 0 = global

1 = per-process

Format of new executable module reference table [one bundle of entries]: bit 4: no internal fixups in executable image

Offset Size Description bit 5: no external fixups in executable image

00h BYTE number of records in this bundle (00h if end of table) bits 8,9,10:

01h BYTE segment indicator 0 = unknown

00h unused 1 = incompatible with PM windowing \

FFh movable segment, segment number is in entry 2 = compatible with PM windowing > (only for

else segment number of fixed segment 3 = uses PM windowing API / programs)

02h N RECORDs bit 13: module not loadable (only for programs)

Format of segment record bits 17,16,15: module type

Offset Size Description 000 program

00h BYTE flags 001 library (DLL)

bit 0: entry is exported 011 protected memory library module

bit 1: entry uses global (shared) data 100 physical device driver

bits 7-3: number of parameter words 110 virtual device driver

---fixed segment--- bit 30: per-process library termination

01h WORD offset (requires valid CS:EIP, can't be set for .EXE)

---moveable segment--- 14h DWORD number of memory pages

01h 2 BYTEs INT 3F instruction (CDh 3Fh) 18h Initial CS:EIP

03h BYTE segment number DWORD object number

05h WORD offset DWORD offset

Note: table entries are numbered starting from 1 20h Initial SS:ESP

DWORD object number

Format of new executable resident/nonresident name table entry: DWORD offset

Offset Size Description 28h DWORD memory page size

00h BYTE length of string (00h if end of table) 2Ch DWORD (Windows LE) bytes on last page

01h N BYTEs ASCII text of string (OS/2 LX) page offset shift count

N+1 WORD ordinal number (index into entry table) 30h DWORD fixup section size

Notes: the first string in the resident name table is the module name; the 34h DWORD fixup section checksum

first entry in the nonresident name table is the module description 38h DWORD loader section size

the strings are case-sensitive; if the executable was linked with 3Ch DWORD loader section checksum

/IGNORECASE, all strings are in uppercase 40h DWORD offset of object table (see below)

44h DWORD object table entries

Format of Linear Executable (enhanced mode executable) header: 48h DWORD object page map table offset

Offset Size Description 4Ch DWORD object iterate data map offset

00h 2 BYTEs "LE" (4Ch 45h) signature (Windows) 50h DWORD resource table offset

"LX" (4Ch 58h) signature (OS/2) 54h DWORD resource table entries

02h BYTE byte order (00h = little-endian, nonzero = big-endian) 58h DWORD resident names table offset

03h BYTE word order (00h = little-endian, nonzero = big-endian) 5Ch DWORD entry table offset

04h DWORD executable format level 60h DWORD module directives table offset

08h WORD CPU type (see also INT 15/AH=C9h) 64h DWORD Module Directives entries

01h Intel 80286 or upwardly compatible 68h DWORD Fixup page table offset

02h Intel 80386 or upwardly compatible 6Ch DWORD Fixup record table offset

03h Intel 80486 or upwardly compatible 70h DWORD imported modules name table offset

04h Intel Pentium (80586) or upwardly compatible 74h DWORD imported modules count

20h Intel i860 (N10) or compatible 78h DWORD imported procedures name table offset

21h Intel "N11" or compatible 7Ch DWORD per-page checksum table offset

40h MIPS Mark I (R2000, R3000) or compatible 80h DWORD data pages offset

41h MIPS Mark II (R6000) or compatible 84h DWORD preload page count

42h MIPS Mark III (R4000) or compatible 88h DWORD non-resident names table offset

0Ah WORD target operating system 8Ch DWORD non-resident names table length

01h OS/2 90h DWORD non-resident names checksum

02h Windows 94h DWORD automatic data object

03h European DOS 4.0 98h DWORD debug information offset

04h Windows 386 9Ch DWORD debug information length

0Ch DWORD module version A0h DWORD preload instance pages number

10h DWORD module type A4h DWORD demand instance pages number
APÉNDICES 389

A8h DWORD extra heap allocation 03h BYTE entry type flags

ACh 20 BYTEs reserved bit 0: exported

C0h WORD device ID (MS-Windows VxD only) bit 1: uses single data rather than instance

C2h WORD DDK version (MS-Windows VxD only) bit 2: reserved

Note: used by EMM386.EXE, QEMM, and Windows 3.0 Enhanced Mode drivers bits 3-7: number of stack parameters

04h DWORD offset of entry point

Format of object table entry: 08h 2 BYTEs ???

Offset Size Description Note: empty bundles (bit flags at 00h = 00h) are used to skip unused

00h DWORD virtual size in bytes indices,

04h DWORD relocation base address and do not contain the remaining nine bytes

08h DWORD object flags (see below)

0Ch DWORD page map index Format of LX linear executable entry table [array]:

10h DWORD page map entries Offset Size Description

14h 4 BYTEs reserved??? (apparently always zeros) 00h BYTE number of bundles following (00h = end of entry table)

01h BYTE bundle type

Bitfields for object flags: 00h empty

bit 0 readable 01h 16-bit entry

bit 1 writable 02h 286 callgate entry

bit 2 executable 03h 32-bit entry

bit 3 resource 04h forwarder entry

bit 4 discardable bit 7 set if additional parameter typing information is

bit 5 shared present

bit 6 preloaded ---bundle type 00h---

bit 7 invalid no additional fields

bit 8-9 type ---bundle type 01h---

00 normal 02h WORD object number

01 zero-filled 04h BYTE entry flags

10 resident bit 0: exported

11 resident and contiguous bits 7-3: number of stack parameters

bit 10 resident and long-lockable 05h WORD offset of entry point in object (shifted by page size shift)

bit 11 reserved ---bundle type 02h---

bit 12 16:16 alias required 02h WORD object number

bit 13 "BIG" (Huge: 32-bit) 04h BYTE entry flags

bit 14 conforming bit 0: exported

bit 15 "OBJECT_I/O_PRIVILEGE_LEVEL" bits 7-3: number of stack parameters

bits 16-31 reserved 05h WORD offset of entry point in object

07h WORD reserved for callgate selector (used by loader)

Format of object page map table entry: ---bundle type 03h---

Offset Size Description 02h WORD object number

00h BYTE ??? (usually 00h) 04h BYTE entry flags

01h WORD (big-endian) index to fixup table bit 0: exported

0000h if no relocation info bits 7-3: number of stack parameters

03h BYTE type (00h hard copy in file, 03h some relocation needed) 05h DWORD offset of entry point in object

---bundle type 04h---

Format of resident names table entry: 02h WORD reserved

Offset Size Description 04h BYTE forwarder flags

00h BYTE length of name bit 0: import by ordinal

01h N BYTEs name bits 7-1 reserved

N+1 3 BYTEs ??? 05h WORD module ordinal

(forwarder's index into Import Module Name table)

Format of LE linear executable entry table: 07h DWORD procedure name offset or import ordinal number

Offset Size Description Note: all fields after the first two bytes are repeated N times

00h BYTE number of entries in table

01h 10 BYTEs per entry Bitfields for linear executable fixup type:

Offset Size Description bit 7 ordinal is BYTE rather than WORD

00h BYTE bit flags bit 6 16-bit rather than 8-bit object number/module ordinal

bit 0: non-empty bundle bit 5 addition with DWORD rather than WORD

bit 1: 32-bit entry bit 4 relocation info has size with new two bytes at end

01h WORD object number bit 3 reserved (0)


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

bit 2 set if add to destination, clear to replace destination 00h 2 BYTEs signature ("P2" for 286 .EXP executable, "P3" for 386 .EXP)

bits 1-0 type 02h WORD level (01h flat-model file, 02h multisegmented file)

00 internal fixup 04h WORD header size

01 external fixup, imported by ordinal 06h DWORD file size in bytes

10 external fixup, imported by name 0Ah WORD checksum

11 internal fixup via entry table 0Ch DWORD offset of run-time parameters within file

10h DWORD size of run-time parameters in bytes

Format of linear executable fixup record: 14h DWORD offset of relocation table within file

Offset Size Description 18h DWORD size of relocation table in bytes

00h BYTE type 1Ch DWORD offset of segment information table within file

bits 7-4: modifier (0001 single, 0011 multiple) 20h DWORD size of segment information table in bytes

bits 3-0: type 24h WORD size of segment information table entry in bytes

0000 byte offset 26h DWORD offset of load image within file

0010 word segment 2Ah DWORD size of load image on disk

0011 16-bit far pointer (DWORD) 2Eh DWORD offset of symbol table within file

0101 16-bit offset 32h DWORD size of symbol table in bytes

0110 32-bit far pointer (PWORD) 36h DWORD offset of GDT within load image

0111 32-bit offset 3Ah DWORD size of GDT in bytes

1000 near call or jump, WORD/DWORD based on seg 3Eh DWORD offset of LDT within load image

attrib 42h DWORD size of LDT in bytes

01h BYTE linear executable fixup type (see above) 46h DWORD offset of IDT within load image

---if single type--- 4Ah DWORD size of IDT in bytes

02h WORD offset within page 4Eh DWORD offset of TSS within load image

04h relocation information 52h DWORD size of TSS in bytes

---internal fixup--- 56h DWORD minimum number of extra bytes to be allocated at end of

BYTE object number program

---external,ordinal--- (level 1 executables only)

BYTE one-based module number in Import Module table 5Ah DWORD maximum number of extra bytes to be allocated at end of

BYTE/WORD ordinal number program

WORD/DWORD value to add (only present if modifier bit 4 set) (level 1 executables only)

---external,name--- 5Eh DWORD base load offset (level 1 executables only)

BYTE one-based module number in Import Module table 62h DWORD initial ESP

WORD offset in Import Procedure names 66h WORD initial SS

WORD/DWORD value to add (only present if modifier bit 4 set) 68h DWORD initial EIP

---if multiple type--- 6Ch WORD initial CS

02h BYTE number of items 6Eh WORD initial LDT

03h var relocation info as for "single" type (see above) 70h WORD initial TSS

N WORDs offsets of items to relocate 72h WORD flags

bit 0: load image is packed

Format of old Phar Lap .EXP file header: bit 1: 32-bit checksum is present

Offset Size Description bits 4-2: type of relocation table

00h 2 BYTEs "MP" (4Dh 50h) signature 74h DWORD memory requirements for load image

02h WORD remainder of image size / page size (page size = 512h) 78h DWORD 32-bit checksum (optional)

04h WORD size of image in pages 7Ch DWORD size of stack segment in bytes

06h WORD number of relocation items 80h 256 BYTEs reserved (0)

08h WORD header size in paragraphs

0Ah WORD minimum number of extra 4K pages to be allocated at the end Format of Phar Lap segment information table entry:

of program, when it is loaded Offset Size Description

0Ch WORD maximum number of extra 4K pages to be allocated at the end 00h WORD selector number

of program, when it is loaded 02h WORD flags

0Eh DWORD initial ESP 04h DWORD base offset of selector

12h WORD word checksum of file 08h DWORD minimum number of extra bytes to be allocated to the segment

14h DWORD initial EIP

18h WORD offset of first relocation item Format of 386|DOS-Extender run-time parameters:

1Ah WORD overlay number Offset Size Description

1Ch WORD ??? (wants to be 1) 00h 2 BYTEs signature "DX" (44h 58h)

02h WORD minimum number of real-mode params to leave free at run time

Format of new Phar Lap .EXP file header: 04h WORD maximum number of real-mode params to leave free at run time

Offset Size Description 06h WORD minimum interrupt buffer size in KB


APÉNDICES 389

08h WORD maximum interrupt buffer size in KB Borland's Open Architecture Handbook

0Ah WORD number of interrupt stacks --------


0Ch WORD size in KB of each interrupt stack D-214C----------------------------------------------------
0Eh DWORD offset of byte past end of real-mode code and data INT 21 - DOS 2+ - "EXIT" - TERMINATE WITH RETURN CODE
12h WORD size in KB of call buffers AH = 4Ch

14h WORD flags AL = return code

bit 0: file is virtual memory manager Return: never returns

bit 1: file is a debugger Notes: unless the process is its own parent (see AH=26h, offset 16h in PSP),

16h WORD unprivileged flag (if nonzero, executes at ring 1, 2, or 3) all open files are closed and all memory belonging to the process

18h 104 BYTEs reserved (0) is freed

all network file locks should be removed before calling this function

Format of Phar Lap repeat block header: SeeAlso: AH=00h,AH=26h,AH=4Bh,AH=4Dh,INT 15/AH=12h/BH=02h,INT 20,INT 22

Offset Size Description SeeAlso: INT 60/DI=0601h

00h WORD byte count --------


02h BYTE repeat string length D-2150----------------------------------------------------
INT 21 - DOS 2+ internal - SET CURRENT PROCESS ID (SET PSP
Format of Borland debugging information header (following load image): ADDRESS)
Offset Size Description AH = 50h

00h WORD signature 52FBh BX = segment of PSP for new process

02h WORD version ID Notes: DOS uses the current PSP address to determine which processes own

04h DWORD size of name pool in bytes files

08h WORD number of names in namem pool and memory; it corresponds to process identifiers used by other OSs

0Ah WORD number of type entries under DOS 2.x, this function cannot be invoked inside an INT 28h

0Ch WORD number of structure members handler without setting the Critical Error flag

0Eh WORD number of symbols under MS-DOS 3+ and DR-DOS 3.41+, this function does not use any of

10h WORD number of global symbols the DOS-internal stacks and may thus be called at any time, even

12h WORD number of modules during another INT 21h call

14h WORD number of locals (optional) some Microsoft applications such as Quick C 2.51 use segments of

16h WORD number of scopes in table 0000h

18h WORD number of line-number entries and FFFFh and direct access to the SDA (see AX=5D06h) to test

1Ah WORD number of include files whether

1Ch WORD number of segment records they are running under MS-DOS rather than a compatible OS; although

1Eh WORD number of segment/file correlations one should only call this function with valid PSP addresses, any

20h DWORD size of load image after removing uninitialized data and program hooking it should be prepared to handle invalid addresses

debug supported by OS/2 compatibility box

info this call was undocumented prior to the release of DOS 5.0

24h DWORD debugger hook; pointer into debugged program whose meaning SeeAlso: AH=26h,AH=51h,AH=62h

depends on program flags --------


28h BYTE program flags D-2151----------------------------------------------------
bit 0: case-sensitive link INT 21 - DOS 2+ internal - GET CURRENT PROCESS ID (GET PSP
bit 1: pascal overlay program ADDRESS)
29h WORD no longer used AH = 51h

2Bh WORD size of data pool in bytes Return: BX = segment of PSP for current process

2Dh BYTE padding Notes: DOS uses the current PSP address to determine which processes own

2Eh WORD size of following header extension (currently 00h, 10h, or files

20h) and memory; it corresponds to process identifiers used by other OSs

30h WORD number of classes under DOS 2.x, this function cannot be invoked inside an INT 28h

32h WORD number of parents handler without setting the Critical Error flag

34h WORD number of global classes (currently unused) under DOS 3+, this function does not use any of the DOS-internal

36h WORD number of overloads (currently unused) stacks

38h WORD number of scope classes and may thus be called at any time, even during another INT 21h

3Ah WORD number of module classes call

3Ch WORD number of coverage offsets supported by OS/2 compatibility box

3Eh DWORD offset relative to symbol base of name pool identical to the documented AH=62h

42h WORD number of browser information records this call was undocumented prior to the release of DOS 5.0

44h WORD number of optimized symbol records SeeAlso: AH=26h,AH=50h,AH=62h

46h WORD debugging flags --------


48h 8 BYTEs padding D-2152----------------------------------------------------
Note: additional information on the Borland debugging info may be found in INT 21 U - DOS 2+ internal - "SYSVARS" - GET LIST OF LISTS
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

AH = 52h 1Ah DWORD pointer to system FCB tables (see below)

Return: ES:BX -> DOS list of lists 1Eh WORD number of protected FCBs (the y in the CONFIG.SYS FCBS=x,y)

Notes: partially supported by OS/2 v1.1 compatibility box (however, most 20h BYTE number of block devices installed

pointers are FFFFh:FFFFh, LASTDRIVE is FFh, and the NUL header 21h BYTE number of available drive letters (largest of 5, installed

"next" block devices, and CONFIG.SYS LASTDRIVE=). Also size of

pointer is FFFFh:FFFFh). current directory structure array.

on return, ES points at the DOS data segment (see also INT 22h 18 BYTEs actual NUL device driver header (not a pointer!)

2F/AX=1203h) NUL is always the first device on DOS's linked list of device

SeeAlso: INT 2F/AX=1203h drivers. (see below)

34h BYTE number of JOIN'ed drives

Format of List of Lists: ---DOS 4.x---

Offset Size Description 10h WORD maximum bytes per sector of any block device

-24 WORD (DOS 3.1+) contents of CX from INT 21/AX=5E01h 12h DWORD pointer to disk buffer info record (see below)

-22 WORD (DOS ???+) LRU counter for FCB caching 16h DWORD pointer to array of current directory structures (see below)

-20 WORD (DOS ???+) LRU counter for FCB opens 1Ah DWORD pointer to system FCB tables (see below)

-18 DWORD (DOS ???+) address of OEM function handler (see INT 1Eh WORD number of protected FCBs (the y in the CONFIG.SYS FCBS=x,y)

21/AH=F8h) (always 00h for DOS 5.0)

FFFFh:FFFFh if not installed or not available 20h BYTE number of block devices installed

-14 WORD (DOS ???+) offset in DOS CS of code to return from INT 21 21h BYTE number of available drive letters (largest of 5, installed

call block devices, and CONFIG.SYS LASTDRIVE=). Also size of

-12 WORD (DOS 3.1+) sharing retry count (see AX=440Bh) current directory structure array.

-10 WORD (DOS 3.1+) sharing retry delay (see AX=440Bh) 22h 18 BYTEs actual NUL device driver header (not a pointer!)

-8 DWORD (DOS 3+) pointer to current disk buffer NUL is always the first device on DOS's linked list of device

-4 WORD (DOS 3+) pointer in DOS data segment of unread CON input drivers. (see below)

when CON is read via a handle, DOS reads an entire line, 34h BYTE number of JOIN'ed drives

and returns the requested portion, buffering the rest 35h WORD pointer within IBMDOS code segment to list of special program

for the next read. 0000h indicates no unread input names (see below)

-2 WORD segment of first memory control block (always 0000h for DOS 5.0)

00h DWORD pointer to first Drive Parameter Block (see AH=32h) 37h DWORD pointer to FAR routine for resident IFS utility functions

04h DWORD pointer to first System File Table (see below) (see below)

08h DWORD pointer to active CLOCK$ device's header (most recently may be called by any IFS driver which does not wish to

loaded service functions 20h or 24h-28h itself

driver with CLOCK bit set) 3Bh DWORD pointer to chain of IFS (installable file system) drivers

0Ch DWORD pointer to active CON device's header (most recently loaded 3Fh WORD the x in BUFFERS x,y (rounded up to multiple of 30 if in EMS)

driver with STDIN bit set) 41h WORD number of lookahead buffers (the y in BUFFERS x,y)

---DOS 2.x--- 43h BYTE boot drive (1=A:)

10h BYTE number of logical drives in system 44h BYTE flag: 01h to use DWORD moves (80386+), 00h otherwise

11h WORD maximum bytes/block of any block device 45h WORD extended memory size in KB

13h DWORD pointer to first disk buffer (see below) ---DOS 5.0-6.0---

17h 18 BYTEs actual NUL device driver header (not a pointer!) 10h 39 BYTEs as for DOS 4.x (see above)

NUL is always the first device on DOS's linked list of device 37h DWORD pointer to SETVER program list or 0000h:0000h

drivers. (see below) 3Bh WORD (DOS=HIGH) offset in DOS CS of function to fix A20 control

---DOS 3.0--- when executing special .COM format

10h BYTE number of block devices 3Dh WORD PSP of most-recently EXECed program if DOS in HMA, 0000h if

11h WORD maximum bytes/block of any block device low

13h DWORD pointer to first disk buffer (see below) 3Fh 8 BYTEs as for DOS 4.x (see above)

17h DWORD pointer to array of current directory structures (see below)

1Bh BYTE value of LASTDRIVE command in CONFIG.SYS (default 5) Format of memory control block (see also below):

1Ch DWORD pointer to STRING= workspace area Offset Size Description

20h WORD size of STRING area (the x in STRING=x from CONFIG.SYS) 00h BYTE block type: 5Ah if last block in chain, otherwise 4Dh

22h DWORD pointer to FCB table 01h WORD PSP segment of owner or

26h WORD the y in FCBS=x,y from CONFIG.SYS 0000h if free

28h 18 BYTEs actual NUL device driver header (not a pointer!) 0006h if DR-DOS XMS UMB

NUL is always the first device on DOS's linked list of device 0007h if DR-DOS excluded upper memory ("hole")

drivers. (see below) 0008h if belongs to DOS

---DOS 3.1-3.3--- FFFAh if 386MAX UMB control block (see AX=4402h"386MAX")

10h WORD maximum bytes per sector of any block device FFFDh if 386MAX locked-out memory

12h DWORD pointer to first disk buffer in buffer chain (see below) FFFEh if 386MAX UMB (immediately follows its control block)

16h DWORD pointer to array of current directory structures (see below) FFFFh if 386MAX 6.01 device driver
APÉNDICES 389

03h WORD size of memory block in paragraphs driver

05h 3 BYTEs unused by MS-DOS was loaded (unused for other types)

(386MAX) if locked-out block, region start/prev region end

---DOS 2.x,3.x--- Format of data at start of STACKS code segment (if present):

08h 8 BYTEs unused Offset Size Description

---DOS 4+ --- 00h WORD ???

08h 8 BYTEs ASCII program name if PSP memory block or DR-DOS UMB, 02h WORD number of stacks (the x in STACKS=x,y)

else garbage 04h WORD size of stack control block array (should be 8*x)

null-terminated if less than 8 characters 06h WORD size of each stack (the y in STACKS=x,y)

Notes: the next MCB is at segment (current + size + 1) 08h DWORD pointer to STACKS data segment

under DOS 3.1+, the first memory block is the DOS data segment, 0Ch WORD offset in STACKS data segment of stack control block array

containing installable drivers, buffers, etc. Under DOS 4+ it is 0Eh WORD offset in STACKS data segment of last element of that array

divided into subsegments, each with its own memory control block 10h WORD offset in STACKS data segment of the entry in that array for

(see below), the first of which is at offset 0000h. the next stack to be allocated (initially same as value in

for DOS 5+, blocks owned by DOS may have either "SC" or "SD" in bytes 0Eh

08h and 09h. "SC" is system code or locked-out inter-UMB memory, and works its way down in steps of 8 to the value in 0Ch as

"SD" is system data, device drivers, etc. hardware interrupts pre-empt each other)

Some versions of DR-DOS use only seven characters of the program Note: the STACKS code segment data may, if present, be located as follows:

name, DOS 3.2: The code segment data is at a paragraph boundary fairly early

placing a NUL in the eighth byte. in the IBMBIO segment (seen at 0070:0190h)

DOS 3.3: The code segment is at a paragraph boundary in the DOS data

Format of MS-DOS 5+ UMB control block: segment, which may be determined by inspecting the segment

Offset Size Description pointers of the vectors for those of interrupts 02h, 08h-0Eh,

00h BYTE type: 5Ah if last block in chain, 4Dh otherwise 70h, 72-77h which have not been redirected by device drivers

01h WORD first available paragraph in UMB if control block at start or

of UMB, 000Ah if control block at end of UMB TSRs.

03h WORD length in paragraphs of following UMB or locked-out region DOS 4+ Identified by sub-segment control block type "S" within the

05h 3 BYTEs unused DOS

08h 8 BYTEs block type name: "UMB" if start block, "SM" if end block in data segment.

UMB SeeAlso: INT B4"STACKMAN"

Format of STARLITE (General Software's Embedded DOS) memory control block: Format of array elements in STACKS data segment:

Offset Size Description Offset Size Description

00h BYTE block type: 5Ah if last block in chain, otherwise 4Dh 00h BYTE status: 00h=free, 01h=in use, 03h=corrupted by overflow of

01h WORD PSP segment of owner, 0000h if free, 0008h if belongs to DOS higher stack.

03h WORD size of memory block in paragraphs 01h BYTE not used

05h BYTE unused 02h WORD previous SP

06h WORD segment address of next memory control block (0000h if last) 04h WORD previous SS

08h WORD segment address of previous memory control block or 0000h 06h WORD ptr to word at top of stack (new value for SP). The word at

0Ah 6 BYTEs reserved the

top of the stack is preset to point back to this control

Format of DOS 4+ data segment subsegment control blocks: block.

Offset Size Description

00h BYTE subsegment type (blocks typically appear in this order) SHARE.EXE hooks (DOS 3.1-6.00):

"D" device driver (offsets from first system file table--pointed at by ListOfLists+04h)

"E" device driver appendage Offset Size Description

"I" IFS (Installable File System) driver -3Ch DWORD pointer to FAR routine for ???

"F" FILES= control block storage area (for FILES>5) Note: not called by MS-DOS 3.3, set to 0000h:0000h by

"X" FCBS= control block storage area, if present SHARE 3.3+

"C" BUFFERS EMS workspace area (if BUFFERS /X option used) -38h DWORD pointer to FAR routine called on opening file

"B" BUFFERS= storage area on call, internal DOS location points at filename(see

"L" LASTDRIVE= current directory structure array storage AX=5D06h)

area Return: CF clear if successful

"S" STACKS= code and data area, if present (see below) CF set on error

"T" INSTALL= transient code AX = DOS error code (24h) (see AH=59h)

01h WORD paragraph of subsegment start (usually the next paragraph) Note: SHARE directly accesses DOS-internal data to get name

03h WORD size of subsegment in paragraphs of

05h 3 BYTEs unused file just opened

08h 8 BYTEs for types "D" and "I", base name of file from which the -34h DWORD pointer to FAR routine called on closing file
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

ES:DI -> system file table starting cluster of file 0Bh 1Ah

Note: does something to every Record Lock Record for file sharing record offset 33h 1Ch

-30h DWORD pointer to FAR routine to close all files for given computer file attribute 04h 1Eh

(called by AX=5D03h) -10h DWORD pointer to FAR routine to get first cluster of FCB file ???

-2Ch DWORD pointer to FAR routine to close all files for given process call with ES:DI -> system file table entry

(called by AX=5D04h) DS:SI -> FCB

-28h DWORD pointer to FAR routine to close file by name Return: CF set if SFT closed or sharing record offsets

(called by AX=5D02h) mismatched

DS:SI -> DOS parameter list (see AX=5D00h) CF clear if successful

DPL's DS:DX -> name of file to close BX = starting cluster number from FCB

Return: CF clear if successful -0Ch DWORD pointer to FAR routine to close file if duplicate for process

CF set on error DS:SI -> system file table

AX = DOS error code (03h) (see AH=59h) Return: AX = number of handle in JFT which already uses SFT

-24h DWORD pointer to FAR routine to lock region of file Note: called during open/create of a file

call with BX = file handle Note: if SFT was opened with inheritance enabled and sharing

---DOS 3.x--- mode 111, does something to all other SFTs owned by

CX:DX = starting offset same process which have the same file open mode and

SI:AX = size sharing record

---DOS 4+--- -08h DWORD pointer to FAR routine for closing file

DS:DX -> lock range Note: closes various handles referring to file most-recently

DWORD start offset opened

DWORD size in bytes -04h DWORD pointer to FAR routine to update directory info in related

Return: CF set on error SFT

AL = DOS error code (21h) (see AH=59h) entries

Note: not called if file is marked as remote call with ES:DI -> system file table entry for file (see

-20h DWORD pointer to FAR routine to unlock region of file below)

call with BX = file handle AX = subfunction (apply to each related SFT)

---DOS 3.x--- 00h: update time stamp (offset 0Dh) and date

CX:DX = starting offset stamp (offset 0Fh)

SI:AX = size 01h: update file size (offset 11h) and

---DOS 4+--- starting

DS:DX -> lock range cluster (offset 0Bh). Sets

DWORD start offset last-accessed

DWORD size in bytes cluster fields to start of file if file

Return: CF set on error never accessed

AL = DOS error code (21h) (see AH=59h) 02h: as function 01h, but last-accessed

Note: not called if file is marked as remote fields

-1Ch DWORD pointer to FAR routine to check if file region is locked always changed

call with ES:DI -> system file table entry for file 03h: do both functions 00h and 02h

CX = length of region from current position in file Note: follows ptr at offset 2Bh in system file table entries

Return: CF set if any portion of region locked Note: NOP if opened with no-inherit or via FCB

AX = 0021h Notes: most of the above hooks (except -04h, -14h, -18h, and -3Ch) assume

-18h DWORD pointer to FAR routine to get open file list entry either that SS=DOS DS or SS=DS=DOS DS and directly access

(called by AX=5D05h) DOS-internal data

call with DS:SI -> DOS parameter list (see AX=5D00h) sharing hooks are not supported by DR-DOS 5-6; will reportedly be

DPL's BX = index of sharing record supported by Novell DOS 7

DPL's CX = index of SFT in SFT chain of sharing rec

Return: CF set on error or not loaded Format of sharing record:

AX = DOS error code (12h) (see AH=59h) Offset Size Description

CF clear if successful 00h BYTE flag

ES:DI -> filename 00h free block

CX = number of locks owned by specified SFT 01h allocated block

BX = network machine number FFh end marker

DX destroyed 01h WORD size of block

-14h DWORD pointer to FAR routine for updating FCB from SFT??? 03h BYTE checksum of pathname (including NUL)

call with DS:SI -> unopened FCB if sum of ASCII values is N, checksum is (N/256 + N%256)

ES:DI -> system file table entry 04h WORD offset in SHARE's DS of first Record Lock Record (see below)

Return: BL = C0h??? 06h DWORD pointer to start of system file table chain for file

Note: copies following fields from SFT to FCB: 0Ah WORD unique sequence number
APÉNDICES 389

0Ch var ASCIZ full pathname 36h WORD ??? apparently always 0000h

Note: not supported by DR-DOS SHARE 1.1 and 2.0; will reportedly be

supported by Novell DOS 7 Format of DOS 3.1-3.3x, DR-DOS 5.0-6.0 system file tables and FCB tables:

Offset Size Description

Format of Record Lock Record (SHARE.EXE): 00h DWORD pointer to next file table (offset FFFFh if last)

Offset Size Description 04h WORD number of files in this table

00h WORD offset in SHARE's DS of next lock table in list or 0000h 06h 35h bytes per file

02h DWORD offset in file of start of locked region Offset Size Description

06h DWORD offset in file of end of locked region 00h WORD number of file handles referring to this file

0Ah DWORD pointer to System File Table entry for this file 02h WORD file open mode (see AH=3Dh)

0Eh WORD PSP segment of lock's owner bit 15 set if this file opened via FCB

---DOS 5+ --- 04h BYTE file attribute (see AX=4301h)

10h WORD lock type: (00h lock all, 01h lock writes only) 05h WORD device info word (see AX=4400h)

bit 15 set if remote file

Format of DOS 2.x system file tables: bit 14 set means do not set file date/time on closing

Offset Size Description bit 12 set means don't inherit on EXEC

00h DWORD pointer to next file table (offset FFFFh if last) bits 5-0 drive number for disk files

04h WORD number of files in this table 07h DWORD pointer to device driver header if character device

06h 28h bytes per file else pointer to DOS Drive Parameter Block (see

Offset Size Description AH=32h)

00h BYTE number of file handles referring to this file 0Bh WORD starting cluster of file

01h BYTE file open mode (see AH=3Dh) 0Dh WORD file time in packed format (see AX=5700h)

02h BYTE file attribute not used for character devices in DR-DOS

03h BYTE drive (0 = character device, 1 = A, 2 = B, etc) 0Fh WORD file date in packed format (see AX=5700h)

04h 11 BYTEs filename in FCB format (no path,no not used for character devices in DR-DOS

period,blank-padded) 11h DWORD file size

0Fh WORD ??? ---system file table---

11h WORD ??? 15h DWORD current offset in file (may be larger than size of

13h DWORD file size??? file; INT 21/AH=42h does not check new position)

17h WORD file date in packed format (see AX=5700h) ---FCB table---

19h WORD file time in packed format (see AX=5700h) 15h WORD counter for last I/O to FCB

1Bh BYTE device attribute (see AX=4400h) 17h WORD counter for last open of FCB

---character device--- (these are separate to determine the times of the

1Ch DWORD pointer to device driver latest I/O and open)

---block device--- ---

1Ch WORD starting cluster of file 19h WORD relative cluster within file of last cluster accessed

1Eh WORD relative cluster in file of last cluster accessed 1Bh WORD absolute cluster number of last cluster accessed

------ 0000h if file never read or written???

20h WORD absolute cluster number of current cluster 1Dh WORD number of sector containing directory entry

22h WORD ??? 1Fh BYTE number of dir entry within sector (byte offset/32)

24h DWORD current file position??? 20h 11 BYTEs filename in FCB format (no path/period, blank-padded)

2Bh DWORD (SHARE.EXE) pointer to previous SFT sharing same file

Format of DOS 3.0 system file tables and FCB tables: 2Fh WORD (SHARE.EXE) network machine number which opened file

Offset Size Description (Windows Enhanced mode DOSMGR uses the virtual

00h DWORD pointer to next file table (offset FFFFh if last) machine

04h WORD number of files in this table ID as the machine number; see INT 2F/AX=1683h)

06h 38h bytes per file 31h WORD PSP segment of file's owner (see AH=26h) (first three

Offset Size Description entries for AUX/CON/PRN contain segment of IO.SYS

00h-1Eh as for DOS 3.1+ (see below) startup code)

1Fh WORD byte offset of directory entry within sector 33h WORD offset within SHARE.EXE code segment of

21h 11 BYTEs filename in FCB format (no path/period, blank-padded) sharing record (see above) 0000h = none

2Ch DWORD (SHARE.EXE) pointer to previous SFT sharing same file

30h WORD (SHARE.EXE) network machine number which opened file Format of DOS 4.0-6.0 system file tables and FCB tables:

(Windows Enhanced mode DOSMGR uses the virtual Offset Size Description

machine 00h DWORD pointer to next file table (offset FFFFh if last)

ID as the machine number; see INT 2F/AX=1683h) 04h WORD number of files in this table

32h WORD PSP segment of file's owner (first three entries for 06h 3Bh bytes per file

AUX/CON/PRN contain segment of IO.SYS startup code) Offset Size Description

34h WORD (SHARE.EXE) offset in SHARE code seg of share record 00h WORD number of file handles referring to this file
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

FFFFh if in use but not referenced exempt from network connection make/break commands;

02h WORD file open mode (see AH=3Dh) set for CD-ROM drives

bit 15 set if this file opened via FCB 45h DWORD pointer to Drive Parameter Block for drive (see AH=32h)

04h BYTE file attribute (see AX=4301h) ---local drives---

05h WORD device info word (see also AX=4400h) 49h WORD starting cluster of current directory

bit 15 set if remote file 0000h = root, FFFFh = never accessed

bit 14 set means do not set file date/time on closing 4Bh WORD ??? seems to be FFFFh always

bit 13 set if named pipe 4Dh WORD ??? seems to be FFFFh always

bit 12 set if no inherit ---network drives---

bit 11 set if network spooler 49h DWORD pointer to redirector or REDIRIFS record, or FFFFh:FFFFh

bit 7 set if device, clear if file (only if local) (DOS 4 only) available for use by IFS driver

bits 6-0 as for AX=4400h 4Dh WORD stored user data from INT 21/AX=5F03h

07h DWORD pointer to device driver header if character device ------

else pointer to DOS Drive Parameter Block (see 4Fh WORD offset in current directory path of backslash corresponding

AH=32h) to

or REDIR data root directory for drive

0Bh WORD starting cluster of file (local files only) this value specifies how many characters to hide from the

0Dh WORD file time in packed format (see AX=5700h) "CHDIR" and "GETDIR" calls; normally set to 2 to hide the

0Fh WORD file date in packed format (see AX=5700h) drive letter and colon, SUBST, JOIN, and networks change it

11h DWORD file size so that only the appropriate portion of the true path is

15h DWORD current offset in file (SFT) visible to the user

LRU counters (FCB table, two WORDs) ---DOS 4+ ---

---local file--- 51h BYTE (DOS 4 only, remote drives) device type

19h WORD relative cluster within file of last cluster accessed 04h network drive

1Bh DWORD number of sector containing directory entry 52h DWORD pointer to IFS driver (DOS 4) or redirector block (DOS 5+)

1Fh BYTE number of dir entry within sector (byte offset/32) for

---network redirector--- this drive, 00000000h if native DOS

19h DWORD pointer to REDIRIFS record 56h WORD available for use by IFS driver

1Dh 3 BYTEs ??? Notes: the path for invalid drives is normally set to X:\, but may be empty

------ after JOIN x: /D in DR-DOS 5.0 or NET USE x: /D in older LAN

20h 11 BYTEs filename in FCB format (no path/period, blank-padded) versions

2Bh DWORD (SHARE.EXE) pointer to previous SFT sharing same file normally, only one of bits 13&12 may be set together with bit 14, but

2Fh WORD (SHARE.EXE) network machine number which opened file DR-DOS 5.0 uses other combinations for bits 15-12: 0111 JOIN,

(Windows Enhanced mode DOSMGR uses the virtual 0001 SUBST, 0101 ASSIGN (see below)

machine

ID as the machine number; see INT 2F/AX=1683h) Format of DR-DOS 5.0-6.0 current directory structure entry (array):

31h WORD PSP segment of file's owner (see AH=26h) (first three Offset Size Description

entries for AUX/CON/PRN contain segment of IO.SYS 00h 67 BYTEs ASCIZ pathname of actual root directory for this logical

startup code) drive

33h WORD offset within SHARE.EXE code segment of 43h WORD drive attributes

sharing record (see above) 0000h = none 1000h SUBSTed drive

35h WORD (local) absolute cluster number of last clustr 3000h??? JOINed drive

accessed 4000h physical drive

(redirector) ??? 5000h ASSIGNed drive

37h DWORD pointer to IFS driver for file, 0000000h if native 7000h JOINed drive

DOS 8000h network drive

Note: the OS/2 2.0 DOS Boot Session does not properly fill in the filename 45h BYTE physical drive number (0=A:) if this logical drive is valid

field due to incomplete support for SFTs; the OS/2 2.0 DOS Window 46h BYTE ??? apparently flags for JOIN and ASSIGN

does not appear to support SFTs at all 47h WORD cluster number of start of parent directory (0000h = root)

49h WORD entry number of current directory in parent directory

Format of current directory structure (CDS) (array, LASTDRIVE entries): 4Bh WORD cluster number of start of current directory

Offset Size Description 4Dh WORD used for media change detection (details not available)

00h 67 BYTEs ASCIZ path in form X:\PATH (local) or \\MACH\PATH (network) 4Fh WORD cluster number of SUBST/JOIN "root" directory

43h WORD drive attributes (see also note below and AX=5F07h) 0000h if physical root directory

bit 15: uses network redirector \ invalid if 00, installable

bit 14: physical drive / file system if 11 Format of device driver header:

bit 13: JOIN'ed \ path above is true path that would be Offset Size Description

bit 12: SUBST'ed / needed if not under SUBST or JOIN 00h DWORD pointer to next driver, offset=FFFFh if last driver

bit 7: remote drive hidden from redirector's assign-list and 04h WORD device attributes
APÉNDICES 389

Character device: 16h 6 BYTEs (CD-ROM driver) signature 'MSCDnn' where 'nn' is version

bit 15 set (indicates character device) (currently '00')

bit 14 IOCTL supported (see AH=44h)

bit 13 (DOS 3+) output until busy supported Format of CLOCK$ transfer record:

bit 12 reserved Offset Size Description

bit 11 (DOS 3+) OPEN/CLOSE/RemMedia calls supported 00h WORD number of days since 1-Jan-1980

bits 10-8 reserved 02h BYTE minutes

bit 7 (DOS 5+) Generic IOCTL check call supported (cmd 03h BYTE hours

19h) 04h BYTE hundredths of second

(see AX=4410h,AX=4411h) 05h BYTE seconds

bit 6 (DOS 3.2+) Generic IOCTL call supported (command

13h) Format of DOS 2.x disk buffer:

(see AX=440Ch,AX=440Dh) Offset Size Description

bit 5 reserved 00h DWORD pointer to next disk buffer, offset = FFFFh if last

bit 4 device is special (use INT 29 "fast console least-recently used buffer is first in chain

output") 04h BYTE drive (0=A, 1=B, etc), FFh if not in use

bit 3 device is CLOCK$ (all reads/writes use transfer 05h 3 BYTEs unused??? (seems always to be 00h 00h 01h)

record described below) 08h WORD logical sector number

bit 2 device is NUL 0Ah BYTE number of copies to write (1 for non-FAT sectors)

bit 1 device is standard output 0Bh BYTE sector offset between copies if multiple copies to be written

bit 0 device is standard input 0Ch DWORD pointer to DOS Drive Parameter Block (see AH=32h)

Block device: 10h buffered data

bit 15 clear (indicates block device)

bit 14 IOCTL supported Format of DOS 3.x disk buffer:

bit 13 non-IBM format Offset Size Description

bit 12 network device (device is remote) 00h DWORD pointer to next disk buffer, offset = FFFFh if last

bit 11 (DOS 3+) OPEN/CLOSE/RemMedia calls supported least-recently used buffer is first in chain

bit 10 reserved 04h BYTE drive (0=A,1=B, etc), FFh if not in use

bit 9 direct I/O not allowed??? 05h BYTE buffer flags

(set by DOS 3.3 DRIVER.SYS for "new" drives) bit 7: ???

bit 8 ??? set by DOS 3.3 DRIVER.SYS for "new" drives bit 6: buffer dirty

bit 7 (DOS 5+) Generic IOCTL check call supported (cmd bit 5: buffer has been referenced

19h) bit 4: ???

(see AX=4410h,AX=4411h) bit 3: sector in data area

bit 6 (DOS 3.2+) Generic IOCTL call supported (command bit 2: sector in a directory, either root or subdirectory

13h) bit 1: sector in FAT

implies support for commands 17h and 18h bit 0: boot sector??? (guess)

(see AX=440Ch,AX=440Dh,AX=440Eh,AX=440Fh) 06h WORD logical sector number

bits 5-2 reserved 08h BYTE number of copies to write (1 for non-FAT sectors)

bit 1 driver supports 32-bit sector addressing (DOS 09h BYTE sector offset between copies if multiple copies to be written

3.31+) 0Ah DWORD pointer to DOS Drive Parameter Block (see AH=32h)

bit 0 reserved 0Eh WORD unused??? (almost always 0)

Note: for European MS-DOS 4.0, bit 11 also indicates that 10h buffered data

bits

8-6 contain a version code (000 = DOS 3.0,3.1; Format of DOS 4.00 (pre UR 25066) disk buffer info:

001 = DOS 3.2, 010 = European DOS 4.0) Offset Size Description

06h WORD device strategy entry point 00h DWORD pointer to array of disk buffer hash chain heads (see below)

call with ES:BX -> request header (see INT 2F/AX=0802h) 04h WORD number of disk buffer hash chains (referred to as NDBCH

08h WORD device interrupt entry point below)

---character device--- 06h DWORD pointer to lookahead buffer, zero if not present

0Ah 8 BYTEs blank-padded character device name 0Ah WORD number of lookahead sectors, else zero (the y in BUFFERS=x,y)

---block device--- 0Ch BYTE 00h if buffers in EMS (/X), FFh if not

0Ah BYTE number of subunits (drives) supported by driver 0Dh WORD EMS handle for buffers, zero if not in EMS

0Bh 7 BYTEs unused 0Fh WORD EMS physical page number used for buffers (usually 255)

--- 11h WORD ??? seems always to be 0001h

12h WORD (CD-ROM driver) reserved, must be 0000h 13h WORD segment of EMS physical page frame

appears to be another device chain 15h WORD ??? seems always to be zero

14h BYTE (CD-ROM driver) drive letter (must initially be 00h) 17h 4 WORDs EMS partial page mapping information???

15h BYTE (CD-ROM driver) number of units


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

Format of DOS 4.01 (from UR 25066 Corrctive Services Disk on) disk buffer 0Bh WORD offset in sectors between copies to write for FAT sectors

info: 0Dh DWORD pointer to DOS Drive Parameter Block (see AH=32h)

Offset Size Description 11h WORD size of data in buffer if remote buffer (see flags above)

00h DWORD pointer to array of disk buffer hash chain heads (see below) 13h BYTE reserved (padding)

04h WORD number of disk buffer hash chains (referred to as NDBCH 14h buffered data

below) Note: for DOS 4.x, all buffered sectors which have the same hash value

06h DWORD pointer to lookahead buffer, zero if not present (computed as the sum of high and low words of the logical sector

0Ah WORD number of lookahead sectors, else zero (the y in BUFFERS=x,y) number divided by the number of disk buffer chains) are on the same

0Ch BYTE 01h, possibly to distinguish from pre-UR 25066 format doubly-linked circular chain; for DOS 5+, only a single circular

0Dh WORD ??? EMS segment for BUFFERS (only with /XD) chain exists.

0Fh WORD ??? EMS physical page number of EMS seg above (only with /XD) the links consist of offset addresses only, the segment being the

11h WORD ??? EMS segment for ??? (only with /XD) same

13h WORD ??? EMS physical page number of above (only with /XD) for all buffers in the chain.

15h BYTE ??? number of EMS page frames present (only with /XD)

16h WORD segment of one-sector workspace buffer allocated in main Format of DOS 5.0-6.0 disk buffer info:

memory Offset Size Description

if BUFFERS/XS or /XD options in effect, possibly to avoid 00h DWORD pointer to least-recently-used buffer header (may be in HMA)

DMA (see above)

into EMS 04h WORD number of dirty disk buffers

18h WORD EMS handle for buffers, zero if not in EMS 06h DWORD pointer to lookahead buffer, zero if not present

1Ah WORD EMS physical page number used for buffers (usually 255) 0Ah WORD number of lookahead sectors, else zero (the y in BUFFERS=x,y)

1Ch WORD ??? appears always to be 0001h 0Ch BYTE buffer location

1Eh WORD segment of EMS physical page frame 00h base memory, no workspace buffer

20h WORD ??? appears always to be zero 01h HMA, workspace buffer in base memory

22h BYTE 00h if /XS, 01h if /XD, FFh if BUFFERS not in EMS 0Dh DWORD pointer to one-segment workspace buffer in base memory

11h 3 BYTEs unused

Format of DOS 4.x disk buffer hash chain head (array, one entry per chain): 14h WORD ???

Offset Size Description 16h BYTE flag: INT 24 fail while making an I/O status call

00h WORD EMS logical page number in which chain is resident, -1 if not 17h BYTE temp storage for user memory allocation strategy during EXEC

in EMS 18h BYTE counter: number of INT 21 calls for which A20 is off

02h DWORD pointer to least recently used buffer header. All buffers on 19h BYTE bit flags

this chain are in the same segment. bit 0: ???

06h BYTE number of dirty buffers on this chain bit 1: SWITCHES=/W specified in CONFIG.SYS (don't load

07h BYTE reserved (00h) WINA20.SYS when MS Windows 3.0 starts)

Notes: buffered disk sectors are assigned to chain N where N is the sector's bit 2: in EXEC state (INT 21/AX=4B05h)

address modulo NDBCH, 0 <= N <= NDBCH-1 1Ah WORD offset of unpack code start (used only during INT

each chain resides completely within one EMS page 21/AX=4B05h)

this structure is in main memory even if buffers are in EMS 1Ch BYTE bit 0 set iff UMB MCB chain linked to normal MCB chain

1Dh WORD minimum paragraphs of memory required by program being EXECed

Format of DOS 4.0-6.0 disk buffer: 1Fh WORD segment of first MCB in upper memory blocks or FFFFh if DOS

Offset Size Description memory chain in base 640K only (first UMB MCB usually at

00h WORD forward ptr, offset only, to next least recently used buffer 9FFFh,

02h WORD backward ptr, offset only locking out video memory with a DOS-owned memory block)

04h BYTE drive (0=A,1=B, etc) if bit 7 clear 21h WORD paragraph from which to start scanning during memory

SFT index if bit 7 set allocation

FFh if not in use

05h BYTE buffer flags Format of IFS driver list:

bit 7: remote buffer Offset Size Description

bit 6: buffer dirty 00h DWORD pointer to next driver header

bit 5: buffer has been referenced (reserved in DOS 5+) 04h 8 BYTEs IFS driver name (blank padded), as used by FILESYS command

bit 4: search data buffer (only valid if remote buffer) 0Ch 4 BYTEs ???

bit 3: sector in data area 10h DWORD pointer to IFS utility function entry point (see below)

bit 2: sector in a directory, either root or subdirectory call with ES:BX -> IFS request (see below)

bit 1: sector in FAT 14h WORD offset in header's segment of driver entry point

bit 0: reserved ???

06h DWORD logical sector number (local buffers only)

0Ah BYTE number of copies to write Call IFS utility function entry point with:

for FAT sectors, same as number of FATs AH = 20h miscellaneous functions

for data and directory sectors, usually 1 AL = 00h get date


APÉNDICES 389

Return: CX = year 03h WORD returned DOS error code

DH = month 05h BYTE IFS driver exit status

DL = day 00h success

AL = 01h get process ID and computer ID 01h ???

Return: BX = current PSP segment 02h ???

DX = active network machine number 03h ???

AL = 05h get file system info 04h ???

ES:DI -> 16-byte info buffer FFh internal failure

Return: buffer filled 06h 16 BYTEs ???

Offset Size Description ---request class 02h---

00h 2 BYTEs unused 16h BYTE function code

02h WORD number of SFTs (actually counts only 04h ???

the first two file table arrays) 17h BYTE unused???

04h WORD number of FCB table entries 18h DWORD pointer to ???

06h WORD number of proctected FCBs 1Ch DWORD pointer to ???

08h 6 BYTEs unused 20h 2 BYTEs ???

0Eh WORD largest sector size supported ---request class 03h---

AL = 06h get machine name 16h BYTE function code

ES:DI -> 18-byte buffer for name 17h BYTE ???

Return: buffer filled with name starting at offset 02h 18h DWORD pointer to ???

AL = 08h get sharing retry count 1Ch DWORD pointer to ???

Return: BX = sharing retry count 22h WORD returned ???

AL = other 24h WORD returned ???

Return: CF set 26h WORD returned ???

AH = 21h get redirection state 28h BYTE returned ???

BH = type (03h disk, 04h printer) 29h BYTE unused???

Return: BH = state (00h off, 01h on) ---request class 04h---

AH = 22h ??? some sort of time calculation 16h DWORD pointer to ???

AL = 00h ??? 1Ah DWORD pointer to ???

nonzero ??? ---request class 05h---

AH = 23h ??? some sort of time calculation 16h BYTE function code

AH = 24h compare filenames 01h flush disk buffers

DS:SI -> first ASCIZ filename 02h get disk space

ES:DI -> second ASCIZ filename 03h MKDIR

Return: ZF set if files are same ignoring case and / vs \ 04h RMDIR

AH = 25h normalize filename 05h CHDIR

DS:SI -> ASCIZ filename 06h delete file

ES:DI -> buffer for result 07h rename file

Return: filename uppercased, forward slashes changed to 08h search directory

backslashes 09h file open/create

AH = 26h get DOS stack 0Ah LSEEK

Return: DS:SI -> top of stack 0Bh read from file

CX = size of stack in bytes 0Ch write to file

AH = 27h increment InDOS flag 0Dh lock region of file

AH = 28h decrement InDOS flag 0Eh commit/close file

Note: IFS drivers which do not wish to implement functions 20h or 24h-28h 0Fh get/set file attributes

may 10h printer control

pass them on to the default handler pointed at by [LoL+37h] 11h ???

12h process termination

Format of IFS request block: 13h ???

Offset Size Description ---class 05h function 01h---

00h WORD total size in bytes of request 17h 7 BYTEs ???

02h BYTE class of request 1Eh DWORD pointer to ???

02h ??? 22h 4 BYTEs ???

03h redirection 26h BYTE ???

04h ??? 27h BYTE ???

05h file access ---class 05h function 02h---

06h convert error code to string 17h 7 BYTEs ???

07h ??? 1Eh DWORD pointer to ???


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

22h 4 BYTEs ??? 2Ah DWORD transfer address

26h WORD returned total clusters ---class 05h function 0Dh---

28h WORD returned sectors per cluster 17h 7 BYTEs ???

2Ah WORD returned bytes per sector 1Eh DWORD pointer to ???

2Ch WORD returned available clusters 22h DWORD pointer to IFS open file structure (see below)

2Eh BYTE returned ??? 26h BYTE file handle???

2Fh BYTE ??? 27h BYTE unused???

---class 05h functions 03h,04h,05h--- 28h WORD ???

17h 7 BYTEs ??? 2Ah WORD ???

1Eh DWORD pointer to ??? 2Ch WORD ???

22h 4 BYTEs ??? 2Eh WORD ???

26h DWORD pointer to directory name ---class 05h function 0Eh---

---class 05h function 06h--- 17h 7 BYTEs ???

17h 7 BYTEs ??? 1Eh DWORD pointer to ???

1Eh DWORD pointer to ??? 22h DWORD pointer to IFS open file structure (see below)

22h 4 BYTEs ??? 26h BYTE 00h commit file

26h WORD attribute mask 01h close file

28h DWORD pointer to filename 27h BYTE unused???

---class 05h function 07h--- ---class 05h function 0Fh---

17h 7 BYTEs ??? 17h 7 BYTEs ???

1Eh DWORD pointer to ??? 1Eh DWORD pointer to ???

22h 4 BYTEs ??? 22h 4 BYTEs ???

26h WORD attribute mask 26h BYTE 02h GET attributes

28h DWORD pointer to source filespec 03h PUT attributes

2Ch DWORD pointer to destination filespec 27h BYTE unused???

---class 05h function 08h--- 28h 12 BYTEs ???

17h 7 BYTEs ??? 34h WORD search attributes???

1Eh DWORD pointer to ??? 36h DWORD pointer to filename

22h 4 BYTEs ??? 3Ah WORD (GET) returned ???

26h BYTE 00h FINDFIRST 3Ch WORD (GET) returned ???

01h FINDNEXT 3Eh WORD (GET) returned ???

28h DWORD pointer to FindFirst search data + 01h if FINDNEXT 40h WORD (GET) returned ???

2Ch WORD search attribute if FINDFIRST 42h WORD (PUT) new attributes

2Eh DWORD pointer to filespec if FINDFIRST (GET) returned attributes

---class 05h function 09h--- ---class 05h function 10h---

17h 7 BYTEs ??? 17h 7 BYTEs ???

1Eh DWORD pointer to ??? 1Eh DWORD pointer to ???

22h DWORD pointer to IFS open file structure (see below) 22h DWORD pointer to IFS open file structure (see below)

26h WORD ??? \ together, specify open vs. create, whether or 26h WORD ???

28h WORD ??? / not to truncate 28h DWORD pointer to ???

2Ah 4 BYTEs ??? 2Ch WORD ???

2Eh DWORD pointer to filename 2Eh BYTE ???

32h 4 BYTEs ??? 2Fh BYTE subfunction

36h WORD file attributes on call 01h get printer setup

returned ??? 03h ???

38h WORD returned ??? 04h ???

---class 05h function 0Ah--- 05h ???

17h 7 BYTEs ??? 06h ???

1Eh DWORD pointer to ??? 07h ???

22h DWORD pointer to IFS open file structure (see below) 21h set printer setup

26h BYTE seek type (02h = from end) ---class 05h function 11h---

28h DWORD offset on call 17h 7 BYTEs ???

returned new absolute position 1Eh DWORD pointer to ???

---class 05h functions 0Bh,0Ch--- 22h DWORD pointer to IFS open file structure (see below)

17h 7 BYTEs ??? 26h BYTE subfunction

1Eh DWORD pointer to ??? 27h BYTE unused???

22h DWORD pointer to IFS open file structure (see below) 28h WORD ???

28h WORD number of bytes to transfer 2Ah WORD ???

returned bytes actually transferred 2Ch WORD ???


APÉNDICES 389

2Eh BYTE ??? 01h low memory best fit

2Fh BYTE ??? 02h low memory last fit

---class 05h function 12h--- ---DOS 5+ ---

17h 15 BYTEs unused??? 40h high memory first fit

26h WORD PSP segment 41h high memory best fit

28h BYTE type of process termination 42h high memory last fit

29h BYTE unused??? 80h first fit, try high then low memory

---class 05h function 13h--- 81h best fit, try high then low memory

17h 15 BYTEs unused??? 82h last fit, try high then low memory

26h WORD PSP segment 01h set allocation strategy

---request class 06h--- BL = new allocation strategy (see above)

16h DWORD returned pointer to string corresponding to error code at 03h BH = 00h (DOS 5+)

1Ah BYTE returned ??? Return: CF clear if successful

1Bh BYTE unused CF set on error

---request class 07h--- AX = error code (01h) (see AH=59h)

16h DWORD pointer to IFS open file structure (see below) Notes: the Set subfunction accepts any value in BL for DOS 3.x and 4.x;

1Ah BYTE ??? 2 or greater means last fit

1Bh BYTE unused??? the Get subfunction returns the last value set

setting an allocation strategy involving high memory does not

Format of IFS open file structure: automatically link in the UMB memory chain; this must be done

Offset Size Description explicitly with AX=5803h in order to actually allocate high memory

00h WORD ??? a program which changes the allocation strategy should restore it

02h WORD device info word before terminating

04h WORD file open mode Toshiba MS-DOS 2.11 supports subfunctions 00h and 01h

06h WORD ??? DR-DOS 3.41 reportedly reverses subfunctions 00h and 01h

08h WORD file attributes SeeAlso: AH=48h,AH=49h,AH=4Ah,INT 2F/AX=4310h,INT 67/AH=3Fh

0Ah WORD owner's network machine number --------


0Ch WORD owner's PSP segment D-2158----------------------------------------------------
0Eh DWORD file size INT 21 - DOS 5+ - GET OR SET UMB LINK STATE
12h DWORD current offset in file AH = 58h

16h WORD file time AL = subfunction

18h WORD file date 02h get UMB link state

1Ah 11 BYTEs filename in FCB format Return: AL = 00h UMBs not part of DOS memory chain

25h WORD ??? = 01h UMBs in DOS memory chain

27h WORD hash value of SFT address 03h set UMB link state

(low word of linear address + segment&F000h) BX = 0000h remove UMBs from DOS memory chain

29h 3 WORDs network info from SFT = 0001h add UMBs to DOS memory chain

2Fh WORD ??? Return: CF clear if successful

CF set on error

Format of one item in DOS 4+ list of special program names: AX = error code (01h) (see AH=59h)

Offset Size Description Note: a program which changes the UMB link state should restore it before

00h BYTE length of name (00h = end of list) terminating

01h N BYTEs name in format name.ext --------


N 2 BYTEs DOS version to return for program (major,minor) D-2159--BX0000--------------------------------------------
(see AH=30h,INT 2F/AX=122Fh) INT 21 - DOS 3+ - GET EXTENDED ERROR INFORMATION
---DOS 4 only--- AH = 59h

N+2 BYTE number of times to return fake version number (FFh = always) BX = 0000h

Note: if the name of the executable for the program making the DOS "get Return: AX = extended error code (see below)

version" call matches one of the names in this list, DOS returns BH = error class (see below)

the BL = recommended action (see below)

specified version rather than the true version number CH = error locus (see below)

-------- ES:DI may be pointer (see error code list below)

D-2158---------------------------------------------------- CL, DX, SI, BP, and DS destroyed

INT 21 - DOS 3+ - GET OR SET MEMORY ALLOCATION STRATEGY Notes: functions available under DOS 2.x map the true DOS 3+ error code into

AH = 58h one supported under DOS 2.x

AL = subfunction you should call this function to retrieve the true error code when an

00h get allocation strategy FCB or DOS 2.x call returns an error

Return: AX = current strategy under DR-DOS 5.0, this function does not use any of the DOS-internal

00h low memory first fit stacks and may thus be called at any time
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

SeeAlso: AH=59h/BX=0001h,AX=5D0Ah,INT 2F/AX=122Dh 3Fh (63) not enough space to print file

40h (64) network name was deleted

Values for extended error code: 41h (65) network: Access denied

00h (0) no error 42h (66) network device type incorrect

01h (1) function number invalid 43h (67) network name not found

02h (2) file not found 44h (68) network name limit exceeded

03h (3) path not found 45h (69) network BIOS session limit exceeded

04h (4) too many open files (no handles available) 46h (70) temporarily paused

05h (5) access denied 47h (71) network request not accepted

06h (6) invalid handle 48h (72) network print/disk redirection paused

07h (7) memory control block destroyed 49h (73) network software not installed

08h (8) insufficient memory (LANtastic) invalid network version

09h (9) memory block address invalid 4Ah (74) unexpected adapter close

0Ah (10) environment invalid (usually >32K in length) (LANtastic) account expired

0Bh (11) format invalid 4Bh (75) (LANtastic) password expired

0Ch (12) access code invalid 4Ch (76) (LANtastic) login attempt invalid at this time

0Dh (13) data invalid 4Dh (77) (LANtastic v3+) disk limit exceeded on network node

0Eh (14) reserved 4Eh (78) (LANtastic v3+) not logged in to network node

0Fh (15) invalid drive 4Fh (79) reserved

10h (16) attempted to remove current directory 50h (80) file exists

11h (17) not same device 51h (81) reserved

12h (18) no more files 52h (82) cannot make directory

---DOS 3+--- 53h (83) fail on INT 24h

13h (19) disk write-protected 54h (84) (DOS 3.3+) too many redirections

14h (20) unknown unit 55h (85) (DOS 3.3+) duplicate redirection

15h (21) drive not ready 56h (86) (DOS 3.3+) invalid password

16h (22) unknown command 57h (87) (DOS 3.3+) invalid parameter

17h (23) data error (CRC) 58h (88) (DOS 3.3+) network write fault

18h (24) bad request structure length 59h (89) (DOS 4+) function not supported on network

19h (25) seek error 5Ah (90) (DOS 4+) required system component not installed

1Ah (26) unknown media type (non-DOS disk) 64h (100) (MSCDEX) unknown error

1Bh (27) sector not found 65h (101) (MSCDEX) not ready

1Ch (28) printer out of paper 66h (102) (MSCDEX) EMS memory no longer valid

1Dh (29) write fault 67h (103) (MSCDEX) not High Sierra or ISO-9660 format

1Eh (30) read fault 68h (104) (MSCDEX) door open

1Fh (31) general failure

20h (32) sharing violation Values for Error Class:

21h (33) lock violation 01h out of resource (storage space or I/O channels)

22h (34) disk change invalid 02h temporary situation (file or record lock)

ES:DI -> ASCIZ volume label of required disk 03h authorization (denied access)

23h (35) FCB unavailable 04h internal (system software bug)

24h (36) sharing buffer overflow 05h hardware failure

25h (37) (DOS 4+) code page mismatch 06h system failure (configuration file missing or incorrect)

26h (38) (DOS 4+) cannot complete file operation (out of input) 07h application program error

27h (39) (DOS 4+) insufficient disk space 08h not found

28h-31h reserved 09h bad format

32h (50) network request not supported 0Ah locked

33h (51) remote computer not listening 0Bh media error

34h (52) duplicate name on network 0Ch already exists

35h (53) network name not found 0Dh unknown

36h (54) network busy

37h (55) network device no longer exists Values for Suggested Action:

38h (56) network BIOS command limit exceeded 01h retry

39h (57) network adapter hardware error 02h delayed retry

3Ah (58) incorrect response from network 03h prompt user to reenter input

3Bh (59) unexpected network error 04h abort after cleanup

3Ch (60) incompatible remote adapter 05h immediate abort

3Dh (61) print queue full 06h ignore

3Eh (62) queue not full 07h retry after user intervention
APÉNDICES 389

16h BYTE current drive

Values for Error Locus: 17h BYTE extended break flag

01h unknown or not appropriate ---remainder need only be swapped if in DOS---

02h block device (disk error) 18h WORD value of AX on call to INT 21

03h network related 1Ah WORD PSP segment for sharing/network

04h serial device (timeout) 1Ch WORD network machine number for sharing/network (0000h = us)

05h memory related 1Eh WORD first usable memory block found when allocating memory

-------- 20h WORD best usable memory block found when allocating memory

D-215D06-------------------------------------------------- 22h WORD last usable memory block found when allocating memory

INT 21 U - DOS 3.0+ internal - GET ADDRESS OF DOS SWAPPABLE DATA 24h WORD memory size in paragraphs (used only during initialization)

AREA 26h WORD last entry checked during directory search

AX = 5D06h 28h BYTE flag: INT 24 returned Fail

Return: CF set on error 29h BYTE flags: allowable INT 24 actions (passed to INT 24 in AH)

AX = error code (see AH=59h) 2Ah BYTE directory flag (00h directory, 01h file)

CF clear if successful 2Bh BYTE flag: FFh if Ctrl-Break termination, 00h otherwise

DS:SI -> nonreentrant data area (includes all three DOS stacks) 2Ch BYTE flag: allow embedded blanks in FCB

(critical error flag is first byte) 2Dh BYTE padding (unused)

CX = size in bytes of area which must be swapped while in DOS 2Eh BYTE day of month

DX = size in bytes of area which must always be swapped 2Fh BYTE month

Notes: the Critical Error flag is used in conjunction with the InDOS flag 30h WORD year - 1980

(see AH=34h) to determine when it is safe to enter DOS from a TSR 32h WORD number of days since 1-1-1980

setting CritErr flag allows use of functions 50h/51h from INT 28h 34h BYTE day of week (0 = Sunday)

under 35h BYTE flag: console swapped during read from device

DOS 2.x by forcing use of correct stack 36h BYTE flag: safe to call INT 28 if nonzero

swapping the data area allows reentering DOS unless DOS is in a 37h BYTE flag: if nonzero, INT 24 Abort turned into INT 24 Fail

critical section delimited by INT 2A/AH=80h and INT 2A/AH=81h,82h (set only during process termination)

under DOS 4.0, AX=5D0Bh should be used instead of this function 38h 26 BYTEs device driver request header (see INT 2F/AX=0802h)

SHARE and other DOS utilities consult the byte at offset 04h in the 52h DWORD pointer to device driver entry point (used in calling driver)

DOS data segment (see INT 2F/AX=1203h) to determine the SDA format 56h 22 BYTEs device driver request header for I/O calls

in use: 00h = DOS 3.x, 01h = DOS 4.0-6.0, other = error. 6Ch 14 BYTEs device driver request header for disk status check

DR-DOS 3.41+ supports this function, but the SDA format beyond the 7Ah DWORD pointer to device I/O buffer???

first 18h bytes is completely different from MS-DOS 7Eh WORD ???

SeeAlso: AX=5D0Bh,INT 2A/AH=80h,INT 2A/AH=81h,INT 2A/AH=82h 80h WORD ???

82h BYTE type of PSP copy (00h=simple for INT 21/AH=26h, FFh=make

Format of DOS 3.10-3.30 Swappable Data Area: child)

Offset Size Description 83h BYTE padding (unused)

-34 BYTE (DOS 3.10+) printer echo flag (00h off, FFh active) 84h 3 BYTEs 24-bit user number (see AH=30h)

-31 BYTE (DOS 3.30) current switch character 87h BYTE OEM number (see AH=30h)

-28 BYTE (DOS 3.30) incremented on each INT 21/AX=5E01h call 88h WORD offset to error code conversion table for INT 25/INT 26

-27 16 BYTEs (DOS 3.30) machine name set by INT 21/AX=5E01h 8Ah 6 BYTEs CLOCK$ transfer record (see AH=52h)

-11 5 WORDs zero-terminated list of offsets which need to be patched to 90h BYTE device I/O buffer for single-byte I/O functions

enable critical-section calls (see INT 2A/AH=80h) 91h BYTE padding??? (unused)

-1 BYTE unused padding 92h 128 BYTEs buffer for filename

---start of actual SDA--- 112h 128 BYTEs buffer for filename

00h BYTE critical error flag ("ErrorMode") 192h 21 BYTEs findfirst/findnext search data block (see AH=4Eh)

01h BYTE InDOS flag (count of active INT 21 calls) 1A7h 32 BYTEs directory entry for found file (see AH=11h)

02h BYTE drive on which current critical error occurred, or FFh 1C7h 81 BYTEs copy of current directory structure for drive being accessed

(DR-DOS sets to drive number during INT 24, 00h otherwise) 218h 11 BYTEs FCB-format filename for device name comparison

03h BYTE locus of last error 223h BYTE terminating NUL for above filename

04h WORD extended error code of last error 224h 11 BYTEs wildcard destination specification for rename (FCB format)

06h BYTE suggested action for last error 22Fh BYTE terminating NUL for above spec

07h BYTE class of last error 230h BYTE ???

08h DWORD ES:DI pointer for last error 231h WORD destination file/directory starting sector

0Ch DWORD current DTA 233h 5 BYTEs ???

10h WORD current PSP 238h BYTE extended FCB file attribute

12h WORD stores SP across an INT 23 239h BYTE type of FCB (00h regular, FFh extended)

14h WORD return code from last process termination (zerod after 23Ah BYTE directory search attributes

reading 23Bh BYTE file open/access mode

with AH=4Dh) 23Ch BYTE file found/delete flag


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

bit 0: file found 2AEh WORD used by INT 21 dispatcher to store caller's BX

bit 4: file deleted 2B0h WORD used by INT 21 dispatcher to store caller's DS

23Dh BYTE flag: device name found on rename, or file not found 2B2h WORD temporary storage while saving/restoring caller's registers

23Eh BYTE splice flag (file name and directory name together) 2B4h DWORD pointer to prev call frame (offset 250h) if INT 21 reentered

23Fh BYTE flag indicating how DOS function was invoked also switched to for duration of INT 24

(00h = direct INT 20/INT 21, FFh = server call AX=5D00h) 2B8h 21 BYTEs FindFirst search data for source file(s) of a rename

240h BYTE sector position within cluster operation

241h BYTE flag: translate sector/cluster (00h no, 01h yes) (see AH=4Eh)

242h BYTE flag: 00h if read, 01h if write 2CDh 32 BYTEs directory entry for file being renamed (see AH=11h for

243h BYTE current working drive number format)

244h BYTE cluster factor 2EDh 331 BYTEs critical error stack

245h BYTE flag: cluster split mode 403h 35 BYTEs scratch SFT

246h BYTE line edit (AH=0Ah) insert mode flag (nonzero = on) 438h 384 BYTEs disk stack (functions greater than 0Ch, INT 25,INT 26)

247h BYTE canonicalized filename referred to existing file/dir if FFh 5B8h 384 BYTEs character I/O stack (functions 01h through 0Ch)

248h BYTE volume ID flag ---DOS 3.2,3.3x only---

249h BYTE type of process termination (00h-03h) (see AH=4Dh) 738h BYTE device driver lookahead flag (usually printer) (see AH=64h)

24Ah BYTE file create flag (00h = no) 739h BYTE volume change flag

24Bh BYTE value with which to replace first byte of deleted file's name 73Ah BYTE flag: virtual open

(normally E5h, but 00h as described under INT 21/AH=13h) 73Bh BYTE ???

24Ch DWORD pointer to Drive Parameter Block for critical error --------
invocation D-215D0A--------------------------------------------------
temp: used during process termination INT 21 - DOS 3.1+ - SET EXTENDED ERROR INFORMATION
250h DWORD pointer to stack frame containing user registers on INT 21 AX = 5D0Ah

254h WORD stores SP across INT 24 DS:DX -> 11-word DOS parameter list (see AX=5D00h)

256h DWORD pointer to DOS Drive Parameter Block for ??? Return: nothing. next call to AH=59h will return values from fields

25Ah WORD saving partial cluster number AX,BX,CX,

25Ch WORD temp: sector of work current cluster DX,DI, and ES in corresponding registers

25Eh WORD high part of cluster number (only low byte referenced) Notes: documented for DOS 5+, but undocumented in earlier versions

260h WORD ??? temp the MS-DOS Programmer's Reference incorrectly states that this call

262h BYTE Media ID byte returned by AH=1Bh,1Ch was

263h BYTE padding (unused) introduced in DOS 4, and fails to mention that the ERROR structure

264h DWORD pointer to device header passed to this function is a DOS parameter list.

268h DWORD pointer to current SFT BUG: DR-DOS 3.41 and 5.0 read the value for ES from the DS field of the

26Ch DWORD pointer to current directory structure for drive being DPL;

accessed fortunately, MS-DOS ignores the DS field, allowing a generic

270h DWORD pointer to caller's FCB routine

274h WORD number of SFT to which file being opened will refer which sets both DS and ES fields to the same value

276h WORD temporary storage for file handle SeeAlso: AH=59h

278h DWORD pointer to a JFT entry in process handle table (see AH=26h) --------
27Ch WORD offset in DOS DS of first filename argument D-215D0B--------------------------------------------------
27Eh WORD offset in DOS DS of second filename argument INT 21 OU - DOS 4.x only internal - GET DOS SWAPPABLE DATA AREAS
280h WORD offset of last component in pathname or FFFFh AX = 5D0Bh

282h WORD offset of transfer address to add Return: CF set on error

284h WORD last relative cluster within file being accessed AX = error code (see AH=59h)

286h WORD temp: absolute cluster number being accessed CF clear if successful

288h WORD directory sector number DS:SI -> swappable data area list (see below)

28Ah WORD ??? current cluster number Notes: copying and restoring the swappable data areas allows DOS to be

28Ch WORD ??? current offset in file DIV bytes per sector reentered unless it is in a critical section delimited by calls to

28Eh WORD current sector number INT 2A/AH=80h and INT 2A/AH=81h,82h

290h WORD current byte offset within sector SHARE and other DOS utilities consult the byte at offset 04h in the

292h DWORD current offset in file DOS data segment (see INT 2F/AX=1203h) to determine the SDA format

296h DWORD temp: file byte count in use: 00h = DOS 3.x, 01h = DOS 4.0-6.0, other = error.

29Ah WORD temp: file byte count DOS 5+ use the SDA format listed below, but revert back to the DOS

29Ch WORD free file cluster entry 3.x

29Eh WORD last file cluster entry call for finding the SDA (see AX=5D06h)

2A0h WORD next file cluster number SeeAlso: AX=5D06h,INT 2A/AH=80h,INT 2A/AH=81h,INT 2A/AH=82h,INT 2F/AX=1203h

2A2h DWORD number of bytes appended to file

2A6h DWORD pointer to current work disk buffer Format of DOS 4.x swappable data area list:

2AAh DWORD pointer to working SFT Offset Size Description


APÉNDICES 389

00h WORD count of data areas 37h BYTE flag: console swapped during read from device

02h N BYTEs "count" copies of data area record 38h BYTE flag: safe to call INT 28 if nonzero

Offset Size Description 39h BYTE flag: abort currently in progress, turn INT 24 Abort into

00h DWORD address Fail

04h WORD length and type 3Ah 30 BYTEs device driver request header (see INT 2F/AX=0802h) for

bit 15 set if swap always, clear if swap in device calls

DOS 58h DWORD pointer to device driver entry point (used in calling driver)

bits 14-0: length in bytes 5Ch 22 BYTEs device driver request header for I/O calls

72h 14 BYTEs device driver request header for disk status check

Format of DOS 4.0-6.0 swappable data area: 80h DWORD pointer to device I/O buffer

Offset Size Description 84h WORD ???

-34 BYTE printer echo flag (00h off, FFh active) 86h WORD ??? (0)

-31 BYTE current switch character (ignored by DOS 5+) 88h BYTE type of PSP copy (00h=simple for INT 21/AH=26h, FFh=make

-28 BYTE incremented on each INT 21/AX=5E01h call child)

-27 16 BYTEs machine name set by INT 21/AX=5E01h 89h DWORD start offset of file region to lock/unlock

-11 5 WORDs zero-terminated list of offsets which need to be patched to 8Dh DWORD length of file region to lock/unlock

enable critical-section calls (see INT 2A/AH=80h) 91h BYTE padding (unused)

(all offsets are 0D0Ch, but this list is still present for 92h 3 BYTEs 24-bit user number (see AH=30h)

DOS 3.x compatibility) 95h BYTE OEM number (see AH=30h)

-1 BYTE unused padding 96h 6 BYTEs CLOCK$ transfer record (see AH=52h)

---start of actual SDA--- 9Ch BYTE device I/O buffer for single-byte I/O functions???

00h BYTE critical error flag ("ErrorMode") 9Dh BYTE padding???

01h BYTE InDOS flag (count of active INT 21 calls) 9Eh 128 BYTEs buffer for filename

02h BYTE drive on which current critical error occurred or FFh 11Eh 128 BYTEs buffer for filename

03h BYTE locus of last error 19Eh 21 BYTEs findfirst/findnext search data block (see AH=4Eh)

04h WORD extended error code of last error 1B3h 32 BYTEs directory entry for found file (see AH=11h)

06h BYTE suggested action for last error 1D3h 88 BYTEs copy of current directory structure for drive being accessed

07h BYTE class of last error 22Bh 11 BYTEs FCB-format filename for device name comparison

08h DWORD ES:DI pointer for last error 236h BYTE terminating NUL for above filename

0Ch DWORD current DTA 237h 11 BYTEs wildcard destination specification for rename (FCB format)

10h WORD current PSP 242h BYTE terminating NUL for above spec

12h WORD stores SP across an INT 23 243h BYTE ???

14h WORD return code from last process termination (zerod after 244h WORD ???

reading 246h 5 BYTEs ???

with AH=4Dh) 24Bh BYTE extended FCB file attributes

16h BYTE current drive 24Ch BYTE type of FCB (00h regular, FFh extended)

17h BYTE extended break flag 24Dh BYTE directory search attributes

18h BYTE flag: code page switching 24Eh BYTE file open/access mode

19h BYTE flag: copy of previous byte in case of INT 24 Abort 24Fh BYTE ??? flag bits

---remainder need only be swapped if in DOS--- 250h BYTE flag: device name found on rename, or file not found

1Ah WORD value of AX on call to INT 21 251h BYTE splice flag??? (file name and directory name together)

1Ch WORD PSP segment for sharing/network 252h BYTE flag indicating how DOS function was invoked

1Eh WORD network machine number for sharing/network (0000h = us) (00h = direct INT 20/INT 21, FFh = server call AX=5D00h)

20h WORD first usable memory block found when allocating memory 253h BYTE ???

22h WORD best usable memory block found when allocating memory 254h BYTE ???

24h WORD last usable memory block found when allocating memory 255h BYTE ???

26h WORD memory size in paragraphs (used only during initialization) 256h BYTE ???

28h WORD last entry checked during directory search 257h BYTE ???

2Ah BYTE flag: nonzero if INT 24 Fail 258h BYTE ???

2Bh BYTE flags: allowable INT 24 responses (passed to INT 24 in AH) 259h BYTE ???

2Ch BYTE flag: do not set directory if nonzero 25Ah BYTE canonicalized filename referred to existing file/dir if FFh

2Dh BYTE flag: program aborted by ^C 25Bh BYTE ???

2Eh BYTE flag: allow embedded blanks in FCB 25Ch BYTE type of process termination (00h-03h)

2Fh BYTE padding (unused) 25Dh BYTE ???

30h BYTE day of month 25Eh BYTE ???

31h BYTE month 25Fh BYTE ???

32h WORD year - 1980 260h DWORD pointer to Drive Parameter Block for critical error

34h WORD number of days since 1-1-1980 invocation

36h BYTE day of week (0 = Sunday) 264h DWORD pointer to stack frame containing user registers on INT 21
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

268h WORD stores SP??? 2F1h WORD ??? bit flags

26Ah DWORD pointer to DOS Drive Parameter Block for ??? 2F3h DWORD pointer to user-supplied filename

26Eh WORD segment of disk buffer 2F7h DWORD pointer to ???

270h WORD ??? 2FBh WORD stores SS during call to [List-of-Lists + 37h]

272h WORD ??? 2FDh WORD stores SP during call to [List-of-Lists + 37h]

274h WORD ??? 2FFh BYTE flag, nonzero if stack switched in calling

276h WORD ??? [List-of-Lists+37h]

278h BYTE Media ID byte returned by AH=1Bh,1Ch 300h 21 BYTEs FindFirst search data for source file(s) of a rename

279h BYTE ??? (doesn't seem to be referenced) operation

27Ah DWORD pointer to ??? (see AH=4Eh)

27Eh DWORD pointer to current SFT 315h 32 BYTEs directory entry for file being renamed (see AH=11h)

282h DWORD pointer to current directory structure for drive being 335h 331 BYTEs critical error stack

accessed 480h 384 BYTEs disk stack (functions greater than 0Ch, INT 25,INT 26)

286h DWORD pointer to caller's FCB 600h 384 BYTEs character I/O stack (functions 01h through 0Ch)

28Ah WORD SFT index to which file being opened will refer 780h BYTE device driver lookahead flag (usually printer) (see AH=64h)

28Ch WORD temporary storage for file handle 781h BYTE volume change flag

28Eh DWORD pointer to a JFT entry in process handle table (see AH=26h) 782h BYTE flag: virtual open

292h WORD offset in DOS DS of first filename argument 783h BYTE ???

294h WORD offset in DOS DS of second filename argument 784h WORD ???

296h WORD ??? 786h WORD ???

298h WORD ??? 788h WORD ???

29Ah WORD ??? 78Ah WORD ???

29Ch WORD ??? --------


29Eh WORD ??? D-2162----------------------------------------------------
2A0h WORD ??? INT 21 - DOS 3+ - GET CURRENT PSP ADDRESS
2A2h WORD ??? directory cluster number??? AH = 62h

2A4h DWORD ??? Return: BX = segment of PSP for current process

2A8h DWORD ??? Notes: under DOS 3+, this function does not use any of the DOS-internal

2ACh WORD ??? stacks

2AEh DWORD offset in file??? and may thus be called at any time, even during another INT 21h

2B2h WORD ??? call

2B4h WORD bytes in partial sector the current PSP is not necessarily the caller's PSP

2B6h WORD number of sectors identical to the undocumented AH=51h

2B8h WORD ??? SeeAlso: AH=50h,AH=51h

2BAh WORD ??? --------


2BCh WORD ??? D-22------------------------------------------------------
2BEh DWORD number of bytes appended to file INT 22 - DOS 1+ - PROGRAM TERMINATION ADDRESS
2C2h DWORD pointer to ??? disk buffer Desc: this vector specifies the address of the routine which is to be given

2C6h DWORD pointer to ??? SFT control after a program is terminated; it should never be called

2CAh WORD used by INT 21 dispatcher to store caller's BX directly, since it does not point at an interrupt handler

2CCh WORD used by INT 21 dispatcher to store caller's DS Notes: this vector is restored from the DWORD at offset 0Ah in the PSP

2CEh WORD temporary storage while saving/restoring caller's registers during

2D0h DWORD pointer to prev call frame (offset 264h) if INT 21 reentered termination, and then a FAR JMP is performed to the address in INT

also switched to for duration of INT 24 22

2D4h WORD open mode/action for INT 21/AX=6C00h normally points at the instruction immediately following INT

2D6h BYTE ??? (set to 00h by INT 21h dispatcher, 02h when a read is 21/AH=4Bh

performed, and 01h or 03h by INT 21/AX=6C00h) call which loaded the current program

2D7h WORD ??? apparently unused SeeAlso: INT 20,INT 21/AH=00h,INT 21/AH=31h,INT 21/AH=4Ch

2D9h DWORD stored ES:DI for AX=6C00h --------


2DDh WORD extended file open action code (see AX=6C00h) D-23------------------------------------------------------
2DFh WORD extended file open attributes (see AX=6C00h) INT 23 - DOS 1+ - CONTROL-C/CONTROL-BREAK HANDLER
2E1h WORD extended file open file mode (see AX=6C00h) ---DOS 1.x---

2E3h DWORD pointer to filename to open (see AX=6C00h) Return: AH = 00h abort program

2E7h WORD ??? if all registers preserved, restart DOS call

2E9h WORD ??? ---DOS 2+---

2EBh BYTE ??? CF clear

2ECh WORD stores DS during call to [List-of-Lists + 37h] Return: all registers preserved

2EEh WORD ??? return via RETF or RETF 2 with CF set

2F0h BYTE ??? DOS will abort program with errorlevel 0


APÉNDICES 389

else (RETF/RETF 2 with CF clear or IRET) WORD original AX on entry to INT 21

interrupted DOS call is restarted WORD BX

Notes: this interrupt is invoked whenever DOS detects a ^C or ^Break; it WORD CX

should never be called directly WORD DX

MS-DOS 1.25 also invokes INT 23 on a divide overflow (INT 00) WORD SI

DOS remembers the stack pointer before calling INT 23, and if it is WORD DI

not the same on return, pops and discards the top word; this is WORD BP

what WORD DS

permits a return with RETF as well as IRET or RETF 2 WORD ES

any DOS call may safely be made within the INT 23 handler, although DWORD return address for INT 21 call

the handler must check for a recursive invocation if it does WORD flags pushed by INT 21

call DOS Handler must return:

SeeAlso: INT 1B AL = action code

-------- 00h ignore error and continue processing request

D-24------------------------------------------------------ 01h retry operation

INT 24 - DOS 1+ - CRITICAL ERROR HANDLER 02h terminate program through the equivalent of INT 21/AH=4Ch

Note: invoked when a critical (usually hardware) error is encountered; (INT 20h for DOS 1.x)

should 03h fail system call in progress

never be called directly SS,SP,DS,ES,BX,CX,DX preserved

SeeAlso: INT 21/AH=95h Notes: the only DOS calls the handler may make are INT 21/AH=01h-0Ch,30h,59h

if the handler returns to the application by popping the stack, DOS

Critical error handler is invoked with: will be in an unstable state until the first call with AH > 0Ch

AH = type and processing flags for DOS 3.1+, IGNORE (AL=00h) is turned into FAIL (AL=03h) on network

bit 7 clear = disk I/O error critical errors

set = -- if block device, bad FAT image in memory if IGNORE specified but not allowed, it is turned into FAIL

-- if char device, error code in DI if RETRY specified but not allowed, it is turned into FAIL

bit 6 unused if FAIL specified but not allowed, it is turned into ABORT

bit 5 = 1 if Ignore allowed, 0 if not (DOS 3+) (DOS 3+) if a critical error occurs inside the critical error

bit 4 = 1 if Retry allowed, 0 if not (DOS 3+) handler,

bit 3 = 1 if Fail allowed, 0 if not (DOS 3+) the DOS call is automatically failed

bit 2 \ disk area of error 00 = DOS area 01 = FAT --------


bit 1 / 10 = root dir 11 = data area D-25------------------------------------------------------
bit 0 = 1 if write, 0 if read INT 25 - DOS 1+ - ABSOLUTE DISK READ (except partitions > 32M)
AL = drive number if AH bit 7 clear AL = drive number (00h = A:, 01h = B:, etc)

BP:SI -> device driver header (BP:[SI+4] bit 15 set if char device) CX = number of sectors to read

DI low byte contains error code if AH bit 7 set DX = starting logical sector number (0000h - highest sector on drive)

00h write-protection violation attempted DS:BX -> buffer for data

01h unknown unit for driver Return: CF clear if successful

02h drive not ready CF set on error

03h unknown command given to driver AH = status

04h data error (bad CRC) 80h device failed to respond (timeout)

05h bad device driver request structure length 40h seek operation failed

06h seek error 20h controller failed

07h unknown media type 10h data error (bad CRC)

08h sector not found 08h DMA failure

09h printer out of paper 04h requested sector not found

0Ah write fault 03h write-protected disk (INT 26 only)

0Bh read fault 02h bad address mark

0Ch general failure 01h bad command

0Dh (DOS 3+) sharing violation AL = error code (same as passed to INT 24 in DI)

0Eh (DOS 3+) lock violation AX = 0207h if more than 64K sectors on drive -- use new-style

0Fh invalid disk change call

10h (DOS 3+) FCB unavailable may destroy all other registers except segment registers

11h (DOS 3+) sharing buffer overflow Notes: original flags are left on stack, and must be popped by caller

12h (DOS 4+) code page mismatch this call bypasses the DOS filesystem

13h (DOS 4+) out of input examination of CPWIN386.CPL indicates that if this call fails with

14h (DOS 4+) insufficient disk space error 0408h on an old-style (<32M) call, one should retry the

STACK: DWORD return address for INT 24 call call with the high bit of the drive number in AL set

WORD flags pushed by INT 24 BUGS: DOS 3.1 through 3.3 set the word at ES:[BP+1Eh] to FFFFh if AL is an
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

invalid drive number --------


DR-DOS 3.41 will return with a jump instead of RETF, leaving the D-26----CXFFFF--------------------------------------------
wrong number of bytes on the stack; use the huge-partition version INT 26 - DOS 3.31+ - ABSOLUTE DISK WRITE (>32M hard-disk
(INT 25/CX=FFFFh) for all partition sizes under DR-DOS 3.41 partition)
SeeAlso: INT 13/AH=02h,INT 25/CX=FFFFh,INT 26 CX = FFFFh

-------- AL = drive number (0=A, 1=B, etc)

D-25----CXFFFF-------------------------------------------- DS:BX -> disk write packet (see below)

INT 25 - DOS 3.31+ - ABSOLUTE DISK READ (>32M hard-disk Return: same as above

partition) Notes: partition is potentially >32M (and requires this form of the call) if

CX = FFFFh bit 1 of device attribute word in device driver is set

AL = drive number (0=A, 1=B, etc) original flags are left on stack, and must be removed by caller

DS:BX -> disk read packet (see below) this call bypasses the DOS filesystem, though DOS 5+ invalidates any

Return: same as above disk buffers referencing sectors which are written with this call

Notes: partition is potentially >32M (and requires this form of the call) if SeeAlso: INT 13/AH=03h,INT 25/CX=FFFFh,INT 26

bit 1 of device attribute word in device driver is set

original flags are left on stack, and must be removed by caller Format of disk write packet:

this call bypasses the DOS filesystem Offset Size Description

SeeAlso: INT 13/AH=02h,INT 25,INT 26/CX=FFFFh 00h DWORD sector number

04h WORD number of sectors to read

Format of disk read packet: 06h DWORD transfer address

Offset Size Description --------


00h DWORD sector number D-27------------------------------------------------------
04h WORD number of sectors to read INT 27 - DOS 1+ - TERMINATE AND STAY RESIDENT
06h DWORD transfer address DX = number of bytes to keep resident (max FFF0h)

-------- CS = segment of PSP

D-26------------------------------------------------------ Return: never

INT 26 - DOS 1+ - ABSOLUTE DISK WRITE (except partitions > 32M) Notes: this is an obsolete call

AL = drive number (00h = A:, 01h = B:, etc) INT 22, INT 23, and INT 24 are restored from the PSP

CX = number of sectors to write does not close any open files

DX = starting logical sector number (0000h - highest sector on drive) the minimum number of bytes which will remain resident is 110h for

DS:BX -> data to write DOS 2.x and 60h for DOS 3+; there is no minimum for DOS 1.x, which

Return: CF clear if successful implements this service in COMMAND.COM rather than the DOS kernel

CF set on error SeeAlso: INT 21/AH=31h

AH = status --------
80h device failed to respond (timeout) D-28------------------------------------------------------
40h seek operation failed INT 28 C - DOS 2+ - DOS IDLE INTERRUPT
20h controller failed SS:SP = top of MS-DOS stack for I/O functions

10h data error (bad CRC) Return: all registers preserved

08h DMA failure Desc: This interrupt is invoked each time one of the DOS character input

04h requested sector not found functions loops while waiting for input. Since a DOS call is in

03h write-protected disk (INT 26 only) progress even though DOS is actually idle during such input waits,

02h bad address mark hooking this function is necessary to allow a TSR to perform DOS

01h bad command calls while the foreground program is waiting for user input. The

AL = error code (same as passed to INT 24 in DI) INT 28h handler may invoke any INT 21h function except functions

AX = 0207h if more than 64K sectors on drive -- use new-style 00h through 0Ch.

call Notes: under DOS 2.x, the critical error flag (the byte immediately after

may destroy all other registers except segment registers the

Notes: original flags are left on stack, and must be popped by caller InDOS flag) must be set in order to call DOS functions 50h/51h from

this call bypasses the DOS filesystem, though DOS 5+ invalidates any the INT 28h handler without destroying the DOS stacks.

disk buffers referencing sectors which are written with this call calls to INT 21/AH=3Fh,40h from within an INT 28 handler may not use

examination of CPWIN386.CPL indicates that if this call fails with a

error 0408h on an old-style (<32M) call, one should retry the handle which refers to CON

call with the high bit of the drive number in AL set at the time of the call, the InDOS flag (see INT 21/AH=34h) is

BUGS: DOS 3.1 through 3.3 set the word at ES:[BP+1Eh] to FFFFh if AL is an normally

invalid drive number set to 01h; if larger, DOS is truly busy and should not be

DR-DOS 3.41 will return with a jump instead of RETF, leaving the reentered

wrong number of bytes on the stack; use the huge-partition version the default handler is an IRET instruction

(INT 26/CX=FFFFh) for all partition sizes under DR-DOS 3.41 supported in OS/2 compatibility box

SeeAlso: INT 13/AH=03h,INT 25,INT 26/CX=FFFFh the _MS-DOS_Programmer's_Reference_ for DOS 5.0 incorrectly documents
APÉNDICES 389

this interrupt as superseded TSR honors specific return address

SeeAlso: INT 21/AH=34h,INT 2A/AH=84h,INT 2F/AX=1680h 03h request pop-up

-------- Return: AL = status

D-29------------------------------------------------------ 00h not implemented or TSR is not a pop-up

INT 29 C - DOS 2+ - FAST CONSOLE OUTPUT 01h can not pop up at this time, try again later

AL = character to display 02h can not pop up yet, will do so when able

Return: nothing 03h already popped up

Notes: automatically called when writing to a device with bit 4 of its 04h unable to pop up, user intervention required

device BX = standard reason code

driver header set (see also INT 21/AH=52h) 0000h unknown failure

COMMAND.COM v3.2 and v3.3 compare the INT 29 vector against the INT 0001h interrupt chain passes through

20 memory

vector and assume that ANSI.SYS is installed if the segment is which must be swapped out to pop up

larger 0002h swap-in failed

the default handler under DOS 2.x and 3.x simply calls INT 10/AH=0Eh CX = application's reason code if nonzero

the default handler under DESQview 2.2 understands the <Esc>[2J FFh TSR popped up and was exited by user

screen-clearing sequence, calls INT 10/AH=0Eh for all others BX = return value

SeeAlso: INT 21/AH=52h,INT 2F/AX=0802h,INT 79 0000h no return value

-------- 0001h TSR unloaded

D-2D------------------------------------------------------ 0002h-00FFh reserved

INT 2D - DOS 2+ - RESERVED 0100h-FFFFh application-dependent

Note: this vector is not used in DOS versions <= 6.00, and points at an 04h determine chained interrupts

IRET BL = interrupt number (except 2Dh)

-------- Return: AL = status

t-2D------------------------------------------------------ 00h not implemented

INT 2D - ALTERNATE MULTIPLEX INTERRUPT SPECIFICATION (AMIS) 01h (obsolete) unable to determine

[v3.5.1] 02h (obsolete) interrupt hooked

AH = multiplex number 03h (obsolete) interrupt hooked, address returned

AL = function DX:BX -> TSR's interrupt BL handler

00h installation check 04h list of hooked interrupts returned

Return: AL = 00h if free DX:BX -> interrupt hook list (see below)

AL = FFh if multiplex number in use FFh interrupt not hooked

CX = binary version number (CH = major, CL = Notes: since INT 2D is known to be hooked, the resident code

minor) need not test for BL=2Dh (to minimize its size),

DX:DI -> signature string (see below) identifying and

the program using the multiplex number the return value is therefore undefined in that

01h get entry point case.

Return: AL = 00h if all API calls via INT 2D BL is ignored if the TSR returns AL=04h; in that

AL = FFh if entry point supported case,

DX:BX -> entry point for bypassing interrupt the caller needs to scan the return list rather

chain than

02h uninstall making additional calls to this function. If the

DX:BX = return address for successful uninstall (may be return is not 00h or 04h, then the caller must

ignored by TSR) cycle

Return: AL = status through the remaining interrupt numbers it wishes

00h not implemented to

01h unsuccessful check.

02h can not uninstall yet, will do so when able return values 01h thru 03h are disparaged and will be

03h safe to remove, but no resident uninstaller removed from the next version of this

(TSR still enabled) specification;

BX = segment of memory block with resident they are included for compatibility with version

code 3.3,

04h safe to remove, but no resident uninstaller though they were probably never used in any

(TSR now disabled) implementation

BX = segment of memory block with resident 05h get hotkeys

code Return: AL = status

05h not safe to remove now, try again later 00h not implemented

FFh successful FFh supported

return at DX:BX with AX destroyed if successful and DX:BX -> hotkey list (see below)
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

06h-0Fh reserved for future enhancements sharing protocol header (see below)

Return: AL = 00h (not implemented)

other application-dependent Format of hotkey list:

Notes: programs should not use fixed multiplex numbers; rather, a program Offset Size Description

should scan all multiplex numbers from 00h to FFh, remembering the 00h BYTE type of hotkey checking

first unused multiplex in case the program is not yet installed. bit 0: checks before chaining INT 09

For multiplex numbers which are in use, the program should compare bit 1: checks after chaining INT 09

the first 16 bytes of the signature string to determine whether it bit 2: checks before chaining INT 15/AH=4Fh

is already installed on that multiplex number. If not previously bit 3: checks after chaining INT 15/AH=4Fh

installed, it should use the first free multiplex number. bit 4: checks on INT 16/AH=00h,01h,02h

functions other than 00h are not valid unless a program is installed bit 5: checks on INT 16/AH=10h,11h,12h

on the selected multiplex number bit 6: checks on INT 16/AH=20h,21h,22h

to be considered fully compliant with version 3.5 of the bit 7: reserved (0)

specification, 01h BYTE number of hotkeys (may be zero if TSR can disable hotkeys)

programs must implement at least functions 00h, 02h (no resident 02h 6N BYTEs array of hotkey definitions

uninstall code required), and 04h (return value 04h). TSRs that (one per hotkey, first should be primary hotkey)

provide hotkeys with which the user can activate them must also Offset Size Description

implement function 05h. The absolute minimum fully-compliant 00h BYTE hotkey scan code (00h/80h if shift states

implementation has an overhead of 64 bytes (80 bytes with function only)

05h) plus 22 bytes per hooked interrupt (for the interrupt sharing hotkey triggers on release if bit 7 set

protocol header and hook list entry). 01h WORD required shift states (see below)

the signature string and description may be used by memory mappers 03h WORD disallowed shift states (see below)

to display the installed programs 05h BYTE flags

users of this proposal should adhere to the IBM interrupt sharing bit 0: hotkey chained before processing

protocol (see below), which will permit removal of TSRs in bit 1: hotkey chained after processing

arbitrary order and interrupt handler reordering. All TSRs bit 2: others should pass through this hotkey

following this proposal should be removable, though they need not so that it can be monitored

keep the code for removing themselves resident; it is acceptable bit 3: hotkey will not activate if other keys

for a separate program to perform the removal. pressed/released before hotkey press

A sample implementation including example TSRs and utility programs is

may be found in a separate package distributed as AMISLnnn.ZIP completed

(AMISL091.ZIP as of this writing). bit 4: this key is remapped into some other

Please let me know if you choose to follow this proposal. The key

signature and a list of the private API calls you use would be bit 5-7: reserved (0)

appreciated, as well. Notes: except for bit 7, the shift states correspond exactly to the return

SeeAlso: INT 2F values from INT 16/AH=12h. A set bit in the required states word

Index: installation check;Alternate Multiplex Interrupt Specification indicates that the corresponding shift state must be active when

Index: installation check;AMIS|installation check;FASTMOUS the

Index: installation check;SPELLER|installation check;Monitor hotkey's scan code is received for the hotkey to be recognized; a

Index: installation check;NOLPT|installation check;NOTE clear bit means that the corresponding state may be ignored. A set

Index: installation check;RBkeyswp|installation check;SWITCHAR bit in the disallowed shift states word indicates that the

Index: installation check;VGABLANK|installation check;EATMEM corresponding shift state must be inactive.

Index: installation check;RECALL|installation check;XPTR2 if bit 2 is set, either control key may be pressed for the hotkey; if

Index: uninstall;Alternate Multiplex Interrupt Specification|uninstall;AMIS bits 8 and 10 are both set, then both control keys must be pressed.

Index: entry point;Alternate Multiplex Interrupt|entry point;AMIS Similarly for bits 3 and 9/11, as well as 7 and 0/1.

for the disallowed-states word, if one of the "either" bits is set,

Format of signature string: then both the corresponding left bit and right bit must be set

Offset Size Description examples:

00h 8 BYTEs blank-padded manufacturer's name (possibly abbreviated) Ctrl-Alt-Del monitoring: 53h 000Ch 0003h 06h

08h 8 BYTEs blank-padded product name Alt-key tap (DESQview): B8h 0000h 0007h 08h

10h 64 BYTEs ASCIZ product description (optional, may be a single 00h) Shf-Shf-N (NOTE.COM): 31h 0003h 000Ch 00h

Note: it is not necessary to reserve a full 64 bytes for the description, Index: hotkeys;AMIS

just enough to store the actual ASCIZ string

Bitfields for shift states:

Format of interrupt hook list [array]: bit 0 right shift pressed

Offset Size Description bit 1 left shift pressed

00h BYTE interrupt number (last entry in array is 2Dh) bit 2 either control key pressed

01h WORD offset within hook list's segment of the interrupt handler bit 3 either Alt key pressed

this will point at the initial short jump of the interrupt bit 4 ScrollLock active
APÉNDICES 389

bit 5 NumLock active INT 2F - Multiplex - NOTES


bit 6 CapsLock active AH = identifier of program which is to handle the interrupt

bit 7 either shift key pressed 00h-7Fh reserved for DOS

bit 8 left control key pressed B8h-BFh reserved for networks

bit 9 left Alt key pressed C0h-FFh reserved for applications

bit 10 right control key pressed AL is the function code

bit 11 right Alt key pressed This is a general mechanism for verifying the presence of a TSR and

bit 12 ScrollLock pressed communicating with it. When searching for a free identifier code for AH

bit 13 NumLock pressed using the installation check (AL=00h), the calling program should set

bit 14 CapsLock pressed BX/CX/DX to 0000h and must not depend on any registers other than CS:IP

bit 15 SysRq key pressed and SS:SP to be valid on return, since numerous programs now use

additional

Format of interrupt sharing protocol interrupt handler entry point: registers on input and/or output for the installation check.

Offset Size Description Notes: Since the multiplex chain is growing so long, and beginning to

00h 2 BYTEs short jump to actual start of interrupt handler, immediately experience multiplex number collisions, I am proposing an alternate

following this data block (EBh 10h) multiplex interrupt on INT 2D. If you decide to use the alternate

02h DWORD address of next handler in chain multiplex, please let me know.

06h WORD signature 424Bh DOS and some other programs return values in the flags register, so

08h BYTE EOI flag any TSR which chains by calling the previous handler rather than

00h software interrupt or secondary hardware interrupt jumping to it should ensure that the returned flags are preserved

handler and passed back to the original caller

80h primary hardware interrupt handler (will issue EOI) SeeAlso: INT 2D

09h 2 BYTEs short jump to hardware reset routine --------


must point at a valid FAR procedure (may be just RETF) t-2F------------------------------------------------------
0Bh 7 BYTEs reserved (0) INT 2F - BMB Compuscience Canada Utilities Interface -
INSTALLATION CHECK
Signatures known to be in use: AH = xx (dynamically assigned based upon a search for a multiplex

'Byrial J' 'EKLAVO ' permits keyboard entry of Esperanto accented letters number which doesn't answer installed)

'CoveSoft' 'Burnout+' shareware screen saver Burnout Plus AL = 00h installation check

'Crynwr ' 'SPELLER ' TSR spelling-checker ES:DI = EBEBh:BEBEh

'CSJewell' 'Modula3L' Curtis Jewell's Modula-3 compiler (non-TSR) Return: AL = 00h not installed

'DAISYCHA' 'INDRIVER' Advanced Parallel Port daisy chain driver (vendor 01h not installed, not OK to install

name FFh installed; if ES:DI was EBEBh:BEBEh on entry, ES:DI will

in product description field, if desired) point

(see also INT 2D/AL=DCh) to a string of the form 'MMMMPPPPPPPPvNNNN' where MMMM is a

'ECLIPSE ' 'PLUMP ' Eclipse Software's printer and plotter spooler short form of the manufacturer's name, PPPPPPPP is a product

'GraySoft' 'GIPC ' GraySoft's Inter-Process Communications driver name and NNNN is the product's version number

'heathh ' 'Monitor ' --------


'J. Berry' 'RATSR ' RemoteAccess Network Manager workstation module t-2F------------------------------------------------------
'JWB ' 'RAMLIGHT' James Birdsall's on-screen RAMdisk activity indicator INT 2F - Ross Wentworth's Turbo Pascal POPUP LIBRARY
'Nildram ' 'ST ' Screen Thief graphics screen grabber AH = programmer-selected multiplex number

'R-Ware ' 'dLite ' run-time data decompression TSR AL = function

'Ralf B ' 'FASTMOUS' example TSR included with sample AMIS library code 00h installation check

'Ralf B ' 'NOLPT n ' example TSR -- turn LPTn into bit-bucket Return: AL = FFh if installed

'Ralf B ' 'NOTE ' example TSR -- popup note-taker 01h get TSR interrupt vectors

'Ralf B ' 'RBkeyswp' RBkeyswap v3.0+ -- swap Esc/~ and LCtrl/CapsLock keys Return: DX:AX -> vector table (see below)

'Ralf B ' 'SWITCHAR' example TSR -- add switchar() support removed from 02h get TSR code segment

DOS5 Return: AX = code segment for all interrupt handlers

'Ralf B ' 'VGABLANK' example TSR -- VGA-only screen blanker 03h call user exit routine and release TSR's memory

'Sally IS' 'Mdisk ' removeable, resizeable RAMdisk 04h get signature string

'Sally IS' 'Scr2Tex ' screen dumper with output in (La)Tex format Return: DX:AX -> counted string containing signature

'Thaco ' 'NEST ' Eirik Pedersen's programmer's delimiter matcher 05h get TSR's INT 2F handler

'TifaWARE' 'EATMEM ' George A. Theall's public domain memory restrictor Return: DX:AX -> INT 2F handler

for 06h enable/disable TSR

testing programs (v1.1+) BL = new state (00h disabled, 01h enabled)

'TifaWARE' 'RECALL ' public domain commandline editor and history (v1.2+) 07h activate TSR (popup if not disabled)

'Todd ' 'XPTR2 ' PC-to-Transputer interface by Todd Radel 08h get hotkeys

-------- BL = which hotkey (00h = hotkey 1, 01h = hotkey 2)

--2F------------------------------------------------------ Return: AX = hotkey (AH = keyflags, AL = scancode)


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

09h set hotkey 00h var "AUTHOR:PROGRAM_NAME:VERSION",0 (variable length, this area

BL = which hotkey (00h = hotkey 1, 01h = hotkey 2) is used in order to determine if the TSR is already resident

CX = new hotkey (CH = keyflags, CL = scancode) and it's version code; the ':' char is used as delimiter)

0Ah-1Fh reserved

Index: installation check;Ross Wentworth POPUP library Format of vector_area table:

Index: hotkeys;Ross Wentworth POPUP library Offset Size Description

-1 BYTE number of vectors intercepted by TSR

Format of vector table entry: 00h BYTE first vector number

Offset Size Description 01h DWORD first vector pointer before installing the TSR

00h BYTE vector number (00h = end of table) 05h BYTE second vector number

01h DWORD original vector 06h DWORD second vector pointer before installing the TSR

05h WORD offset of interrupt handler in TSR's code segment 0Ah ... (and so on)

-------- Note: the TSR must use these variables to invoke the previous interrupt

t-2F------------------------------------------------------ handler routines

INT 2F - CiriSOFT Spanish University of Valladolid TSR's


Interface Format of extra_area table (needed only to improve relocation feature):

AH = xx (dynamically assigned based upon a search for a multiplex Offset Size Description

number from C0h to FFh which doesn't answer installed) 00h WORD offset to external_ctrl table (0 if not supported)

AL = 00h installation check 02h WORD reserved for future use (0)

ES:DI = 1492h:1992h

Return: AL = 00h not installed Format of external_ctrl table:

01h not installed, not OK to install Offset Size Description

FFh installed; and if ES:DI was 1492h:1992h on entry, ES:DI will 00h BYTE bit 0: TSR is relocatable (no absolute segment references)

point to author_name_ver table (see below) 01h WORD offset to a variable which can activate/inhibit the TSR

AH = FFh ---And if bit 0 in offset 00h is off:

Note: this interface permits advanced communication with TSRs: it is 03h DWORD pointer to ASCIZ pathname for executable file which supports

possible /SR parameter (silent installation & inhibit)

to make a generic uninstall utility, advanced TSR relocator 07h DWORD pointer to first variable to initialize on the copy reloaded

programs from the previous TSR still resident

in order to fit fragmented memory areas, etc. 0Bh DWORD pointer to last variable (all variables packed in one block)

See also: INT 2D"AMIS",INT 2F"Compuscience" --------


Index: installation check;CiriSOFT TSR interface W-2F1600--------------------------------------------------
Index: uninstall;CiriSOFT TSR interface INT 2F - MS Windows - WINDOWS ENHANCED MODE INSTALLATION CHECK
AX = 1600h

Format of author_name_ver table: Return: AL = status

Offset Size Description 00h neither Windows 3.x enhanced mode nor Windows/386 2.x running

-16 WORD segment of the start of the resident TSR code (CS in programs 01h Windows/386 2.x running

with PSP, XMS upper memory segment if installed as UMB...) 80h XMS version 1 driver installed (neither Windows 3.x enhanced

-14 WORD offset of the start of the resident TSR code (frequently 100h mode nor Windows/386 2.x running) (obsolete--see note)

in *.COM programs and 0 in upper memory TSR's). FFh Windows/386 2.x running

-12 WORD memory used by TSR (in paragraphs). Knowing the memory area AL = anything else

used by TSR is possible to determine if hooked vectors are AL = Windows major version number >= 3

still pointing it (and if it is safe to uninstall). AH = Windows minor version number

-10 BYTE characteristics byte Notes: INT 2F/AH=16h comprises an API for non-Windows programs (DOS device

bits 0-2: 000 normal program (with PSP) drivers, TSRs, and applications) to cooperate with multitasking

001 upper XMS memory block (needed HIMEM.SYS Windows/386 2.x and Windows 3.x and higher enhanced mode.

function certain calls are also supported in the Microsoft 80286 DOS extender

to free memory when uninstalling) in

010 device driver (*.SYS) Windows standard mode

011 device driver in EXE format this function served as the installation check and AX=1610h served to

1xx others (reserved) get the driver entry point for XMS version 1, which is now

bits 3-6 reserved obsolete.

bit 7 set if extra_table defined and supported Use AX=4300h and AX=4310h instead

-9 BYTE number of multiplex entry used (redefinition available). Note SeeAlso: AX=160Ah,AX=1610h,AX=4300h,AX=4680h

that the TSR must use THIS variable in it's INT 2Fh handler. Index: installation check;XMS version 1

-8 WORD offset to vector_area table (see below) --------


-6 WORD offset to extra_area table (see bit 7 in offset -10 and W-2F4680--------------------------------------------------
below) INT 2F U - MS Windows v3.0 - INSTALLATION CHECK
-4 4 BYTEs signature string "*##*" AX = 4680h
APÉNDICES 389

Return: AX = 0000h MS Windows 3.0 running in real (/R) or standard (/S) mode, Offset Size Description

or DOS 5 DOSSHELL active 00h WORD pointer to next item, FFFFh = last

nonzero no Windows, Windows prior to 3.0, or Windows3 in enhanced 02h WORD code page

mode 04h 2 BYTEs ???

Note: Windows 3.1 finally provides an installation check which works in all

modes (see AX=160Ah) Format of translation data:

SeeAlso: AX=1600h,AX=160Ah Offset Size Description

-------- 00h WORD size of data in bytes, including this word

V-2FAD00-------------------------------------------------- 02h N-2 BYTEs ???

INT 2F U - DOS 3.3+ DISPLAY.SYS internal - INSTALLATION CHECK --------


AX = AD00h B-4A------------------------------------------------------
Return: AL = FFh if installed INT 4A C - SYSTEM - USER ALARM HANDLER
BX = ??? (0100h for MS-DOS 3.3+) Desc: This interrupt is invoked by the BIOS when a real-time clock alarm

Note: DOS 5+ DISPLAY.SYS chains to previous handler if AL is not one of the occurs; an application may use it to perform an action at a

subfunctions listed here predetermined time.

-------- Note: this interrupt is called from within a hardware interrupt handler,

O-2FAD00-------------------------------------------------- so all usual precautions against reentering DOS must be taken

INT 2F U - DR-DOS 3.41,5.0 KEYB - INSTALLATION CHECK SeeAlso: INT 1A/AH=06h

AX = AD00h --------
Return: AX = FFFFh if installed E-67DE00--------------------------------------------------
SeeAlso: AX=AD80h INT 67 - Virtual Control Program Interface - INSTALLATION CHECK
-------- AX = DE00h

K-2FAD80-------------------------------------------------- Return: AH = 00h VCPI is present

INT 2F u - DOS 3.3+ KEYB.COM internal - INSTALLATION CHECK BH = major version number

AX = AD80h BL = minor version number

Return: AL = FFh if installed AH nonzero VCPI not present

BX = version number (BH = major, BL = minor) BUG: MS Windows 3.00 is reported to "object violently" to this call.

ES:DI -> internal data (see below) SeeAlso: INT 2F/AX=1687h

Notes: MS-DOS 3.30, PC-DOS 4.01, and MS-DOS 5.00 all report version 1.00. --------
undocumented prior to the release of DOS 5.0 H-70------------------------------------------------------
INT 70 - IRQ8 - CMOS REAL-TIME CLOCK
Format of KEYB internal data: Desc: this interrupt is called when the real-time clock chip generates an

Offset Size Description alarm or periodic interrupt, among others. The periodic interrupt

00h DWORD original INT 09 occurs 1024 times per second.

04h DWORD original INT 2F Nots: many BIOSes turn off the periodic interrupt in the INT 70h handler

08h 6 BYTEs ??? unless in an event wait (see INT 15/AH=83h or INT 15/AH=86h).

0Eh WORD flags may be masked by setting bit 0 on I/O port A1h

10h BYTE ??? SeeAlso: INT 08,INT 0F"HP 95LX",INT 15/AH=01h"Amstrad",INT 15/AH=83h

11h BYTE ??? SeeAlso: INT 15/AH=86h,INT 1A/AH=02h,INT 58"DESQview"

12h 4 BYTEs ??? --------


16h 2 BYTEs country ID letters H-71------------------------------------------------------
18h WORD current code page INT 71 - IRQ9 - REDIRECTED TO INT 0A BY BIOS
---DOS 3.3--- Notes: may be masked by setting bit 1 on I/O port A1h

1Ah WORD pointer to first item in list of code page tables??? the default BIOS handler invokes INT 0A for compatibility, since the

1Ch WORD pointer to ??? item in list of code page tables pin for IRQ2 on the PC expansion bus became the pin for IRQ9 on the

1Eh 2 BYTEs ??? AT expansion bus.

20h WORD pointer to key translation data under DESQview, only the INT 15h vector and BASIC segment address

22h WORD pointer to last item in code page table list (see below) (the

24h 9 BYTEs ??? word at 0000h:0510h) may be assumed to be valid for the handler's

---DOS 4.01--- process

1Ah 2 BYTEs ??? SeeAlso: INT 0A,INT 59

1Ch WORD pointer to first item in list of code page tables???

1Eh WORD pointer to ??? item in list of code page tables

20h 2 BYTEs ???

22h WORD pointer to key translation data

24h WORD pointer to last item in code page table list (see below)

26h 9 BYTEs ???

Format of code page table list entries:


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES

-------- 07h Query A20 state

m-2F4300-------------------------------------------------- Return: AX = 0001h enabled

INT 2F - EXTENDED MEMORY SPECIFICATION (XMS) v2+ - INSTALLATION = 0000h disabled

CHECK BL = error code (00h,80h,81h) (see below)

AX = 4300h 08h Query free extended memory, not counting HMA

Return: AL = 80h XMS driver installed BL = 00h (some implementations leave BL unchanged on success)

AL <> 80h no driver Return: AX = size of largest extended memory block in KB

Notes: XMS gives access to extended memory and noncontiguous/nonEMS memory DX = total extended memory in KB

above 640K BL = error code (00h,80h,81h,A0h) (see below)

this installation check DOES NOT follow the format used by other 09h Allocate extended memory block

software DX = Kbytes needed

SeeAlso: AX=4310h Return: AX = 0001h success

Index: installation check;XMS version 2+ DX = handle for memory block

-------- = 0000h failure

m-2F4310-------------------------------------------------- BL = error code (80h,81h,A0h) (see below)

INT 2F - EXTENDED MEMORY SPECIFICATION (XMS) v2+ - GET DRIVER 0Ah Free extended memory block

ADDRESS DX = handle of block to free

AX = 4310h Return: AX = 0001h success

Return: ES:BX -> driver entry point = 0000h failure

Note: HIMEM.SYS v2.77 chains to previous handler if AH is not 00h or 10h BL = error code (80h,81h,A2h,ABh) (see below)

SeeAlso: AX=4300h 0Bh Move extended memory block

DS:SI -> EMM structure (see below)

Perform a FAR call to the driver entry point with AH set to the function code Note: if either handle is 0000h, the corresponding offset is

AH function considered to be an absolute segment:offset address in

00h Get XMS version number directly addressable memory

Return: AX = XMS version (in BCD, AH=major, AL=minor) Return: AX = 0001h success

BX = internal revision number = 0000h failure

DX = 0001h if HMA (1M to 1M + 64K) exists BL = error code (80h-82h,A3h-A9h) (see below)

0000h if HMA does not exist 0Ch Lock extended memory block

01h Request High Memory Area (1M to 1M + 64K) DX = handle of block to lock

DX = memory in bytes (for TSR or device drivers) Return: AX = 0001h success

FFFFh if application program DX:BX = 32-bit linear address of locked block

Return: AX = 0001h success = 0000h failure

= 0000h failure BL = error code (80h,81h,A2h,ACh,ADh) (see below)

BL = error code (80h,81h,90h,91h,92h) (see below) Note: MS Windows 3.x rejects this function for handles allocated

02h Release High Memory Area after Windows started

Return: AX = 0001h success 0Dh Unlock extended memory block

= 0000h failure DX = handle of block to unlock

BL = error code (80h,81h,90h,93h) (see below) Return: AX = 0001h success

03h Global enable A20, for using the HMA = 0000h failure

Return: AX = 0001h success BL = error code (80h,81h,A2h,AAh) (see below)

= 0000h failure 0Eh Get handle information

BL = error code (80h,81h,82h) (see below) DX = handle for which to get info

04h Global disable A20 Return: AX = 0001h success

Return: AX = 0001h success BH = block's lock count

= 0000h failure BL = number of free handles left

BL = error code (80h,81h,82h,94h) (see below) DX = block size in KB

05h Local enable A20, for direct access to extended memory = 0000h failure

Return: AX = 0001h success BL = error code (80h,81h,A2h) (see below)

= 0000h failure BUG: MS Windows 3.10 acts as though unallocated handles are in

BL = error code (80h,81h,82h) (see below) use

06h Local disable A20 Note: MS Windows 3.00 has problems with this call

Return: AX = 0001h success 0Fh Reallocate extended memory block

= 0000h failure DX = handle of block

BL = error code (80h,81h,82h,94h) (see below) BX = new size of block in KB


APÉNDICES 389

Return: AX = 0001h success BL = status

= 0000h failure 00h success

BL = error code (80h,81h,A0h-A2h,ABh) (see below) 80h not implemented (i.e. on a 286 system)

10h Request upper memory block (nonEMS memory above 640K) 81h VDISK detected

DX = size of block in paragraphs A0h all extended memory allocated

Return: AX = 0001h success ECX = physical address of highest byte of memory

BX = segment address of UMB (valid even on error codes 81h and A0h)

DX = actual size of block EDX = total Kbytes of extended memory (0 if status A0h)

= 0000h failure 89h (XMS v3.0) Allocate any extended memory

BL = error code (80h,B0h,B1h) (see below) EDX = Kbytes needed

DX = largest available block Return: AX = 0001h success

11h Release upper memory block DX = handle for allocated block (free with

DX = segment address of UMB to release AH=0Ah)

Return: AX = 0001h success = 0000h failure

= 0000h failure BL = status (80h,81h,A0h,A1h,A2h) (see below)

BL = error code (80h,B2h) (see below) 8Eh (XMS v3.0) Get extended EMB handle information

12h (XMS v3.0) Reallocate upper memory block DX = handle

DX = segment address of UMB to resize Return: AX = 0001h success

BX = new size of block in paragraphs BH = block's lock count

Return: AX = 0001h success CX = number of free handles left

= 0000h failure EDX = block size in KB

BL = error code (80h,B0h,B2h) (see below) = 0000h failure

DX = maximum available size (RM386) BL = status (80h,81h,A2h) (see below)

34h (QEMM 5.11 only, undocumented) ??? BUG: DOS 6.0 HIMEM.SYS leaves CX unchanged

44h (QEMM 5.11 only, undocumented) ??? 8Fh (XMS v3.0) Reallocate any extended memory block

80h (Netroom RM386 v6.00) Reallocate upper memory block DX = unlocked handle

this function is identical to function 12h EBX = new size in KB

81h (Netroom RM386 v6.00) re-enable HMA allocation Return: AX = 0001h success

Return: AX = 0001h (success) = 0000h failure

82h (Netroom RM386 v6.00) Cloaking API BL = status (80h,81h,A0h-A2h,ABh) (see below)

DX = XMS handle of block containing protected-mode code Notes: HIMEM.SYS requires at least 256 bytes free stack space

CL = code size (00h 16-bit, else 32-bit) the XMS driver need not implement functions 10h through 12h to be

ESI, EDI = parameters to pass to protected-mode code considered compliant with the standard

Return: AX = status BUG: HIMEM v3.03-3.07 crash on an 80286 machine if any of the 8Xh

0001h success functions

0000h failed are called

BL = error code (A2h,B0h) (see below)

Note: this calls offset 0 in the XMS memory block with Error codes returned in BL:

EBX = physical address of block's start 00h successful

CS = code selector for XMS block at EBX (16-bit or 32-bit) 80h function not implemented

DS = data selector for XMS block, starting at EBX 81h Vdisk was detected

ES = selector for V86 memory access to full real-mode 1088K 82h an A20 error occurred

GS = selector for full flat address space 8Eh a general driver error

ESI, EDI from V86 mode 8Fh unrecoverable driver error

83h (Netroom RM386 v6.00) Create new UMB entry 90h HMA does not exist

BX = segment of high-memory block 91h HMA is already in use

DX = first page of start of block 92h DX is less than the /HMAMIN= parameter

CX = number of consecutive pages in block 93h HMA is not allocated

DI = start of UMB in block 94h A20 line still enabled

Return: AX = 0001h (success) A0h all extended memory is allocated

DI = segment of first high-DOS block A1h all available extended memory handles are allocated

Note: the new UMB is not linked into the high-memory chain A2h invalid handle

84h (Netroom RM386 v6.00) Get all XMS handles info A3h source handle is invalid

CX = size of buffer for handle info A4h source offset is invalid

ES:DI -> buffer for handle info (see below) A5h destination handle is invalid

Return: AX = 0001h (success) A6h destination offset is invalid

DX = current number of allocated XMS handles A7h length is invalid

88h (XMS v3.0) Query free extended memory A8h move has an invalid overlap

Return: EAX = largest block of extended memory, in KB A9h parity error occurred
389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

AAh block is not locked 94h conventional and expanded memory regions overlap

ABh block is locked 95h offset within logical page exceeds size of logical page

ACh block lock count overflowed 96h region length exceeds 1M

ADh lock failed 97h source and destination EMS regions have same handle and overlap

B0h only a smaller UMB is available 98h memory source or destination type undefined

B1h no UMB's are available 9Ah specified alternate map register or DMA register set not supported

B2h UMB segment number is invalid 9Bh all alternate map register or DMA register sets currently allocated

9Ch alternate map register or DMA register sets not supported

Format of EMM structure: 9Dh undefined or unallocated alternate map register or DMA register set

Offset Size Description 9Eh dedicated DMA channels not supported

00h DWORD number of bytes to move (must be even) 9Fh specified dedicated DMA channel not supported

04h WORD source handle A0h no such handle name

06h DWORD offset into source block A1h a handle found had no name, or duplicate handle name

0Ah WORD destination handle A2h attempted to wrap around 1M conventional address space

0Ch DWORD offset into destination block A3h source array corrupted

Notes: if source and destination overlap, only forward moves (source base A4h operating system denied access

less than destination base) are guaranteed to work properly --------


if either handle is zero, the corresponding offset is interpreted m-6741----------------------------------------------------
as a real-mode address referring to memory directly addressable INT 67 - LIM EMS - GET PAGE FRAME SEGMENT
by the processor AH = 41h

Return: AH = status (see also AH=40h)

Format of XMS handle info [array]: 00h function successful

Offset Size Description BX = segment of page frame

00h BYTE handle SeeAlso: AH=58h,AH=68h

01h BYTE lock count --------


02h DWORD handle size m-6742----------------------------------------------------
06h DWORD handle physical address (only valid if lock count nonzero) INT 67 - LIM EMS - GET NUMBER OF PAGES
-------- AH = 42h

m-6740---------------------------------------------------- Return: AH = status (see also AH=40h)

INT 67 - LIM EMS - GET MANAGER STATUS 00h function successful

AH = 40h BX = number of unallocated pages

Return: AH = status (00h,80h,81h,84h) (see below) DX = total number of pages

Note: this call can be used only after establishing that the EMS driver is BUG: DOS 6.0 EMM386.EXE causes a system lock-up or reboot if in AUTO mode

in when this call is made; use AH=46h to ensure that EMM386 is ON

fact present before making this call

SeeAlso: AH=3Fh,AX=FFA5h SeeAlso: INT 2F/AX=2702h

--------
Values for EMS function status: m-6743----------------------------------------------------
00h successful INT 67 - LIM EMS - GET HANDLE AND ALLOCATE MEMORY
80h internal error AH = 43h

81h hardware malfunction BX = number of logical pages to allocate

83h invalid handle Return: AH = status (00h,80h,81h,84h,85h,87h,88h,89h) (see AH=40h)

84h undefined function requested by application DX = handle if AH=00h

85h no more handles available SeeAlso: AH=45h

86h error in save or restore of mapping context --------


87h insufficient memory pages in system m-6744----------------------------------------------------
88h insufficient memory pages available INT 67 - LIM EMS - MAP MEMORY
89h zero pages requested AH = 44h

8Ah invalid logical page number encountered AL = physical page number (0-3)

8Bh invalid physical page number encountered BX = logical page number

8Ch page-mapping hardware state save area is full or FFFFh to unmap (QEMM)

8Dh save of mapping context failed DX = handle

8Eh restore of mapping context failed Return: AH = status (00h,80h,81h,83h,84h,8Ah,8Bh) (see AH=40h)

8Fh undefined subfunction SeeAlso: AH=69h

90h undefined attribute type --------


91h feature not supported m-6745----------------------------------------------------
92h successful, but a portion of the source region has been overwritten INT 67 - LIM EMS - RELEASE HANDLE AND MEMORY
93h length of source or destination region exceeds length of region AH = 45h

allocated to either source or destination handle DX = EMM handle


APÉNDICES 389

Return: AH = status (00h,80h,81h,83h,84h,86h) (see AH=40h) DX = EMM handle

SeeAlso: AH=43h Return: AH = status (see AH=4Bh)

-------- BX = number of logical pages if AH=00h

m-6746---------------------------------------------------- SeeAlso: AH=4Dh

INT 67 - LIM EMS - GET EMM VERSION --------


AH = 46h m-674D----------------------------------------------------
Return: AH = status (00h,80h,81h,84h) (see AH=40h) INT 67 - LIM EMS - GET PAGES FOR ALL HANDLES
AL = EMM version number if AH=00h AH = 4Dh

-------- ES:DI -> array to receive information

m-6747---------------------------------------------------- Return: AH = status (00h,80h,81h,84h) (see AH=4Bh)

INT 67 - LIM EMS - SAVE MAPPING CONTEXT ---if AH=00h---

AH = 47h BX = number of active EMM handles

DX = handle array filled with 2-word entries, consisting of a handle and the

Return: AH = status (see below) number of pages allocated to that handle

SeeAlso: AH=48h SeeAlso: AH=4Ch

--------
Values for status: m-674E----------------------------------------------------
00h successful INT 67 - LIM EMS - GET OR SET PAGE MAP
80h internal error AH = 4Eh

81h hardware malfunction AL = 00h if getting mapping registers

83h invalid handle 01h if setting mapping registers

84h undefined function requested 02h if getting and setting mapping registers at once

8Ch page-mapping hardware state save area is full 03h if getting size of page-mapping array

8Dh save of mapping context failed DS:SI -> array holding information (AL=01h/02h)

8Eh restore of mapping context failed ES:DI -> array to receive information (AL=00h/02h)

-------- Return: AH = status

m-6748---------------------------------------------------- 00h successful

INT 67 - LIM EMS - RESTORE MAPPING CONTEXT AL = bytes in page-mapping array (AL=03h only)

AH = 48h array pointed to by ES:DI receives mapping info (AL=00h/02h)

DX = handle 80h internal error

Return: AH = status (00h,80h,81h,83h,84h,8Eh) (see AH=47h) 81h hardware malfunction

SeeAlso: AH=47h 84h undefined function requested

-------- 8Fh undefined subfunction parameter

m-6749---------------------------------------------------- A3h contents of source array corrupted (EMS 4.0?)

INT 67 - LIM EMS - reserved - GET I/O PORT ADDRESSES Notes: this function was designed to be used by multitasking operating

AH = 49h systems

Note: defined in EMS 3.0, but undocumented in EMS 3.2 and should not ordinarily be used by appplication software.

-------- MD386 returns the size of the page-mapping array in AX instead of AL

m-674A---------------------------------------------------- SeeAlso: AH=4Fh

INT 67 - LIM EMS - reserved - GET TRANSLATION ARRAY --------


AH = 4Ah m-674F----------------------------------------------------
Note: defined in EMS 3.0, but undocumented in EMS 3.2 INT 67 - LIM EMS 4.0 - GET/SET PARTIAL PAGE MAP
-------- AH = 4Fh

m-674B---------------------------------------------------- AL = subfunction

INT 67 - LIM EMS - GET NUMBER OF EMM HANDLES 00h get partial page map

AH = 4Bh DS:SI -> structure containing list of segments whose mapping

Return: AH = status (see below) contexts are to be saved

BX = number of EMM handles if AH=00h ES:DI -> array to receive page map

01h set partial page map

Values for status: DS:SI -> structure containing saved partial page map

00h successful 02h get size of partial page map

80h internal error BX = number of mappable segments in the partial map to be

81h hardware malfunction saved

83h invalid handle Return: AH = status

84h undefined function requested 00h successful

-------- 80h internal error

m-674C---------------------------------------------------- 81h hardware malfunction

INT 67 - LIM EMS - GET PAGES OWNED BY HANDLE 84h undefined function requested

AH = 4Ch 8Bh one of specified segments is not mappable


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

8Fh undefined subfunction parameter 00h handle is volatile

A3h contents of partial page map corrupted or count of mappable 01h handle is nonvolatile

segments exceeds total number of mappable segments in system 01h set handle attributes

AL = size of partial page map for subfunction 02h BL = new attribute (see returned AL)

SeeAlso: AH=4Eh 02h get attribute capability

-------- Return: AL = attribute capability

m-6750---------------------------------------------------- 00h only volatile handles supported

INT 67 - LIM EMS 4.0 - MAP/UNMAP MULTIPLE HANDLE PAGES 01h both volatile and non-volatile supported

AH = 50h DX = handle

AL = subfunction Return: AH = status (00h,80h,81h,83h,84h,8Fh-91h) (see AH=51h)

00h use physical page numbers SeeAlso: AH=53h

01h use segment addresses --------


DX = handle m-6753----------------------------------------------------
CX = number of entries in array INT 67 - LIM EMS 4.0 - GET/SET HANDLE NAME
DS:SI -> mapping array (see below) AH = 53h

Return: AH = status AL = subfunction

00h successful 00h get handle name

80h internal error ES:DI -> 8-byte buffer for handle name

81h hardware malfunction 01h set handle name

83h invalid handle DS:SI -> 8-byte handle name

84h undefined function requested DX = handle

8Ah one or more logical pages are invalid Return: AH = status (00h,80h,81h,83h,84h,8Fh,A1h) (see AH=51h)

8Bh one or more physical pages are invalid SeeAlso: AH=52h

8Fh invalid subfunction --------


SeeAlso: AH=40h m-6754----------------------------------------------------
INT 67 - LIM EMS 4.0 - GET HANDLE DIRECTORY
Format of mapping array entry: AH = 54h

Offset Size Description AL = subfunction

00h WORD logical page number or FFFFh to unmap physical page 00h get handle directory

02h WORD physical page number or segment address ES:DI -> buffer for handle directory (see below)

-------- 01h search for named handle

m-6751---------------------------------------------------- DS:SI -> 8-byte name

INT 67 - LIM EMS 4.0 - REALLOCATE PAGES 02h get total number of handles

AH = 51h Return: AL = number of entries in handle directory (subfunction 00h)

DX = handle DX = value of named handle (subfunction 01h)

BX = number of pages to be allocated to handle BX = total number of handles (subfunction 02h)

Return: AH = status (00h,80h,81h,83h,84h,87h,88h) (see below) AH = status (00h,80h,81h,84h,8Fh,A0h,A1h) (see also AH=51h)

BX = actual number of pages allocated to handle A1h a handle found had no name

Values for status: Format of handle directory entry:

00h successful Offset Size Description

80h internal error 00h WORD handle

81h hardware malfunction 02h 8 BYTEs handle's name

83h invalid handle --------


84h undefined function requested m-6755----------------------------------------------------
87h more pages requested than present in system INT 67 - LIM EMS 4.0 - ALTER PAGE MAP AND JUMP
88h more pages requested than currently available AH = 55h

8Fh undefined subfunction AL = subfunction

90h undefined attribute type 00h physical page numbers provided by caller

91h feature not supported 01h segment addresses provided by caller

A0h no such handle name DX = handle

A1h duplicate handle name DS:SI -> structure containing map and jump address

-------- Return: (at target address unless error)

m-6752---------------------------------------------------- AH = status (see below)

INT 67 - LIM EMS 4.0 - GET/SET HANDLE ATTRIBUTES SeeAlso: AH=56h

AH = 52h

AL = subfunction Values for status:

00h get handle attributes 00h successful

Return: AL = attribute 80h internal error


APÉNDICES 389

81h hardware failure 04h BYTE source memory type

83h invalid handle 00h conventional

84h undefined function requested 01h expanded

8Ah invalid logical page number encountered 05h WORD source handle (0000h if conventional memory)

8Bh invalid physical page number encountered 07h WORD source initial offset (within page if EMS, segment if

8Fh invalid subfunction convent)

-------- 09h WORD source initial segment (conv mem) or logical page (EMS)

m-6756---------------------------------------------------- 0Bh BYTE destination memory type

INT 67 - LIM EMS 4.0 - ALTER PAGE MAP AND CALL 00h conventional

AH = 56h 01h expanded

AL = subfunction 0Ch WORD destination handle

00h physical page numbers provided by caller 0Eh WORD destination initial offset

DX = handle 10h WORD destination initial segment or page

DS:SI -> structure containing page map and call address --------
01h segment addresses provided by caller m-6758----------------------------------------------------
DX = handle INT 67 - LIM EMS 4.0 - GET MAPPABLE PHYSICAL ADDRESS ARRAY
DS:SI -> structure containing page map and call address AH = 58h

02h get page map stack space required AL = subfunction

Return: BX = stack space required 00h get mappable physical address array

Return: (if successful, the target address is called. Use a RETF to return ES:DI -> buffer to be filled with array

and 01h get number of entries in m.p.a. array

restore mapping context) Return: CX = number of entries in array

AH = status (see AH=55h) AH = status (00h,80h,81h,84h,8Fh) (see AH=57h)

SeeAlso: AH=55h Note: the returned array for subfunction 00h is filled in physical segment

-------- address order

m-6757----------------------------------------------------
INT 67 - LIM EMS 4.0 - MOVE/EXCHANGE MEMORY REGION Format of mappable physical address entry:

AH = 57h Offset Size Description

AL = subfunction 00h WORD physical page segment

00h move memory region 02h WORD physical page number

01h exchange memory region --------


DS:SI -> structure describing source and destination (see below) m-6759----------------------------------------------------
Return: AH = status (see below) INT 67 - LIM EMS 4.0 - GET EXPANDED MEMORY HARDWARE INFORMATION
Note: source and destination may overlap for a move, in which case the copy AH = 59h

direction is chosen such that the destination receives an intact AL = subfunction

copy 00h get hardware configuration array

of the source region ES:DI -> buffer to be filled with array (see below)

01h get unallocated raw page count

Values for status: Return: BX = unallocated raw pages

00h successful DX = total raw pages

80h internal error Return: AH = status (see also AH=58h"EMS 4.0")

81h hardware failure A4h access denied by operating system

83h invalid handle Note: subfunction 00h is for use by operating systems only, and can be

84h undefined function requested enabled or disabled at any time by the operating system

8Ah invalid logical page number encountered

8Fh undefined subfunction Format of hardware configuration array:

92h successful, but a portion of the source region has been overwritten Offset Size Description

93h length of source or destination region exceeds length of region 00h WORD size of raw EMM pages in paragraphs

allocated to either source or destination handle 02h WORD number of alternate register sets

94h conventional and expanded memory regions overlap 04h WORD size of mapping-context save area in bytes

95h offset within logical page exceeds size of logical page 06h WORD number of register sets assignable to DMA

96h region length exceeds 1M 08h WORD DMA operation type

97h source and destination EMS regions have same handle and overlap 0000h DMA with alternate register sets

98h memory source or destination type undefined 0001h only one DMA register set

A2h attempted to wrap around 1M conventional address space --------


m-675A----------------------------------------------------
Format of EMS copy data: INT 67 - LIM EMS 4.0 - ALLOCATE STANDARD/RAW PAGES
Offset Size Description AH = 5Ah

00h DWORD region length in bytes AL = subfunction


389 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

00h allocate standard pages BL = DMA register set number

01h allocate raw pages 08h deallocate DMA register set

BX = number of pages to allocate BL = DMA register set number

Return: DX = handle Return: AH = status (00h,80h,81h,84h,8Fh,9Ah-9Fh,A3h,A4h) (see AH=5Ah)

AH = status Note: this function is for use by operating systems only, and can be

00h successful enabled or disabled at any time by the operating system

80h internal error --------


81h hardware failure m-675C----------------------------------------------------
84h undefined function requested INT 67 - LIM EMS 4.0 - PREPARE EXPANDED MEMORY HARDWARE FOR WARM
85h no more handles available BOOT
87h insufficient memory pages in system AH = 5Ch

88h insufficient memory pages available Return: AH = status (see below)

8Fh undefined subfunction

-------- Values for status:

m-675B---------------------------------------------------- 00h successful

INT 67 - LIM EMS 4.0 - ALTERNATE MAP REGISTER SET 80h internal error

AH = 5Bh 81h hardware malfunction

AL = subfunction 84h undefined function requested

00h get alternate map register set --------


Return: BL = current active alternate map register set number m-675D----------------------------------------------------
ES:DI -> map register context save area if BL=00h INT 67 - LIM EMS 4.0 - ENABLE/DISABLE OS FUNCTION SET FUNCTIONS
01h set alternate map register set AH = 5Dh

BL = new alternate map register set number AL = subfunction

ES:DI -> map register context save area if BL=0 00h enable OS Function Set

02h get alternate map save array size 01h disable OS Function Set

Return: DX = array size in bytes 02h return access key (resets memory manager, returns access key

03h allocate alternate map register set at

Return: BL = number of map register set; 00h = not supported next invocation)

04h deallocate alternate map register set BX,CX = access key returned by first invocation

BL = number of alternate map register set Return: BX,CX = access key, returned only on first invocation of function

Return: AH = status (00h,80h,81h,84h,8Fh,9Ah-9Dh,A3h,A4h) (see below) AH = status (see also AH=5Ch)

Note: this function is for use by operating systems only, and can be 8Fh undefined subfunction

enabled or disabled at any time by the operating system A4h operating system denied access

Values for status:

00h successful

80h internal error

81h hardware malfunction

84h undefined function requested

8Fh undefined subfunction

9Ah specified alternate map register or DMA register set not supported

9Bh all alternate map register or DMA register sets currently allocated

9Ch alternate map register or DMA register sets not supported

9Dh undefined or unallocated alternate map register/DMA register set

9Eh dedicated DMA channels not supported

9Fh specified dedicated DMA channel not supported

A3h source array corrupted

A4h operating system denied access

--------
m-675B----------------------------------------------------
INT 67 - LIM EMS 4.0 - ALTERNATE MAP REGISTER SET - DMA REGISTERS
AH = 5Bh

AL = subfunction

05h allocate DMA register set

Return: BL = DMA register set number, 00h if not supported

06h enable DMA on alternate map register set

BL = DMA register set number

DL = DMA channel number

07h disable DMA on alternate map register set


424 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

Apéndice X - JUEGO DE CARACTERES ASCII EXTENDIDO

Hex Dec Hex Dec Hex Dec Hex Dec Hex Dec Hex Dec
─────── ─────── ─────── ─────── ─────── ───────
00 000 + 2B 043 V 56 086 ü 81 129 ¼ AC 172 ╫ D7 215
☺ 01 001 , 2C 044 W 57 087 é 82 130 ¡ AD 173 ╪ D8 216
☺ 02 002 - 2D 045 X 58 088 â 83 131 « AE 174 ┘ D9 217
♥ 03 003 . 2E 046 Y 59 089 ä 84 132 » AF 175 ┌ DA 218
♦ 04 004 / 2F 047 Z 5A 090 à 85 133 ░ B0 176 █ DB 219
♣ 05 005 0 30 048 [ 5B 091 å 86 134 ▒ B1 177 ▄ DC 220
♠ 06 006 1 31 049 \ 5C 092 ç 87 135 ▓ B2 178 ▌ DD 221
• 07 007 2 32 050 ] 5D 093 ê 88 136 │ B3 179 ▐ DE 222
_ 08 008 3 33 051 ^ 5E 094 ë 89 137 ┤ B4 180 ▀ DF 223
09 009 4 34 052 _ 5F 095 è 8A 138 ╡ B5 181 α E0 224
_ 0A 010 5 35 053 ` 60 096 ï 8B 139 ╢ B6 182 ß E1 225
_ 0B 011 6 36 054 a 61 097 î 8C 140 ╖ B7 183 Γ E2 226
_ 0C 012 7 37 055 b 62 098 ì 8D 141 ╕ B8 184 π E3 227
_ 0D 013 8 38 056 c 63 099 Ä 8E 142 ╣ B9 185 Σ E4 228
_ 0E 014 9 39 057 d 64 100 Å 8F 143 ║ BA 186 σ E5 229
0F 015 : 3A 058 e 65 101 É 90 144 ╗ BB 187 µ E6 230
10 016 ; 3B 059 f 66 102 æ 91 145 ╝ BC 188 τ E7 231
11 017 < 3C 060 g 67 103 Æ 92 146 ╜ BD 189 Φ E8 232
12 018 = 3D 061 h 68 104 ô 93 147 ╛ BE 190 Θ E9 233
! 13 019 > 3E 062 i 69 105 ö 94 148 ┐ BF 191 Ω EA 234
¶ 14 020 ? 3F 063 j 6A 106 ò 95 149 └ C0 192 δ EB 235
§ 15 021 @ 40 064 k 6B 107 û 96 150 ┴ C1 193 ∞ EC 236
16 022 A 41 065 l 6C 108 ù 97 151 ┬ C2 194 φ ED 237
↑ 17 023 B 42 066 m 6D 109 _ 98 152 ├ C3 195 ε EE 238
↑ 18 024 C 43 067 n 6E 110 Ö 99 153 ─ C4 196 ∩ EF 239
↓ 19 025 D 44 058 o 6F 111 Ü 9A 154 ┼ C5 197 ≡ F0 240
→ 1A 026 E 45 069 p 70 112 ¢ 9B 155 ╞ C6 198 ± F1 241
← 1B 027 F 46 070 q 71 113 £ 9C 156 ╟ C7 199 ≥ F2 242
_ 1C 028 G 47 071 r 72 114 ¥ 9D 157 ╚ C8 200 ≤ F3 243
↔ 1D 029 H 48 072 s 73 115 _ 9E 158 ╔ C9 201 ⌠ F4 244
1E 030 I 49 073 t 74 116 ƒ 9F 159 ╩ CA 202 ⌡ F5 245
1F 031 J 4A 074 u 75 117 á A0 160 ╦ CB 203 ÷ F6 246
20 032 K 4B 075 v 76 118 í A1 161 ╠ CC 204 ≈ F7 247
! 21 033 L 4C 076 w 77 119 ó A2 162 ═ CD 205 ° F8 248
" 22 034 M 4D 077 x 78 120 ú A3 163 ╬ CE 206 ⋅ F9 249
# 23 035 N 4E 078 y 79 121 ñ A4 164 ╧ CF 207 ⋅ FA 250
$ 24 036 O 4F 079 z 7A 122 Ñ A5 165 ╨ D0 208 √ FB 251
% 25 037 P 50 080 { 7B 123 ª A6 166 ╤ D1 209 _ FC 252
& 26 038 Q 51 081 | 7C 124 º A7 167 ╥ D2 210 ² FD 253
' 27 039 R 52 082 } 7D 125 ¿ A8 168 ╙ D3 211 FE 254
( 28 040 S 53 083 ~ 7E 126 _ A9 169 ╘ D4 212 FF 255
) 29 041 T 54 084 _ 7F 127 ¬ AA 170 ╒ D5 213
* 2A 042 U 55 085 Ç 80 128 ½ AB 171 ╓ D6 214

205 203 196 194


201 ╔ ═ ╦ ╗ 187
218 ┌ ─ ┬ ┐ 191 219 223
186 ║ 179 │ █ ▀ █
204 ╠ 206 ╬ ╣ 185 195 ├ 197 ┼ ┤ 180
█ ▄ █
200 ╚ 202 ╩ ╝ 188 192 └ 193 ┴ ┘ 217 220
APÉNDICES 424

205 209 196 210


213 ╒ ═ ╤ ╕ 184 214 ╓ ─ ╥ ╖ 183 222 223 221
179 │ 186 ║ ▐ ▀ ▌
198 ╞ 216 ╪ ╡ 181 199 ╟ 215 ╫ ╢ 182
▐ ▄ ▌
212 ╘ 207 ╧ ╛ 190 211 ╙ 208 ╨ ╜ 189 220

176 ░░░░░░░░░░ 177 ▒▒▒▒▒▒▒▒▒▒ 178 ▓▓▓▓▓▓▓▓▓▓ 219 ██████████


424 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2
Apéndice XI - BIBLIOGRAFÍA

Ralf Brown.
Ralf Brown publica periódicamente un fichero (en inglés) con información muy detallada sobre
interrupciones (INTERRUP.LST), muy superior a la de cualquier libro. Contiene todas las funciones de
la BIOS, con información de máquinas y marcas concretas, así como de casi todas las tarjetas (por
ejemplo, de vídeo) del mercado. También están todas las interrupciones y funciones del DOS, tanto las
documentadas como las secretas o indocumentadas. Aquí se pueden encontrar las funciones (vía
llamada a interrupciones) de los controladores de memoria expandida y extendida, del ratón, de las
extensiones CD-ROM, de Desqview, de Windows,... en resumen: de casi todos los programas
importantes del mercado. Además, se trata de un fichero de dominio público. Periódicamente es
actualizado con la información que altruistamente le envían personas de todo el mundo. La versión 55
(mediados de 1997) ocupa unos 5 Mbytes, tras descomprimir y juntar los diversos ficheros en que viene
repartido. Se puede conseguir en Internet y en las principales BBS.

Michael Tischer.
PC Interno. Programación de sistema.
Editorial Marcombo - Data Becker, 1993. 1404 páginas + disco.
Este gigantesco libro reúne en una sola obra un ingente volumen de información útil, relacionada con la
programación de sistemas de los PC. La primera parte constituye una especie de introducción a la
programación de sistemas (100 páginas). La segunda parte (600 páginas) describe los gráficos mejor
que muchos otros libros especializados en la materia, explica el teclado, los disquetes y discos duros,
los puertos paralelo y serie, la programación del ratón y el joystick, el reloj de tiempo real, las
memorias EMS y XMS, la creación de sonido, la detección del tipo de microprocesador... La tercera
parte (250 páginas) comenta la estructura del sistema operativo DOS, las formatos COM y EXE, la
gestión de archivos, la gestión de memoria, los controladores de dispositivo,... La cuarta parte (100
páginas) trata de la creación de programas residentes, del acceso al modo protegido, los extensores del
DOS, DMPI, VPCI,... Por último, la quinta parte consta de 14 apéndices (del A al N) con un resumen
de las principales funciones de la BIOS, el DOS, EMS, XMS, ratón... (300 páginas).
Este libro resulta imprescindible para el programador, al reunir en una sola obra un elevado volumen de
información. Tiene puntos débiles, como las escasas 5 páginas que dedica al puerto serie; en otros
aspectos, como en materia de discos, la información es bastante buena sin ser muy profunda (no toca
para nada la programación directa de la controladora de disquetes); lo mismo sucede en temas como la
creación de programas residentes (información correcta pero muy, muy justa). Sin embargo, en otras
áreas, como en gráficos, arrasa en comparación con otros muchos libros del mercado que se dedican
solo a este tema. El libro viene con cientos de listados de programas de ejemplo en C, Pascal,
QuickBasic y ensamblador (con letra muy pequeña a dos columnas) que ejemplifican la aplicación de
lo que se explica y están incluidos en el disquete que acompaña -lo que es una garantía de que
funcionan-.
Aunque pudiera parecer que PC Interno es el rival de la obra que tiene el lector entre sus manos, no es
realmente así: PC Interno abarca muchas más áreas de programación y a todos los niveles, si bien en el
acceso directo a los chips se queda muy escaso, aspecto que aquí es el más relevante. Pero hay que
tener en cuenta que no se pueden tratar las cosas con tanta profundidad cuando son tantos los temas que
se abarcan, ni siquiera con 1400 páginas.

Michael Tischer.
PC Interno 2.0. Programación de sistema.
Editorial Marcombo - Data Becker, 1996.
Versión más reciente del libro anterior, sustituye el disquete por un CD conteniendo todo el libro
(incluso con más capítulos que los impresos). Más completo y actualizado.

Andrew Schulman, Raymond J. Michels, Jim Kyle, Tim Paterson, David Maxey y Ralf Brown.
Undocumented DOS.
Editorial Addison-Wesley, USA. 694 páginas + 2 discos.
Este libro contiene casi todas las funciones indocumentadas del sistema operativo, esas que utilizan los
427 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

mejores programas comerciales para dejarnos sorprendidos. Indispensable para el programador


avanzado que sepa ensamblador y/o C e inglés. Viene con dos discos de 1,2 Mb. Al principio se centra
en la gestión de recursos del DOS, explicando a fondo la gestión de memoria del sistema al más bajo
nivel y la gestión de procesos y dispositivos. A continuación trata en profundidad la gestión del sistema
de ficheros, exponiendo una información valiosísima. Después hay casi 100 páginas destinadas a
explicar la creación de programas residentes que utilicen servicios del DOS, explicando detalladamente
todos los pasos y la solución de los problemas. Finalmente, estudia el intérprete de comandos del DOS
y aspectos relacionados con la creación de depuradores de código. Contiene un apéndice donde se listan
sólo las funciones indocumentadas del DOS (extraído del INTERRUP.LST). La edición que comento
es de Junio de 1991 y, aunque en portada indica lo contrario, no es cierta la afirmación de que cubre el
DOS 5.0. Sin embargo, es un libro casi indispensable para los programadores de sistemas bajo DOS.

Andrew Schulman, Ralf Brown, David Maxey, Raymond J. Michels, Jim Kyle.
El DOS no documentado.
Editorial Addison-Wesley/Díaz de Santos. - 1995. 1043 páginas.
Versión en castellano del libro anterior, en una edición más moderna que cubre también aspectos
relacionados con Windows, DR-DOS, Netware y MVDM de OS/2 y Windows NT.

A. Cattania.
80386: Arquitectura y programación.
Grupo editorial Jackson. 300 páginas.
Este libro describe profundamente el procesador 80386, con un capítulo específico para el diseño de
sistemas hardware basados en el mismo. Al final, reproduce el conjunto de instrucciones aunque no es
la obra más recomendable para consultar esta información.

Intel.
80386: Guía del programador de sistemas.
Anaya Multimedia - Intel. 175 páginas.
Libro oficial de Intel sobre el 386; describe los aspectos relacionados con su programación a nivel de
sistemas, con un profundo tratamiento de la gestión de memoria, las tareas, las interrupciones, las
llamadas al sistema, la entrada/salida, el coprocesador, la compatibilidad con el 286 y 8086. Al final,
culmina con un ejemplo de posible implementación del sistema UNIX. Carece por completo, sin
embargo, de información acerca de las instrucciones del procesador.

80386: Guia del programador y manual de referencia.


Anaya Multimedia - Intel. 492 páginas.
Otro libro oficial relacionado con el 386 pero no especializado en la programación de sistemas sino en
la programación general. Describe en una 1ª parte la programación de aplicaciones, mostrando la
organización de la memoria y todo el conjunto de instrucciones del 386, una a una. En una 2ª parte,
aparecen capítulos destinados a la programación de sistemas, con la gestión de memoria, la protección,
la programación multitarea, la entrada/salida, las excepciones e interrupciones, el coprocesador y el
modo de depuración de programas del 386. En la 3ª parte del libro se trata el tema de la compatibilidad
con procesadores anteriores, el modo virtual-86 y la mezcla de códigos de 16 y 32 bits. Al final, la 4ª
parte resume exhaustivamente todo el juego de instrucciones, con los tiempos de ejecución... Este libro
es el complemento del anterior.

Jon Beltrán de Heredia.


Lenguaje Ensamblador de los 80x86.
Anaya Multimedia, S.A., - 1994. 300 páginas.
Este libro perteneciente a la colección de Guías Prácticas es una útil herramienta para aprender
ensamblador, desde los conceptos más básicos hasta adquirir un conocimiento considerable del
APÉNDICES 427

lenguaje. En la segunda mitad describe el entorno de desarrollo y aspectos fundamentales del mundo de
la programación del PC, de manera que el lector pueda comenzar a desarrollar sus propios programas
en este lenguaje. Un excelente libro para comenzar, en particular para todos aquellos que hayan tenido
una primera mala experiencia con algún manual de esos que listan áridamente las instrucciones.

Peter Norton / John Socha.


Nueva Guía del programador en ensamblador para IBM PC/XT/AT y compatibles.
Anaya Multimedia, S.A., - 1991. 448 páginas + disco.
Este libro resulta especialmente útil para quienes comienzan con el lenguaje ensamblador. Contiene
numerosos ejemplos de programación. A lo largo de la obra se desarrolla un completo editor de
sectores de disco, de una manera estructurada y clara.

Leo J. Scanlon.
80286: Programación ensamblador en entorno MS-DOS.
Anaya Multimedia, S.A. - 1987. 368 páginas.
Aunque su título parezca indicar lo contrario, se trata de un gran libro para aprender ensamblador del
8086. Las instrucciones exclusivas del 286 (muy pocas y sólo las del modo real) se distinguen con
claridad de las estándar del 8086. Realmente, no es un libro sobre el 286. Empieza desde un nivel
básico y enseña progresivamente la sintaxis del lenguaje y el manejo del programa ensamblador hasta
unos niveles bastante aceptables. Es el libro que con más sencillez, claridad y profundidad describe las
instrucciones del ensamblador. Tiene un capítulo dedicado a la aritmética de 32 bits; otro al manejo de
estructuras de datos; otro a los recursos del DOS (poco profundo) y otro relacionado con las macros
(muy superficial).

B. Kernigham / D. Ritchie.
El lenguaje de programación C.
Ed. prentice-Hall.
Libro clásico sobre la programación en C, escrito por los creadores originales del lenguaje. No está
actualizado, sin embargo, sobre las últimas revisiones ANSI. Imprescindible para cualquier
programador en C. Juez inapelable sobre cómo deben hacerse las cosas en C.

P. J. Plauger / Jim Brodie.


C estándar.
Anaya multimedia, S.A. - 1990. 240 páginas.
Guía de referencia rápida sobre este lenguaje de programación; no es una obra didáctica sino un librillo
de consulta que incluye los últimos estándares incorporados al C (ANSI e ISO), dividido en dos partes:
C estándar y la librería estándar del C.

Scott Zimmerman/ Beverly B. Zimmerman.


La biblia del Turbo C.
Anaya Multimedia, S.A. 656 páginas + 2 discos.
Este libro va acompañado de dos disquetes con las rutinas desarrolladas; junto con los manuales del
compilador es una buena fuente de información.

B. Costales.
Introducción al Lenguaje C.
Editorial Gustavo Gili, S.A. - 1987. 291 páginas.
El título original de la obra (C from A to Z) es más descriptivo de su contenido. Se trata de un
extraordinario libro para aprender C y llegar hasta un nivel de conocimientos respetable. Es un libro
fácil de seguir y contiene numerosas referencias sobre cómo han de ser los programas en C para ser
427 EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2

realmente portables entre distintos ordenadores. A mi juicio, mucho más didáctico y útil que el famoso
Kernigham & Ritchie. Además, su precio es asequible. Su punto débil es no estar especializado en los
PC, para lo que harán falta más libros de apoyo.

Richard Wilton.
Sistemas de Vídeo.
Editorial Anaya Multimedia, S.A., 1990. 568 páginas + disco.
Describe con profundidad todos los sistemas de vídeo estándar de los PC's, desde la Hércules a la VGA
(pasando por la Incolor, HGC+, etc.); orientado al programador en C y/o ensamblador. Trata la
programación directa del hardware de vídeo, los modos alfanuméricos (incluyendo los aspectos
avanzados de EGA y VGA) y gráficos, las técnicas para trazar puntos, líneas, circunferencias, rellenar
superficies, etc.; se trata de una de las obras más extensas sobre el tema. Sin embargo, en programación
a bajo nivel cuesta encontrar a veces la información -que está bastante mal organizada- o no se
encuentra (registros que se mencionan pero no se describen, etc.). Digamos que es útil en el tema de los
algoritmos de trazado de líneas, círculos, rutinas rápidas en ensamblador... pero se rinde ante PC
Interno en todo lo demás (aunque también puede echar una mano).

IBM corp.
IBM AT Technical Reference. - 1984. 600 páginas.
Libro oficial de IBM que describe la organización interna del AT, incluyendo el listado fuente de la
ROM-BIOS de la máquina. Resulta útil para obtener esa información que no se puede encontrar en
otros sitios; aunque ésta es poco exhaustiva en cuanto a especificaciones técnicas de los integrados se
refiere, conviene no olvidar que casi todos los demás libros sobre programación avanzada del PC se
basan siempre de una u otra forma en éste. Publicado en inglés y relativamente difícil de conseguir,
como evidencia la fecha de la versión que comento.

Harris.
Digital Product Data Book.
Se trata de la línea de datasheets técnicos del conocido fabricante, editada en inglés. Puede conseguirse
en tiendas de electrónica de ciudades importantes, o directamente en Internet (en el WEB de Harris).
Harris fabrica chips CMOS compatibles con el 8088, 286, 8255, 8253/4, 8237, 8259 ... en resumen:
casi todos los que podamos encontrar dentro de un PC, descritos a fondo (por venir, vienen hasta las
instrucciones del 286 y se explica el funcionamiento del modo protegido del integrado). Aunque la obra
está destinada en parte a los fabricantes de hardware, tal vez entre los más beneficiados de su lectura
estén aquellos programadores a bajo nivel, tanto de ensamblador como de C, que desean saber TODA
la información acerca de un chip, sin errores y sin olvidos; de manera clara, concisa y completa.

También podría gustarte