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.
Interrupciones hardware: Son las generadas por la circuitería del ordenador en respuesta a algún
evento. Las más importantes son:
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:
«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
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
El «método correcto» es similar al «psé», consiste en cambiar el vector «de un tirón» (cambiar
a la vez 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).
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.
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 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
ASSUME CS:mays, DS:mays
ORG 100h ; programa .COM ordinario
inicio:
MOV AH,15 ; función para obtener modo de vídeo
INT 10h ; llamar a la BIOS
MOV BX,0B000h ; segmento de pantalla monocroma
MOV CX,2000 ; tamaño (caracteres) de la pantalla
CMP AL,7 ; ¿es realmente modo monocromo?
JE datos_ok ; en efecto
MOV BX,0B800h ; segmento de pantalla de color
CMP AL,3 ; ¿es modo de texto de 80 columnas?
JE pant_color ; en efecto
CMP AL,2 ; ¿es modo de texto de 80 columnas?
JE pant_color ; en efecto
MOV CX,1000 ; tamaño (caract.) pantalla 40 col.
CMP AL,1 ; ¿es modo texto de 40 columnas?
JBE pant_color ; así es
MOV AL,1 ; pantalla gráfica o desconocida:
JMP final ; fin de programa (errorlevel=1)
pant_color: MOV AX,40h ; considerar página activa<>0
MOV DS,AX ; DS = 40h (variables de la BIOS)
MOV AX,DS:[4Eh] ; desplazamiento de la página activa
SHR AX,1 ; desplazamiento / 2
SHR AX,1 ; desplazamiento / 4
SHR AX,1 ; desplazamiento / 8
SHR AX,1 ; desplazamiento / 16 (párrafos)
ADD BX,AX ; segmento de vídeo efectivo
datos_ok: MOV DS,BX ; DS = segmento de pantalla
XOR BX,BX ; BX = 0 (primer carácter)
otra_letra: CMP BYTE PTR [BX],'a'; ¿código ASCII menor que 'a'?
JB no_minuscula ; luego no puede ser minúscula
CMP BYTE PTR [BX],'z'; ¿código ASCII mayor de 'z'?
JA no_minuscula ; luego no puede ser minúscula
AND BYTE PTR [BX],0DFh ; poner en mayúsculas
no_minuscula: ADD BX,2 ; apuntar siguiente carácter
LOOP otra_letra ; repetir con los CX caracteres
MOV AL,0 ; fin programa (errorlevel=0)
final: MOV AH,4Ch
INT 21h
mays ENDS
END inicio
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 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
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
La tarjeta VGA es el estándar actual en ordenadores personales, siendo el sistema de vídeo mínimo
que incluye la máquina más asequible. En este apartado estudiaremos la forma básica de programar sus
modos gráficos, haciendo un especial hincapié en el tema menos claramente explicado por lo general: el
color. Se ignorarán por completo las tarjetas CGA y Hércules, aunque sí se indicará qué parte de lo expuesto
se puede aplicar también a la EGA. Tampoco se considerará la MCGA, un híbrido entre EGA y VGA que
solo equipa a los PS/2-30 de IBM, bastante incompatible además con la EGA y la VGA.
La VGA soporta todos los modos gráficos estándar de las tarjetas anteriores, resumidos en la figura
7.4.3.1, si bien los correspondientes a la CGA (320x200 en 4 colores y 640x200 monocromo) son
inservibles para prácticamente cualquier aplicación gráfica actual.
| Modo (hex) | Resolución | Colores | Segmento | Organización | Adaptador |
| 4 y 5 | 320 x 200 | 4 | B800 | entrelazado | CGA |
| 6 | 640 x 200 | 2 | B800 | entrelazado | CGA |
| 0Dh | 320 x 200 | 16 | A000 | planos de bit | EGA |
| 0Eh | 640 x 200 | 16 | A000 | planos de bit | EGA |
| 0Fh | 640 x 350 | 2 | A000 | planos de bit | EGA |
| 10h | 640 x 350 | 4 | A000 | planos de bit | EGA |
| 10h | 640 x 350 | 16 | A000 | 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
|
La organización de la memoria (entrelazado,
planos de bit o lineal) es la manera en que se direcciona la memoria de
vídeo por parte de la CPU. Por ejemplo, en el modo 6, cada pixel de la
pantalla está asociado a un bit (8 pixels por byte) a partir de la dirección
B800:0000; sin embargo, cuando se recorren 80 bytes en la memoria (640 bits
o pixels, primera líneacompleta) 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 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 */
}
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 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).
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 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:
/*********************************************************************
* 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 *
* EGA (POR DEFECTO EMULADOS POR EL DAC DE LA VGA). *
*********************************************************************/
#include <dos.h>
#include <graphics.h>
void main()
{
struct REGPACK r;
int gdrv, gmodo, coderr, i, x, color, pixel;
char paleta[17];
/* ESTABLECER MODO EGA/VGA 640x350 - 16 COLORES */
detectgraph (&gdrv, &gmodo); coderr=graphresult();
if (((gdrv!=EGA) && (gdrv!=VGA)) || (coderr!=grOk))
{ printf("\nNecesaria tarjeta EGA o VGA.\n"); exit(1); }
gmodo=EGAHI; initgraph(&gdrv, &gmodo, ""); coderr=graphresult();
if (coderr!=grOk)
{ printf("Error gráfico: %s.\n", grapherrormsg(coderr)); exit(1);}
/* DIBUJAR BANDAS VERTICALES DE EJEMPLO */
for (x=color=0; color<16; color++)
for (pixel=0; pixel<getmaxx()/16; pixel++, x++) {
setcolor (color); line (x, 0, x, getmaxy());
}
/* DEFINIR NUEVA PALETA */
paleta[0]=0; /* __rgbRGB = 0 --> negro */
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 */
for (i=4; i<17; i++) paleta[i]=0; /* resto colores y borde negros */
r.r_es=FP_SEG(paleta); r.r_dx=FP_OFF(paleta);
r.r_ax=0x1002; intr (0x10, &r); /* establecer paleta y borde */
getch(); closegraph();
}
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, rojooscuro 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 programa completo de demostración,
desarrollado a partir del anterior, que requiere ya un auténtico adaptador
VGA. Lo primero que se hace es seleccionar el modo de 16 páginas en el DAC,
estableciendo la página 2 como activa (exclusivamente por antojo mio). Ello
significa que se emplearán los elementos 32..47 del DAC (la página 0
apuntaría a los elementos 0..15, la 1 hubieran sido los elementos 16..31 y
así sucesivamente). Los registros de paleta, simples índices en el DAC,
toman los valores 0,1,...,15 (excepto el 17º byte, color del borde, puesto a
0 para seleccionar el negro). A continuación, basta programar los registros
32..47 del DAC con los colores deseados, entre los 262.144 posibles. Como
cada componente puede variar entre 0 y 63, elegimos 16 valores espaciados
proporcionalmente (0, 4, 8,..., 60) y los asignamos a las componentes roja y
verde (rojo+verde=amarillo), apareciendo en la pantalla una escala de 16
amarillos (el primero, negro absoluto) de intensidad creciente. Si bien 16
colores son pocos, son suficientes para representar con relativa precisión
algunas imágenes, especialmente en las que predomina un color determinado
(los ficheros gráficos se ven normalmente tan mal en los modos de 16 colores
debido a que respetan la paleta de la EGA, en la VGA sería otra historia).
FIGURA 7.4.3.4:
/*********************************************************************
* 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 *
*********************************************************************/
#include <dos.h>
#include <graphics.h>
void main()
{
struct REGPACK r;
int gdrv, gmodo, coderr, pagina, i, x, color, pixel;
char paleta[17], dac[256][3];
/* ESTABLECER MODO VGA 640x480 - 16 COLORES */
detectgraph (&gdrv, &gmodo); coderr=graphresult();
if ((gdrv!=VGA) || (coderr!=grOk))
{ printf("\nNecesaria tarjeta VGA.\n"); exit(1); }
gmodo=VGAHI; 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());
}
/* SELECCIONAR 16 BLOQUES DE 16 ELEMENTOS EN EL DAC */
r.r_ax=0x1013; r.r_bx=0x0100; intr (0x10, &r);
/* PAGINA 2: LA PALETA SE APOYARA EN ELEMENTOS 32..47 DEL DAC */
pagina=2; r.r_ax=0x1013; r.r_bx=(pagina<<8) | 1; intr (0x10, &r);
/* APUNTAR REGISTROS DE PALETA A ELEMENTOS CONSECUTIVOS DEL DAC */
for (i=0; i<16; i++) paleta[i]=i;
paleta[16]=0; /* color del borde */
r.r_es=FP_SEG(paleta); r.r_dx=FP_OFF(paleta);
r.r_ax=0x1002; intr (0x10, &r); /* establecer paleta y borde */
/* LLENAR ELEMENTOS 32..47 DEL DAC DE AMARILLOS CRECIENTES */
for (i=32; i<48; i++) {
dac[i][0]=i*4; /* valores crecientes 0..60 de rojo */
dac[i][1]=i*4; /* valores crecientes 0..60 de verde */
dac[i][2]=0; /* sin componente azul */
}
r.r_bx=32; /* primer elemento del DAC */
r.r_cx=16; /* número de elementos a definir */
r.r_es=FP_SEG(dac[32]); r.r_dx=FP_OFF(dac[32]);
r.r_ax=0x1012; intr (0x10, &r); /* programar elementos del DAC */
getch();
closegraph();
}
Por supuesto, existen más funciones que éstas, entre ellas las que permiten cambiar sólo un registro
de paleta o un elemento del DAC (y no un bloque); sin embargo, son más lentas cuando se va a cambiar un
conjunto de registros. En cualquier caso, el lector puede consultarlas en el fichero INTERRUP.LST si lo
desea. También existen en la VGA las funciones inversas (obtener paletas y registros del DAC). El acceso
por medio de la BIOS para cambiar la paleta es a menudo más cómodo que emplear funciones del lenguaje
de programación y garantiza en ocasiones un mayor nivel de independencia respecto a la evolución futura
del hardware (aunque si la librería gráfica llama a la BIOS...). Sin embargo, para otras aplicaciones, es mejor
no usar la BIOS. Por ejemplo, el programa de la figura 7.4.3.5 accede directamente a los registros de la VGA
para modificar la paleta en dos bucles, en el primero disminuyendo la luminosidad de la pantalla (hasta
dejarla negra) y en el segundo restaurándola de nuevo. Este efecto cinematográfico hubiera sido imposible
a través de la BIOS por razones de velocidad: el acceso directo al hardware, con precauciones (en este caso,
esperar el retrazado vertical para evitar interferencias) es a veces inevitable. El programa de ejemplo funciona
también en monitores monocromos, aunque en la práctica sólo actúe en ellos sobre la componente verde. El
lector deberá consultar bibliografía especializada para realizar este tipo de programación.
FIGURA 7.4.3.5:
/*********************************************************************
* EFECTO «CINEMATOGRAFICO» DE DESVANECIMIENTO Y POSTERIOR *
* REAPARICION DE LA PANTALLA CON ACCESO DIRECTO AL HARDWARE VGA. *
*********************************************************************/
#include <dos.h>
void main()
{
unsigned char dac[256][3];
register i, j;
for (i=0; i<256; i++) { /* anotar la paleta activa */
disable();
outportb (0x3C7, i);
dac [i][0] = inportb (0x3C9); /* R */
dac [i][1] = inportb (0x3C9); /* G */
dac [i][2] = inportb (0x3C9); /* B */
enable();
}
/* claridad descendente desde el
64/64-avo al 0/64-avo de intensidad */
for (i=64; i>=0; i--) {
while (!((inportb(0x3DA) & 8)==8)); /* esperar retrazo vertical */
while (!((inportb(0x3DA) & 8)==0)); /* esperar su fin */
for (j=0; j<256; j++) {
disable();
outportb (0x3C8, j);
outportb (0x3C9, dac[j][0]*i >> 6);
outportb (0x3C9, dac[j][1]*i >> 6);
outportb (0x3C9, dac[j][2]*i >> 6);
enable();
}
}
/* 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();
}
}
}
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 trazar los puntos. La causa de
este problema no reside en que empleen rutinas multipropósito para todos los
modos, ya que existen básicamente sólo tres tipos de arquitectura de
pantalla (modos CGA, 16 colores y 256 colores). El fallo reside,
simplemente, en que han sido desarrollados sin pensar en la velocidad. Por
ejemplo, la BIOS emplea el algoritmo más lento posible que existe para
trazar puntos en los modos de 16 colores. Lo más conveniente es utilizar los
recursos del lenguaje de programación o, mejor aún, acceder directamente a
la memoria de pantalla con subrutinas en ensamblador. Este es el
procedimiento seguido por la mayoría de las aplicaciones comerciales. Sin
embargo, la BIOS tiene la ventaja de que permite normalizar el acceso a la
pantalla. Así, un programa puede fácilmente trazar un punto en el modo
1024x768x256 de una SuperVGA (y nunca mejor dicho, porque como sean muchos
más de uno...). Para trazar un punto se coloca en CX la coordenada X, en DX
la coordenada Y, en AL el color, en BH la página y en AH el valor 0Ch.
A continuación se llama,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
memoria de vídeo se puede almacenar más de una imagen completa (caso por
ejemplo del modo 640x350x16): existen entonces varias páginas (2, 4, etc.)
que se reparten el segmento a partes iguales. Se puede en estas
circunstancias visualizar una página 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
arriba a abajo, a partir del segmento A000. Cada
punto está asociado a un byte, cuyo valor (0-255)
referencia directamente a un elemento del DAC. En
la figura 7.4.3.6 hay un nuevo listado de ejemplo,
en este caso sin emplear la librería gráfica del
Turbo C. El programa se limita a activar este modo
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.
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);
}
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
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.
; ********************************************************************
; * *
; * RED.ASM - Demostración de gráfica en CGA utilizando BIOS *
; * *
; ********************************************************************
modo EQU 6 ; modo de vídeo
max_x EQU 640
max_y EQU 200
max_color EQU 2
red SEGMENT
ASSUME CS:red, DS:red
ORG 100h
inicio:
MOV AX,modo
INT 10h ; modo de pantalla
MOV AL,max_color-1 ; color visible
MOV BX,0 ; contador para eje Y
MOV BP,0 ; contador para eje X
otras_cuatro: MOV CX,0
MOV DX,BX
MOV SI,BP
MOV DI,max_y-1
CALL recta ; primera recta
MOV CX,max_x-1
MOV SI,max_x-1
SUB SI,BP
CALL recta ; segunda
MOV CX,BP
MOV DX,0
MOV SI,0
MOV DI,max_y-1
SUB DI,BX
CALL recta ; tercera
MOV CX,max_x-1
SUB CX,BP
MOV SI,max_x-1
CALL recta ; cuarta
ADD BX,6
ADD BP,14
CMP BX,max_y
JB otras_cuatro
MOV AH,0
INT 16h ; esperar pulsación de tecla
MOV AX,3
INT 10h ; volver a modo texto
INT 20h ; fin de programa
recta PROC
PUSH AX ; de (CX,DX) a (SI,DI) color AL
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
PUSH BP
MOV color,AL
MOV AX,SI
SUB AX,CX ; AX = X2-X1
JNC absx2x1
NEG AX
XCHG CX,SI
XCHG DX,DI
absx2x1: MOV BX,DI ; AX = ABS(X2-X1) = «dx»
SUB BX,DX
MOV BP,1 ; BP = 1 = «yincr» si Y2>Y1
JNC absy2y1
NEG BP ; BP = -1 = «yincr» si Y2<=Y1
NEG BX
absy2y1: CMP AX,BX ; BX = ABS(Y2-Y1) = «dy»
PUSHF
JA noswap ; ABS(pendiente) menor de 1
XCHG AX,BX
noswap: SHL BX,1 ; BX = «dy» * 2
MOV SI,BX
SUB SI,AX ; SI = «dy» * 2 - «dx» = «d»
MOV DI,BX
SUB DI,AX
SUB DI,AX ; DI = «dy»*2-«dx»*2 = «incr2»
POPF
JBE penmay1 ; pendiente mayor de 1
penmen1: PUSH AX
MOV AL,color
CALL punto ; en (CX, DX) = («x», «y»)
POP AX
INC CX ; «x»++
AND SI,SI ; (SI>0) ? -> «d» > 0 ?
JS noincy
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»
DEC AX ; «dx»--
JNZ penmay1
fin: POP BP
POP DI
POP SI
POP DX
POP CX
POP BX
POP AX
RET
color DB 0
recta ENDP
punto PROC
PUSH BX ; preservar registros (salvo AX)
PUSH CX
PUSH DX
PUSH BP
PUSH SI
PUSH DI
MOV AH,0Ch ; trazar punto usando BIOS
XOR BX,BX
INT 10h
POP DI
POP SI
POP BP
POP DX
POP CX
POP BX
RET
punto ENDP
red ENDS
END inicio
Quizá el lector opine que RED.ASM no es tan rápido. Y tiene razón: la culpa es de la BIOS, que
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)
PUSH DS ; sólo se corrompe AX
PUSH BX
PUSH CX
PUSH DX
MOV BX,0B800h ; segmento de pantalla CGA
MOV DS,BX
MOV AH,CL ; preservar parte baja de «cx»
XCHG BX,CX ; BX = «cx»
MOV CL,3
SHR BX,CL ; BX = «cx» / 8
SHR DX,1 ; DX = int («cy» / 2)
JNC no_add
ADD BX,8192 ; BX = «cx» / 8 + («cy» MOD 2) * 8192
no_add: INC CL ; CL = 4
SHL DX,CL ; DX = («cy» / 2) * 16
ADD BX,DX ; BX = BX + («cy» / 2) * 16
SHL DX,1
SHL DX,1 ; DX = («cy» / 2) * 64
ADD BX,DX ; BX = BX + («cy» / 2) * 80
MOV CL,AH ; recuperar parte baja de «cx»
AND CL,7 ; dejar nº de bit a pintar (0..7)
XOR CL,7 ; invertir orden de numeración
MOV AH,1 ; bit a borrar de la pantalla en AH
SHL AX,CL ; AH = bit a borrar, AL = bit a pintar
NOT AH
AND [BX],AH ; borrar punto anterior
OR [BX],AL ; ubicar nuevo punto (1/0)
POP DX
POP CX
POP BX
POP DS
RET
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).
; ********************************************************************
; * *
; * OVILLO.ASM - Demostración de gráfica en VGA utilizando hardware *
; * *
; ********************************************************************
modo EQU 13h ; modo de vídeo
max_x EQU 320
max_y EQU 200
max_color EQU 256
oviseg SEGMENT
ASSUME CS:oviseg, DS:oviseg
ORG 100h
inicio:
MOV AX,modo
INT 10h
CALL paleta_verde
MOV CX,max_x
SHR CX,1 ; CX = max_x / 2
MOV DX,max_y
SHR DX,1 ; DX = max_y / 2
MOV BX,DX
SHR BX,1 ; BX = ma_y / 4
CALL ovillo ; en (CX, DX) de radio BX
MOV AH,0
INT 16h ; esperar pulsación de tecla
MOV AX,3
INT 10h ; volver a modo texto
INT 20h ; fin de programa
paleta_verde PROC
MOV CX,256 ; los 256 registros
MOV DX,3C8h
otro_reg: MOV AL,CL
OUT DX,AL ; registro a programar
INC DX
XOR AL,AL
OUT DX,AL ; componente roja
MOV AL,CL
REPT max_x/320
SHR AL,1
ENDM
OUT DX,AL ; componente verde
XOR AL,AL
OUT DX,AL ; componente azul
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
SUB DX,DI
CALL circunferencia ; en (x-SI, y-DI)
INC AL
ADD CX,SI
ADD CX,SI
CALL circunferencia ; en (x+SI, y-DI)
INC AL
SUB CX,SI
ADD DX,DI
ADD CX,DI
ADD DX,SI
CALL circunferencia ; en (x+DI, y+SI)
INC AL
SUB CX,DI
SUB CX,DI
CALL circunferencia ; en (x-DI, y+SI)
INC AL
SUB DX,SI
SUB DX,SI
CALL circunferencia ; en (x-DI, y-SI)
INC AL
ADD CX,DI
ADD CX,DI
CALL circunferencia ; en (x+DI, y-SI)
INC AL
SUB CX,DI
ADD DX,SI ; CX = x, DX = y
CMP BP,0
JG ovillo_decx
ADD BP,DI
ADD BP,DI
ADD BP,DI
ADD BP,DI
ADD BP,6
JMP ovillo_incy
ovillo_decx: DEC SI
PUSH AX
MOV AX,DI
SUB AX,SI
SHL AX,1
SHL AX,1
ADD BP,AX
POP AX
ADD BP,10
ovillo_incy: INC DI
JMP ovillo_acaba
ovillo_ok: RET
ovillo ENDP
circunferencia PROC ; en (CX,DX) con radio BX y color AL
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
MOV SI,BX
XOR DI,DI
SHL BX,1
SUB BX,3
NEG BX ; BX = 3 - 2 * BX
circunf_acaba: CMP DI,SI
JG circunf_ok ; circunferencia completada
ADD CX,SI
ADD DX,DI
CALL punto ; en (x+SI, y+DI)
SUB CX,SI
SUB CX,SI
CALL punto ; en (x-SI, y+DI)
SUB DX,DI
SUB DX,DI
CALL punto ; en (x-SI, y-DI)
ADD CX,SI
ADD CX,SI
CALL punto ; en (x+SI, y-DI)
SUB CX,SI
ADD DX,DI
ADD CX,DI
ADD DX,SI
CALL punto ; en (x+DI, y+SI)
SUB CX,DI
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
ADD BX,DI
ADD BX,DI
ADD BX,6
JMP circunf_incy
circunf_decx: DEC SI
PUSH AX
MOV AX,DI
SUB AX,SI
SHL AX,1
SHL AX,1
ADD BX,AX
POP AX
ADD BX,10
circunf_incy: INC DI
JMP circunf_acaba
circunf_ok: POP DI
POP SI
POP DX
POP CX
POP BX
RET
circunferencia ENDP
punto PROC ; trazar punto en 320x200 con 256 col.
PUSH DS ; en (CX, DX) con color AL
PUSH CX
PUSH DX
XCHG DH,DL ; DX = «cy» * 256
ADD CX,DX ; CX = «cy» * 256 + «cx»
SHR DX,1
SHR DX,1 ; DX = «cy» * 64
ADD CX,DX ; CX = «cy» * 320 + «cx»
MOV DX,0A000h
MOV DS,DX ; segmento VGA
XCHG BX,CX ; preservar BX en CX, BX = offset
MOV [BX],AL ; pintar el punto
XCHG BX,CX ; restaurar BX
POP DX ; restaurar demás registros
POP CX
POP DS
RET
punto ENDP
oviseg ENDS
END inicio
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 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.
/*********************************************************************
* *
* ESTANDAR GRAFICO VESA: EJEMPLO DE USO DEL MEJOR MODO DE 256 *
* COLORES EN CUALQUIER SUPERVGA. *
* *
*********************************************************************/
#include <dos.h>
#include <alloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define M640x400x256 0x100 /* modos VESA normales de 256c */
#define M640x480x256 0x101
#define M800x600x256 0x103
#define M1024x768x256 0x105
#define M1280x1024x256 0x107
unsigned
testvesa (void), /* Detectar soporte VESA */
existe_modo (unsigned), /* Comprobar si un modo es soportado */
getbest256 (void); /* Obtener mejor modo de 256c */
void
setbank (long, unsigned), /* Conmutar banco de memoria */
setmode (unsigned, long *, /* Establecer modo VESA */
unsigned *),
getinfo (unsigned, /* Obtener información del modo */
unsigned *,
unsigned *, unsigned *, unsigned *);
/* DEMOSTRACION */
void main()
{
struct REGPACK r;
long
ConmutaBanco; /* dirección FAR del conmutador de banco */
unsigned
video_seg, /* dirección del segmento de vídeo */
far *pantalla,
i, modo, max_x, max_y, vram, bancos, banco, limite;
if (!testvesa()) {
printf ("\nNecesario soporte VESA para este programa.\n");
exit (1);
}
modo = getbest256();
setmode (modo, &ConmutaBanco, &video_seg);
getinfo (modo, &max_x, &max_y, &vram, &bancos);
for (banco=0; banco<bancos; banco++) {
setbank (ConmutaBanco, banco); /* direccionar banco */
pantalla=MK_FP(video_seg, 0); /* normalmente 0xA000:0 */
if (banco!=bancos-1)
limite=32768; /* todo el segmento de 64 Kb */
else
limite=(vram-banco*64)*512; /* palabras último banco */
for (i=0; i<=limite; i++) *pantalla++=0x55AA; /* pintar */
}
setbank (ConmutaBanco, 0);
printf ("Modo de %dx%dx256 con %d Kb\n\n", max_x, max_y, vram);
}
/* COMPROBAR QUE EXISTE SOPORTE VESA */
unsigned testvesa(void)
{
struct REGPACK r;
char far *mem;
unsigned vesa;
mem = farmalloc (256L);
r.r_es = FP_SEG (mem); r.r_di = FP_OFF (mem);
r.r_ax = 0x4F00; intr (0x10, &r);
mem[4]=0; if (strcmp (mem, "VESA")==0) vesa=1; else vesa=0;
farfree (mem);
return (vesa);
}
/* BUSCAR EL MODO DE 256 COLORES DE MAYOR RESOLUCION */
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);
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
}
}
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.
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.
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 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
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 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 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
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.
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:
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 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:
160; Esta tabla comienza en un offset 1BEh del sector
(al principio está el código ejecutable); cada partición de las 4 posibles
ocupa 16 bytes; al final de las cuatro está la marca 0AA55h, ubicada en el
offset 1FEh, que indica que la tabla es válida. Los 16 bytes que la forman
se interpretan como indica el cuadro:
+-----------------------------------------------------------------------------+
| byte 0: 0 para partición inactiva, 80h en la de arranque. |
| byte 1: cabeza donde comienza la partición. |
| byte 2: bits 0 al 5: sector de inicio de la partición; 6, 7: parte alta del |
| número de cilindro. |
| byte 3: parte baja del número de cilindro de inicio de la partición. |
| byte 4: tipo de partición, las más comunes son 0: No usada; 1: DOS-12 (FAT |
| 12 bits); 4: DOS-16 (FAT 16 bits); 5: DOS Extendida; 6:BIGDOS (más |
| de 32Mb); 7: OS/2 HPFS ó WinNT NTFS; 0Ah: OS/2 Boot Manager; 0Bh: |
| 32-bit FAT Win95 (0Ch con LBA); 0Eh y 0Fh (como 06 y 05 pero con |
| LBA); 81h Linux; 82h Linux swap; 83h: Linux native; 0A5h: FreeBSD |
| o BSD/386; 0F2h: partición secundaria (no estudiada en este libro). |
| 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 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
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
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, 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
; Entrada: AX = posición de dicho elemento
; DS:BX = FAT completamente cargada en memoria
; DX = nuevo valor de dicho elemento
poke_fat PROC
PUSH AX ; preservar registros
PUSH BX
PUSH DX
ADD BX,AX ; BX = BX + cluster
SHR AX,1 ; AX = cluster / 2
PUSHF ; CF = 1 si impar
ADD BX,AX ; BX = BX + cluster * 1,5
MOV AX,[BX] ; AX = palabra con dato 12 bits
POPF
JC poke_fat_imp
AND AX,1111000000000000b ; preservar la otra entrada
JMP poke_fat_ok
poke_fat_imp: AND AX,0000000000001111b ; preservar la otra entrada
PUSH CX
MOV CL,4
SHL DX,CL ; colocarlo: 4 bits a la izda
POP CX
poke_fat_ok: OR AX,DX ; «mezclar»
MOV [BX],AX ; nuevo valor en la FAT
POP DX
POP BX
POP AX
RET ; retorno sin alterar registros
poke_fat ENDP
; ************ Leer un elemento de una FAT de 12 bits
; 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.
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).
Considerando el nombre completo de un fichero, con toda la trayectoria de directorios, el proceso
a seguir para localizarlo en el disco es ir recorriendo los ficheros subdirectorio de uno en uno, hasta llegar
al fichero subdirectorio donde está registrado el fichero y, en la posición correspondiente, obtener su punto
de entrada en la FAT.
Dicho sea de paso, tal vez sea una pena que el disco no conste de un único «fichero raíz» privilegiado
de directorio, que podríamos denominar «subdirectorio raíz». Ello permitiría también un número ilimitado
de entradas (en vez de 112, 224, etc.) y sería más lógico que una ristra de sectores. Sin embargo, esta
peculiar circunstancia también aparece en otros sistemas operativos, como el UNIX. Sus motivos tendrá.
7.6.6. - EL BPB Y DPB.
El BPB (Bios Parameter Block) es una estructura de datos que contiene información relativa a la
unidad de disco. El BPB es una pieza vital en los controladores de dispositivo de bloques, como veremos en
un futuro capítulo, por lo que a continuación se expone su contenido (idéntico a una parte del sector 0):
+---------------------------------------------------------------------------+ | offset 0 DW bytes_por_sector | | offset 2 DB sectores_por_cluster | | offset 3 DW sectores_reservados_al_comienzo_del_disco | | offset 5 DB número_de_FATs | | offset 6 DW número_de_entradas_en_el_directorio_raíz | | offset 8 DW número_total_de_sectores (0 con nº de sector de 32 bits) | | offset 10 DB byte_descriptor_de_medio | | offset 11 DW numero_de_sectores_por_FAT | | -- A partir del DOS 3.0: | | offset 13 DW sectores_por_pista | | offset 15 DW número_de_cabezas | | offset 17 DD número_de_sectores_ocultos | | -- A partir del DOS 4.0 (más bien DOS 3.31) | | offset 21 DD número_de_sectores (unidades con direccionamiento de | | 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 | +---------------------------------------------------------------------------+
El DOS convierte internamente el BPB en DPB
(Drive Parameter Block), una estructura similar con más información útil.
Para obtener el DPB de una unidad determinada, puede utilizarse la función
32h del DOS, Get Drive Parameter Block (indocumentada); la cadena de
DPBs del DOS puede recorrerse a partir del primer DPB (obtenido con la
función 52h del DOS, Get List of Lists, también indocumentada).
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:3Eh | Estado 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:3Fh | Estado 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:40h | Cuenta para la detención del motor. Este byte es decrementado por la interrupción periódica del temporizador; cuando llega a 0 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:41h | Estado de la última operación: se actualiza tras cada acceso al disco, indicando los errores producidos (0 = ninguno). |
| Bytes 40h:42h | A 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:8Bh | Control del soporte (AT). Esta variable almacena, entre otros, la última velocidad de transferencia seleccionada. |
| Byte 40h:8Fh | Informació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:90h | Estado 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:91h | Lo mismo que el byte anterior, pero para la unidad B. |
| Byte 40h:92h | Estado del soporte en la unidad A al inicio de la operación. |
| Byte 40h:93h | Estado del soporte en la unidad B al inicio de la operación. |
| Byte 40h:94h | Número de cilindro en curso en la unidad A. |
| Byte 40h:95h | Número de cilindro en curso en la unidad B. |
Además de estas variables, la BIOS utiliza también una tabla de parámetros apuntada por la INT 1Eh.
Los valores para programar ciertas características del FDC según el tipo de disco pueden variar, aunque
algunos son comunes. Esta tabla determina las principales características de operación del disco. Dicha tabla
está inicialmente en la ROM, en la posición 0F000h:0EFC7h de todas las BIOS compatibles (prácticamente
el 100%), aunque el DOS suele desviarla a la RAM para poder actualizarla. El formato de la misma es:
| byte 0: | Se corresponde con el byte 1 del comando 'Specify' del 765, que indica el step rate (el tiempo de acceso cilindro-cilindro, a menudo es 0Dh = 3 ó6 ms) y el head unload time (normalmente, 0Fh = 240 ó480 ms). |
| byte 1: | Es el byte 2 del comando 'Specify': los bits 7..1 indican el head load time (normalmente 01h = 2 ó4 ms) y el bit 0 suele estar a 0 para indicar modo DMA. |
| byte 2: | Tics de reloj (pulsos de la interrupción 8) que transcurren tras el acceso hasta que se para el motor. |
| byte 3: | Bytes por sector (0=128, 1=256, 2=512, 3=1024). |
| byte 4: | Sectores por pista. |
| byte 5: | Longitud del GAP entre sectores (normalmente 2Ah en unidades de 5¼ y 1Bh en las de 3½). |
| byte 6: | Longitud de sector (ignorado si el byte 3 no es 0). |
| byte 7: | Longitud del GAP 3 al formatear (80 en 5¼ y 3½-DD, 84 en 5¼-HD y 108 en 3½-HD). |
| byte 8: | Byte de relleno al formatear (normalmente 0F6h). |
| byte 9: | Tiempo de estabilización del cabezal en ms. |
| byte 10: | Tiempo de aceleración del motor (en unidades de 1/8 de segundo). |
El tiempo de estabilización del cabezal es el tiempo que hay que esperar tras mover el cabezal al
cilindro adecuado, hasta que éste se asiente, con objeto de garantizar el éxito de las operaciones futuras; esta
breve pausa es establecida en 25 milisegundos en la BIOS del PC original, aunque otras BIOS y el propio
DOS suelen bajarlo a 15. Del mismo modo, el tiempo de aceleración del motor (byte 10) es el tiempo que
se espera a que el motor adquiera la velocidad de rotación correcta, nada más ponerlo en marcha. En
cualquier caso, es norma general intentar tres veces el acceso a disco (con resets de por medio) hasta
considerar que un error es real. En general, pese a estos valores usuales, la flexibilidad del sistema de disco
es extraordinaria y suele responder favorablemente con unos altísimos niveles de tolerancia en las
temporizaciones. Una excepción quizá la constituye el valor de GAP empleado al formatear, al ser un
parámetro demasiado importante.
7.6.8. - DISQUETES FLOPTICAL 3½ DE 20 MB.
Las unidades que soportan estos disquetes, que también admiten los de 720K y 1.44M (aunque a
menudo no los de 2.88M) trabajan con controladoras SCSI e incorporan una BIOS propia para dar soporte
a estos dispositivos. El secreto de estos disquetes está en el posicionamiento óptico del cabezal, lo que
permite elevar notablemente el número de pistas. Por ejemplo, las unidades de 20 Mb parecen estar equipadas
con 753 cilindros y 27 sectores/pista. Aunque en el sector de arranque indica que posee 251 cilindros y 6
cabezales, el sentido común nos permite deducir que esto no puede ser así. Lo de los 27 sectores por pista
parece indicar que la velocidad de transferencia de estos disquetes es exactamente un 50% mayor que la de
los convencionales de 1.44M (750 Kbit/seg frente a 500 Kbit/seg).
El FORMAT del DOS 5.0 y posteriores puede formatear los disquetes floptical, pero lo hace a bajo
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:
fichnom EQU $
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 (?)-.
; ********************************************************************
; * *
; * MIRA.ASM - Utilidad para visualizar ficheros de texto. *
; * *
; ********************************************************************
mira SEGMENT
ASSUME CS:mira, DS:mira
ORG 100h ; programa de tipo .COM
inicio:
LEA DX,input_txt ; mensaje
MOV AH,9 ; función de impresión
INT 21h ; llamar al DOS
LEA DX,fichnom ; dirección para el «input»
MOV BYTE PTR [fichnom],60 ; no más de 60 caracteres
MOV AH,10 ; función de entrada de teclado
INT 21h ; llamar al DOS
MOV BL,[fichnom+1] ; longitud efectiva tecleada
MOV BH,0 ; en BX
ADD BX,OFFSET fichnom ; apuntar al final
MOV BYTE PTR [BX+2],0 ; poner un cero al final
LEA DX,fichnom+2 ; offset a cadena ASCIIZ nombre
MOV AL,0 ; modo de lectura
MOV AH,3Dh ; función para abrir fichero
INT 21h ; llamar al DOS
JC error ; CF=1 --> error
MOV handle,AX ; código de acceso al fichero
trocito: MOV BX,handle ; código de acceso al fichero
MOV CX,2048 ; número de bytes a leer
LEA DX,buffer ; dirección del buffer
MOV AH,3Fh ; función para leer del fichero
INT 21h ; llamar al DOS
JC error ; CF=1 --> error
MOV CX,AX ; bytes leídos realmente
JCXZ cerrar ; no hay nada que imprimir
PUSH AX ; preservarlos
LEA BX,buffer ; imprimir buffer ...
imprime: MOV DL,[BX] ; carácter a carácter
MOV AH,2 ; ir llamando al servicio 2 del
INT 21h ; DOS para imprimir en pantalla
INC BX ; siguiente carácter
LOOP imprime ; acabar caracteres
POP AX ; recuperar nº de bytes leídos
CMP AX,2048 ; ¿leidos 2048 bytes?
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
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 -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.
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:
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.
- 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) 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
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
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
. . . . . . . . . . . . . . . . .
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 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).